git repos / blockattack-game

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

normal view · raw

1193b4be Poul Sander 2025-02-03 21:28 1
// dear imgui, v1.91.4
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.
1193b4be Poul Sander 2025-02-03 21:28 73
#pragma clang diagnostic ignored "-Wformat-nonliteral"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
1193b4be Poul Sander 2025-02-03 21:28 74
#pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
1193b4be Poul Sander 2025-02-03 21:28 75
#pragma clang diagnostic ignored "-Wunused-macros"                  // warning: macro is not used                                // we define snprintf/vsnprintf on Windows so they are available, but not always used.
1193b4be Poul Sander 2025-02-03 21:28 76
#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 77
#pragma clang diagnostic ignored "-Wdouble-promotion"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
1193b4be Poul Sander 2025-02-03 21:28 78
#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 79
#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 80
#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 81
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
1193b4be Poul Sander 2025-02-03 21:28 82
#elif defined(__GNUC__)
1193b4be Poul Sander 2025-02-03 21:28 83
#pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
1193b4be Poul Sander 2025-02-03 21:28 84
#pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
1193b4be Poul Sander 2025-02-03 21:28 85
#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
1193b4be Poul Sander 2025-02-03 21:28 86
#pragma GCC 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 87
#endif
1193b4be Poul Sander 2025-02-03 21:28 88
1193b4be Poul Sander 2025-02-03 21:28 89
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 90
// Data
1193b4be Poul Sander 2025-02-03 21:28 91
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 92
1193b4be Poul Sander 2025-02-03 21:28 93
// Widgets
1193b4be Poul Sander 2025-02-03 21:28 94
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 95
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 96
1193b4be Poul Sander 2025-02-03 21:28 97
// Those MIN/MAX values are not define because we need to point to them
1193b4be Poul Sander 2025-02-03 21:28 98
static const signed char    IM_S8_MIN  = -128;
1193b4be Poul Sander 2025-02-03 21:28 99
static const signed char    IM_S8_MAX  = 127;
1193b4be Poul Sander 2025-02-03 21:28 100
static const unsigned char  IM_U8_MIN  = 0;
1193b4be Poul Sander 2025-02-03 21:28 101
static const unsigned char  IM_U8_MAX  = 0xFF;
1193b4be Poul Sander 2025-02-03 21:28 102
static const signed short   IM_S16_MIN = -32768;
1193b4be Poul Sander 2025-02-03 21:28 103
static const signed short   IM_S16_MAX = 32767;
1193b4be Poul Sander 2025-02-03 21:28 104
static const unsigned short IM_U16_MIN = 0;
1193b4be Poul Sander 2025-02-03 21:28 105
static const unsigned short IM_U16_MAX = 0xFFFF;
1193b4be Poul Sander 2025-02-03 21:28 106
static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
1193b4be Poul Sander 2025-02-03 21:28 107
static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
1193b4be Poul Sander 2025-02-03 21:28 108
static const ImU32          IM_U32_MIN = 0;
1193b4be Poul Sander 2025-02-03 21:28 109
static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
1193b4be Poul Sander 2025-02-03 21:28 110
#ifdef LLONG_MIN
1193b4be Poul Sander 2025-02-03 21:28 111
static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
1193b4be Poul Sander 2025-02-03 21:28 112
static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
1193b4be Poul Sander 2025-02-03 21:28 113
#else
1193b4be Poul Sander 2025-02-03 21:28 114
static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
1193b4be Poul Sander 2025-02-03 21:28 115
static const ImS64          IM_S64_MAX = 9223372036854775807LL;
1193b4be Poul Sander 2025-02-03 21:28 116
#endif
1193b4be Poul Sander 2025-02-03 21:28 117
static const ImU64          IM_U64_MIN = 0;
1193b4be Poul Sander 2025-02-03 21:28 118
#ifdef ULLONG_MAX
1193b4be Poul Sander 2025-02-03 21:28 119
static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
1193b4be Poul Sander 2025-02-03 21:28 120
#else
1193b4be Poul Sander 2025-02-03 21:28 121
static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
1193b4be Poul Sander 2025-02-03 21:28 122
#endif
1193b4be Poul Sander 2025-02-03 21:28 123
1193b4be Poul Sander 2025-02-03 21:28 124
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 125
// [SECTION] Forward Declarations
1193b4be Poul Sander 2025-02-03 21:28 126
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 127
1193b4be Poul Sander 2025-02-03 21:28 128
// For InputTextEx()
1193b4be Poul Sander 2025-02-03 21:28 129
static bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
1193b4be Poul Sander 2025-02-03 21:28 130
static int      InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
1193b4be Poul Sander 2025-02-03 21:28 131
static ImVec2   InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
1193b4be Poul Sander 2025-02-03 21:28 132
1193b4be Poul Sander 2025-02-03 21:28 133
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 134
// [SECTION] Widgets: Text, etc.
1193b4be Poul Sander 2025-02-03 21:28 135
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 136
// - TextEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 137
// - TextUnformatted()
1193b4be Poul Sander 2025-02-03 21:28 138
// - Text()
1193b4be Poul Sander 2025-02-03 21:28 139
// - TextV()
1193b4be Poul Sander 2025-02-03 21:28 140
// - TextColored()
1193b4be Poul Sander 2025-02-03 21:28 141
// - TextColoredV()
1193b4be Poul Sander 2025-02-03 21:28 142
// - TextDisabled()
1193b4be Poul Sander 2025-02-03 21:28 143
// - TextDisabledV()
1193b4be Poul Sander 2025-02-03 21:28 144
// - TextWrapped()
1193b4be Poul Sander 2025-02-03 21:28 145
// - TextWrappedV()
1193b4be Poul Sander 2025-02-03 21:28 146
// - LabelText()
1193b4be Poul Sander 2025-02-03 21:28 147
// - LabelTextV()
1193b4be Poul Sander 2025-02-03 21:28 148
// - BulletText()
1193b4be Poul Sander 2025-02-03 21:28 149
// - BulletTextV()
1193b4be Poul Sander 2025-02-03 21:28 150
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 151
1193b4be Poul Sander 2025-02-03 21:28 152
void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 153
{
1193b4be Poul Sander 2025-02-03 21:28 154
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 155
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 156
        return;
1193b4be Poul Sander 2025-02-03 21:28 157
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 158
1193b4be Poul Sander 2025-02-03 21:28 159
    // Accept null ranges
1193b4be Poul Sander 2025-02-03 21:28 160
    if (text == text_end)
1193b4be Poul Sander 2025-02-03 21:28 161
        text = text_end = "";
1193b4be Poul Sander 2025-02-03 21:28 162
1193b4be Poul Sander 2025-02-03 21:28 163
    // Calculate length
1193b4be Poul Sander 2025-02-03 21:28 164
    const char* text_begin = text;
1193b4be Poul Sander 2025-02-03 21:28 165
    if (text_end == NULL)
1193b4be Poul Sander 2025-02-03 21:28 166
        text_end = text + strlen(text); // FIXME-OPT
1193b4be Poul Sander 2025-02-03 21:28 167
1193b4be Poul Sander 2025-02-03 21:28 168
    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 169
    const float wrap_pos_x = window->DC.TextWrapPos;
1193b4be Poul Sander 2025-02-03 21:28 170
    const bool wrap_enabled = (wrap_pos_x >= 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 171
    if (text_end - text <= 2000 || wrap_enabled)
1193b4be Poul Sander 2025-02-03 21:28 172
    {
1193b4be Poul Sander 2025-02-03 21:28 173
        // Common case
1193b4be Poul Sander 2025-02-03 21:28 174
        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 175
        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
1193b4be Poul Sander 2025-02-03 21:28 176
1193b4be Poul Sander 2025-02-03 21:28 177
        ImRect bb(text_pos, text_pos + text_size);
1193b4be Poul Sander 2025-02-03 21:28 178
        ItemSize(text_size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 179
        if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 180
            return;
1193b4be Poul Sander 2025-02-03 21:28 181
1193b4be Poul Sander 2025-02-03 21:28 182
        // Render (we don't hide text after ## in this end-user function)
1193b4be Poul Sander 2025-02-03 21:28 183
        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
1193b4be Poul Sander 2025-02-03 21:28 184
    }
1193b4be Poul Sander 2025-02-03 21:28 185
    else
1193b4be Poul Sander 2025-02-03 21:28 186
    {
1193b4be Poul Sander 2025-02-03 21:28 187
        // Long text!
1193b4be Poul Sander 2025-02-03 21:28 188
        // Perform manual coarse clipping to optimize for long multi-line text
1193b4be Poul Sander 2025-02-03 21:28 189
        // - 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 190
        // - 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 191
        // - 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 192
        const char* line = text;
1193b4be Poul Sander 2025-02-03 21:28 193
        const float line_height = GetTextLineHeight();
1193b4be Poul Sander 2025-02-03 21:28 194
        ImVec2 text_size(0, 0);
1193b4be Poul Sander 2025-02-03 21:28 195
1193b4be Poul Sander 2025-02-03 21:28 196
        // Lines to skip (can't skip when logging text)
1193b4be Poul Sander 2025-02-03 21:28 197
        ImVec2 pos = text_pos;
1193b4be Poul Sander 2025-02-03 21:28 198
        if (!g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 199
        {
1193b4be Poul Sander 2025-02-03 21:28 200
            int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
1193b4be Poul Sander 2025-02-03 21:28 201
            if (lines_skippable > 0)
1193b4be Poul Sander 2025-02-03 21:28 202
            {
1193b4be Poul Sander 2025-02-03 21:28 203
                int lines_skipped = 0;
1193b4be Poul Sander 2025-02-03 21:28 204
                while (line < text_end && lines_skipped < lines_skippable)
1193b4be Poul Sander 2025-02-03 21:28 205
                {
1193b4be Poul Sander 2025-02-03 21:28 206
                    const char* line_end = (const char*)memchr(line, '\n', text_end - line);
1193b4be Poul Sander 2025-02-03 21:28 207
                    if (!line_end)
1193b4be Poul Sander 2025-02-03 21:28 208
                        line_end = text_end;
1193b4be Poul Sander 2025-02-03 21:28 209
                    if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
1193b4be Poul Sander 2025-02-03 21:28 210
                        text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
1193b4be Poul Sander 2025-02-03 21:28 211
                    line = line_end + 1;
1193b4be Poul Sander 2025-02-03 21:28 212
                    lines_skipped++;
1193b4be Poul Sander 2025-02-03 21:28 213
                }
1193b4be Poul Sander 2025-02-03 21:28 214
                pos.y += lines_skipped * line_height;
1193b4be Poul Sander 2025-02-03 21:28 215
            }
1193b4be Poul Sander 2025-02-03 21:28 216
        }
1193b4be Poul Sander 2025-02-03 21:28 217
1193b4be Poul Sander 2025-02-03 21:28 218
        // Lines to render
1193b4be Poul Sander 2025-02-03 21:28 219
        if (line < text_end)
1193b4be Poul Sander 2025-02-03 21:28 220
        {
1193b4be Poul Sander 2025-02-03 21:28 221
            ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
1193b4be Poul Sander 2025-02-03 21:28 222
            while (line < text_end)
1193b4be Poul Sander 2025-02-03 21:28 223
            {
1193b4be Poul Sander 2025-02-03 21:28 224
                if (IsClippedEx(line_rect, 0))
1193b4be Poul Sander 2025-02-03 21:28 225
                    break;
1193b4be Poul Sander 2025-02-03 21:28 226
1193b4be Poul Sander 2025-02-03 21:28 227
                const char* line_end = (const char*)memchr(line, '\n', text_end - line);
1193b4be Poul Sander 2025-02-03 21:28 228
                if (!line_end)
1193b4be Poul Sander 2025-02-03 21:28 229
                    line_end = text_end;
1193b4be Poul Sander 2025-02-03 21:28 230
                text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
1193b4be Poul Sander 2025-02-03 21:28 231
                RenderText(pos, line, line_end, false);
1193b4be Poul Sander 2025-02-03 21:28 232
                line = line_end + 1;
1193b4be Poul Sander 2025-02-03 21:28 233
                line_rect.Min.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 234
                line_rect.Max.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 235
                pos.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 236
            }
1193b4be Poul Sander 2025-02-03 21:28 237
1193b4be Poul Sander 2025-02-03 21:28 238
            // Count remaining lines
1193b4be Poul Sander 2025-02-03 21:28 239
            int lines_skipped = 0;
1193b4be Poul Sander 2025-02-03 21:28 240
            while (line < text_end)
1193b4be Poul Sander 2025-02-03 21:28 241
            {
1193b4be Poul Sander 2025-02-03 21:28 242
                const char* line_end = (const char*)memchr(line, '\n', text_end - line);
1193b4be Poul Sander 2025-02-03 21:28 243
                if (!line_end)
1193b4be Poul Sander 2025-02-03 21:28 244
                    line_end = text_end;
1193b4be Poul Sander 2025-02-03 21:28 245
                if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
1193b4be Poul Sander 2025-02-03 21:28 246
                    text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
1193b4be Poul Sander 2025-02-03 21:28 247
                line = line_end + 1;
1193b4be Poul Sander 2025-02-03 21:28 248
                lines_skipped++;
1193b4be Poul Sander 2025-02-03 21:28 249
            }
1193b4be Poul Sander 2025-02-03 21:28 250
            pos.y += lines_skipped * line_height;
1193b4be Poul Sander 2025-02-03 21:28 251
        }
1193b4be Poul Sander 2025-02-03 21:28 252
        text_size.y = (pos - text_pos).y;
1193b4be Poul Sander 2025-02-03 21:28 253
1193b4be Poul Sander 2025-02-03 21:28 254
        ImRect bb(text_pos, text_pos + text_size);
1193b4be Poul Sander 2025-02-03 21:28 255
        ItemSize(text_size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 256
        ItemAdd(bb, 0);
1193b4be Poul Sander 2025-02-03 21:28 257
    }
1193b4be Poul Sander 2025-02-03 21:28 258
}
1193b4be Poul Sander 2025-02-03 21:28 259
1193b4be Poul Sander 2025-02-03 21:28 260
void ImGui::TextUnformatted(const char* text, const char* text_end)
1193b4be Poul Sander 2025-02-03 21:28 261
{
1193b4be Poul Sander 2025-02-03 21:28 262
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
1193b4be Poul Sander 2025-02-03 21:28 263
}
1193b4be Poul Sander 2025-02-03 21:28 264
1193b4be Poul Sander 2025-02-03 21:28 265
void ImGui::Text(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 266
{
1193b4be Poul Sander 2025-02-03 21:28 267
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 268
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 269
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 270
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 271
}
1193b4be Poul Sander 2025-02-03 21:28 272
1193b4be Poul Sander 2025-02-03 21:28 273
void ImGui::TextV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 274
{
1193b4be Poul Sander 2025-02-03 21:28 275
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 276
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 277
        return;
1193b4be Poul Sander 2025-02-03 21:28 278
1193b4be Poul Sander 2025-02-03 21:28 279
    const char* text, *text_end;
1193b4be Poul Sander 2025-02-03 21:28 280
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 281
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
1193b4be Poul Sander 2025-02-03 21:28 282
}
1193b4be Poul Sander 2025-02-03 21:28 283
1193b4be Poul Sander 2025-02-03 21:28 284
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 285
{
1193b4be Poul Sander 2025-02-03 21:28 286
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 287
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 288
    TextColoredV(col, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 289
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 290
}
1193b4be Poul Sander 2025-02-03 21:28 291
1193b4be Poul Sander 2025-02-03 21:28 292
void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 293
{
1193b4be Poul Sander 2025-02-03 21:28 294
    PushStyleColor(ImGuiCol_Text, col);
1193b4be Poul Sander 2025-02-03 21:28 295
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 296
    PopStyleColor();
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::TextDisabled(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 300
{
1193b4be Poul Sander 2025-02-03 21:28 301
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 302
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 303
    TextDisabledV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 304
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 305
}
1193b4be Poul Sander 2025-02-03 21:28 306
1193b4be Poul Sander 2025-02-03 21:28 307
void ImGui::TextDisabledV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 308
{
1193b4be Poul Sander 2025-02-03 21:28 309
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 310
    PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
1193b4be Poul Sander 2025-02-03 21:28 311
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 312
    PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 313
}
1193b4be Poul Sander 2025-02-03 21:28 314
1193b4be Poul Sander 2025-02-03 21:28 315
void ImGui::TextWrapped(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 316
{
1193b4be Poul Sander 2025-02-03 21:28 317
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 318
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 319
    TextWrappedV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 320
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 321
}
1193b4be Poul Sander 2025-02-03 21:28 322
1193b4be Poul Sander 2025-02-03 21:28 323
void ImGui::TextWrappedV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 324
{
1193b4be Poul Sander 2025-02-03 21:28 325
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 326
    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 327
    if (need_backup)
1193b4be Poul Sander 2025-02-03 21:28 328
        PushTextWrapPos(0.0f);
1193b4be Poul Sander 2025-02-03 21:28 329
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 330
    if (need_backup)
1193b4be Poul Sander 2025-02-03 21:28 331
        PopTextWrapPos();
1193b4be Poul Sander 2025-02-03 21:28 332
}
1193b4be Poul Sander 2025-02-03 21:28 333
1193b4be Poul Sander 2025-02-03 21:28 334
void ImGui::LabelText(const char* label, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 335
{
1193b4be Poul Sander 2025-02-03 21:28 336
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 337
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 338
    LabelTextV(label, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 339
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 340
}
1193b4be Poul Sander 2025-02-03 21:28 341
1193b4be Poul Sander 2025-02-03 21:28 342
// Add a label+text combo aligned to other label+value widgets
1193b4be Poul Sander 2025-02-03 21:28 343
void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 344
{
1193b4be Poul Sander 2025-02-03 21:28 345
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 346
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 347
        return;
1193b4be Poul Sander 2025-02-03 21:28 348
1193b4be Poul Sander 2025-02-03 21:28 349
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 350
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 351
    const float w = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 352
1193b4be Poul Sander 2025-02-03 21:28 353
    const char* value_text_begin, *value_text_end;
1193b4be Poul Sander 2025-02-03 21:28 354
    ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 355
    const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
1193b4be Poul Sander 2025-02-03 21:28 356
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 357
1193b4be Poul Sander 2025-02-03 21:28 358
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 359
    const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
1193b4be Poul Sander 2025-02-03 21:28 360
    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 361
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 362
    if (!ItemAdd(total_bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 363
        return;
1193b4be Poul Sander 2025-02-03 21:28 364
1193b4be Poul Sander 2025-02-03 21:28 365
    // Render
1193b4be Poul Sander 2025-02-03 21:28 366
    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 367
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 368
        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 369
}
1193b4be Poul Sander 2025-02-03 21:28 370
1193b4be Poul Sander 2025-02-03 21:28 371
void ImGui::BulletText(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 372
{
1193b4be Poul Sander 2025-02-03 21:28 373
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 374
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 375
    BulletTextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 376
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 377
}
1193b4be Poul Sander 2025-02-03 21:28 378
1193b4be Poul Sander 2025-02-03 21:28 379
// Text with a little bullet aligned to the typical tree node.
1193b4be Poul Sander 2025-02-03 21:28 380
void ImGui::BulletTextV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 381
{
1193b4be Poul Sander 2025-02-03 21:28 382
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 383
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 384
        return;
1193b4be Poul Sander 2025-02-03 21:28 385
1193b4be Poul Sander 2025-02-03 21:28 386
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 387
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 388
1193b4be Poul Sander 2025-02-03 21:28 389
    const char* text_begin, *text_end;
1193b4be Poul Sander 2025-02-03 21:28 390
    ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 391
    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
1193b4be Poul Sander 2025-02-03 21:28 392
    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 393
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 394
    pos.y += window->DC.CurrLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 395
    ItemSize(total_size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 396
    const ImRect bb(pos, pos + total_size);
1193b4be Poul Sander 2025-02-03 21:28 397
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 398
        return;
1193b4be Poul Sander 2025-02-03 21:28 399
1193b4be Poul Sander 2025-02-03 21:28 400
    // Render
1193b4be Poul Sander 2025-02-03 21:28 401
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 402
    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 403
    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 404
}
1193b4be Poul Sander 2025-02-03 21:28 405
1193b4be Poul Sander 2025-02-03 21:28 406
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 407
// [SECTION] Widgets: Main
1193b4be Poul Sander 2025-02-03 21:28 408
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 409
// - ButtonBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 410
// - Button()
1193b4be Poul Sander 2025-02-03 21:28 411
// - SmallButton()
1193b4be Poul Sander 2025-02-03 21:28 412
// - InvisibleButton()
1193b4be Poul Sander 2025-02-03 21:28 413
// - ArrowButton()
1193b4be Poul Sander 2025-02-03 21:28 414
// - CloseButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 415
// - CollapseButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 416
// - GetWindowScrollbarID() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 417
// - GetWindowScrollbarRect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 418
// - Scrollbar() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 419
// - ScrollbarEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 420
// - Image()
1193b4be Poul Sander 2025-02-03 21:28 421
// - ImageButton()
1193b4be Poul Sander 2025-02-03 21:28 422
// - Checkbox()
1193b4be Poul Sander 2025-02-03 21:28 423
// - CheckboxFlagsT() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 424
// - CheckboxFlags()
1193b4be Poul Sander 2025-02-03 21:28 425
// - RadioButton()
1193b4be Poul Sander 2025-02-03 21:28 426
// - ProgressBar()
1193b4be Poul Sander 2025-02-03 21:28 427
// - Bullet()
1193b4be Poul Sander 2025-02-03 21:28 428
// - Hyperlink()
1193b4be Poul Sander 2025-02-03 21:28 429
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 430
1193b4be Poul Sander 2025-02-03 21:28 431
// The ButtonBehavior() function is key to many interactions and used by many/most widgets.
1193b4be Poul Sander 2025-02-03 21:28 432
// 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 433
// this code is a little complex.
1193b4be Poul Sander 2025-02-03 21:28 434
// 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 435
// See the series of events below and the corresponding state reported by dear imgui:
1193b4be Poul Sander 2025-02-03 21:28 436
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 437
// with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 438
//   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 439
//   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 440
//   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
1193b4be Poul Sander 2025-02-03 21:28 441
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 442
//   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 443
//   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 444
//   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
1193b4be Poul Sander 2025-02-03 21:28 445
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 446
//   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 447
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 448
// with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 449
//   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
1193b4be Poul Sander 2025-02-03 21:28 450
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 451
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
1193b4be Poul Sander 2025-02-03 21:28 452
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 453
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 454
// with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 455
//   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
1193b4be Poul Sander 2025-02-03 21:28 456
//   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 457
//   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 458
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 459
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 460
// with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 461
//   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
1193b4be Poul Sander 2025-02-03 21:28 462
//   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 463
//   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 464
//   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 465
//   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
1193b4be Poul Sander 2025-02-03 21:28 466
//   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 467
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
1193b4be Poul Sander 2025-02-03 21:28 468
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 469
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 470
// Note that some combinations are supported,
1193b4be Poul Sander 2025-02-03 21:28 471
// - PressedOnDragDropHold can generally be associated with any flag.
1193b4be Poul Sander 2025-02-03 21:28 472
// - 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 473
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 474
// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
1193b4be Poul Sander 2025-02-03 21:28 475
//                                         Repeat+                  Repeat+           Repeat+             Repeat+
1193b4be Poul Sander 2025-02-03 21:28 476
//                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
1193b4be Poul Sander 2025-02-03 21:28 477
//-------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 478
//   Frame N+0 (mouse button is down)       -                        true              -                   true
1193b4be Poul Sander 2025-02-03 21:28 479
//   ...                                    -                        -                 -                   -
1193b4be Poul Sander 2025-02-03 21:28 480
//   Frame N + RepeatDelay                  true                     true              -                   true
1193b4be Poul Sander 2025-02-03 21:28 481
//   ...                                    -                        -                 -                   -
1193b4be Poul Sander 2025-02-03 21:28 482
//   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
1193b4be Poul Sander 2025-02-03 21:28 483
//-------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 484
1193b4be Poul Sander 2025-02-03 21:28 485
// - 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 486
//   And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
1193b4be Poul Sander 2025-02-03 21:28 487
//   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 488
// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.
1193b4be Poul Sander 2025-02-03 21:28 489
//   One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior()
1193b4be Poul Sander 2025-02-03 21:28 490
//   with same ID and different MouseButton (see #8030). You can fix it by:
1193b4be Poul Sander 2025-02-03 21:28 491
//       (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.
1193b4be Poul Sander 2025-02-03 21:28 492
//    or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
1193b4be Poul Sander 2025-02-03 21:28 493
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 494
{
1193b4be Poul Sander 2025-02-03 21:28 495
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 496
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 497
1193b4be Poul Sander 2025-02-03 21:28 498
    // Default only reacts to left mouse button
1193b4be Poul Sander 2025-02-03 21:28 499
    if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 500
        flags |= ImGuiButtonFlags_MouseButtonLeft;
1193b4be Poul Sander 2025-02-03 21:28 501
1193b4be Poul Sander 2025-02-03 21:28 502
    // Default behavior requires click + release inside bounding box
1193b4be Poul Sander 2025-02-03 21:28 503
    if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 504
        flags |= ImGuiButtonFlags_PressedOnDefault_;
1193b4be Poul Sander 2025-02-03 21:28 505
1193b4be Poul Sander 2025-02-03 21:28 506
    // Default behavior inherited from item flags
1193b4be Poul Sander 2025-02-03 21:28 507
    // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
1193b4be Poul Sander 2025-02-03 21:28 508
    ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 509
    if (flags & ImGuiButtonFlags_AllowOverlap)
1193b4be Poul Sander 2025-02-03 21:28 510
        item_flags |= ImGuiItemFlags_AllowOverlap;
1193b4be Poul Sander 2025-02-03 21:28 511
1193b4be Poul Sander 2025-02-03 21:28 512
    ImGuiWindow* backup_hovered_window = g.HoveredWindow;
1193b4be Poul Sander 2025-02-03 21:28 513
    const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
1193b4be Poul Sander 2025-02-03 21:28 514
    if (flatten_hovered_children)
1193b4be Poul Sander 2025-02-03 21:28 515
        g.HoveredWindow = window;
1193b4be Poul Sander 2025-02-03 21:28 516
1193b4be Poul Sander 2025-02-03 21:28 517
#ifdef IMGUI_ENABLE_TEST_ENGINE
1193b4be Poul Sander 2025-02-03 21:28 518
    // Alternate registration spot, for when caller didn't use ItemAdd()
1193b4be Poul Sander 2025-02-03 21:28 519
    if (g.LastItemData.ID != id)
1193b4be Poul Sander 2025-02-03 21:28 520
        IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
1193b4be Poul Sander 2025-02-03 21:28 521
#endif
1193b4be Poul Sander 2025-02-03 21:28 522
1193b4be Poul Sander 2025-02-03 21:28 523
    bool pressed = false;
1193b4be Poul Sander 2025-02-03 21:28 524
    bool hovered = ItemHoverable(bb, id, item_flags);
1193b4be Poul Sander 2025-02-03 21:28 525
1193b4be Poul Sander 2025-02-03 21:28 526
    // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
1193b4be Poul Sander 2025-02-03 21:28 527
    if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
1193b4be Poul Sander 2025-02-03 21:28 528
        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
1193b4be Poul Sander 2025-02-03 21:28 529
        {
1193b4be Poul Sander 2025-02-03 21:28 530
            hovered = true;
1193b4be Poul Sander 2025-02-03 21:28 531
            SetHoveredID(id);
1193b4be Poul Sander 2025-02-03 21:28 532
            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 533
            {
1193b4be Poul Sander 2025-02-03 21:28 534
                pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 535
                g.DragDropHoldJustPressedId = id;
1193b4be Poul Sander 2025-02-03 21:28 536
                FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 537
            }
1193b4be Poul Sander 2025-02-03 21:28 538
        }
1193b4be Poul Sander 2025-02-03 21:28 539
1193b4be Poul Sander 2025-02-03 21:28 540
    if (flatten_hovered_children)
1193b4be Poul Sander 2025-02-03 21:28 541
        g.HoveredWindow = backup_hovered_window;
1193b4be Poul Sander 2025-02-03 21:28 542
1193b4be Poul Sander 2025-02-03 21:28 543
    // Mouse handling
1193b4be Poul Sander 2025-02-03 21:28 544
    const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
1193b4be Poul Sander 2025-02-03 21:28 545
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 546
    {
1193b4be Poul Sander 2025-02-03 21:28 547
        IM_ASSERT(id != 0); // Lazily check inside rare path.
1193b4be Poul Sander 2025-02-03 21:28 548
1193b4be Poul Sander 2025-02-03 21:28 549
        // Poll mouse buttons
1193b4be Poul Sander 2025-02-03 21:28 550
        // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
1193b4be Poul Sander 2025-02-03 21:28 551
        // - 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 552
        int mouse_button_clicked = -1;
1193b4be Poul Sander 2025-02-03 21:28 553
        int mouse_button_released = -1;
1193b4be Poul Sander 2025-02-03 21:28 554
        for (int button = 0; button < 3; button++)
1193b4be Poul Sander 2025-02-03 21:28 555
            if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
1193b4be Poul Sander 2025-02-03 21:28 556
            {
1193b4be Poul Sander 2025-02-03 21:28 557
                if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
1193b4be Poul Sander 2025-02-03 21:28 558
                if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
1193b4be Poul Sander 2025-02-03 21:28 559
            }
1193b4be Poul Sander 2025-02-03 21:28 560
1193b4be Poul Sander 2025-02-03 21:28 561
        // Process initial action
1193b4be Poul Sander 2025-02-03 21:28 562
        const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt);
1193b4be Poul Sander 2025-02-03 21:28 563
        if (mods_ok)
1193b4be Poul Sander 2025-02-03 21:28 564
        {
1193b4be Poul Sander 2025-02-03 21:28 565
            if (mouse_button_clicked != -1 && g.ActiveId != id)
1193b4be Poul Sander 2025-02-03 21:28 566
            {
1193b4be Poul Sander 2025-02-03 21:28 567
                if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
1193b4be Poul Sander 2025-02-03 21:28 568
                    SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
1193b4be Poul Sander 2025-02-03 21:28 569
                if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
1193b4be Poul Sander 2025-02-03 21:28 570
                {
1193b4be Poul Sander 2025-02-03 21:28 571
                    SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 572
                    g.ActiveIdMouseButton = mouse_button_clicked;
1193b4be Poul Sander 2025-02-03 21:28 573
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
1193b4be Poul Sander 2025-02-03 21:28 574
                    {
1193b4be Poul Sander 2025-02-03 21:28 575
                        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 576
                        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 577
                    }
1193b4be Poul Sander 2025-02-03 21:28 578
                    else
1193b4be Poul Sander 2025-02-03 21:28 579
                    {
1193b4be Poul Sander 2025-02-03 21:28 580
                        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 581
                    }
1193b4be Poul Sander 2025-02-03 21:28 582
                }
1193b4be Poul Sander 2025-02-03 21:28 583
                if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
1193b4be Poul Sander 2025-02-03 21:28 584
                {
1193b4be Poul Sander 2025-02-03 21:28 585
                    pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 586
                    if (flags & ImGuiButtonFlags_NoHoldingActiveId)
1193b4be Poul Sander 2025-02-03 21:28 587
                        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 588
                    else
1193b4be Poul Sander 2025-02-03 21:28 589
                        SetActiveID(id, window); // Hold on ID
1193b4be Poul Sander 2025-02-03 21:28 590
                    g.ActiveIdMouseButton = mouse_button_clicked;
1193b4be Poul Sander 2025-02-03 21:28 591
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
1193b4be Poul Sander 2025-02-03 21:28 592
                    {
1193b4be Poul Sander 2025-02-03 21:28 593
                        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 594
                        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 595
                    }
1193b4be Poul Sander 2025-02-03 21:28 596
                    else
1193b4be Poul Sander 2025-02-03 21:28 597
                    {
1193b4be Poul Sander 2025-02-03 21:28 598
                        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 599
                    }
1193b4be Poul Sander 2025-02-03 21:28 600
                }
1193b4be Poul Sander 2025-02-03 21:28 601
            }
1193b4be Poul Sander 2025-02-03 21:28 602
            if (flags & ImGuiButtonFlags_PressedOnRelease)
1193b4be Poul Sander 2025-02-03 21:28 603
            {
1193b4be Poul Sander 2025-02-03 21:28 604
                if (mouse_button_released != -1)
1193b4be Poul Sander 2025-02-03 21:28 605
                {
1193b4be Poul Sander 2025-02-03 21:28 606
                    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 607
                    if (!has_repeated_at_least_once)
1193b4be Poul Sander 2025-02-03 21:28 608
                        pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 609
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
1193b4be Poul Sander 2025-02-03 21:28 610
                        SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why.
1193b4be Poul Sander 2025-02-03 21:28 611
                    ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 612
                }
1193b4be Poul Sander 2025-02-03 21:28 613
            }
1193b4be Poul Sander 2025-02-03 21:28 614
1193b4be Poul Sander 2025-02-03 21:28 615
            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
1193b4be Poul Sander 2025-02-03 21:28 616
            // 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 617
            if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
1193b4be Poul Sander 2025-02-03 21:28 618
                if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id))
1193b4be Poul Sander 2025-02-03 21:28 619
                    pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 620
        }
1193b4be Poul Sander 2025-02-03 21:28 621
1193b4be Poul Sander 2025-02-03 21:28 622
        if (pressed && g.IO.ConfigNavCursorVisibleAuto)
1193b4be Poul Sander 2025-02-03 21:28 623
            g.NavCursorVisible = false;
1193b4be Poul Sander 2025-02-03 21:28 624
    }
1193b4be Poul Sander 2025-02-03 21:28 625
1193b4be Poul Sander 2025-02-03 21:28 626
    // Keyboard/Gamepad navigation handling
1193b4be Poul Sander 2025-02-03 21:28 627
    // 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 628
    if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)
1193b4be Poul Sander 2025-02-03 21:28 629
        if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
1193b4be Poul Sander 2025-02-03 21:28 630
            hovered = true;
1193b4be Poul Sander 2025-02-03 21:28 631
    if (g.NavActivateDownId == id)
1193b4be Poul Sander 2025-02-03 21:28 632
    {
1193b4be Poul Sander 2025-02-03 21:28 633
        bool nav_activated_by_code = (g.NavActivateId == id);
1193b4be Poul Sander 2025-02-03 21:28 634
        bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
1193b4be Poul Sander 2025-02-03 21:28 635
        if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
1193b4be Poul Sander 2025-02-03 21:28 636
        {
1193b4be Poul Sander 2025-02-03 21:28 637
            // Avoid pressing multiple keys from triggering excessive amount of repeat events
1193b4be Poul Sander 2025-02-03 21:28 638
            const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
1193b4be Poul Sander 2025-02-03 21:28 639
            const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
1193b4be Poul Sander 2025-02-03 21:28 640
            const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
1193b4be Poul Sander 2025-02-03 21:28 641
            const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
1193b4be Poul Sander 2025-02-03 21:28 642
            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 643
        }
1193b4be Poul Sander 2025-02-03 21:28 644
        if (nav_activated_by_code || nav_activated_by_inputs)
1193b4be Poul Sander 2025-02-03 21:28 645
        {
1193b4be Poul Sander 2025-02-03 21:28 646
            // 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 647
            pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 648
            SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 649
            g.ActiveIdSource = g.NavInputSource;
1193b4be Poul Sander 2025-02-03 21:28 650
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
1193b4be Poul Sander 2025-02-03 21:28 651
                SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 652
            if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
1193b4be Poul Sander 2025-02-03 21:28 653
                g.ActiveIdFromShortcut = true;
1193b4be Poul Sander 2025-02-03 21:28 654
        }
1193b4be Poul Sander 2025-02-03 21:28 655
    }
1193b4be Poul Sander 2025-02-03 21:28 656
1193b4be Poul Sander 2025-02-03 21:28 657
    // Process while held
1193b4be Poul Sander 2025-02-03 21:28 658
    bool held = false;
1193b4be Poul Sander 2025-02-03 21:28 659
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 660
    {
1193b4be Poul Sander 2025-02-03 21:28 661
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
1193b4be Poul Sander 2025-02-03 21:28 662
        {
1193b4be Poul Sander 2025-02-03 21:28 663
            if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 664
                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
1193b4be Poul Sander 2025-02-03 21:28 665
1193b4be Poul Sander 2025-02-03 21:28 666
            const int mouse_button = g.ActiveIdMouseButton;
1193b4be Poul Sander 2025-02-03 21:28 667
            if (mouse_button == -1)
1193b4be Poul Sander 2025-02-03 21:28 668
            {
1193b4be Poul Sander 2025-02-03 21:28 669
                // 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 670
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 671
            }
1193b4be Poul Sander 2025-02-03 21:28 672
            else if (IsMouseDown(mouse_button, test_owner_id))
1193b4be Poul Sander 2025-02-03 21:28 673
            {
1193b4be Poul Sander 2025-02-03 21:28 674
                held = true;
1193b4be Poul Sander 2025-02-03 21:28 675
            }
1193b4be Poul Sander 2025-02-03 21:28 676
            else
1193b4be Poul Sander 2025-02-03 21:28 677
            {
1193b4be Poul Sander 2025-02-03 21:28 678
                bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
1193b4be Poul Sander 2025-02-03 21:28 679
                bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
1193b4be Poul Sander 2025-02-03 21:28 680
                if ((release_in || release_anywhere) && !g.DragDropActive)
1193b4be Poul Sander 2025-02-03 21:28 681
                {
1193b4be Poul Sander 2025-02-03 21:28 682
                    // Report as pressed when releasing the mouse (this is the most common path)
1193b4be Poul Sander 2025-02-03 21:28 683
                    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 684
                    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 685
                    bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
1193b4be Poul Sander 2025-02-03 21:28 686
                    if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
1193b4be Poul Sander 2025-02-03 21:28 687
                        pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 688
                }
1193b4be Poul Sander 2025-02-03 21:28 689
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 690
            }
1193b4be Poul Sander 2025-02-03 21:28 691
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto)
1193b4be Poul Sander 2025-02-03 21:28 692
                g.NavCursorVisible = false;
1193b4be Poul Sander 2025-02-03 21:28 693
        }
1193b4be Poul Sander 2025-02-03 21:28 694
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
1193b4be Poul Sander 2025-02-03 21:28 695
        {
1193b4be Poul Sander 2025-02-03 21:28 696
            // When activated using Nav, we hold on the ActiveID until activation button is released
1193b4be Poul Sander 2025-02-03 21:28 697
            if (g.NavActivateDownId == id)
1193b4be Poul Sander 2025-02-03 21:28 698
                held = true; // hovered == true not true as we are already likely hovered on direct activation.
1193b4be Poul Sander 2025-02-03 21:28 699
            else
1193b4be Poul Sander 2025-02-03 21:28 700
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 701
        }
1193b4be Poul Sander 2025-02-03 21:28 702
        if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 703
            g.ActiveIdHasBeenPressedBefore = 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
    // Activation highlight (this may be a remote activation)
1193b4be Poul Sander 2025-02-03 21:28 707
    if (g.NavHighlightActivatedId == id)
1193b4be Poul Sander 2025-02-03 21:28 708
        hovered = true;
1193b4be Poul Sander 2025-02-03 21:28 709
1193b4be Poul Sander 2025-02-03 21:28 710
    if (out_hovered) *out_hovered = hovered;
1193b4be Poul Sander 2025-02-03 21:28 711
    if (out_held) *out_held = held;
1193b4be Poul Sander 2025-02-03 21:28 712
1193b4be Poul Sander 2025-02-03 21:28 713
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 714
}
1193b4be Poul Sander 2025-02-03 21:28 715
1193b4be Poul Sander 2025-02-03 21:28 716
bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 717
{
1193b4be Poul Sander 2025-02-03 21:28 718
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 719
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 720
        return false;
1193b4be Poul Sander 2025-02-03 21:28 721
1193b4be Poul Sander 2025-02-03 21:28 722
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 723
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 724
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 725
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 726
1193b4be Poul Sander 2025-02-03 21:28 727
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 728
    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 729
        pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 730
    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 731
1193b4be Poul Sander 2025-02-03 21:28 732
    const ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 733
    ItemSize(size, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 734
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 735
        return false;
1193b4be Poul Sander 2025-02-03 21:28 736
1193b4be Poul Sander 2025-02-03 21:28 737
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 738
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 739
1193b4be Poul Sander 2025-02-03 21:28 740
    // Render
1193b4be Poul Sander 2025-02-03 21:28 741
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 742
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 743
    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 744
1193b4be Poul Sander 2025-02-03 21:28 745
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 746
        LogSetNextTextDecoration("[", "]");
1193b4be Poul Sander 2025-02-03 21:28 747
    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
1193b4be Poul Sander 2025-02-03 21:28 748
1193b4be Poul Sander 2025-02-03 21:28 749
    // Automatically close popups
1193b4be Poul Sander 2025-02-03 21:28 750
    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
1193b4be Poul Sander 2025-02-03 21:28 751
    //    CloseCurrentPopup();
1193b4be Poul Sander 2025-02-03 21:28 752
1193b4be Poul Sander 2025-02-03 21:28 753
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 754
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 755
}
1193b4be Poul Sander 2025-02-03 21:28 756
1193b4be Poul Sander 2025-02-03 21:28 757
bool ImGui::Button(const char* label, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 758
{
1193b4be Poul Sander 2025-02-03 21:28 759
    return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 760
}
1193b4be Poul Sander 2025-02-03 21:28 761
1193b4be Poul Sander 2025-02-03 21:28 762
// Small buttons fits within text without additional vertical spacing.
1193b4be Poul Sander 2025-02-03 21:28 763
bool ImGui::SmallButton(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 764
{
1193b4be Poul Sander 2025-02-03 21:28 765
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 766
    float backup_padding_y = g.Style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 767
    g.Style.FramePadding.y = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 768
    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
1193b4be Poul Sander 2025-02-03 21:28 769
    g.Style.FramePadding.y = backup_padding_y;
1193b4be Poul Sander 2025-02-03 21:28 770
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 771
}
1193b4be Poul Sander 2025-02-03 21:28 772
1193b4be Poul Sander 2025-02-03 21:28 773
// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
1193b4be Poul Sander 2025-02-03 21:28 774
// 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 775
bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 776
{
1193b4be Poul Sander 2025-02-03 21:28 777
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 778
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 779
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 780
        return false;
1193b4be Poul Sander 2025-02-03 21:28 781
1193b4be Poul Sander 2025-02-03 21:28 782
    // 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 783
    IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 784
1193b4be Poul Sander 2025-02-03 21:28 785
    const ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 786
    ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 787
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 788
    ItemSize(size);
1193b4be Poul Sander 2025-02-03 21:28 789
    if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav))
1193b4be Poul Sander 2025-02-03 21:28 790
        return false;
1193b4be Poul Sander 2025-02-03 21:28 791
1193b4be Poul Sander 2025-02-03 21:28 792
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 793
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 794
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 795
1193b4be Poul Sander 2025-02-03 21:28 796
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 797
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 798
}
1193b4be Poul Sander 2025-02-03 21:28 799
1193b4be Poul Sander 2025-02-03 21:28 800
bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 801
{
1193b4be Poul Sander 2025-02-03 21:28 802
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 803
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 804
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 805
        return false;
1193b4be Poul Sander 2025-02-03 21:28 806
1193b4be Poul Sander 2025-02-03 21:28 807
    const ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 808
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 809
    const float default_size = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 810
    ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
1193b4be Poul Sander 2025-02-03 21:28 811
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 812
        return false;
1193b4be Poul Sander 2025-02-03 21:28 813
1193b4be Poul Sander 2025-02-03 21:28 814
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 815
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 816
1193b4be Poul Sander 2025-02-03 21:28 817
    // Render
1193b4be Poul Sander 2025-02-03 21:28 818
    const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 819
    const ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 820
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 821
    RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 822
    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 823
1193b4be Poul Sander 2025-02-03 21:28 824
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 825
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 826
}
1193b4be Poul Sander 2025-02-03 21:28 827
1193b4be Poul Sander 2025-02-03 21:28 828
bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
1193b4be Poul Sander 2025-02-03 21:28 829
{
1193b4be Poul Sander 2025-02-03 21:28 830
    float sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 831
    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 832
}
1193b4be Poul Sander 2025-02-03 21:28 833
1193b4be Poul Sander 2025-02-03 21:28 834
// Button to close a window
1193b4be Poul Sander 2025-02-03 21:28 835
bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
1193b4be Poul Sander 2025-02-03 21:28 836
{
1193b4be Poul Sander 2025-02-03 21:28 837
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 838
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 839
1193b4be Poul Sander 2025-02-03 21:28 840
    // 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 841
    // 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 842
    const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
1193b4be Poul Sander 2025-02-03 21:28 843
    ImRect bb_interact = bb;
1193b4be Poul Sander 2025-02-03 21:28 844
    const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
1193b4be Poul Sander 2025-02-03 21:28 845
    if (area_to_visible_ratio < 1.5f)
1193b4be Poul Sander 2025-02-03 21:28 846
        bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));
1193b4be Poul Sander 2025-02-03 21:28 847
1193b4be Poul Sander 2025-02-03 21:28 848
    // 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 849
    // (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 850
    bool is_clipped = !ItemAdd(bb_interact, id);
1193b4be Poul Sander 2025-02-03 21:28 851
1193b4be Poul Sander 2025-02-03 21:28 852
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 853
    bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 854
    if (is_clipped)
1193b4be Poul Sander 2025-02-03 21:28 855
        return pressed;
1193b4be Poul Sander 2025-02-03 21:28 856
1193b4be Poul Sander 2025-02-03 21:28 857
    // Render
1193b4be Poul Sander 2025-02-03 21:28 858
    ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
1193b4be Poul Sander 2025-02-03 21:28 859
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 860
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
1193b4be Poul Sander 2025-02-03 21:28 861
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
1193b4be Poul Sander 2025-02-03 21:28 862
    ImU32 cross_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 863
    ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 864
    float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 865
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 866
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 867
1193b4be Poul Sander 2025-02-03 21:28 868
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 869
}
1193b4be Poul Sander 2025-02-03 21:28 870
1193b4be Poul Sander 2025-02-03 21:28 871
// The Collapse button also functions as a Dock Menu button.
1193b4be Poul Sander 2025-02-03 21:28 872
bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
1193b4be Poul Sander 2025-02-03 21:28 873
{
1193b4be Poul Sander 2025-02-03 21:28 874
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 875
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 876
1193b4be Poul Sander 2025-02-03 21:28 877
    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
1193b4be Poul Sander 2025-02-03 21:28 878
    bool is_clipped = !ItemAdd(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 879
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 880
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 881
    if (is_clipped)
1193b4be Poul Sander 2025-02-03 21:28 882
        return pressed;
1193b4be Poul Sander 2025-02-03 21:28 883
1193b4be Poul Sander 2025-02-03 21:28 884
    // Render
1193b4be Poul Sander 2025-02-03 21:28 885
    //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
1193b4be Poul Sander 2025-02-03 21:28 886
    ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 887
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 888
    if (hovered || held)
1193b4be Poul Sander 2025-02-03 21:28 889
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
1193b4be Poul Sander 2025-02-03 21:28 890
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
1193b4be Poul Sander 2025-02-03 21:28 891
1193b4be Poul Sander 2025-02-03 21:28 892
    if (dock_node)
1193b4be Poul Sander 2025-02-03 21:28 893
        RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col);
1193b4be Poul Sander 2025-02-03 21:28 894
    else
1193b4be Poul Sander 2025-02-03 21:28 895
        RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 896
1193b4be Poul Sander 2025-02-03 21:28 897
    // Switch to moving the window after mouse is moved beyond the initial drag threshold
1193b4be Poul Sander 2025-02-03 21:28 898
    if (IsItemActive() && IsMouseDragging(0))
1193b4be Poul Sander 2025-02-03 21:28 899
        StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button
1193b4be Poul Sander 2025-02-03 21:28 900
1193b4be Poul Sander 2025-02-03 21:28 901
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 902
}
1193b4be Poul Sander 2025-02-03 21:28 903
1193b4be Poul Sander 2025-02-03 21:28 904
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
1193b4be Poul Sander 2025-02-03 21:28 905
{
1193b4be Poul Sander 2025-02-03 21:28 906
    return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
1193b4be Poul Sander 2025-02-03 21:28 907
}
1193b4be Poul Sander 2025-02-03 21:28 908
1193b4be Poul Sander 2025-02-03 21:28 909
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
1193b4be Poul Sander 2025-02-03 21:28 910
ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
1193b4be Poul Sander 2025-02-03 21:28 911
{
1193b4be Poul Sander 2025-02-03 21:28 912
    const ImRect outer_rect = window->Rect();
1193b4be Poul Sander 2025-02-03 21:28 913
    const ImRect inner_rect = window->InnerRect;
1193b4be Poul Sander 2025-02-03 21:28 914
    const float border_size = window->WindowBorderSize;
1193b4be Poul Sander 2025-02-03 21:28 915
    const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
1193b4be Poul Sander 2025-02-03 21:28 916
    IM_ASSERT(scrollbar_size > 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 917
    if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 918
        return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
1193b4be Poul Sander 2025-02-03 21:28 919
    else
1193b4be Poul Sander 2025-02-03 21:28 920
        return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
1193b4be Poul Sander 2025-02-03 21:28 921
}
1193b4be Poul Sander 2025-02-03 21:28 922
1193b4be Poul Sander 2025-02-03 21:28 923
void ImGui::Scrollbar(ImGuiAxis axis)
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
    const ImGuiID id = GetWindowScrollbarID(window, axis);
1193b4be Poul Sander 2025-02-03 21:28 928
1193b4be Poul Sander 2025-02-03 21:28 929
    // Calculate scrollbar bounding box
1193b4be Poul Sander 2025-02-03 21:28 930
    ImRect bb = GetWindowScrollbarRect(window, axis);
1193b4be Poul Sander 2025-02-03 21:28 931
    ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
1193b4be Poul Sander 2025-02-03 21:28 932
    if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 933
    {
1193b4be Poul Sander 2025-02-03 21:28 934
        rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
1193b4be Poul Sander 2025-02-03 21:28 935
        if (!window->ScrollbarY)
1193b4be Poul Sander 2025-02-03 21:28 936
            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
1193b4be Poul Sander 2025-02-03 21:28 937
    }
1193b4be Poul Sander 2025-02-03 21:28 938
    else
1193b4be Poul Sander 2025-02-03 21:28 939
    {
1193b4be Poul Sander 2025-02-03 21:28 940
        if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
1193b4be Poul Sander 2025-02-03 21:28 941
            rounding_corners |= ImDrawFlags_RoundCornersTopRight;
1193b4be Poul Sander 2025-02-03 21:28 942
        if (!window->ScrollbarX)
1193b4be Poul Sander 2025-02-03 21:28 943
            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
1193b4be Poul Sander 2025-02-03 21:28 944
    }
1193b4be Poul Sander 2025-02-03 21:28 945
    float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
1193b4be Poul Sander 2025-02-03 21:28 946
    float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 947
    ImS64 scroll = (ImS64)window->Scroll[axis];
1193b4be Poul Sander 2025-02-03 21:28 948
    ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners);
1193b4be Poul Sander 2025-02-03 21:28 949
    window->Scroll[axis] = (float)scroll;
1193b4be Poul Sander 2025-02-03 21:28 950
}
1193b4be Poul Sander 2025-02-03 21:28 951
1193b4be Poul Sander 2025-02-03 21:28 952
// Vertical/Horizontal scrollbar
1193b4be Poul Sander 2025-02-03 21:28 953
// The entire piece of code below is rather confusing because:
1193b4be Poul Sander 2025-02-03 21:28 954
// - 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 955
// - 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 956
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
1193b4be Poul Sander 2025-02-03 21:28 957
// Still, the code should probably be made simpler..
1193b4be Poul Sander 2025-02-03 21:28 958
bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 959
{
1193b4be Poul Sander 2025-02-03 21:28 960
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 961
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 962
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 963
        return false;
1193b4be Poul Sander 2025-02-03 21:28 964
1193b4be Poul Sander 2025-02-03 21:28 965
    const float bb_frame_width = bb_frame.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 966
    const float bb_frame_height = bb_frame.GetHeight();
1193b4be Poul Sander 2025-02-03 21:28 967
    if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 968
        return false;
1193b4be Poul Sander 2025-02-03 21:28 969
1193b4be Poul Sander 2025-02-03 21:28 970
    // 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 971
    float alpha = 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 972
    if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
1193b4be Poul Sander 2025-02-03 21:28 973
        alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 974
    if (alpha <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 975
        return false;
1193b4be Poul Sander 2025-02-03 21:28 976
1193b4be Poul Sander 2025-02-03 21:28 977
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 978
    const bool allow_interaction = (alpha >= 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 979
1193b4be Poul Sander 2025-02-03 21:28 980
    ImRect bb = bb_frame;
1193b4be Poul Sander 2025-02-03 21:28 981
    bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
1193b4be Poul Sander 2025-02-03 21:28 982
1193b4be Poul Sander 2025-02-03 21:28 983
    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
1193b4be Poul Sander 2025-02-03 21:28 984
    const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
1193b4be Poul Sander 2025-02-03 21:28 985
1193b4be Poul Sander 2025-02-03 21:28 986
    // 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 987
    // 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 988
    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 989
    const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
1193b4be Poul Sander 2025-02-03 21:28 990
    const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
1193b4be Poul Sander 2025-02-03 21:28 991
    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
1193b4be Poul Sander 2025-02-03 21:28 992
1193b4be Poul Sander 2025-02-03 21:28 993
    // 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 994
    bool held = false;
1193b4be Poul Sander 2025-02-03 21:28 995
    bool hovered = false;
1193b4be Poul Sander 2025-02-03 21:28 996
    ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
1193b4be Poul Sander 2025-02-03 21:28 997
    ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
1193b4be Poul Sander 2025-02-03 21:28 998
1193b4be Poul Sander 2025-02-03 21:28 999
    const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v);
1193b4be Poul Sander 2025-02-03 21:28 1000
    float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1001
    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 1002
    if (held && allow_interaction && grab_h_norm < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 1003
    {
1193b4be Poul Sander 2025-02-03 21:28 1004
        const float scrollbar_pos_v = bb.Min[axis];
1193b4be Poul Sander 2025-02-03 21:28 1005
        const float mouse_pos_v = g.IO.MousePos[axis];
1193b4be Poul Sander 2025-02-03 21:28 1006
1193b4be Poul Sander 2025-02-03 21:28 1007
        // Click position in scrollbar normalized space (0.0f->1.0f)
1193b4be Poul Sander 2025-02-03 21:28 1008
        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
1193b4be Poul Sander 2025-02-03 21:28 1009
1193b4be Poul Sander 2025-02-03 21:28 1010
        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 1011
        if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 1012
        {
1193b4be Poul Sander 2025-02-03 21:28 1013
            // 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 1014
            const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0);
1193b4be Poul Sander 2025-02-03 21:28 1015
            g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir;
1193b4be Poul Sander 2025-02-03 21:28 1016
            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 1017
        }
1193b4be Poul Sander 2025-02-03 21:28 1018
1193b4be Poul Sander 2025-02-03 21:28 1019
        // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
1193b4be Poul Sander 2025-02-03 21:28 1020
        // 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 1021
        if (g.ScrollbarSeekMode == 0)
1193b4be Poul Sander 2025-02-03 21:28 1022
        {
1193b4be Poul Sander 2025-02-03 21:28 1023
            // Absolute seeking
1193b4be Poul Sander 2025-02-03 21:28 1024
            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 1025
            *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1026
        }
1193b4be Poul Sander 2025-02-03 21:28 1027
        else
1193b4be Poul Sander 2025-02-03 21:28 1028
        {
1193b4be Poul Sander 2025-02-03 21:28 1029
            // Page by page
1193b4be Poul Sander 2025-02-03 21:28 1030
            if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode)
1193b4be Poul Sander 2025-02-03 21:28 1031
            {
1193b4be Poul Sander 2025-02-03 21:28 1032
                float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f;
1193b4be Poul Sander 2025-02-03 21:28 1033
                *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 1034
            }
1193b4be Poul Sander 2025-02-03 21:28 1035
        }
1193b4be Poul Sander 2025-02-03 21:28 1036
1193b4be Poul Sander 2025-02-03 21:28 1037
        // Update values for rendering
1193b4be Poul Sander 2025-02-03 21:28 1038
        scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1039
        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
1193b4be Poul Sander 2025-02-03 21:28 1040
1193b4be Poul Sander 2025-02-03 21:28 1041
        // Update distance to grab now that we have seek'ed and saturated
1193b4be Poul Sander 2025-02-03 21:28 1042
        //if (seek_absolute)
1193b4be Poul Sander 2025-02-03 21:28 1043
        //    g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 1044
    }
1193b4be Poul Sander 2025-02-03 21:28 1045
1193b4be Poul Sander 2025-02-03 21:28 1046
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1047
    const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
1193b4be Poul Sander 2025-02-03 21:28 1048
    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
1193b4be Poul Sander 2025-02-03 21:28 1049
    window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
1193b4be Poul Sander 2025-02-03 21:28 1050
    ImRect grab_rect;
1193b4be Poul Sander 2025-02-03 21:28 1051
    if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 1052
        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 1053
    else
1193b4be Poul Sander 2025-02-03 21:28 1054
        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 1055
    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
1193b4be Poul Sander 2025-02-03 21:28 1056
1193b4be Poul Sander 2025-02-03 21:28 1057
    return held;
1193b4be Poul Sander 2025-02-03 21:28 1058
}
1193b4be Poul Sander 2025-02-03 21:28 1059
1193b4be Poul Sander 2025-02-03 21:28 1060
// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
1193b4be Poul Sander 2025-02-03 21:28 1061
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
1193b4be Poul Sander 2025-02-03 21:28 1062
void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
1193b4be Poul Sander 2025-02-03 21:28 1063
{
1193b4be Poul Sander 2025-02-03 21:28 1064
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1065
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1066
        return;
1193b4be Poul Sander 2025-02-03 21:28 1067
1193b4be Poul Sander 2025-02-03 21:28 1068
    const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1069
    const ImVec2 padding(border_size, border_size);
1193b4be Poul Sander 2025-02-03 21:28 1070
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1071
    ItemSize(bb);
1193b4be Poul Sander 2025-02-03 21:28 1072
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1073
        return;
1193b4be Poul Sander 2025-02-03 21:28 1074
1193b4be Poul Sander 2025-02-03 21:28 1075
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1076
    if (border_size > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1077
        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size);
1193b4be Poul Sander 2025-02-03 21:28 1078
    window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1193b4be Poul Sander 2025-02-03 21:28 1079
}
1193b4be Poul Sander 2025-02-03 21:28 1080
1193b4be Poul Sander 2025-02-03 21:28 1081
// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
1193b4be Poul Sander 2025-02-03 21:28 1082
// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
1193b4be Poul Sander 2025-02-03 21:28 1083
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 1084
{
1193b4be Poul Sander 2025-02-03 21:28 1085
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1086
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1087
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1088
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1089
1193b4be Poul Sander 2025-02-03 21:28 1090
    const ImVec2 padding = g.Style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 1091
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1092
    ItemSize(bb);
1193b4be Poul Sander 2025-02-03 21:28 1093
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1094
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1095
1193b4be Poul Sander 2025-02-03 21:28 1096
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1097
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 1098
1193b4be Poul Sander 2025-02-03 21:28 1099
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1100
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 1101
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1102
    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 1103
    if (bg_col.w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1104
        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
1193b4be Poul Sander 2025-02-03 21:28 1105
    window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1193b4be Poul Sander 2025-02-03 21:28 1106
1193b4be Poul Sander 2025-02-03 21:28 1107
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1108
}
1193b4be Poul Sander 2025-02-03 21:28 1109
1193b4be Poul Sander 2025-02-03 21:28 1110
// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
1193b4be Poul Sander 2025-02-03 21:28 1111
bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
1193b4be Poul Sander 2025-02-03 21:28 1112
{
1193b4be Poul Sander 2025-02-03 21:28 1113
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1114
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1115
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1116
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1117
1193b4be Poul Sander 2025-02-03 21:28 1118
    return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col);
1193b4be Poul Sander 2025-02-03 21:28 1119
}
1193b4be Poul Sander 2025-02-03 21:28 1120
1193b4be Poul Sander 2025-02-03 21:28 1121
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 1122
// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
1193b4be Poul Sander 2025-02-03 21:28 1123
// - 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 1124
// - new ImageButton() requires an explicit 'const char* str_id'
1193b4be Poul Sander 2025-02-03 21:28 1125
// - old ImageButton() had frame_padding' override argument.
1193b4be Poul Sander 2025-02-03 21:28 1126
// - new ImageButton() always use style.FramePadding.
1193b4be Poul Sander 2025-02-03 21:28 1127
/*
1193b4be Poul Sander 2025-02-03 21:28 1128
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 1129
{
1193b4be Poul Sander 2025-02-03 21:28 1130
    // Default to using texture ID as ID. User can still push string/integer prefixes.
1193b4be Poul Sander 2025-02-03 21:28 1131
    PushID((ImTextureID)(intptr_t)user_texture_id);
1193b4be Poul Sander 2025-02-03 21:28 1132
    if (frame_padding >= 0)
1193b4be Poul Sander 2025-02-03 21:28 1133
        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
1193b4be Poul Sander 2025-02-03 21:28 1134
    bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col);
1193b4be Poul Sander 2025-02-03 21:28 1135
    if (frame_padding >= 0)
1193b4be Poul Sander 2025-02-03 21:28 1136
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 1137
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 1138
    return ret;
1193b4be Poul Sander 2025-02-03 21:28 1139
}
1193b4be Poul Sander 2025-02-03 21:28 1140
*/
1193b4be Poul Sander 2025-02-03 21:28 1141
#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 1142
1193b4be Poul Sander 2025-02-03 21:28 1143
bool ImGui::Checkbox(const char* label, bool* v)
1193b4be Poul Sander 2025-02-03 21:28 1144
{
1193b4be Poul Sander 2025-02-03 21:28 1145
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1146
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1147
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1148
1193b4be Poul Sander 2025-02-03 21:28 1149
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1150
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1151
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1152
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 1153
1193b4be Poul Sander 2025-02-03 21:28 1154
    const float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 1155
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1156
    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 1157
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1158
    const bool is_visible = ItemAdd(total_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1159
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 1160
    if (!is_visible)
1193b4be Poul Sander 2025-02-03 21:28 1161
        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 1162
        {
1193b4be Poul Sander 2025-02-03 21:28 1163
            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1193b4be Poul Sander 2025-02-03 21:28 1164
            return false;
1193b4be Poul Sander 2025-02-03 21:28 1165
        }
1193b4be Poul Sander 2025-02-03 21:28 1166
1193b4be Poul Sander 2025-02-03 21:28 1167
    // Range-Selection/Multi-selection support (header)
1193b4be Poul Sander 2025-02-03 21:28 1168
    bool checked = *v;
1193b4be Poul Sander 2025-02-03 21:28 1169
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 1170
        MultiSelectItemHeader(id, &checked, NULL);
1193b4be Poul Sander 2025-02-03 21:28 1171
1193b4be Poul Sander 2025-02-03 21:28 1172
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1173
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1174
1193b4be Poul Sander 2025-02-03 21:28 1175
    // Range-Selection/Multi-selection support (footer)
1193b4be Poul Sander 2025-02-03 21:28 1176
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 1177
        MultiSelectItemFooter(id, &checked, &pressed);
1193b4be Poul Sander 2025-02-03 21:28 1178
    else if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1179
        checked = !checked;
1193b4be Poul Sander 2025-02-03 21:28 1180
1193b4be Poul Sander 2025-02-03 21:28 1181
    if (*v != checked)
1193b4be Poul Sander 2025-02-03 21:28 1182
    {
1193b4be Poul Sander 2025-02-03 21:28 1183
        *v = checked;
1193b4be Poul Sander 2025-02-03 21:28 1184
        pressed = true; // return value
1193b4be Poul Sander 2025-02-03 21:28 1185
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 1186
    }
1193b4be Poul Sander 2025-02-03 21:28 1187
1193b4be Poul Sander 2025-02-03 21:28 1188
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1193b4be Poul Sander 2025-02-03 21:28 1189
    const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;
1193b4be Poul Sander 2025-02-03 21:28 1190
    if (is_visible)
1193b4be Poul Sander 2025-02-03 21:28 1191
    {
1193b4be Poul Sander 2025-02-03 21:28 1192
        RenderNavCursor(total_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1193
        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 1194
        ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1193b4be Poul Sander 2025-02-03 21:28 1195
        if (mixed_value)
1193b4be Poul Sander 2025-02-03 21:28 1196
        {
1193b4be Poul Sander 2025-02-03 21:28 1197
            // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1193b4be Poul Sander 2025-02-03 21:28 1198
            // 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 1199
            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 1200
            window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1201
        }
1193b4be Poul Sander 2025-02-03 21:28 1202
        else if (*v)
1193b4be Poul Sander 2025-02-03 21:28 1203
        {
1193b4be Poul Sander 2025-02-03 21:28 1204
            const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1193b4be Poul Sander 2025-02-03 21:28 1205
            RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1206
        }
1193b4be Poul Sander 2025-02-03 21:28 1207
    }
1193b4be Poul Sander 2025-02-03 21:28 1208
    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 1209
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1210
        LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1193b4be Poul Sander 2025-02-03 21:28 1211
    if (is_visible && label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1212
        RenderText(label_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 1213
1193b4be Poul Sander 2025-02-03 21:28 1214
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1193b4be Poul Sander 2025-02-03 21:28 1215
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1216
}
1193b4be Poul Sander 2025-02-03 21:28 1217
1193b4be Poul Sander 2025-02-03 21:28 1218
template<typename T>
1193b4be Poul Sander 2025-02-03 21:28 1219
bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1220
{
1193b4be Poul Sander 2025-02-03 21:28 1221
    bool all_on = (*flags & flags_value) == flags_value;
1193b4be Poul Sander 2025-02-03 21:28 1222
    bool any_on = (*flags & flags_value) != 0;
1193b4be Poul Sander 2025-02-03 21:28 1223
    bool pressed;
1193b4be Poul Sander 2025-02-03 21:28 1224
    if (!all_on && any_on)
1193b4be Poul Sander 2025-02-03 21:28 1225
    {
1193b4be Poul Sander 2025-02-03 21:28 1226
        ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1227
        g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
1193b4be Poul Sander 2025-02-03 21:28 1228
        pressed = Checkbox(label, &all_on);
1193b4be Poul Sander 2025-02-03 21:28 1229
    }
1193b4be Poul Sander 2025-02-03 21:28 1230
    else
1193b4be Poul Sander 2025-02-03 21:28 1231
    {
1193b4be Poul Sander 2025-02-03 21:28 1232
        pressed = Checkbox(label, &all_on);
1193b4be Poul Sander 2025-02-03 21:28 1233
1193b4be Poul Sander 2025-02-03 21:28 1234
    }
1193b4be Poul Sander 2025-02-03 21:28 1235
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1236
    {
1193b4be Poul Sander 2025-02-03 21:28 1237
        if (all_on)
1193b4be Poul Sander 2025-02-03 21:28 1238
            *flags |= flags_value;
1193b4be Poul Sander 2025-02-03 21:28 1239
        else
1193b4be Poul Sander 2025-02-03 21:28 1240
            *flags &= ~flags_value;
1193b4be Poul Sander 2025-02-03 21:28 1241
    }
1193b4be Poul Sander 2025-02-03 21:28 1242
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1243
}
1193b4be Poul Sander 2025-02-03 21:28 1244
1193b4be Poul Sander 2025-02-03 21:28 1245
bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1246
{
1193b4be Poul Sander 2025-02-03 21:28 1247
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1248
}
1193b4be Poul Sander 2025-02-03 21:28 1249
1193b4be Poul Sander 2025-02-03 21:28 1250
bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1251
{
1193b4be Poul Sander 2025-02-03 21:28 1252
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1253
}
1193b4be Poul Sander 2025-02-03 21:28 1254
1193b4be Poul Sander 2025-02-03 21:28 1255
bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1256
{
1193b4be Poul Sander 2025-02-03 21:28 1257
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1258
}
1193b4be Poul Sander 2025-02-03 21:28 1259
1193b4be Poul Sander 2025-02-03 21:28 1260
bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1261
{
1193b4be Poul Sander 2025-02-03 21:28 1262
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1263
}
1193b4be Poul Sander 2025-02-03 21:28 1264
1193b4be Poul Sander 2025-02-03 21:28 1265
bool ImGui::RadioButton(const char* label, bool active)
1193b4be Poul Sander 2025-02-03 21:28 1266
{
1193b4be Poul Sander 2025-02-03 21:28 1267
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1268
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1269
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1270
1193b4be Poul Sander 2025-02-03 21:28 1271
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1272
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1273
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1274
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 1275
1193b4be Poul Sander 2025-02-03 21:28 1276
    const float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 1277
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1278
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1193b4be Poul Sander 2025-02-03 21:28 1279
    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 1280
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1281
    if (!ItemAdd(total_bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1282
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1283
1193b4be Poul Sander 2025-02-03 21:28 1284
    ImVec2 center = check_bb.GetCenter();
1193b4be Poul Sander 2025-02-03 21:28 1285
    center.x = IM_ROUND(center.x);
1193b4be Poul Sander 2025-02-03 21:28 1286
    center.y = IM_ROUND(center.y);
1193b4be Poul Sander 2025-02-03 21:28 1287
    const float radius = (square_sz - 1.0f) * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 1288
1193b4be Poul Sander 2025-02-03 21:28 1289
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1290
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1291
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1292
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 1293
1193b4be Poul Sander 2025-02-03 21:28 1294
    RenderNavCursor(total_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1295
    const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1193b4be Poul Sander 2025-02-03 21:28 1296
    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 1297
    if (active)
1193b4be Poul Sander 2025-02-03 21:28 1298
    {
1193b4be Poul Sander 2025-02-03 21:28 1299
        const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1193b4be Poul Sander 2025-02-03 21:28 1300
        window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
1193b4be Poul Sander 2025-02-03 21:28 1301
    }
1193b4be Poul Sander 2025-02-03 21:28 1302
1193b4be Poul Sander 2025-02-03 21:28 1303
    if (style.FrameBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1304
    {
1193b4be Poul Sander 2025-02-03 21:28 1305
        window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 1306
        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);
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
    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 1310
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1311
        LogRenderedText(&label_pos, active ? "(x)" : "( )");
1193b4be Poul Sander 2025-02-03 21:28 1312
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1313
        RenderText(label_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 1314
1193b4be Poul Sander 2025-02-03 21:28 1315
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
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
// 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 1320
bool ImGui::RadioButton(const char* label, int* v, int v_button)
1193b4be Poul Sander 2025-02-03 21:28 1321
{
1193b4be Poul Sander 2025-02-03 21:28 1322
    const bool pressed = RadioButton(label, *v == v_button);
1193b4be Poul Sander 2025-02-03 21:28 1323
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1324
        *v = v_button;
1193b4be Poul Sander 2025-02-03 21:28 1325
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1326
}
1193b4be Poul Sander 2025-02-03 21:28 1327
1193b4be Poul Sander 2025-02-03 21:28 1328
// 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 1329
void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1193b4be Poul Sander 2025-02-03 21:28 1330
{
1193b4be Poul Sander 2025-02-03 21:28 1331
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1332
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1333
        return;
1193b4be Poul Sander 2025-02-03 21:28 1334
1193b4be Poul Sander 2025-02-03 21:28 1335
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1336
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1337
1193b4be Poul Sander 2025-02-03 21:28 1338
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1339
    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1340
    ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 1341
    ItemSize(size, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1342
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1343
        return;
1193b4be Poul Sander 2025-02-03 21:28 1344
1193b4be Poul Sander 2025-02-03 21:28 1345
    // Fraction < 0.0f will display an indeterminate progress bar animation
1193b4be Poul Sander 2025-02-03 21:28 1346
    // 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 1347
    const bool is_indeterminate = (fraction < 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1348
    if (!is_indeterminate)
1193b4be Poul Sander 2025-02-03 21:28 1349
        fraction = ImSaturate(fraction);
1193b4be Poul Sander 2025-02-03 21:28 1350
1193b4be Poul Sander 2025-02-03 21:28 1351
    // Out of courtesy we accept a NaN fraction without crashing
1193b4be Poul Sander 2025-02-03 21:28 1352
    float fill_n0 = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1353
    float fill_n1 = (fraction == fraction) ? fraction : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1354
1193b4be Poul Sander 2025-02-03 21:28 1355
    if (is_indeterminate)
1193b4be Poul Sander 2025-02-03 21:28 1356
    {
1193b4be Poul Sander 2025-02-03 21:28 1357
        const float fill_width_n = 0.2f;
1193b4be Poul Sander 2025-02-03 21:28 1358
        fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n;
1193b4be Poul Sander 2025-02-03 21:28 1359
        fill_n1 = ImSaturate(fill_n0 + fill_width_n);
1193b4be Poul Sander 2025-02-03 21:28 1360
        fill_n0 = ImSaturate(fill_n0);
1193b4be Poul Sander 2025-02-03 21:28 1361
    }
1193b4be Poul Sander 2025-02-03 21:28 1362
1193b4be Poul Sander 2025-02-03 21:28 1363
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1364
    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1365
    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1193b4be Poul Sander 2025-02-03 21:28 1366
    RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1367
1193b4be Poul Sander 2025-02-03 21:28 1368
    // Default displaying the fraction as percentage string, but user can override it
1193b4be Poul Sander 2025-02-03 21:28 1369
    // Don't display text for indeterminate bars by default
1193b4be Poul Sander 2025-02-03 21:28 1370
    char overlay_buf[32];
1193b4be Poul Sander 2025-02-03 21:28 1371
    if (!is_indeterminate || overlay != NULL)
1193b4be Poul Sander 2025-02-03 21:28 1372
    {
1193b4be Poul Sander 2025-02-03 21:28 1373
        if (!overlay)
1193b4be Poul Sander 2025-02-03 21:28 1374
        {
1193b4be Poul Sander 2025-02-03 21:28 1375
            ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1193b4be Poul Sander 2025-02-03 21:28 1376
            overlay = overlay_buf;
1193b4be Poul Sander 2025-02-03 21:28 1377
        }
1193b4be Poul Sander 2025-02-03 21:28 1378
1193b4be Poul Sander 2025-02-03 21:28 1379
        ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1193b4be Poul Sander 2025-02-03 21:28 1380
        if (overlay_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1381
        {
1193b4be Poul Sander 2025-02-03 21:28 1382
            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 1383
            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 1384
        }
1193b4be Poul Sander 2025-02-03 21:28 1385
    }
1193b4be Poul Sander 2025-02-03 21:28 1386
}
1193b4be Poul Sander 2025-02-03 21:28 1387
1193b4be Poul Sander 2025-02-03 21:28 1388
void ImGui::Bullet()
1193b4be Poul Sander 2025-02-03 21:28 1389
{
1193b4be Poul Sander 2025-02-03 21:28 1390
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1391
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1392
        return;
1193b4be Poul Sander 2025-02-03 21:28 1393
1193b4be Poul Sander 2025-02-03 21:28 1394
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1395
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1396
    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 1397
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1193b4be Poul Sander 2025-02-03 21:28 1398
    ItemSize(bb);
1193b4be Poul Sander 2025-02-03 21:28 1399
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1400
    {
1193b4be Poul Sander 2025-02-03 21:28 1401
        SameLine(0, style.FramePadding.x * 2);
1193b4be Poul Sander 2025-02-03 21:28 1402
        return;
1193b4be Poul Sander 2025-02-03 21:28 1403
    }
1193b4be Poul Sander 2025-02-03 21:28 1404
1193b4be Poul Sander 2025-02-03 21:28 1405
    // Render and stay on same line
1193b4be Poul Sander 2025-02-03 21:28 1406
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 1407
    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 1408
    SameLine(0, style.FramePadding.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1409
}
1193b4be Poul Sander 2025-02-03 21:28 1410
1193b4be Poul Sander 2025-02-03 21:28 1411
// This is provided as a convenience for being an often requested feature.
1193b4be Poul Sander 2025-02-03 21:28 1412
// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system.
1193b4be Poul Sander 2025-02-03 21:28 1413
// Because of this we currently don't provide many styling options for this widget
1193b4be Poul Sander 2025-02-03 21:28 1414
// (e.g. hovered/active colors are automatically inferred from a single color).
1193b4be Poul Sander 2025-02-03 21:28 1415
bool ImGui::TextLink(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 1416
{
1193b4be Poul Sander 2025-02-03 21:28 1417
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1418
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1419
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1420
1193b4be Poul Sander 2025-02-03 21:28 1421
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1422
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1423
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 1424
1193b4be Poul Sander 2025-02-03 21:28 1425
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1426
    ImVec2 size = CalcTextSize(label, label_end, true);
1193b4be Poul Sander 2025-02-03 21:28 1427
    ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 1428
    ItemSize(size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1429
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1430
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1431
1193b4be Poul Sander 2025-02-03 21:28 1432
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1433
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1434
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1435
1193b4be Poul Sander 2025-02-03 21:28 1436
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 1437
        SetMouseCursor(ImGuiMouseCursor_Hand);
1193b4be Poul Sander 2025-02-03 21:28 1438
1193b4be Poul Sander 2025-02-03 21:28 1439
    ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink];
1193b4be Poul Sander 2025-02-03 21:28 1440
    ImVec4 line_colf = text_colf;
1193b4be Poul Sander 2025-02-03 21:28 1441
    {
1193b4be Poul Sander 2025-02-03 21:28 1442
        // 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 1443
        // as we are currently experimenting/planning a different styling system.
1193b4be Poul Sander 2025-02-03 21:28 1444
        float h, s, v;
1193b4be Poul Sander 2025-02-03 21:28 1445
        ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v);
1193b4be Poul Sander 2025-02-03 21:28 1446
        if (held || hovered)
1193b4be Poul Sander 2025-02-03 21:28 1447
        {
1193b4be Poul Sander 2025-02-03 21:28 1448
            v = ImSaturate(v + (held ? 0.4f : 0.3f));
1193b4be Poul Sander 2025-02-03 21:28 1449
            h = ImFmod(h + 0.02f, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1450
        }
1193b4be Poul Sander 2025-02-03 21:28 1451
        ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z);
1193b4be Poul Sander 2025-02-03 21:28 1452
        v = ImSaturate(v - 0.20f);
1193b4be Poul Sander 2025-02-03 21:28 1453
        ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
1193b4be Poul Sander 2025-02-03 21:28 1454
    }
1193b4be Poul Sander 2025-02-03 21:28 1455
1193b4be Poul Sander 2025-02-03 21:28 1456
    float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f);
1193b4be Poul Sander 2025-02-03 21:28 1457
    window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode.
1193b4be Poul Sander 2025-02-03 21:28 1458
1193b4be Poul Sander 2025-02-03 21:28 1459
    PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
1193b4be Poul Sander 2025-02-03 21:28 1460
    RenderText(bb.Min, label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 1461
    PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 1462
1193b4be Poul Sander 2025-02-03 21:28 1463
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 1464
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1465
}
1193b4be Poul Sander 2025-02-03 21:28 1466
1193b4be Poul Sander 2025-02-03 21:28 1467
void ImGui::TextLinkOpenURL(const char* label, const char* url)
1193b4be Poul Sander 2025-02-03 21:28 1468
{
1193b4be Poul Sander 2025-02-03 21:28 1469
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1470
    if (url == NULL)
1193b4be Poul Sander 2025-02-03 21:28 1471
        url = label;
1193b4be Poul Sander 2025-02-03 21:28 1472
    if (TextLink(label))
1193b4be Poul Sander 2025-02-03 21:28 1473
        if (g.PlatformIO.Platform_OpenInShellFn != NULL)
1193b4be Poul Sander 2025-02-03 21:28 1474
            g.PlatformIO.Platform_OpenInShellFn(&g, url);
1193b4be Poul Sander 2025-02-03 21:28 1475
    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 1476
    if (BeginPopupContextItem())
1193b4be Poul Sander 2025-02-03 21:28 1477
    {
1193b4be Poul Sander 2025-02-03 21:28 1478
        if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))
1193b4be Poul Sander 2025-02-03 21:28 1479
            SetClipboardText(url);
1193b4be Poul Sander 2025-02-03 21:28 1480
        EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 1481
    }
1193b4be Poul Sander 2025-02-03 21:28 1482
}
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
// [SECTION] Widgets: Low-level Layout helpers
1193b4be Poul Sander 2025-02-03 21:28 1486
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1487
// - Spacing()
1193b4be Poul Sander 2025-02-03 21:28 1488
// - Dummy()
1193b4be Poul Sander 2025-02-03 21:28 1489
// - NewLine()
1193b4be Poul Sander 2025-02-03 21:28 1490
// - AlignTextToFramePadding()
1193b4be Poul Sander 2025-02-03 21:28 1491
// - SeparatorEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1492
// - Separator()
1193b4be Poul Sander 2025-02-03 21:28 1493
// - SplitterBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1494
// - ShrinkWidths() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1495
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1496
1193b4be Poul Sander 2025-02-03 21:28 1497
void ImGui::Spacing()
1193b4be Poul Sander 2025-02-03 21:28 1498
{
1193b4be Poul Sander 2025-02-03 21:28 1499
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1500
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1501
        return;
1193b4be Poul Sander 2025-02-03 21:28 1502
    ItemSize(ImVec2(0, 0));
1193b4be Poul Sander 2025-02-03 21:28 1503
}
1193b4be Poul Sander 2025-02-03 21:28 1504
1193b4be Poul Sander 2025-02-03 21:28 1505
void ImGui::Dummy(const ImVec2& size)
1193b4be Poul Sander 2025-02-03 21:28 1506
{
1193b4be Poul Sander 2025-02-03 21:28 1507
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1508
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1509
        return;
1193b4be Poul Sander 2025-02-03 21:28 1510
1193b4be Poul Sander 2025-02-03 21:28 1511
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 1512
    ItemSize(size);
1193b4be Poul Sander 2025-02-03 21:28 1513
    ItemAdd(bb, 0);
1193b4be Poul Sander 2025-02-03 21:28 1514
}
1193b4be Poul Sander 2025-02-03 21:28 1515
1193b4be Poul Sander 2025-02-03 21:28 1516
void ImGui::NewLine()
1193b4be Poul Sander 2025-02-03 21:28 1517
{
1193b4be Poul Sander 2025-02-03 21:28 1518
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1519
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1520
        return;
1193b4be Poul Sander 2025-02-03 21:28 1521
1193b4be Poul Sander 2025-02-03 21:28 1522
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1523
    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1193b4be Poul Sander 2025-02-03 21:28 1524
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
1193b4be Poul Sander 2025-02-03 21:28 1525
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 1526
    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 1527
        ItemSize(ImVec2(0, 0));
1193b4be Poul Sander 2025-02-03 21:28 1528
    else
1193b4be Poul Sander 2025-02-03 21:28 1529
        ItemSize(ImVec2(0.0f, g.FontSize));
1193b4be Poul Sander 2025-02-03 21:28 1530
    window->DC.LayoutType = backup_layout_type;
1193b4be Poul Sander 2025-02-03 21:28 1531
}
1193b4be Poul Sander 2025-02-03 21:28 1532
1193b4be Poul Sander 2025-02-03 21:28 1533
void ImGui::AlignTextToFramePadding()
1193b4be Poul Sander 2025-02-03 21:28 1534
{
1193b4be Poul Sander 2025-02-03 21:28 1535
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1536
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1537
        return;
1193b4be Poul Sander 2025-02-03 21:28 1538
1193b4be Poul Sander 2025-02-03 21:28 1539
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1540
    window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1193b4be Poul Sander 2025-02-03 21:28 1541
    window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1542
}
1193b4be Poul Sander 2025-02-03 21:28 1543
1193b4be Poul Sander 2025-02-03 21:28 1544
// Horizontal/vertical separating line
1193b4be Poul Sander 2025-02-03 21:28 1545
// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1193b4be Poul Sander 2025-02-03 21:28 1546
// 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 1547
void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1193b4be Poul Sander 2025-02-03 21:28 1548
{
1193b4be Poul Sander 2025-02-03 21:28 1549
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1550
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1551
        return;
1193b4be Poul Sander 2025-02-03 21:28 1552
1193b4be Poul Sander 2025-02-03 21:28 1553
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1554
    IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 1555
    IM_ASSERT(thickness > 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1556
1193b4be Poul Sander 2025-02-03 21:28 1557
    if (flags & ImGuiSeparatorFlags_Vertical)
1193b4be Poul Sander 2025-02-03 21:28 1558
    {
1193b4be Poul Sander 2025-02-03 21:28 1559
        // Vertical separator, for menu bars (use current line height).
1193b4be Poul Sander 2025-02-03 21:28 1560
        float y1 = window->DC.CursorPos.y;
1193b4be Poul Sander 2025-02-03 21:28 1561
        float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1193b4be Poul Sander 2025-02-03 21:28 1562
        const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1193b4be Poul Sander 2025-02-03 21:28 1563
        ItemSize(ImVec2(thickness, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 1564
        if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1565
            return;
1193b4be Poul Sander 2025-02-03 21:28 1566
1193b4be Poul Sander 2025-02-03 21:28 1567
        // Draw
1193b4be Poul Sander 2025-02-03 21:28 1568
        window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1193b4be Poul Sander 2025-02-03 21:28 1569
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1570
            LogText(" |");
1193b4be Poul Sander 2025-02-03 21:28 1571
    }
1193b4be Poul Sander 2025-02-03 21:28 1572
    else if (flags & ImGuiSeparatorFlags_Horizontal)
1193b4be Poul Sander 2025-02-03 21:28 1573
    {
1193b4be Poul Sander 2025-02-03 21:28 1574
        // Horizontal Separator
1193b4be Poul Sander 2025-02-03 21:28 1575
        float x1 = window->DC.CursorPos.x;
1193b4be Poul Sander 2025-02-03 21:28 1576
        float x2 = window->WorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 1577
1193b4be Poul Sander 2025-02-03 21:28 1578
        // Preserve legacy behavior inside Columns()
1193b4be Poul Sander 2025-02-03 21:28 1579
        // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1193b4be Poul Sander 2025-02-03 21:28 1580
        // 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 1581
        ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1193b4be Poul Sander 2025-02-03 21:28 1582
        if (columns)
1193b4be Poul Sander 2025-02-03 21:28 1583
        {
1193b4be Poul Sander 2025-02-03 21:28 1584
            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 1585
            x2 = window->Pos.x + window->Size.x;
1193b4be Poul Sander 2025-02-03 21:28 1586
            PushColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 1587
        }
1193b4be Poul Sander 2025-02-03 21:28 1588
1193b4be Poul Sander 2025-02-03 21:28 1589
        // 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 1590
        // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1193b4be Poul Sander 2025-02-03 21:28 1591
        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 1592
        const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1193b4be Poul Sander 2025-02-03 21:28 1593
        ItemSize(ImVec2(0.0f, thickness_for_layout));
1193b4be Poul Sander 2025-02-03 21:28 1594
1193b4be Poul Sander 2025-02-03 21:28 1595
        if (ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1596
        {
1193b4be Poul Sander 2025-02-03 21:28 1597
            // Draw
1193b4be Poul Sander 2025-02-03 21:28 1598
            window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1193b4be Poul Sander 2025-02-03 21:28 1599
            if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1600
                LogRenderedText(&bb.Min, "--------------------------------\n");
1193b4be Poul Sander 2025-02-03 21:28 1601
1193b4be Poul Sander 2025-02-03 21:28 1602
        }
1193b4be Poul Sander 2025-02-03 21:28 1603
        if (columns)
1193b4be Poul Sander 2025-02-03 21:28 1604
        {
1193b4be Poul Sander 2025-02-03 21:28 1605
            PopColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 1606
            columns->LineMinY = window->DC.CursorPos.y;
1193b4be Poul Sander 2025-02-03 21:28 1607
        }
1193b4be Poul Sander 2025-02-03 21:28 1608
    }
1193b4be Poul Sander 2025-02-03 21:28 1609
}
1193b4be Poul Sander 2025-02-03 21:28 1610
1193b4be Poul Sander 2025-02-03 21:28 1611
void ImGui::Separator()
1193b4be Poul Sander 2025-02-03 21:28 1612
{
1193b4be Poul Sander 2025-02-03 21:28 1613
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1614
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1615
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1616
        return;
1193b4be Poul Sander 2025-02-03 21:28 1617
1193b4be Poul Sander 2025-02-03 21:28 1618
    // Those flags should eventually be configurable by the user
1193b4be Poul Sander 2025-02-03 21:28 1619
    // 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 1620
    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1193b4be Poul Sander 2025-02-03 21:28 1621
1193b4be Poul Sander 2025-02-03 21:28 1622
    // Only applies to legacy Columns() api as they relied on Separator() a lot.
1193b4be Poul Sander 2025-02-03 21:28 1623
    if (window->DC.CurrentColumns)
1193b4be Poul Sander 2025-02-03 21:28 1624
        flags |= ImGuiSeparatorFlags_SpanAllColumns;
1193b4be Poul Sander 2025-02-03 21:28 1625
1193b4be Poul Sander 2025-02-03 21:28 1626
    SeparatorEx(flags, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1627
}
1193b4be Poul Sander 2025-02-03 21:28 1628
1193b4be Poul Sander 2025-02-03 21:28 1629
void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1193b4be Poul Sander 2025-02-03 21:28 1630
{
1193b4be Poul Sander 2025-02-03 21:28 1631
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1632
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1633
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1634
1193b4be Poul Sander 2025-02-03 21:28 1635
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1193b4be Poul Sander 2025-02-03 21:28 1636
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1637
    const ImVec2 padding = style.SeparatorTextPadding;
1193b4be Poul Sander 2025-02-03 21:28 1638
1193b4be Poul Sander 2025-02-03 21:28 1639
    const float separator_thickness = style.SeparatorTextBorderSize;
1193b4be Poul Sander 2025-02-03 21:28 1640
    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 1641
    const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1193b4be Poul Sander 2025-02-03 21:28 1642
    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 1643
    ItemSize(min_size, text_baseline_y);
1193b4be Poul Sander 2025-02-03 21:28 1644
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1645
        return;
1193b4be Poul Sander 2025-02-03 21:28 1646
1193b4be Poul Sander 2025-02-03 21:28 1647
    const float sep1_x1 = pos.x;
1193b4be Poul Sander 2025-02-03 21:28 1648
    const float sep2_x2 = bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 1649
    const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
1193b4be Poul Sander 2025-02-03 21:28 1650
1193b4be Poul Sander 2025-02-03 21:28 1651
    const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1652
    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 1653
1193b4be Poul Sander 2025-02-03 21:28 1654
    // This allows using SameLine() to position something in the 'extra_w'
1193b4be Poul Sander 2025-02-03 21:28 1655
    window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1193b4be Poul Sander 2025-02-03 21:28 1656
1193b4be Poul Sander 2025-02-03 21:28 1657
    const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);
1193b4be Poul Sander 2025-02-03 21:28 1658
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1659
    {
1193b4be Poul Sander 2025-02-03 21:28 1660
        const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 1661
        const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 1662
        if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1663
            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 1664
        if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1665
            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 1666
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1667
            LogSetNextTextDecoration("---", NULL);
1193b4be Poul Sander 2025-02-03 21:28 1668
        RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);
1193b4be Poul Sander 2025-02-03 21:28 1669
    }
1193b4be Poul Sander 2025-02-03 21:28 1670
    else
1193b4be Poul Sander 2025-02-03 21:28 1671
    {
1193b4be Poul Sander 2025-02-03 21:28 1672
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1673
            LogText("---");
1193b4be Poul Sander 2025-02-03 21:28 1674
        if (separator_thickness > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1675
            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 1676
    }
1193b4be Poul Sander 2025-02-03 21:28 1677
}
1193b4be Poul Sander 2025-02-03 21:28 1678
1193b4be Poul Sander 2025-02-03 21:28 1679
void ImGui::SeparatorText(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 1680
{
1193b4be Poul Sander 2025-02-03 21:28 1681
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1682
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1683
        return;
1193b4be Poul Sander 2025-02-03 21:28 1684
1193b4be Poul Sander 2025-02-03 21:28 1685
    // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1193b4be Poul Sander 2025-02-03 21:28 1686
    // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1193b4be Poul Sander 2025-02-03 21:28 1687
    // - 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 1688
    // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1193b4be Poul Sander 2025-02-03 21:28 1689
    // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1193b4be Poul Sander 2025-02-03 21:28 1690
    // and then we can turn this into a format function.
1193b4be Poul Sander 2025-02-03 21:28 1691
    SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1692
}
1193b4be Poul Sander 2025-02-03 21:28 1693
1193b4be Poul Sander 2025-02-03 21:28 1694
// 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 1695
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 1696
{
1193b4be Poul Sander 2025-02-03 21:28 1697
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1698
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1699
1193b4be Poul Sander 2025-02-03 21:28 1700
    if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))
1193b4be Poul Sander 2025-02-03 21:28 1701
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1702
1193b4be Poul Sander 2025-02-03 21:28 1703
    // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1193b4be Poul Sander 2025-02-03 21:28 1704
    // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1193b4be Poul Sander 2025-02-03 21:28 1705
    // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1193b4be Poul Sander 2025-02-03 21:28 1706
    ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1193b4be Poul Sander 2025-02-03 21:28 1707
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 1708
    button_flags |= ImGuiButtonFlags_AllowOverlap;
1193b4be Poul Sander 2025-02-03 21:28 1709
#endif
1193b4be Poul Sander 2025-02-03 21:28 1710
1193b4be Poul Sander 2025-02-03 21:28 1711
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1712
    ImRect bb_interact = bb;
1193b4be Poul Sander 2025-02-03 21:28 1713
    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 1714
    ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 1715
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 1716
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1193b4be Poul Sander 2025-02-03 21:28 1717
1193b4be Poul Sander 2025-02-03 21:28 1718
    if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1193b4be Poul Sander 2025-02-03 21:28 1719
        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1193b4be Poul Sander 2025-02-03 21:28 1720
1193b4be Poul Sander 2025-02-03 21:28 1721
    ImRect bb_render = bb;
1193b4be Poul Sander 2025-02-03 21:28 1722
    if (held)
1193b4be Poul Sander 2025-02-03 21:28 1723
    {
1193b4be Poul Sander 2025-02-03 21:28 1724
        float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1193b4be Poul Sander 2025-02-03 21:28 1725
1193b4be Poul Sander 2025-02-03 21:28 1726
        // Minimum pane size
1193b4be Poul Sander 2025-02-03 21:28 1727
        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1193b4be Poul Sander 2025-02-03 21:28 1728
        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1193b4be Poul Sander 2025-02-03 21:28 1729
        if (mouse_delta < -size_1_maximum_delta)
1193b4be Poul Sander 2025-02-03 21:28 1730
            mouse_delta = -size_1_maximum_delta;
1193b4be Poul Sander 2025-02-03 21:28 1731
        if (mouse_delta > size_2_maximum_delta)
1193b4be Poul Sander 2025-02-03 21:28 1732
            mouse_delta = size_2_maximum_delta;
1193b4be Poul Sander 2025-02-03 21:28 1733
1193b4be Poul Sander 2025-02-03 21:28 1734
        // Apply resize
1193b4be Poul Sander 2025-02-03 21:28 1735
        if (mouse_delta != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1736
        {
1193b4be Poul Sander 2025-02-03 21:28 1737
            *size1 = ImMax(*size1 + mouse_delta, min_size1);
1193b4be Poul Sander 2025-02-03 21:28 1738
            *size2 = ImMax(*size2 - mouse_delta, min_size2);
1193b4be Poul Sander 2025-02-03 21:28 1739
            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1193b4be Poul Sander 2025-02-03 21:28 1740
            MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 1741
        }
1193b4be Poul Sander 2025-02-03 21:28 1742
    }
1193b4be Poul Sander 2025-02-03 21:28 1743
1193b4be Poul Sander 2025-02-03 21:28 1744
    // Render at new position
1193b4be Poul Sander 2025-02-03 21:28 1745
    if (bg_col & IM_COL32_A_MASK)
1193b4be Poul Sander 2025-02-03 21:28 1746
        window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1747
    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 1748
    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1749
1193b4be Poul Sander 2025-02-03 21:28 1750
    return held;
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
static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 1754
{
1193b4be Poul Sander 2025-02-03 21:28 1755
    const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1193b4be Poul Sander 2025-02-03 21:28 1756
    const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1193b4be Poul Sander 2025-02-03 21:28 1757
    if (int d = (int)(b->Width - a->Width))
1193b4be Poul Sander 2025-02-03 21:28 1758
        return d;
1193b4be Poul Sander 2025-02-03 21:28 1759
    return (b->Index - a->Index);
1193b4be Poul Sander 2025-02-03 21:28 1760
}
1193b4be Poul Sander 2025-02-03 21:28 1761
1193b4be Poul Sander 2025-02-03 21:28 1762
// Shrink excess width from a set of item, by removing width from the larger items first.
1193b4be Poul Sander 2025-02-03 21:28 1763
// Set items Width to -1.0f to disable shrinking this item.
1193b4be Poul Sander 2025-02-03 21:28 1764
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1193b4be Poul Sander 2025-02-03 21:28 1765
{
1193b4be Poul Sander 2025-02-03 21:28 1766
    if (count == 1)
1193b4be Poul Sander 2025-02-03 21:28 1767
    {
1193b4be Poul Sander 2025-02-03 21:28 1768
        if (items[0].Width >= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1769
            items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1770
        return;
1193b4be Poul Sander 2025-02-03 21:28 1771
    }
1193b4be Poul Sander 2025-02-03 21:28 1772
    ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
1193b4be Poul Sander 2025-02-03 21:28 1773
    int count_same_width = 1;
1193b4be Poul Sander 2025-02-03 21:28 1774
    while (width_excess > 0.0f && count_same_width < count)
1193b4be Poul Sander 2025-02-03 21:28 1775
    {
1193b4be Poul Sander 2025-02-03 21:28 1776
        while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1193b4be Poul Sander 2025-02-03 21:28 1777
            count_same_width++;
1193b4be Poul Sander 2025-02-03 21:28 1778
        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);
1193b4be Poul Sander 2025-02-03 21:28 1779
        if (max_width_to_remove_per_item <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1780
            break;
1193b4be Poul Sander 2025-02-03 21:28 1781
        float 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 1782
        for (int item_n = 0; item_n < count_same_width; item_n++)
1193b4be Poul Sander 2025-02-03 21:28 1783
            items[item_n].Width -= width_to_remove_per_item;
1193b4be Poul Sander 2025-02-03 21:28 1784
        width_excess -= width_to_remove_per_item * count_same_width;
1193b4be Poul Sander 2025-02-03 21:28 1785
    }
1193b4be Poul Sander 2025-02-03 21:28 1786
1193b4be Poul Sander 2025-02-03 21:28 1787
    // Round width and redistribute remainder
1193b4be Poul Sander 2025-02-03 21:28 1788
    // 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 1789
    width_excess = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1790
    for (int n = 0; n < count; n++)
1193b4be Poul Sander 2025-02-03 21:28 1791
    {
1193b4be Poul Sander 2025-02-03 21:28 1792
        float width_rounded = ImTrunc(items[n].Width);
1193b4be Poul Sander 2025-02-03 21:28 1793
        width_excess += items[n].Width - width_rounded;
1193b4be Poul Sander 2025-02-03 21:28 1794
        items[n].Width = width_rounded;
1193b4be Poul Sander 2025-02-03 21:28 1795
    }
1193b4be Poul Sander 2025-02-03 21:28 1796
    while (width_excess > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1797
        for (int n = 0; n < count && width_excess > 0.0f; n++)
1193b4be Poul Sander 2025-02-03 21:28 1798
        {
1193b4be Poul Sander 2025-02-03 21:28 1799
            float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1800
            items[n].Width += width_to_add;
1193b4be Poul Sander 2025-02-03 21:28 1801
            width_excess -= width_to_add;
1193b4be Poul Sander 2025-02-03 21:28 1802
        }
1193b4be Poul Sander 2025-02-03 21:28 1803
}
1193b4be Poul Sander 2025-02-03 21:28 1804
1193b4be Poul Sander 2025-02-03 21:28 1805
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1806
// [SECTION] Widgets: ComboBox
1193b4be Poul Sander 2025-02-03 21:28 1807
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1808
// - CalcMaxPopupHeightFromItemCount() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1809
// - BeginCombo()
1193b4be Poul Sander 2025-02-03 21:28 1810
// - BeginComboPopup() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1811
// - EndCombo()
1193b4be Poul Sander 2025-02-03 21:28 1812
// - BeginComboPreview() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1813
// - EndComboPreview() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1814
// - Combo()
1193b4be Poul Sander 2025-02-03 21:28 1815
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1816
1193b4be Poul Sander 2025-02-03 21:28 1817
static float CalcMaxPopupHeightFromItemCount(int items_count)
1193b4be Poul Sander 2025-02-03 21:28 1818
{
1193b4be Poul Sander 2025-02-03 21:28 1819
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1820
    if (items_count <= 0)
1193b4be Poul Sander 2025-02-03 21:28 1821
        return FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 1822
    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 1823
}
1193b4be Poul Sander 2025-02-03 21:28 1824
1193b4be Poul Sander 2025-02-03 21:28 1825
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 1826
{
1193b4be Poul Sander 2025-02-03 21:28 1827
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1828
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1829
1193b4be Poul Sander 2025-02-03 21:28 1830
    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1193b4be Poul Sander 2025-02-03 21:28 1831
    g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 1832
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1833
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1834
1193b4be Poul Sander 2025-02-03 21:28 1835
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1836
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1837
    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 1838
    if (flags & ImGuiComboFlags_WidthFitPreview)
1193b4be Poul Sander 2025-02-03 21:28 1839
        IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
1193b4be Poul Sander 2025-02-03 21:28 1840
1193b4be Poul Sander 2025-02-03 21:28 1841
    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 1842
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 1843
    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 1844
    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 1845
    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 1846
    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 1847
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1848
    if (!ItemAdd(total_bb, id, &bb))
1193b4be Poul Sander 2025-02-03 21:28 1849
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1850
1193b4be Poul Sander 2025-02-03 21:28 1851
    // Open on click
1193b4be Poul Sander 2025-02-03 21:28 1852
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1853
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1854
    const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1193b4be Poul Sander 2025-02-03 21:28 1855
    bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 1856
    if (pressed && !popup_open)
1193b4be Poul Sander 2025-02-03 21:28 1857
    {
1193b4be Poul Sander 2025-02-03 21:28 1858
        OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 1859
        popup_open = true;
1193b4be Poul Sander 2025-02-03 21:28 1860
    }
1193b4be Poul Sander 2025-02-03 21:28 1861
1193b4be Poul Sander 2025-02-03 21:28 1862
    // Render shape
1193b4be Poul Sander 2025-02-03 21:28 1863
    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 1864
    const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1193b4be Poul Sander 2025-02-03 21:28 1865
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1866
    if (!(flags & ImGuiComboFlags_NoPreview))
1193b4be Poul Sander 2025-02-03 21:28 1867
        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 1868
    if (!(flags & ImGuiComboFlags_NoArrowButton))
1193b4be Poul Sander 2025-02-03 21:28 1869
    {
1193b4be Poul Sander 2025-02-03 21:28 1870
        ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 1871
        ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 1872
        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 1873
        if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1193b4be Poul Sander 2025-02-03 21:28 1874
            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 1875
    }
1193b4be Poul Sander 2025-02-03 21:28 1876
    RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1877
1193b4be Poul Sander 2025-02-03 21:28 1878
    // Custom preview
1193b4be Poul Sander 2025-02-03 21:28 1879
    if (flags & ImGuiComboFlags_CustomPreview)
1193b4be Poul Sander 2025-02-03 21:28 1880
    {
1193b4be Poul Sander 2025-02-03 21:28 1881
        g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1193b4be Poul Sander 2025-02-03 21:28 1882
        IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1193b4be Poul Sander 2025-02-03 21:28 1883
        preview_value = NULL;
1193b4be Poul Sander 2025-02-03 21:28 1884
    }
1193b4be Poul Sander 2025-02-03 21:28 1885
1193b4be Poul Sander 2025-02-03 21:28 1886
    // Render preview and label
1193b4be Poul Sander 2025-02-03 21:28 1887
    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1193b4be Poul Sander 2025-02-03 21:28 1888
    {
1193b4be Poul Sander 2025-02-03 21:28 1889
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1890
            LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 1891
        RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
1193b4be Poul Sander 2025-02-03 21:28 1892
    }
1193b4be Poul Sander 2025-02-03 21:28 1893
    if (label_size.x > 0)
1193b4be Poul Sander 2025-02-03 21:28 1894
        RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 1895
1193b4be Poul Sander 2025-02-03 21:28 1896
    if (!popup_open)
1193b4be Poul Sander 2025-02-03 21:28 1897
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1898
1193b4be Poul Sander 2025-02-03 21:28 1899
    g.NextWindowData.Flags = backup_next_window_data_flags;
1193b4be Poul Sander 2025-02-03 21:28 1900
    return BeginComboPopup(popup_id, bb, flags);
1193b4be Poul Sander 2025-02-03 21:28 1901
}
1193b4be Poul Sander 2025-02-03 21:28 1902
1193b4be Poul Sander 2025-02-03 21:28 1903
bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 1904
{
1193b4be Poul Sander 2025-02-03 21:28 1905
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1906
    if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 1907
    {
1193b4be Poul Sander 2025-02-03 21:28 1908
        g.NextWindowData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 1909
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1910
    }
1193b4be Poul Sander 2025-02-03 21:28 1911
1193b4be Poul Sander 2025-02-03 21:28 1912
    // Set popup size
1193b4be Poul Sander 2025-02-03 21:28 1913
    float w = bb.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 1914
    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1193b4be Poul Sander 2025-02-03 21:28 1915
    {
1193b4be Poul Sander 2025-02-03 21:28 1916
        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1193b4be Poul Sander 2025-02-03 21:28 1917
    }
1193b4be Poul Sander 2025-02-03 21:28 1918
    else
1193b4be Poul Sander 2025-02-03 21:28 1919
    {
1193b4be Poul Sander 2025-02-03 21:28 1920
        if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 1921
            flags |= ImGuiComboFlags_HeightRegular;
1193b4be Poul Sander 2025-02-03 21:28 1922
        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1193b4be Poul Sander 2025-02-03 21:28 1923
        int popup_max_height_in_items = -1;
1193b4be Poul Sander 2025-02-03 21:28 1924
        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
1193b4be Poul Sander 2025-02-03 21:28 1925
        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
1193b4be Poul Sander 2025-02-03 21:28 1926
        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
1193b4be Poul Sander 2025-02-03 21:28 1927
        ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
1193b4be Poul Sander 2025-02-03 21:28 1928
        if ((g.NextWindowData.Flags & 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 1929
            constraint_min.x = w;
1193b4be Poul Sander 2025-02-03 21:28 1930
        if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1931
            constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 1932
        SetNextWindowSizeConstraints(constraint_min, constraint_max);
1193b4be Poul Sander 2025-02-03 21:28 1933
    }
1193b4be Poul Sander 2025-02-03 21:28 1934
1193b4be Poul Sander 2025-02-03 21:28 1935
    // This is essentially a specialized version of BeginPopupEx()
1193b4be Poul Sander 2025-02-03 21:28 1936
    char name[16];
1193b4be Poul Sander 2025-02-03 21:28 1937
    ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
1193b4be Poul Sander 2025-02-03 21:28 1938
1193b4be Poul Sander 2025-02-03 21:28 1939
    // Set position given a custom constraint (peak into expected window size so we can position it)
1193b4be Poul Sander 2025-02-03 21:28 1940
    // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1193b4be Poul Sander 2025-02-03 21:28 1941
    // 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 1942
    if (ImGuiWindow* popup_window = FindWindowByName(name))
1193b4be Poul Sander 2025-02-03 21:28 1943
        if (popup_window->WasActive)
1193b4be Poul Sander 2025-02-03 21:28 1944
        {
1193b4be Poul Sander 2025-02-03 21:28 1945
            // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1193b4be Poul Sander 2025-02-03 21:28 1946
            ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
1193b4be Poul Sander 2025-02-03 21:28 1947
            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 1948
            ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
1193b4be Poul Sander 2025-02-03 21:28 1949
            ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
1193b4be Poul Sander 2025-02-03 21:28 1950
            SetNextWindowPos(pos);
1193b4be Poul Sander 2025-02-03 21:28 1951
        }
1193b4be Poul Sander 2025-02-03 21:28 1952
1193b4be Poul Sander 2025-02-03 21:28 1953
    // 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 1954
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1193b4be Poul Sander 2025-02-03 21:28 1955
    PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text
1193b4be Poul Sander 2025-02-03 21:28 1956
    bool ret = Begin(name, NULL, window_flags);
1193b4be Poul Sander 2025-02-03 21:28 1957
    PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 1958
    if (!ret)
1193b4be Poul Sander 2025-02-03 21:28 1959
    {
1193b4be Poul Sander 2025-02-03 21:28 1960
        EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 1961
        IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
1193b4be Poul Sander 2025-02-03 21:28 1962
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1963
    }
1193b4be Poul Sander 2025-02-03 21:28 1964
    g.BeginComboDepth++;
1193b4be Poul Sander 2025-02-03 21:28 1965
    return true;
1193b4be Poul Sander 2025-02-03 21:28 1966
}
1193b4be Poul Sander 2025-02-03 21:28 1967
1193b4be Poul Sander 2025-02-03 21:28 1968
void ImGui::EndCombo()
1193b4be Poul Sander 2025-02-03 21:28 1969
{
1193b4be Poul Sander 2025-02-03 21:28 1970
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1971
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 1972
    g.BeginComboDepth--;
1193b4be Poul Sander 2025-02-03 21:28 1973
}
1193b4be Poul Sander 2025-02-03 21:28 1974
1193b4be Poul Sander 2025-02-03 21:28 1975
// 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 1976
// (Experimental, see GitHub issues: #1658, #4168)
1193b4be Poul Sander 2025-02-03 21:28 1977
bool ImGui::BeginComboPreview()
1193b4be Poul Sander 2025-02-03 21:28 1978
{
1193b4be Poul Sander 2025-02-03 21:28 1979
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1980
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1981
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1193b4be Poul Sander 2025-02-03 21:28 1982
1193b4be Poul Sander 2025-02-03 21:28 1983
    if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
1193b4be Poul Sander 2025-02-03 21:28 1984
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1985
    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 1986
    if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)
1193b4be Poul Sander 2025-02-03 21:28 1987
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1988
1193b4be Poul Sander 2025-02-03 21:28 1989
    // FIXME: This could be contained in a PushWorkRect() api
1193b4be Poul Sander 2025-02-03 21:28 1990
    preview_data->BackupCursorPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1991
    preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 1992
    preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1193b4be Poul Sander 2025-02-03 21:28 1993
    preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 1994
    preview_data->BackupLayout = window->DC.LayoutType;
1193b4be Poul Sander 2025-02-03 21:28 1995
    window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 1996
    window->DC.CursorMaxPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1997
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1193b4be Poul Sander 2025-02-03 21:28 1998
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 1999
    PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
1193b4be Poul Sander 2025-02-03 21:28 2000
1193b4be Poul Sander 2025-02-03 21:28 2001
    return true;
1193b4be Poul Sander 2025-02-03 21:28 2002
}
1193b4be Poul Sander 2025-02-03 21:28 2003
1193b4be Poul Sander 2025-02-03 21:28 2004
void ImGui::EndComboPreview()
1193b4be Poul Sander 2025-02-03 21:28 2005
{
1193b4be Poul Sander 2025-02-03 21:28 2006
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2007
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 2008
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1193b4be Poul Sander 2025-02-03 21:28 2009
1193b4be Poul Sander 2025-02-03 21:28 2010
    // 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 2011
    ImDrawList* draw_list = window->DrawList;
1193b4be Poul Sander 2025-02-03 21:28 2012
    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 2013
        if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1193b4be Poul Sander 2025-02-03 21:28 2014
        {
1193b4be Poul Sander 2025-02-03 21:28 2015
            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 2016
            draw_list->_TryMergeDrawCmds();
1193b4be Poul Sander 2025-02-03 21:28 2017
        }
1193b4be Poul Sander 2025-02-03 21:28 2018
    PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 2019
    window->DC.CursorPos = preview_data->BackupCursorPos;
1193b4be Poul Sander 2025-02-03 21:28 2020
    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
1193b4be Poul Sander 2025-02-03 21:28 2021
    window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1193b4be Poul Sander 2025-02-03 21:28 2022
    window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 2023
    window->DC.LayoutType = preview_data->BackupLayout;
1193b4be Poul Sander 2025-02-03 21:28 2024
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 2025
    preview_data->PreviewRect = ImRect();
1193b4be Poul Sander 2025-02-03 21:28 2026
}
1193b4be Poul Sander 2025-02-03 21:28 2027
1193b4be Poul Sander 2025-02-03 21:28 2028
// Getter for the old Combo() API: const char*[]
1193b4be Poul Sander 2025-02-03 21:28 2029
static const char* Items_ArrayGetter(void* data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 2030
{
1193b4be Poul Sander 2025-02-03 21:28 2031
    const char* const* items = (const char* const*)data;
1193b4be Poul Sander 2025-02-03 21:28 2032
    return items[idx];
1193b4be Poul Sander 2025-02-03 21:28 2033
}
1193b4be Poul Sander 2025-02-03 21:28 2034
1193b4be Poul Sander 2025-02-03 21:28 2035
// Getter for the old Combo() API: "item1\0item2\0item3\0"
1193b4be Poul Sander 2025-02-03 21:28 2036
static const char* Items_SingleStringGetter(void* data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 2037
{
1193b4be Poul Sander 2025-02-03 21:28 2038
    const char* items_separated_by_zeros = (const char*)data;
1193b4be Poul Sander 2025-02-03 21:28 2039
    int items_count = 0;
1193b4be Poul Sander 2025-02-03 21:28 2040
    const char* p = items_separated_by_zeros;
1193b4be Poul Sander 2025-02-03 21:28 2041
    while (*p)
1193b4be Poul Sander 2025-02-03 21:28 2042
    {
1193b4be Poul Sander 2025-02-03 21:28 2043
        if (idx == items_count)
1193b4be Poul Sander 2025-02-03 21:28 2044
            break;
1193b4be Poul Sander 2025-02-03 21:28 2045
        p += strlen(p) + 1;
1193b4be Poul Sander 2025-02-03 21:28 2046
        items_count++;
1193b4be Poul Sander 2025-02-03 21:28 2047
    }
1193b4be Poul Sander 2025-02-03 21:28 2048
    return *p ? p : NULL;
1193b4be Poul Sander 2025-02-03 21:28 2049
}
1193b4be Poul Sander 2025-02-03 21:28 2050
1193b4be Poul Sander 2025-02-03 21:28 2051
// Old API, prefer using BeginCombo() nowadays if you can.
1193b4be Poul Sander 2025-02-03 21:28 2052
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 2053
{
1193b4be Poul Sander 2025-02-03 21:28 2054
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2055
1193b4be Poul Sander 2025-02-03 21:28 2056
    // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1193b4be Poul Sander 2025-02-03 21:28 2057
    const char* preview_value = NULL;
1193b4be Poul Sander 2025-02-03 21:28 2058
    if (*current_item >= 0 && *current_item < items_count)
1193b4be Poul Sander 2025-02-03 21:28 2059
        preview_value = getter(user_data, *current_item);
1193b4be Poul Sander 2025-02-03 21:28 2060
1193b4be Poul Sander 2025-02-03 21:28 2061
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 2062
    if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1193b4be Poul Sander 2025-02-03 21:28 2063
        SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1193b4be Poul Sander 2025-02-03 21:28 2064
1193b4be Poul Sander 2025-02-03 21:28 2065
    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 2066
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2067
1193b4be Poul Sander 2025-02-03 21:28 2068
    // Display items
1193b4be Poul Sander 2025-02-03 21:28 2069
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 2070
    ImGuiListClipper clipper;
1193b4be Poul Sander 2025-02-03 21:28 2071
    clipper.Begin(items_count);
1193b4be Poul Sander 2025-02-03 21:28 2072
    clipper.IncludeItemByIndex(*current_item);
1193b4be Poul Sander 2025-02-03 21:28 2073
    while (clipper.Step())
1193b4be Poul Sander 2025-02-03 21:28 2074
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
1193b4be Poul Sander 2025-02-03 21:28 2075
        {
1193b4be Poul Sander 2025-02-03 21:28 2076
            const char* item_text = getter(user_data, i);
1193b4be Poul Sander 2025-02-03 21:28 2077
            if (item_text == NULL)
1193b4be Poul Sander 2025-02-03 21:28 2078
                item_text = "*Unknown item*";
1193b4be Poul Sander 2025-02-03 21:28 2079
1193b4be Poul Sander 2025-02-03 21:28 2080
            PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 2081
            const bool item_selected = (i == *current_item);
1193b4be Poul Sander 2025-02-03 21:28 2082
            if (Selectable(item_text, item_selected) && *current_item != i)
1193b4be Poul Sander 2025-02-03 21:28 2083
            {
1193b4be Poul Sander 2025-02-03 21:28 2084
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 2085
                *current_item = i;
1193b4be Poul Sander 2025-02-03 21:28 2086
            }
1193b4be Poul Sander 2025-02-03 21:28 2087
            if (item_selected)
1193b4be Poul Sander 2025-02-03 21:28 2088
                SetItemDefaultFocus();
1193b4be Poul Sander 2025-02-03 21:28 2089
            PopID();
1193b4be Poul Sander 2025-02-03 21:28 2090
        }
1193b4be Poul Sander 2025-02-03 21:28 2091
1193b4be Poul Sander 2025-02-03 21:28 2092
    EndCombo();
1193b4be Poul Sander 2025-02-03 21:28 2093
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 2094
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 2095
1193b4be Poul Sander 2025-02-03 21:28 2096
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2097
}
1193b4be Poul Sander 2025-02-03 21:28 2098
1193b4be Poul Sander 2025-02-03 21:28 2099
// Combo box helper allowing to pass an array of strings.
1193b4be Poul Sander 2025-02-03 21:28 2100
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 2101
{
1193b4be Poul Sander 2025-02-03 21:28 2102
    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 2103
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2104
}
1193b4be Poul Sander 2025-02-03 21:28 2105
1193b4be Poul Sander 2025-02-03 21:28 2106
// 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 2107
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 2108
{
1193b4be Poul Sander 2025-02-03 21:28 2109
    int items_count = 0;
1193b4be Poul Sander 2025-02-03 21:28 2110
    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 2111
    while (*p)
1193b4be Poul Sander 2025-02-03 21:28 2112
    {
1193b4be Poul Sander 2025-02-03 21:28 2113
        p += strlen(p) + 1;
1193b4be Poul Sander 2025-02-03 21:28 2114
        items_count++;
1193b4be Poul Sander 2025-02-03 21:28 2115
    }
1193b4be Poul Sander 2025-02-03 21:28 2116
    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 2117
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2118
}
1193b4be Poul Sander 2025-02-03 21:28 2119
1193b4be Poul Sander 2025-02-03 21:28 2120
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 2121
1193b4be Poul Sander 2025-02-03 21:28 2122
struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
1193b4be Poul Sander 2025-02-03 21:28 2123
static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 2124
{
1193b4be Poul Sander 2025-02-03 21:28 2125
    ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
1193b4be Poul Sander 2025-02-03 21:28 2126
    const char* s = NULL;
1193b4be Poul Sander 2025-02-03 21:28 2127
    data->OldCallback(data->UserData, idx, &s);
1193b4be Poul Sander 2025-02-03 21:28 2128
    return s;
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
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 2132
{
1193b4be Poul Sander 2025-02-03 21:28 2133
    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
1193b4be Poul Sander 2025-02-03 21:28 2134
    return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 2135
}
1193b4be Poul Sander 2025-02-03 21:28 2136
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 2137
{
1193b4be Poul Sander 2025-02-03 21:28 2138
    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
1193b4be Poul Sander 2025-02-03 21:28 2139
    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 2140
}
1193b4be Poul Sander 2025-02-03 21:28 2141
1193b4be Poul Sander 2025-02-03 21:28 2142
#endif
1193b4be Poul Sander 2025-02-03 21:28 2143
1193b4be Poul Sander 2025-02-03 21:28 2144
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2145
// [SECTION] Data Type and Data Formatting Helpers [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2146
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2147
// - DataTypeGetInfo()
1193b4be Poul Sander 2025-02-03 21:28 2148
// - DataTypeFormatString()
1193b4be Poul Sander 2025-02-03 21:28 2149
// - DataTypeApplyOp()
1193b4be Poul Sander 2025-02-03 21:28 2150
// - DataTypeApplyFromText()
1193b4be Poul Sander 2025-02-03 21:28 2151
// - DataTypeCompare()
1193b4be Poul Sander 2025-02-03 21:28 2152
// - DataTypeClamp()
1193b4be Poul Sander 2025-02-03 21:28 2153
// - GetMinimumStepAtDecimalPrecision
1193b4be Poul Sander 2025-02-03 21:28 2154
// - RoundScalarWithFormat<>()
1193b4be Poul Sander 2025-02-03 21:28 2155
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2156
1193b4be Poul Sander 2025-02-03 21:28 2157
static const ImGuiDataTypeInfo GDataTypeInfo[] =
1193b4be Poul Sander 2025-02-03 21:28 2158
{
1193b4be Poul Sander 2025-02-03 21:28 2159
    { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
1193b4be Poul Sander 2025-02-03 21:28 2160
    { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
1193b4be Poul Sander 2025-02-03 21:28 2161
    { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
1193b4be Poul Sander 2025-02-03 21:28 2162
    { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
1193b4be Poul Sander 2025-02-03 21:28 2163
    { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
1193b4be Poul Sander 2025-02-03 21:28 2164
    { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
1193b4be Poul Sander 2025-02-03 21:28 2165
#ifdef _MSC_VER
1193b4be Poul Sander 2025-02-03 21:28 2166
    { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
1193b4be Poul Sander 2025-02-03 21:28 2167
    { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
1193b4be Poul Sander 2025-02-03 21:28 2168
#else
1193b4be Poul Sander 2025-02-03 21:28 2169
    { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
1193b4be Poul Sander 2025-02-03 21:28 2170
    { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
1193b4be Poul Sander 2025-02-03 21:28 2171
#endif
1193b4be Poul Sander 2025-02-03 21:28 2172
    { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
1193b4be Poul Sander 2025-02-03 21:28 2173
    { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
1193b4be Poul Sander 2025-02-03 21:28 2174
    { sizeof(bool),             "bool", "%d",   "%d"    },  // ImGuiDataType_Bool
1193b4be Poul Sander 2025-02-03 21:28 2175
};
1193b4be Poul Sander 2025-02-03 21:28 2176
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1193b4be Poul Sander 2025-02-03 21:28 2177
1193b4be Poul Sander 2025-02-03 21:28 2178
const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
1193b4be Poul Sander 2025-02-03 21:28 2179
{
1193b4be Poul Sander 2025-02-03 21:28 2180
    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
1193b4be Poul Sander 2025-02-03 21:28 2181
    return &GDataTypeInfo[data_type];
1193b4be Poul Sander 2025-02-03 21:28 2182
}
1193b4be Poul Sander 2025-02-03 21:28 2183
1193b4be Poul Sander 2025-02-03 21:28 2184
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 2185
{
1193b4be Poul Sander 2025-02-03 21:28 2186
    // Signedness doesn't matter when pushing integer arguments
1193b4be Poul Sander 2025-02-03 21:28 2187
    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
1193b4be Poul Sander 2025-02-03 21:28 2188
        return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2189
    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1193b4be Poul Sander 2025-02-03 21:28 2190
        return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2191
    if (data_type == ImGuiDataType_Float)
1193b4be Poul Sander 2025-02-03 21:28 2192
        return ImFormatString(buf, buf_size, format, *(const float*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2193
    if (data_type == ImGuiDataType_Double)
1193b4be Poul Sander 2025-02-03 21:28 2194
        return ImFormatString(buf, buf_size, format, *(const double*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2195
    if (data_type == ImGuiDataType_S8)
1193b4be Poul Sander 2025-02-03 21:28 2196
        return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2197
    if (data_type == ImGuiDataType_U8)
1193b4be Poul Sander 2025-02-03 21:28 2198
        return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2199
    if (data_type == ImGuiDataType_S16)
1193b4be Poul Sander 2025-02-03 21:28 2200
        return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2201
    if (data_type == ImGuiDataType_U16)
1193b4be Poul Sander 2025-02-03 21:28 2202
        return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2203
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2204
    return 0;
1193b4be Poul Sander 2025-02-03 21:28 2205
}
1193b4be Poul Sander 2025-02-03 21:28 2206
1193b4be Poul Sander 2025-02-03 21:28 2207
void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
1193b4be Poul Sander 2025-02-03 21:28 2208
{
1193b4be Poul Sander 2025-02-03 21:28 2209
    IM_ASSERT(op == '+' || op == '-');
1193b4be Poul Sander 2025-02-03 21:28 2210
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2211
    {
1193b4be Poul Sander 2025-02-03 21:28 2212
        case ImGuiDataType_S8:
1193b4be Poul Sander 2025-02-03 21:28 2213
            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 2214
            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 2215
            return;
1193b4be Poul Sander 2025-02-03 21:28 2216
        case ImGuiDataType_U8:
1193b4be Poul Sander 2025-02-03 21:28 2217
            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 2218
            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 2219
            return;
1193b4be Poul Sander 2025-02-03 21:28 2220
        case ImGuiDataType_S16:
1193b4be Poul Sander 2025-02-03 21:28 2221
            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 2222
            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 2223
            return;
1193b4be Poul Sander 2025-02-03 21:28 2224
        case ImGuiDataType_U16:
1193b4be Poul Sander 2025-02-03 21:28 2225
            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 2226
            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 2227
            return;
1193b4be Poul Sander 2025-02-03 21:28 2228
        case ImGuiDataType_S32:
1193b4be Poul Sander 2025-02-03 21:28 2229
            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 2230
            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 2231
            return;
1193b4be Poul Sander 2025-02-03 21:28 2232
        case ImGuiDataType_U32:
1193b4be Poul Sander 2025-02-03 21:28 2233
            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 2234
            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 2235
            return;
1193b4be Poul Sander 2025-02-03 21:28 2236
        case ImGuiDataType_S64:
1193b4be Poul Sander 2025-02-03 21:28 2237
            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 2238
            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 2239
            return;
1193b4be Poul Sander 2025-02-03 21:28 2240
        case ImGuiDataType_U64:
1193b4be Poul Sander 2025-02-03 21:28 2241
            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 2242
            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 2243
            return;
1193b4be Poul Sander 2025-02-03 21:28 2244
        case ImGuiDataType_Float:
1193b4be Poul Sander 2025-02-03 21:28 2245
            if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2246
            if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2247
            return;
1193b4be Poul Sander 2025-02-03 21:28 2248
        case ImGuiDataType_Double:
1193b4be Poul Sander 2025-02-03 21:28 2249
            if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2250
            if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2251
            return;
1193b4be Poul Sander 2025-02-03 21:28 2252
        case ImGuiDataType_COUNT: break;
1193b4be Poul Sander 2025-02-03 21:28 2253
    }
1193b4be Poul Sander 2025-02-03 21:28 2254
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2255
}
1193b4be Poul Sander 2025-02-03 21:28 2256
1193b4be Poul Sander 2025-02-03 21:28 2257
// User can input math operators (e.g. +100) to edit a numerical values.
1193b4be Poul Sander 2025-02-03 21:28 2258
// 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 2259
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 2260
{
1193b4be Poul Sander 2025-02-03 21:28 2261
    // 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 2262
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
1193b4be Poul Sander 2025-02-03 21:28 2263
    ImGuiDataTypeStorage data_backup;
1193b4be Poul Sander 2025-02-03 21:28 2264
    memcpy(&data_backup, p_data, type_info->Size);
1193b4be Poul Sander 2025-02-03 21:28 2265
1193b4be Poul Sander 2025-02-03 21:28 2266
    while (ImCharIsBlankA(*buf))
1193b4be Poul Sander 2025-02-03 21:28 2267
        buf++;
1193b4be Poul Sander 2025-02-03 21:28 2268
    if (!buf[0])
1193b4be Poul Sander 2025-02-03 21:28 2269
    {
1193b4be Poul Sander 2025-02-03 21:28 2270
        if (p_data_when_empty != NULL)
1193b4be Poul Sander 2025-02-03 21:28 2271
        {
1193b4be Poul Sander 2025-02-03 21:28 2272
            memcpy(p_data, p_data_when_empty, type_info->Size);
1193b4be Poul Sander 2025-02-03 21:28 2273
            return memcmp(&data_backup, p_data, type_info->Size) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2274
        }
1193b4be Poul Sander 2025-02-03 21:28 2275
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2276
    }
1193b4be Poul Sander 2025-02-03 21:28 2277
1193b4be Poul Sander 2025-02-03 21:28 2278
    // Sanitize format
1193b4be Poul Sander 2025-02-03 21:28 2279
    // - 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 2280
    // - 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 2281
    char format_sanitized[32];
1193b4be Poul Sander 2025-02-03 21:28 2282
    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
1193b4be Poul Sander 2025-02-03 21:28 2283
        format = type_info->ScanFmt;
1193b4be Poul Sander 2025-02-03 21:28 2284
    else
1193b4be Poul Sander 2025-02-03 21:28 2285
        format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
1193b4be Poul Sander 2025-02-03 21:28 2286
1193b4be Poul Sander 2025-02-03 21:28 2287
    // Small types need a 32-bit buffer to receive the result from scanf()
1193b4be Poul Sander 2025-02-03 21:28 2288
    int v32 = 0;
1193b4be Poul Sander 2025-02-03 21:28 2289
    if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
1193b4be Poul Sander 2025-02-03 21:28 2290
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2291
    if (type_info->Size < 4)
1193b4be Poul Sander 2025-02-03 21:28 2292
    {
1193b4be Poul Sander 2025-02-03 21:28 2293
        if (data_type == ImGuiDataType_S8)
1193b4be Poul Sander 2025-02-03 21:28 2294
            *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2295
        else if (data_type == ImGuiDataType_U8)
1193b4be Poul Sander 2025-02-03 21:28 2296
            *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2297
        else if (data_type == ImGuiDataType_S16)
1193b4be Poul Sander 2025-02-03 21:28 2298
            *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2299
        else if (data_type == ImGuiDataType_U16)
1193b4be Poul Sander 2025-02-03 21:28 2300
            *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2301
        else
1193b4be Poul Sander 2025-02-03 21:28 2302
            IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2303
    }
1193b4be Poul Sander 2025-02-03 21:28 2304
1193b4be Poul Sander 2025-02-03 21:28 2305
    return memcmp(&data_backup, p_data, type_info->Size) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2306
}
1193b4be Poul Sander 2025-02-03 21:28 2307
1193b4be Poul Sander 2025-02-03 21:28 2308
template<typename T>
1193b4be Poul Sander 2025-02-03 21:28 2309
static int DataTypeCompareT(const T* lhs, const T* rhs)
1193b4be Poul Sander 2025-02-03 21:28 2310
{
1193b4be Poul Sander 2025-02-03 21:28 2311
    if (*lhs < *rhs) return -1;
1193b4be Poul Sander 2025-02-03 21:28 2312
    if (*lhs > *rhs) return +1;
1193b4be Poul Sander 2025-02-03 21:28 2313
    return 0;
1193b4be Poul Sander 2025-02-03 21:28 2314
}
1193b4be Poul Sander 2025-02-03 21:28 2315
1193b4be Poul Sander 2025-02-03 21:28 2316
int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
1193b4be Poul Sander 2025-02-03 21:28 2317
{
1193b4be Poul Sander 2025-02-03 21:28 2318
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2319
    {
1193b4be Poul Sander 2025-02-03 21:28 2320
    case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2321
    case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2322
    case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2323
    case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2324
    case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2325
    case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2326
    case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2327
    case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2328
    case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2329
    case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2330
    case ImGuiDataType_COUNT:  break;
1193b4be Poul Sander 2025-02-03 21:28 2331
    }
1193b4be Poul Sander 2025-02-03 21:28 2332
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2333
    return 0;
1193b4be Poul Sander 2025-02-03 21:28 2334
}
1193b4be Poul Sander 2025-02-03 21:28 2335
1193b4be Poul Sander 2025-02-03 21:28 2336
template<typename T>
1193b4be Poul Sander 2025-02-03 21:28 2337
static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
1193b4be Poul Sander 2025-02-03 21:28 2338
{
1193b4be Poul Sander 2025-02-03 21:28 2339
    // Clamp, both sides are optional, return true if modified
1193b4be Poul Sander 2025-02-03 21:28 2340
    if (v_min && *v < *v_min) { *v = *v_min; return true; }
1193b4be Poul Sander 2025-02-03 21:28 2341
    if (v_max && *v > *v_max) { *v = *v_max; return true; }
1193b4be Poul Sander 2025-02-03 21:28 2342
    return false;
1193b4be Poul Sander 2025-02-03 21:28 2343
}
1193b4be Poul Sander 2025-02-03 21:28 2344
1193b4be Poul Sander 2025-02-03 21:28 2345
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 2346
{
1193b4be Poul Sander 2025-02-03 21:28 2347
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2348
    {
1193b4be Poul Sander 2025-02-03 21:28 2349
    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 2350
    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 2351
    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 2352
    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 2353
    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 2354
    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 2355
    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 2356
    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 2357
    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 2358
    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 2359
    case ImGuiDataType_COUNT:  break;
1193b4be Poul Sander 2025-02-03 21:28 2360
    }
1193b4be Poul Sander 2025-02-03 21:28 2361
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2362
    return false;
1193b4be Poul Sander 2025-02-03 21:28 2363
}
1193b4be Poul Sander 2025-02-03 21:28 2364
1193b4be Poul Sander 2025-02-03 21:28 2365
bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data)
1193b4be Poul Sander 2025-02-03 21:28 2366
{
1193b4be Poul Sander 2025-02-03 21:28 2367
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2368
    return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0;
1193b4be Poul Sander 2025-02-03 21:28 2369
}
1193b4be Poul Sander 2025-02-03 21:28 2370
1193b4be Poul Sander 2025-02-03 21:28 2371
static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
1193b4be Poul Sander 2025-02-03 21:28 2372
{
1193b4be Poul Sander 2025-02-03 21:28 2373
    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 2374
    if (decimal_precision < 0)
1193b4be Poul Sander 2025-02-03 21:28 2375
        return FLT_MIN;
1193b4be Poul Sander 2025-02-03 21:28 2376
    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 2377
}
1193b4be Poul Sander 2025-02-03 21:28 2378
1193b4be Poul Sander 2025-02-03 21:28 2379
template<typename TYPE>
1193b4be Poul Sander 2025-02-03 21:28 2380
TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
1193b4be Poul Sander 2025-02-03 21:28 2381
{
1193b4be Poul Sander 2025-02-03 21:28 2382
    IM_UNUSED(data_type);
1193b4be Poul Sander 2025-02-03 21:28 2383
    IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 2384
    const char* fmt_start = ImParseFormatFindStart(format);
1193b4be Poul Sander 2025-02-03 21:28 2385
    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 2386
        return v;
1193b4be Poul Sander 2025-02-03 21:28 2387
1193b4be Poul Sander 2025-02-03 21:28 2388
    // Sanitize format
1193b4be Poul Sander 2025-02-03 21:28 2389
    char fmt_sanitized[32];
1193b4be Poul Sander 2025-02-03 21:28 2390
    ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
1193b4be Poul Sander 2025-02-03 21:28 2391
    fmt_start = fmt_sanitized;
1193b4be Poul Sander 2025-02-03 21:28 2392
1193b4be Poul Sander 2025-02-03 21:28 2393
    // Format value with our rounding, and read back
1193b4be Poul Sander 2025-02-03 21:28 2394
    char v_str[64];
1193b4be Poul Sander 2025-02-03 21:28 2395
    ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
1193b4be Poul Sander 2025-02-03 21:28 2396
    const char* p = v_str;
1193b4be Poul Sander 2025-02-03 21:28 2397
    while (*p == ' ')
1193b4be Poul Sander 2025-02-03 21:28 2398
        p++;
1193b4be Poul Sander 2025-02-03 21:28 2399
    v = (TYPE)ImAtof(p);
1193b4be Poul Sander 2025-02-03 21:28 2400
1193b4be Poul Sander 2025-02-03 21:28 2401
    return v;
1193b4be Poul Sander 2025-02-03 21:28 2402
}
1193b4be Poul Sander 2025-02-03 21:28 2403
1193b4be Poul Sander 2025-02-03 21:28 2404
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2405
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 2406
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2407
// - DragBehaviorT<>() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2408
// - DragBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2409
// - DragScalar()
1193b4be Poul Sander 2025-02-03 21:28 2410
// - DragScalarN()
1193b4be Poul Sander 2025-02-03 21:28 2411
// - DragFloat()
1193b4be Poul Sander 2025-02-03 21:28 2412
// - DragFloat2()
1193b4be Poul Sander 2025-02-03 21:28 2413
// - DragFloat3()
1193b4be Poul Sander 2025-02-03 21:28 2414
// - DragFloat4()
1193b4be Poul Sander 2025-02-03 21:28 2415
// - DragFloatRange2()
1193b4be Poul Sander 2025-02-03 21:28 2416
// - DragInt()
1193b4be Poul Sander 2025-02-03 21:28 2417
// - DragInt2()
1193b4be Poul Sander 2025-02-03 21:28 2418
// - DragInt3()
1193b4be Poul Sander 2025-02-03 21:28 2419
// - DragInt4()
1193b4be Poul Sander 2025-02-03 21:28 2420
// - DragIntRange2()
1193b4be Poul Sander 2025-02-03 21:28 2421
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2422
1193b4be Poul Sander 2025-02-03 21:28 2423
// 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 2424
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2425
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 2426
{
1193b4be Poul Sander 2025-02-03 21:28 2427
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2428
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
1193b4be Poul Sander 2025-02-03 21:28 2429
    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 2430
    const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround);
1193b4be Poul Sander 2025-02-03 21:28 2431
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2432
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 2433
1193b4be Poul Sander 2025-02-03 21:28 2434
    // Default tweak speed
1193b4be Poul Sander 2025-02-03 21:28 2435
    if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX))
1193b4be Poul Sander 2025-02-03 21:28 2436
        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
1193b4be Poul Sander 2025-02-03 21:28 2437
1193b4be Poul Sander 2025-02-03 21:28 2438
    // 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 2439
    float adjust_delta = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2440
    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
1193b4be Poul Sander 2025-02-03 21:28 2441
    {
1193b4be Poul Sander 2025-02-03 21:28 2442
        adjust_delta = g.IO.MouseDelta[axis];
1193b4be Poul Sander 2025-02-03 21:28 2443
        if (g.IO.KeyAlt)
1193b4be Poul Sander 2025-02-03 21:28 2444
            adjust_delta *= 1.0f / 100.0f;
1193b4be Poul Sander 2025-02-03 21:28 2445
        if (g.IO.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 2446
            adjust_delta *= 10.0f;
1193b4be Poul Sander 2025-02-03 21:28 2447
    }
1193b4be Poul Sander 2025-02-03 21:28 2448
    else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
1193b4be Poul Sander 2025-02-03 21:28 2449
    {
1193b4be Poul Sander 2025-02-03 21:28 2450
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
1193b4be Poul Sander 2025-02-03 21:28 2451
        const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
1193b4be Poul Sander 2025-02-03 21:28 2452
        const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
1193b4be Poul Sander 2025-02-03 21:28 2453
        const float tweak_factor = tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 2454
        adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
1193b4be Poul Sander 2025-02-03 21:28 2455
        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
1193b4be Poul Sander 2025-02-03 21:28 2456
    }
1193b4be Poul Sander 2025-02-03 21:28 2457
    adjust_delta *= v_speed;
1193b4be Poul Sander 2025-02-03 21:28 2458
1193b4be Poul Sander 2025-02-03 21:28 2459
    // 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 2460
    if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 2461
        adjust_delta = -adjust_delta;
1193b4be Poul Sander 2025-02-03 21:28 2462
1193b4be Poul Sander 2025-02-03 21:28 2463
    // For logarithmic use our range is effectively 0..1 so scale the delta into that range
1193b4be Poul Sander 2025-02-03 21:28 2464
    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 2465
        adjust_delta /= (float)(v_max - v_min);
1193b4be Poul Sander 2025-02-03 21:28 2466
1193b4be Poul Sander 2025-02-03 21:28 2467
    // Clear current value on activation
1193b4be Poul Sander 2025-02-03 21:28 2468
    // 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 2469
    const bool is_just_activated = g.ActiveIdIsJustActivated;
1193b4be Poul Sander 2025-02-03 21:28 2470
    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 2471
    if (is_just_activated || is_already_past_limits_and_pushing_outward)
1193b4be Poul Sander 2025-02-03 21:28 2472
    {
1193b4be Poul Sander 2025-02-03 21:28 2473
        g.DragCurrentAccum = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2474
        g.DragCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 2475
    }
1193b4be Poul Sander 2025-02-03 21:28 2476
    else if (adjust_delta != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2477
    {
1193b4be Poul Sander 2025-02-03 21:28 2478
        g.DragCurrentAccum += adjust_delta;
1193b4be Poul Sander 2025-02-03 21:28 2479
        g.DragCurrentAccumDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 2480
    }
1193b4be Poul Sander 2025-02-03 21:28 2481
1193b4be Poul Sander 2025-02-03 21:28 2482
    if (!g.DragCurrentAccumDirty)
1193b4be Poul Sander 2025-02-03 21:28 2483
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2484
1193b4be Poul Sander 2025-02-03 21:28 2485
    TYPE v_cur = *v;
1193b4be Poul Sander 2025-02-03 21:28 2486
    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2487
1193b4be Poul Sander 2025-02-03 21:28 2488
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
1193b4be Poul Sander 2025-02-03 21:28 2489
    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 2490
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2491
    {
1193b4be Poul Sander 2025-02-03 21:28 2492
        // 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 2493
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
1193b4be Poul Sander 2025-02-03 21:28 2494
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
1193b4be Poul Sander 2025-02-03 21:28 2495
1193b4be Poul Sander 2025-02-03 21:28 2496
        // Convert to parametric space, apply delta, convert back
1193b4be Poul Sander 2025-02-03 21:28 2497
        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 2498
        float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
1193b4be Poul Sander 2025-02-03 21:28 2499
        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 2500
        v_old_ref_for_accum_remainder = v_old_parametric;
1193b4be Poul Sander 2025-02-03 21:28 2501
    }
1193b4be Poul Sander 2025-02-03 21:28 2502
    else
1193b4be Poul Sander 2025-02-03 21:28 2503
    {
1193b4be Poul Sander 2025-02-03 21:28 2504
        v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
1193b4be Poul Sander 2025-02-03 21:28 2505
    }
1193b4be Poul Sander 2025-02-03 21:28 2506
1193b4be Poul Sander 2025-02-03 21:28 2507
    // Round to user desired precision based on format string
1193b4be Poul Sander 2025-02-03 21:28 2508
    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
1193b4be Poul Sander 2025-02-03 21:28 2509
        v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
1193b4be Poul Sander 2025-02-03 21:28 2510
1193b4be Poul Sander 2025-02-03 21:28 2511
    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
1193b4be Poul Sander 2025-02-03 21:28 2512
    g.DragCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 2513
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2514
    {
1193b4be Poul Sander 2025-02-03 21:28 2515
        // Convert to parametric space, apply delta, convert back
1193b4be Poul Sander 2025-02-03 21:28 2516
        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 2517
        g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
1193b4be Poul Sander 2025-02-03 21:28 2518
    }
1193b4be Poul Sander 2025-02-03 21:28 2519
    else
1193b4be Poul Sander 2025-02-03 21:28 2520
    {
1193b4be Poul Sander 2025-02-03 21:28 2521
        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
1193b4be Poul Sander 2025-02-03 21:28 2522
    }
1193b4be Poul Sander 2025-02-03 21:28 2523
1193b4be Poul Sander 2025-02-03 21:28 2524
    // Lose zero sign for float/double
1193b4be Poul Sander 2025-02-03 21:28 2525
    if (v_cur == (TYPE)-0)
1193b4be Poul Sander 2025-02-03 21:28 2526
        v_cur = (TYPE)0;
1193b4be Poul Sander 2025-02-03 21:28 2527
1193b4be Poul Sander 2025-02-03 21:28 2528
    if (*v != v_cur && is_bounded)
1193b4be Poul Sander 2025-02-03 21:28 2529
    {
1193b4be Poul Sander 2025-02-03 21:28 2530
        if (is_wrapped)
1193b4be Poul Sander 2025-02-03 21:28 2531
        {
1193b4be Poul Sander 2025-02-03 21:28 2532
            // Wrap values
1193b4be Poul Sander 2025-02-03 21:28 2533
            if (v_cur < v_min)
1193b4be Poul Sander 2025-02-03 21:28 2534
                v_cur += v_max - v_min + (is_floating_point ? 0 : 1);
1193b4be Poul Sander 2025-02-03 21:28 2535
            if (v_cur > v_max)
1193b4be Poul Sander 2025-02-03 21:28 2536
                v_cur -= v_max - v_min + (is_floating_point ? 0 : 1);
1193b4be Poul Sander 2025-02-03 21:28 2537
        }
1193b4be Poul Sander 2025-02-03 21:28 2538
        else
1193b4be Poul Sander 2025-02-03 21:28 2539
        {
1193b4be Poul Sander 2025-02-03 21:28 2540
            // Clamp values + handle overflow/wrap-around for integer types.
1193b4be Poul Sander 2025-02-03 21:28 2541
            if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
1193b4be Poul Sander 2025-02-03 21:28 2542
                v_cur = v_min;
1193b4be Poul Sander 2025-02-03 21:28 2543
            if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
1193b4be Poul Sander 2025-02-03 21:28 2544
                v_cur = v_max;
1193b4be Poul Sander 2025-02-03 21:28 2545
        }
1193b4be Poul Sander 2025-02-03 21:28 2546
    }
1193b4be Poul Sander 2025-02-03 21:28 2547
1193b4be Poul Sander 2025-02-03 21:28 2548
    // Apply result
1193b4be Poul Sander 2025-02-03 21:28 2549
    if (*v == v_cur)
1193b4be Poul Sander 2025-02-03 21:28 2550
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2551
    *v = v_cur;
1193b4be Poul Sander 2025-02-03 21:28 2552
    return true;
1193b4be Poul Sander 2025-02-03 21:28 2553
}
1193b4be Poul Sander 2025-02-03 21:28 2554
1193b4be Poul Sander 2025-02-03 21:28 2555
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 2556
{
1193b4be Poul Sander 2025-02-03 21:28 2557
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
1193b4be Poul Sander 2025-02-03 21:28 2558
    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 2559
1193b4be Poul Sander 2025-02-03 21:28 2560
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2561
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 2562
    {
1193b4be Poul Sander 2025-02-03 21:28 2563
        // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
1193b4be Poul Sander 2025-02-03 21:28 2564
        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
1193b4be Poul Sander 2025-02-03 21:28 2565
            ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 2566
        else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 2567
            ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 2568
    }
1193b4be Poul Sander 2025-02-03 21:28 2569
    if (g.ActiveId != id)
1193b4be Poul Sander 2025-02-03 21:28 2570
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2571
    if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
1193b4be Poul Sander 2025-02-03 21:28 2572
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2573
1193b4be Poul Sander 2025-02-03 21:28 2574
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2575
    {
1193b4be Poul Sander 2025-02-03 21:28 2576
    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 2577
    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 2578
    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 2579
    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 2580
    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 2581
    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 2582
    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 2583
    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 2584
    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 2585
    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 2586
    case ImGuiDataType_COUNT:  break;
1193b4be Poul Sander 2025-02-03 21:28 2587
    }
1193b4be Poul Sander 2025-02-03 21:28 2588
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2589
    return false;
1193b4be Poul Sander 2025-02-03 21:28 2590
}
1193b4be Poul Sander 2025-02-03 21:28 2591
1193b4be Poul Sander 2025-02-03 21:28 2592
// 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 2593
// 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 2594
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 2595
{
1193b4be Poul Sander 2025-02-03 21:28 2596
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2597
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2598
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2599
1193b4be Poul Sander 2025-02-03 21:28 2600
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2601
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 2602
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 2603
    const float w = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2604
1193b4be Poul Sander 2025-02-03 21:28 2605
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 2606
    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 2607
    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 2608
1193b4be Poul Sander 2025-02-03 21:28 2609
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
1193b4be Poul Sander 2025-02-03 21:28 2610
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 2611
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
1193b4be Poul Sander 2025-02-03 21:28 2612
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2613
1193b4be Poul Sander 2025-02-03 21:28 2614
    // Default format string when passing NULL
1193b4be Poul Sander 2025-02-03 21:28 2615
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 2616
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 2617
1193b4be Poul Sander 2025-02-03 21:28 2618
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 2619
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
1193b4be Poul Sander 2025-02-03 21:28 2620
    if (!temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2621
    {
1193b4be Poul Sander 2025-02-03 21:28 2622
        // Tabbing or CTRL-clicking on Drag turns it into an InputText
1193b4be Poul Sander 2025-02-03 21:28 2623
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
1193b4be Poul Sander 2025-02-03 21:28 2624
        const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
1193b4be Poul Sander 2025-02-03 21:28 2625
        const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
1193b4be Poul Sander 2025-02-03 21:28 2626
        if (make_active && (clicked || double_clicked))
1193b4be Poul Sander 2025-02-03 21:28 2627
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 2628
        if (make_active && temp_input_allowed)
1193b4be Poul Sander 2025-02-03 21:28 2629
            if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
1193b4be Poul Sander 2025-02-03 21:28 2630
                temp_input_is_active = true;
1193b4be Poul Sander 2025-02-03 21:28 2631
1193b4be Poul Sander 2025-02-03 21:28 2632
        // (Optional) simple click (without moving) turns Drag into an InputText
1193b4be Poul Sander 2025-02-03 21:28 2633
        if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2634
            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 2635
            {
1193b4be Poul Sander 2025-02-03 21:28 2636
                g.NavActivateId = id;
1193b4be Poul Sander 2025-02-03 21:28 2637
                g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
1193b4be Poul Sander 2025-02-03 21:28 2638
                temp_input_is_active = true;
1193b4be Poul Sander 2025-02-03 21:28 2639
            }
1193b4be Poul Sander 2025-02-03 21:28 2640
1193b4be Poul Sander 2025-02-03 21:28 2641
        if (make_active && !temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2642
        {
1193b4be Poul Sander 2025-02-03 21:28 2643
            SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 2644
            SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 2645
            FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 2646
            g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 2647
        }
1193b4be Poul Sander 2025-02-03 21:28 2648
    }
1193b4be Poul Sander 2025-02-03 21:28 2649
1193b4be Poul Sander 2025-02-03 21:28 2650
    if (temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2651
    {
1193b4be Poul Sander 2025-02-03 21:28 2652
        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
1193b4be Poul Sander 2025-02-03 21:28 2653
        bool clamp_enabled = false;
1193b4be Poul Sander 2025-02-03 21:28 2654
        if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))
1193b4be Poul Sander 2025-02-03 21:28 2655
        {
1193b4be Poul Sander 2025-02-03 21:28 2656
            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 2657
            if (p_min == NULL || p_max == NULL || clamp_range_dir < 0)
1193b4be Poul Sander 2025-02-03 21:28 2658
                clamp_enabled = true;
1193b4be Poul Sander 2025-02-03 21:28 2659
            else if (clamp_range_dir == 0)
1193b4be Poul Sander 2025-02-03 21:28 2660
                clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true;
1193b4be Poul Sander 2025-02-03 21:28 2661
        }
1193b4be Poul Sander 2025-02-03 21:28 2662
        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 2663
    }
1193b4be Poul Sander 2025-02-03 21:28 2664
1193b4be Poul Sander 2025-02-03 21:28 2665
    // Draw frame
1193b4be Poul Sander 2025-02-03 21:28 2666
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 2667
    RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 2668
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 2669
1193b4be Poul Sander 2025-02-03 21:28 2670
    // Drag behavior
1193b4be Poul Sander 2025-02-03 21:28 2671
    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 2672
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 2673
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 2674
1193b4be Poul Sander 2025-02-03 21:28 2675
    // 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 2676
    char value_buf[64];
1193b4be Poul Sander 2025-02-03 21:28 2677
    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 2678
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 2679
        LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 2680
    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 2681
1193b4be Poul Sander 2025-02-03 21:28 2682
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2683
        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 2684
1193b4be Poul Sander 2025-02-03 21:28 2685
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
1193b4be Poul Sander 2025-02-03 21:28 2686
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2687
}
1193b4be Poul Sander 2025-02-03 21:28 2688
1193b4be Poul Sander 2025-02-03 21:28 2689
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 2690
{
1193b4be Poul Sander 2025-02-03 21:28 2691
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2692
    if (window->SkipItems)
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
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2696
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 2697
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 2698
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 2699
    PushMultiItemsWidths(components, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 2700
    size_t type_size = GDataTypeInfo[data_type].Size;
1193b4be Poul Sander 2025-02-03 21:28 2701
    for (int i = 0; i < components; i++)
1193b4be Poul Sander 2025-02-03 21:28 2702
    {
1193b4be Poul Sander 2025-02-03 21:28 2703
        PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 2704
        if (i > 0)
1193b4be Poul Sander 2025-02-03 21:28 2705
            SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2706
        value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2707
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 2708
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2709
        p_data = (void*)((char*)p_data + type_size);
1193b4be Poul Sander 2025-02-03 21:28 2710
    }
1193b4be Poul Sander 2025-02-03 21:28 2711
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 2712
1193b4be Poul Sander 2025-02-03 21:28 2713
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 2714
    if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 2715
    {
1193b4be Poul Sander 2025-02-03 21:28 2716
        SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2717
        TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 2718
    }
1193b4be Poul Sander 2025-02-03 21:28 2719
1193b4be Poul Sander 2025-02-03 21:28 2720
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 2721
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2722
}
1193b4be Poul Sander 2025-02-03 21:28 2723
1193b4be Poul Sander 2025-02-03 21:28 2724
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 2725
{
1193b4be Poul Sander 2025-02-03 21:28 2726
    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2727
}
1193b4be Poul Sander 2025-02-03 21:28 2728
1193b4be Poul Sander 2025-02-03 21:28 2729
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 2730
{
1193b4be Poul Sander 2025-02-03 21:28 2731
    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
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
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 2735
{
1193b4be Poul Sander 2025-02-03 21:28 2736
    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2737
}
1193b4be Poul Sander 2025-02-03 21:28 2738
1193b4be Poul Sander 2025-02-03 21:28 2739
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 2740
{
1193b4be Poul Sander 2025-02-03 21:28 2741
    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2742
}
1193b4be Poul Sander 2025-02-03 21:28 2743
1193b4be Poul Sander 2025-02-03 21:28 2744
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
1193b4be Poul Sander 2025-02-03 21:28 2745
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 2746
{
1193b4be Poul Sander 2025-02-03 21:28 2747
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2748
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2749
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2750
1193b4be Poul Sander 2025-02-03 21:28 2751
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2752
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 2753
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 2754
    PushMultiItemsWidths(2, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 2755
1193b4be Poul Sander 2025-02-03 21:28 2756
    float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
1193b4be Poul Sander 2025-02-03 21:28 2757
    float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
1193b4be Poul Sander 2025-02-03 21:28 2758
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2759
    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 2760
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2761
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2762
1193b4be Poul Sander 2025-02-03 21:28 2763
    float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
1193b4be Poul Sander 2025-02-03 21:28 2764
    float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
1193b4be Poul Sander 2025-02-03 21:28 2765
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2766
    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 2767
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2768
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2769
1193b4be Poul Sander 2025-02-03 21:28 2770
    TextEx(label, FindRenderedTextEnd(label));
1193b4be Poul Sander 2025-02-03 21:28 2771
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 2772
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 2773
1193b4be Poul Sander 2025-02-03 21:28 2774
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2775
}
1193b4be Poul Sander 2025-02-03 21:28 2776
1193b4be Poul Sander 2025-02-03 21:28 2777
// NB: v_speed is float to allow adjusting the drag speed with more precision
1193b4be Poul Sander 2025-02-03 21:28 2778
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 2779
{
1193b4be Poul Sander 2025-02-03 21:28 2780
    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2781
}
1193b4be Poul Sander 2025-02-03 21:28 2782
1193b4be Poul Sander 2025-02-03 21:28 2783
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 2784
{
1193b4be Poul Sander 2025-02-03 21:28 2785
    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2786
}
1193b4be Poul Sander 2025-02-03 21:28 2787
1193b4be Poul Sander 2025-02-03 21:28 2788
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 2789
{
1193b4be Poul Sander 2025-02-03 21:28 2790
    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2791
}
1193b4be Poul Sander 2025-02-03 21:28 2792
1193b4be Poul Sander 2025-02-03 21:28 2793
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 2794
{
1193b4be Poul Sander 2025-02-03 21:28 2795
    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2796
}
1193b4be Poul Sander 2025-02-03 21:28 2797
1193b4be Poul Sander 2025-02-03 21:28 2798
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
1193b4be Poul Sander 2025-02-03 21:28 2799
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 2800
{
1193b4be Poul Sander 2025-02-03 21:28 2801
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2802
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2803
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2804
1193b4be Poul Sander 2025-02-03 21:28 2805
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2806
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 2807
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 2808
    PushMultiItemsWidths(2, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 2809
1193b4be Poul Sander 2025-02-03 21:28 2810
    int min_min = (v_min >= v_max) ? INT_MIN : v_min;
1193b4be Poul Sander 2025-02-03 21:28 2811
    int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
1193b4be Poul Sander 2025-02-03 21:28 2812
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2813
    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 2814
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2815
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2816
1193b4be Poul Sander 2025-02-03 21:28 2817
    int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
1193b4be Poul Sander 2025-02-03 21:28 2818
    int max_max = (v_min >= v_max) ? INT_MAX : v_max;
1193b4be Poul Sander 2025-02-03 21:28 2819
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2820
    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 2821
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2822
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2823
1193b4be Poul Sander 2025-02-03 21:28 2824
    TextEx(label, FindRenderedTextEnd(label));
1193b4be Poul Sander 2025-02-03 21:28 2825
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 2826
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 2827
1193b4be Poul Sander 2025-02-03 21:28 2828
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2829
}
1193b4be Poul Sander 2025-02-03 21:28 2830
1193b4be Poul Sander 2025-02-03 21:28 2831
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2832
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 2833
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2834
// - ScaleRatioFromValueT<> [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2835
// - ScaleValueFromRatioT<> [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2836
// - SliderBehaviorT<>() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2837
// - SliderBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2838
// - SliderScalar()
1193b4be Poul Sander 2025-02-03 21:28 2839
// - SliderScalarN()
1193b4be Poul Sander 2025-02-03 21:28 2840
// - SliderFloat()
1193b4be Poul Sander 2025-02-03 21:28 2841
// - SliderFloat2()
1193b4be Poul Sander 2025-02-03 21:28 2842
// - SliderFloat3()
1193b4be Poul Sander 2025-02-03 21:28 2843
// - SliderFloat4()
1193b4be Poul Sander 2025-02-03 21:28 2844
// - SliderAngle()
1193b4be Poul Sander 2025-02-03 21:28 2845
// - SliderInt()
1193b4be Poul Sander 2025-02-03 21:28 2846
// - SliderInt2()
1193b4be Poul Sander 2025-02-03 21:28 2847
// - SliderInt3()
1193b4be Poul Sander 2025-02-03 21:28 2848
// - SliderInt4()
1193b4be Poul Sander 2025-02-03 21:28 2849
// - VSliderScalar()
1193b4be Poul Sander 2025-02-03 21:28 2850
// - VSliderFloat()
1193b4be Poul Sander 2025-02-03 21:28 2851
// - VSliderInt()
1193b4be Poul Sander 2025-02-03 21:28 2852
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2853
1193b4be Poul Sander 2025-02-03 21:28 2854
// 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 2855
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2856
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 2857
{
1193b4be Poul Sander 2025-02-03 21:28 2858
    if (v_min == v_max)
1193b4be Poul Sander 2025-02-03 21:28 2859
        return 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2860
    IM_UNUSED(data_type);
1193b4be Poul Sander 2025-02-03 21:28 2861
1193b4be Poul Sander 2025-02-03 21:28 2862
    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 2863
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2864
    {
1193b4be Poul Sander 2025-02-03 21:28 2865
        bool flipped = v_max < v_min;
1193b4be Poul Sander 2025-02-03 21:28 2866
1193b4be Poul Sander 2025-02-03 21:28 2867
        if (flipped) // Handle the case where the range is backwards
1193b4be Poul Sander 2025-02-03 21:28 2868
            ImSwap(v_min, v_max);
1193b4be Poul Sander 2025-02-03 21:28 2869
1193b4be Poul Sander 2025-02-03 21:28 2870
        // Fudge min/max to avoid getting close to log(0)
1193b4be Poul Sander 2025-02-03 21:28 2871
        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 2872
        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 2873
1193b4be Poul Sander 2025-02-03 21:28 2874
        // 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 2875
        if ((v_min == 0.0f) && (v_max < 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 2876
            v_min_fudged = -logarithmic_zero_epsilon;
1193b4be Poul Sander 2025-02-03 21:28 2877
        else if ((v_max == 0.0f) && (v_min < 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 2878
            v_max_fudged = -logarithmic_zero_epsilon;
1193b4be Poul Sander 2025-02-03 21:28 2879
1193b4be Poul Sander 2025-02-03 21:28 2880
        float result;
1193b4be Poul Sander 2025-02-03 21:28 2881
        if (v_clamped <= v_min_fudged)
1193b4be Poul Sander 2025-02-03 21:28 2882
            result = 0.0f; // Workaround for values that are in-range but below our fudge
1193b4be Poul Sander 2025-02-03 21:28 2883
        else if (v_clamped >= v_max_fudged)
1193b4be Poul Sander 2025-02-03 21:28 2884
            result = 1.0f; // Workaround for values that are in-range but above our fudge
1193b4be Poul Sander 2025-02-03 21:28 2885
        else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
1193b4be Poul Sander 2025-02-03 21:28 2886
        {
1193b4be Poul Sander 2025-02-03 21:28 2887
            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 2888
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 2889
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 2890
            if (v == 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2891
                result = zero_point_center; // Special case for exactly zero
1193b4be Poul Sander 2025-02-03 21:28 2892
            else if (v < 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2893
                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 2894
            else
1193b4be Poul Sander 2025-02-03 21:28 2895
                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 2896
        }
1193b4be Poul Sander 2025-02-03 21:28 2897
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
1193b4be Poul Sander 2025-02-03 21:28 2898
            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 2899
        else
1193b4be Poul Sander 2025-02-03 21:28 2900
            result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
1193b4be Poul Sander 2025-02-03 21:28 2901
1193b4be Poul Sander 2025-02-03 21:28 2902
        return flipped ? (1.0f - result) : result;
1193b4be Poul Sander 2025-02-03 21:28 2903
    }
1193b4be Poul Sander 2025-02-03 21:28 2904
    else
1193b4be Poul Sander 2025-02-03 21:28 2905
    {
1193b4be Poul Sander 2025-02-03 21:28 2906
        // Linear slider
1193b4be Poul Sander 2025-02-03 21:28 2907
        return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
1193b4be Poul Sander 2025-02-03 21:28 2908
    }
1193b4be Poul Sander 2025-02-03 21:28 2909
}
1193b4be Poul Sander 2025-02-03 21:28 2910
1193b4be Poul Sander 2025-02-03 21:28 2911
// 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 2912
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2913
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 2914
{
1193b4be Poul Sander 2025-02-03 21:28 2915
    // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
1193b4be Poul Sander 2025-02-03 21:28 2916
    // 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 2917
    if (t <= 0.0f || v_min == v_max)
1193b4be Poul Sander 2025-02-03 21:28 2918
        return v_min;
1193b4be Poul Sander 2025-02-03 21:28 2919
    if (t >= 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 2920
        return v_max;
1193b4be Poul Sander 2025-02-03 21:28 2921
1193b4be Poul Sander 2025-02-03 21:28 2922
    TYPE result = (TYPE)0;
1193b4be Poul Sander 2025-02-03 21:28 2923
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2924
    {
1193b4be Poul Sander 2025-02-03 21:28 2925
        // Fudge min/max to avoid getting silly results close to zero
1193b4be Poul Sander 2025-02-03 21:28 2926
        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 2927
        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 2928
1193b4be Poul Sander 2025-02-03 21:28 2929
        const bool flipped = v_max < v_min; // Check if range is "backwards"
1193b4be Poul Sander 2025-02-03 21:28 2930
        if (flipped)
1193b4be Poul Sander 2025-02-03 21:28 2931
            ImSwap(v_min_fudged, v_max_fudged);
1193b4be Poul Sander 2025-02-03 21:28 2932
1193b4be Poul Sander 2025-02-03 21:28 2933
        // 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 2934
        if ((v_max == 0.0f) && (v_min < 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 2935
            v_max_fudged = -logarithmic_zero_epsilon;
1193b4be Poul Sander 2025-02-03 21:28 2936
1193b4be Poul Sander 2025-02-03 21:28 2937
        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 2938
1193b4be Poul Sander 2025-02-03 21:28 2939
        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 2940
        {
1193b4be Poul Sander 2025-02-03 21:28 2941
            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 2942
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 2943
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 2944
            if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
1193b4be Poul Sander 2025-02-03 21:28 2945
                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 2946
            else if (t_with_flip < zero_point_center)
1193b4be Poul Sander 2025-02-03 21:28 2947
                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 2948
            else
1193b4be Poul Sander 2025-02-03 21:28 2949
                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 2950
        }
1193b4be Poul Sander 2025-02-03 21:28 2951
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
1193b4be Poul Sander 2025-02-03 21:28 2952
            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 2953
        else
1193b4be Poul Sander 2025-02-03 21:28 2954
            result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
1193b4be Poul Sander 2025-02-03 21:28 2955
    }
1193b4be Poul Sander 2025-02-03 21:28 2956
    else
1193b4be Poul Sander 2025-02-03 21:28 2957
    {
1193b4be Poul Sander 2025-02-03 21:28 2958
        // Linear slider
1193b4be Poul Sander 2025-02-03 21:28 2959
        const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 2960
        if (is_floating_point)
1193b4be Poul Sander 2025-02-03 21:28 2961
        {
1193b4be Poul Sander 2025-02-03 21:28 2962
            result = ImLerp(v_min, v_max, t);
1193b4be Poul Sander 2025-02-03 21:28 2963
        }
1193b4be Poul Sander 2025-02-03 21:28 2964
        else if (t < 1.0)
1193b4be Poul Sander 2025-02-03 21:28 2965
        {
1193b4be Poul Sander 2025-02-03 21:28 2966
            // - 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 2967
            //   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 2968
            // - 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 2969
            //   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 2970
            FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
1193b4be Poul Sander 2025-02-03 21:28 2971
            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 2972
        }
1193b4be Poul Sander 2025-02-03 21:28 2973
    }
1193b4be Poul Sander 2025-02-03 21:28 2974
1193b4be Poul Sander 2025-02-03 21:28 2975
    return result;
1193b4be Poul Sander 2025-02-03 21:28 2976
}
1193b4be Poul Sander 2025-02-03 21:28 2977
1193b4be Poul Sander 2025-02-03 21:28 2978
// FIXME: Try to move more of the code into shared SliderBehavior()
1193b4be Poul Sander 2025-02-03 21:28 2979
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2980
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 2981
{
1193b4be Poul Sander 2025-02-03 21:28 2982
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2983
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 2984
1193b4be Poul Sander 2025-02-03 21:28 2985
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
1193b4be Poul Sander 2025-02-03 21:28 2986
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2987
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 2988
    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 2989
1193b4be Poul Sander 2025-02-03 21:28 2990
    // Calculate bounds
1193b4be Poul Sander 2025-02-03 21:28 2991
    const float grab_padding = 2.0f; // FIXME: Should be part of style.
1193b4be Poul Sander 2025-02-03 21:28 2992
    const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 2993
    float grab_sz = style.GrabMinSize;
1193b4be Poul Sander 2025-02-03 21:28 2994
    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 2995
        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 2996
    grab_sz = ImMin(grab_sz, slider_sz);
1193b4be Poul Sander 2025-02-03 21:28 2997
    const float slider_usable_sz = slider_sz - grab_sz;
1193b4be Poul Sander 2025-02-03 21:28 2998
    const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 2999
    const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 3000
1193b4be Poul Sander 2025-02-03 21:28 3001
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
1193b4be Poul Sander 2025-02-03 21:28 3002
    float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
1193b4be Poul Sander 2025-02-03 21:28 3003
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 3004
    {
1193b4be Poul Sander 2025-02-03 21:28 3005
        // 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 3006
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
1193b4be Poul Sander 2025-02-03 21:28 3007
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
1193b4be Poul Sander 2025-02-03 21:28 3008
        zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 3009
    }
1193b4be Poul Sander 2025-02-03 21:28 3010
1193b4be Poul Sander 2025-02-03 21:28 3011
    // Process interacting with the slider
1193b4be Poul Sander 2025-02-03 21:28 3012
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3013
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 3014
    {
1193b4be Poul Sander 2025-02-03 21:28 3015
        bool set_new_value = false;
1193b4be Poul Sander 2025-02-03 21:28 3016
        float clicked_t = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3017
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
1193b4be Poul Sander 2025-02-03 21:28 3018
        {
1193b4be Poul Sander 2025-02-03 21:28 3019
            if (!g.IO.MouseDown[0])
1193b4be Poul Sander 2025-02-03 21:28 3020
            {
1193b4be Poul Sander 2025-02-03 21:28 3021
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 3022
            }
1193b4be Poul Sander 2025-02-03 21:28 3023
            else
1193b4be Poul Sander 2025-02-03 21:28 3024
            {
1193b4be Poul Sander 2025-02-03 21:28 3025
                const float mouse_abs_pos = g.IO.MousePos[axis];
1193b4be Poul Sander 2025-02-03 21:28 3026
                if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 3027
                {
1193b4be Poul Sander 2025-02-03 21:28 3028
                    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 3029
                    if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 3030
                        grab_t = 1.0f - grab_t;
1193b4be Poul Sander 2025-02-03 21:28 3031
                    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
1193b4be Poul Sander 2025-02-03 21:28 3032
                    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 3033
                    g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3034
                }
1193b4be Poul Sander 2025-02-03 21:28 3035
                if (slider_usable_sz > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3036
                    clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
1193b4be Poul Sander 2025-02-03 21:28 3037
                if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 3038
                    clicked_t = 1.0f - clicked_t;
1193b4be Poul Sander 2025-02-03 21:28 3039
                set_new_value = true;
1193b4be Poul Sander 2025-02-03 21:28 3040
            }
1193b4be Poul Sander 2025-02-03 21:28 3041
        }
1193b4be Poul Sander 2025-02-03 21:28 3042
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
1193b4be Poul Sander 2025-02-03 21:28 3043
        {
1193b4be Poul Sander 2025-02-03 21:28 3044
            if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 3045
            {
1193b4be Poul Sander 2025-02-03 21:28 3046
                g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
1193b4be Poul Sander 2025-02-03 21:28 3047
                g.SliderCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 3048
            }
1193b4be Poul Sander 2025-02-03 21:28 3049
1193b4be Poul Sander 2025-02-03 21:28 3050
            float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
1193b4be Poul Sander 2025-02-03 21:28 3051
            if (input_delta != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3052
            {
1193b4be Poul Sander 2025-02-03 21:28 3053
                const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
1193b4be Poul Sander 2025-02-03 21:28 3054
                const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
1193b4be Poul Sander 2025-02-03 21:28 3055
                const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
1193b4be Poul Sander 2025-02-03 21:28 3056
                if (decimal_precision > 0)
1193b4be Poul Sander 2025-02-03 21:28 3057
                {
1193b4be Poul Sander 2025-02-03 21:28 3058
                    input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds
1193b4be Poul Sander 2025-02-03 21:28 3059
                    if (tweak_slow)
1193b4be Poul Sander 2025-02-03 21:28 3060
                        input_delta /= 10.0f;
1193b4be Poul Sander 2025-02-03 21:28 3061
                }
1193b4be Poul Sander 2025-02-03 21:28 3062
                else
1193b4be Poul Sander 2025-02-03 21:28 3063
                {
1193b4be Poul Sander 2025-02-03 21:28 3064
                    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 3065
                        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 3066
                    else
1193b4be Poul Sander 2025-02-03 21:28 3067
                        input_delta /= 100.0f;
1193b4be Poul Sander 2025-02-03 21:28 3068
                }
1193b4be Poul Sander 2025-02-03 21:28 3069
                if (tweak_fast)
1193b4be Poul Sander 2025-02-03 21:28 3070
                    input_delta *= 10.0f;
1193b4be Poul Sander 2025-02-03 21:28 3071
1193b4be Poul Sander 2025-02-03 21:28 3072
                g.SliderCurrentAccum += input_delta;
1193b4be Poul Sander 2025-02-03 21:28 3073
                g.SliderCurrentAccumDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 3074
            }
1193b4be Poul Sander 2025-02-03 21:28 3075
1193b4be Poul Sander 2025-02-03 21:28 3076
            float delta = g.SliderCurrentAccum;
1193b4be Poul Sander 2025-02-03 21:28 3077
            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 3078
            {
1193b4be Poul Sander 2025-02-03 21:28 3079
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 3080
            }
1193b4be Poul Sander 2025-02-03 21:28 3081
            else if (g.SliderCurrentAccumDirty)
1193b4be Poul Sander 2025-02-03 21:28 3082
            {
1193b4be Poul Sander 2025-02-03 21:28 3083
                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 3084
1193b4be Poul Sander 2025-02-03 21:28 3085
                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 3086
                {
1193b4be Poul Sander 2025-02-03 21:28 3087
                    set_new_value = false;
1193b4be Poul Sander 2025-02-03 21:28 3088
                    g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
1193b4be Poul Sander 2025-02-03 21:28 3089
                }
1193b4be Poul Sander 2025-02-03 21:28 3090
                else
1193b4be Poul Sander 2025-02-03 21:28 3091
                {
1193b4be Poul Sander 2025-02-03 21:28 3092
                    set_new_value = true;
1193b4be Poul Sander 2025-02-03 21:28 3093
                    float old_clicked_t = clicked_t;
1193b4be Poul Sander 2025-02-03 21:28 3094
                    clicked_t = ImSaturate(clicked_t + delta);
1193b4be Poul Sander 2025-02-03 21:28 3095
1193b4be Poul Sander 2025-02-03 21:28 3096
                    // 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 3097
                    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 3098
                    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
1193b4be Poul Sander 2025-02-03 21:28 3099
                        v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
1193b4be Poul Sander 2025-02-03 21:28 3100
                    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 3101
1193b4be Poul Sander 2025-02-03 21:28 3102
                    if (delta > 0)
1193b4be Poul Sander 2025-02-03 21:28 3103
                        g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
1193b4be Poul Sander 2025-02-03 21:28 3104
                    else
1193b4be Poul Sander 2025-02-03 21:28 3105
                        g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
1193b4be Poul Sander 2025-02-03 21:28 3106
                }
1193b4be Poul Sander 2025-02-03 21:28 3107
1193b4be Poul Sander 2025-02-03 21:28 3108
                g.SliderCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 3109
            }
1193b4be Poul Sander 2025-02-03 21:28 3110
        }
1193b4be Poul Sander 2025-02-03 21:28 3111
1193b4be Poul Sander 2025-02-03 21:28 3112
        if (set_new_value)
1193b4be Poul Sander 2025-02-03 21:28 3113
            if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
1193b4be Poul Sander 2025-02-03 21:28 3114
                set_new_value = false;
1193b4be Poul Sander 2025-02-03 21:28 3115
1193b4be Poul Sander 2025-02-03 21:28 3116
        if (set_new_value)
1193b4be Poul Sander 2025-02-03 21:28 3117
        {
1193b4be Poul Sander 2025-02-03 21:28 3118
            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 3119
1193b4be Poul Sander 2025-02-03 21:28 3120
            // Round to user desired precision based on format string
1193b4be Poul Sander 2025-02-03 21:28 3121
            if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
1193b4be Poul Sander 2025-02-03 21:28 3122
                v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
1193b4be Poul Sander 2025-02-03 21:28 3123
1193b4be Poul Sander 2025-02-03 21:28 3124
            // Apply result
1193b4be Poul Sander 2025-02-03 21:28 3125
            if (*v != v_new)
1193b4be Poul Sander 2025-02-03 21:28 3126
            {
1193b4be Poul Sander 2025-02-03 21:28 3127
                *v = v_new;
1193b4be Poul Sander 2025-02-03 21:28 3128
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 3129
            }
1193b4be Poul Sander 2025-02-03 21:28 3130
        }
1193b4be Poul Sander 2025-02-03 21:28 3131
    }
1193b4be Poul Sander 2025-02-03 21:28 3132
1193b4be Poul Sander 2025-02-03 21:28 3133
    if (slider_sz < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 3134
    {
1193b4be Poul Sander 2025-02-03 21:28 3135
        *out_grab_bb = ImRect(bb.Min, bb.Min);
1193b4be Poul Sander 2025-02-03 21:28 3136
    }
1193b4be Poul Sander 2025-02-03 21:28 3137
    else
1193b4be Poul Sander 2025-02-03 21:28 3138
    {
1193b4be Poul Sander 2025-02-03 21:28 3139
        // Output grab position so it can be displayed by the caller
1193b4be Poul Sander 2025-02-03 21:28 3140
        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 3141
        if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 3142
            grab_t = 1.0f - grab_t;
1193b4be Poul Sander 2025-02-03 21:28 3143
        const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
1193b4be Poul Sander 2025-02-03 21:28 3144
        if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 3145
            *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 3146
        else
1193b4be Poul Sander 2025-02-03 21:28 3147
            *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 3148
    }
1193b4be Poul Sander 2025-02-03 21:28 3149
1193b4be Poul Sander 2025-02-03 21:28 3150
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3151
}
1193b4be Poul Sander 2025-02-03 21:28 3152
1193b4be Poul Sander 2025-02-03 21:28 3153
// For 32-bit and larger types, slider bounds are limited to half the natural type range.
1193b4be Poul Sander 2025-02-03 21:28 3154
// 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 3155
// 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 3156
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 3157
{
1193b4be Poul Sander 2025-02-03 21:28 3158
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
1193b4be Poul Sander 2025-02-03 21:28 3159
    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 3160
    IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX()
1193b4be Poul Sander 2025-02-03 21:28 3161
1193b4be Poul Sander 2025-02-03 21:28 3162
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 3163
    {
1193b4be Poul Sander 2025-02-03 21:28 3164
    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 3165
    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 3166
    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 3167
    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 3168
    case ImGuiDataType_S32:
1193b4be Poul Sander 2025-02-03 21:28 3169
        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 3170
        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 3171
    case ImGuiDataType_U32:
1193b4be Poul Sander 2025-02-03 21:28 3172
        IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
1193b4be Poul Sander 2025-02-03 21:28 3173
        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 3174
    case ImGuiDataType_S64:
1193b4be Poul Sander 2025-02-03 21:28 3175
        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 3176
        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 3177
    case ImGuiDataType_U64:
1193b4be Poul Sander 2025-02-03 21:28 3178
        IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
1193b4be Poul Sander 2025-02-03 21:28 3179
        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 3180
    case ImGuiDataType_Float:
1193b4be Poul Sander 2025-02-03 21:28 3181
        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 3182
        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 3183
    case ImGuiDataType_Double:
1193b4be Poul Sander 2025-02-03 21:28 3184
        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 3185
        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 3186
    case ImGuiDataType_COUNT: break;
1193b4be Poul Sander 2025-02-03 21:28 3187
    }
1193b4be Poul Sander 2025-02-03 21:28 3188
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 3189
    return false;
1193b4be Poul Sander 2025-02-03 21:28 3190
}
1193b4be Poul Sander 2025-02-03 21:28 3191
1193b4be Poul Sander 2025-02-03 21:28 3192
// 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 3193
// 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 3194
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 3195
{
1193b4be Poul Sander 2025-02-03 21:28 3196
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3197
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3198
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3199
1193b4be Poul Sander 2025-02-03 21:28 3200
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3201
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3202
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 3203
    const float w = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 3204
1193b4be Poul Sander 2025-02-03 21:28 3205
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 3206
    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 3207
    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 3208
1193b4be Poul Sander 2025-02-03 21:28 3209
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
1193b4be Poul Sander 2025-02-03 21:28 3210
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 3211
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
1193b4be Poul Sander 2025-02-03 21:28 3212
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3213
1193b4be Poul Sander 2025-02-03 21:28 3214
    // Default format string when passing NULL
1193b4be Poul Sander 2025-02-03 21:28 3215
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3216
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3217
1193b4be Poul Sander 2025-02-03 21:28 3218
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 3219
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
1193b4be Poul Sander 2025-02-03 21:28 3220
    if (!temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 3221
    {
1193b4be Poul Sander 2025-02-03 21:28 3222
        // Tabbing or CTRL-clicking on Slider turns it into an input box
1193b4be Poul Sander 2025-02-03 21:28 3223
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
1193b4be Poul Sander 2025-02-03 21:28 3224
        const bool make_active = (clicked || g.NavActivateId == id);
1193b4be Poul Sander 2025-02-03 21:28 3225
        if (make_active && clicked)
1193b4be Poul Sander 2025-02-03 21:28 3226
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 3227
        if (make_active && temp_input_allowed)
1193b4be Poul Sander 2025-02-03 21:28 3228
            if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
1193b4be Poul Sander 2025-02-03 21:28 3229
                temp_input_is_active = true;
1193b4be Poul Sander 2025-02-03 21:28 3230
1193b4be Poul Sander 2025-02-03 21:28 3231
        if (make_active && !temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 3232
        {
1193b4be Poul Sander 2025-02-03 21:28 3233
            SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3234
            SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3235
            FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 3236
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 3237
        }
1193b4be Poul Sander 2025-02-03 21:28 3238
    }
1193b4be Poul Sander 2025-02-03 21:28 3239
1193b4be Poul Sander 2025-02-03 21:28 3240
    if (temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 3241
    {
1193b4be Poul Sander 2025-02-03 21:28 3242
        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
1193b4be Poul Sander 2025-02-03 21:28 3243
        const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0;
1193b4be Poul Sander 2025-02-03 21:28 3244
        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 3245
    }
1193b4be Poul Sander 2025-02-03 21:28 3246
1193b4be Poul Sander 2025-02-03 21:28 3247
    // Draw frame
1193b4be Poul Sander 2025-02-03 21:28 3248
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 3249
    RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 3250
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 3251
1193b4be Poul Sander 2025-02-03 21:28 3252
    // Slider behavior
1193b4be Poul Sander 2025-02-03 21:28 3253
    ImRect grab_bb;
1193b4be Poul Sander 2025-02-03 21:28 3254
    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 3255
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3256
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 3257
1193b4be Poul Sander 2025-02-03 21:28 3258
    // Render grab
1193b4be Poul Sander 2025-02-03 21:28 3259
    if (grab_bb.Max.x > grab_bb.Min.x)
1193b4be Poul Sander 2025-02-03 21:28 3260
        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 3261
1193b4be Poul Sander 2025-02-03 21:28 3262
    // 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 3263
    char value_buf[64];
1193b4be Poul Sander 2025-02-03 21:28 3264
    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 3265
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 3266
        LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 3267
    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 3268
1193b4be Poul Sander 2025-02-03 21:28 3269
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3270
        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 3271
1193b4be Poul Sander 2025-02-03 21:28 3272
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
1193b4be Poul Sander 2025-02-03 21:28 3273
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3274
}
1193b4be Poul Sander 2025-02-03 21:28 3275
1193b4be Poul Sander 2025-02-03 21:28 3276
// Add multiple sliders on 1 line for compact edition of multiple components
1193b4be Poul Sander 2025-02-03 21:28 3277
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 3278
{
1193b4be Poul Sander 2025-02-03 21:28 3279
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3280
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3281
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3282
1193b4be Poul Sander 2025-02-03 21:28 3283
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3284
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3285
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 3286
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 3287
    PushMultiItemsWidths(components, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 3288
    size_t type_size = GDataTypeInfo[data_type].Size;
1193b4be Poul Sander 2025-02-03 21:28 3289
    for (int i = 0; i < components; i++)
1193b4be Poul Sander 2025-02-03 21:28 3290
    {
1193b4be Poul Sander 2025-02-03 21:28 3291
        PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 3292
        if (i > 0)
1193b4be Poul Sander 2025-02-03 21:28 3293
            SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3294
        value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3295
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 3296
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 3297
        v = (void*)((char*)v + type_size);
1193b4be Poul Sander 2025-02-03 21:28 3298
    }
1193b4be Poul Sander 2025-02-03 21:28 3299
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 3300
1193b4be Poul Sander 2025-02-03 21:28 3301
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 3302
    if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 3303
    {
1193b4be Poul Sander 2025-02-03 21:28 3304
        SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3305
        TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 3306
    }
1193b4be Poul Sander 2025-02-03 21:28 3307
1193b4be Poul Sander 2025-02-03 21:28 3308
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 3309
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3310
}
1193b4be Poul Sander 2025-02-03 21:28 3311
1193b4be Poul Sander 2025-02-03 21:28 3312
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 3313
{
1193b4be Poul Sander 2025-02-03 21:28 3314
    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3315
}
1193b4be Poul Sander 2025-02-03 21:28 3316
1193b4be Poul Sander 2025-02-03 21:28 3317
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 3318
{
1193b4be Poul Sander 2025-02-03 21:28 3319
    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3320
}
1193b4be Poul Sander 2025-02-03 21:28 3321
1193b4be Poul Sander 2025-02-03 21:28 3322
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 3323
{
1193b4be Poul Sander 2025-02-03 21:28 3324
    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3325
}
1193b4be Poul Sander 2025-02-03 21:28 3326
1193b4be Poul Sander 2025-02-03 21:28 3327
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 3328
{
1193b4be Poul Sander 2025-02-03 21:28 3329
    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3330
}
1193b4be Poul Sander 2025-02-03 21:28 3331
1193b4be Poul Sander 2025-02-03 21:28 3332
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 3333
{
1193b4be Poul Sander 2025-02-03 21:28 3334
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3335
        format = "%.0f deg";
1193b4be Poul Sander 2025-02-03 21:28 3336
    float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 3337
    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3338
    *v_rad = v_deg * (2 * IM_PI) / 360.0f;
1193b4be Poul Sander 2025-02-03 21:28 3339
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3340
}
1193b4be Poul Sander 2025-02-03 21:28 3341
1193b4be Poul Sander 2025-02-03 21:28 3342
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 3343
{
1193b4be Poul Sander 2025-02-03 21:28 3344
    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3345
}
1193b4be Poul Sander 2025-02-03 21:28 3346
1193b4be Poul Sander 2025-02-03 21:28 3347
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 3348
{
1193b4be Poul Sander 2025-02-03 21:28 3349
    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3350
}
1193b4be Poul Sander 2025-02-03 21:28 3351
1193b4be Poul Sander 2025-02-03 21:28 3352
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 3353
{
1193b4be Poul Sander 2025-02-03 21:28 3354
    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3355
}
1193b4be Poul Sander 2025-02-03 21:28 3356
1193b4be Poul Sander 2025-02-03 21:28 3357
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 3358
{
1193b4be Poul Sander 2025-02-03 21:28 3359
    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3360
}
1193b4be Poul Sander 2025-02-03 21:28 3361
1193b4be Poul Sander 2025-02-03 21:28 3362
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 3363
{
1193b4be Poul Sander 2025-02-03 21:28 3364
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3365
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3366
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3367
1193b4be Poul Sander 2025-02-03 21:28 3368
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3369
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3370
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 3371
1193b4be Poul Sander 2025-02-03 21:28 3372
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 3373
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 3374
    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 3375
1193b4be Poul Sander 2025-02-03 21:28 3376
    ItemSize(bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 3377
    if (!ItemAdd(frame_bb, id))
1193b4be Poul Sander 2025-02-03 21:28 3378
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3379
1193b4be Poul Sander 2025-02-03 21:28 3380
    // Default format string when passing NULL
1193b4be Poul Sander 2025-02-03 21:28 3381
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3382
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3383
1193b4be Poul Sander 2025-02-03 21:28 3384
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 3385
    const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
1193b4be Poul Sander 2025-02-03 21:28 3386
    if (clicked || g.NavActivateId == id)
1193b4be Poul Sander 2025-02-03 21:28 3387
    {
1193b4be Poul Sander 2025-02-03 21:28 3388
        if (clicked)
1193b4be Poul Sander 2025-02-03 21:28 3389
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 3390
        SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3391
        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3392
        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 3393
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
1193b4be Poul Sander 2025-02-03 21:28 3394
    }
1193b4be Poul Sander 2025-02-03 21:28 3395
1193b4be Poul Sander 2025-02-03 21:28 3396
    // Draw frame
1193b4be Poul Sander 2025-02-03 21:28 3397
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 3398
    RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 3399
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 3400
1193b4be Poul Sander 2025-02-03 21:28 3401
    // Slider behavior
1193b4be Poul Sander 2025-02-03 21:28 3402
    ImRect grab_bb;
1193b4be Poul Sander 2025-02-03 21:28 3403
    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 3404
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3405
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 3406
1193b4be Poul Sander 2025-02-03 21:28 3407
    // Render grab
1193b4be Poul Sander 2025-02-03 21:28 3408
    if (grab_bb.Max.y > grab_bb.Min.y)
1193b4be Poul Sander 2025-02-03 21:28 3409
        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 3410
1193b4be Poul Sander 2025-02-03 21:28 3411
    // 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 3412
    // For the vertical slider we allow centered text to overlap the frame padding
1193b4be Poul Sander 2025-02-03 21:28 3413
    char value_buf[64];
1193b4be Poul Sander 2025-02-03 21:28 3414
    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 3415
    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 3416
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3417
        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 3418
1193b4be Poul Sander 2025-02-03 21:28 3419
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3420
}
1193b4be Poul Sander 2025-02-03 21:28 3421
1193b4be Poul Sander 2025-02-03 21:28 3422
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 3423
{
1193b4be Poul Sander 2025-02-03 21:28 3424
    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3425
}
1193b4be Poul Sander 2025-02-03 21:28 3426
1193b4be Poul Sander 2025-02-03 21:28 3427
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 3428
{
1193b4be Poul Sander 2025-02-03 21:28 3429
    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
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
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3433
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 3434
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3435
// - ImParseFormatFindStart() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3436
// - ImParseFormatFindEnd() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3437
// - ImParseFormatTrimDecorations() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3438
// - ImParseFormatSanitizeForPrinting() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3439
// - ImParseFormatSanitizeForScanning() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3440
// - ImParseFormatPrecision() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3441
// - TempInputTextScalar() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3442
// - InputScalar()
1193b4be Poul Sander 2025-02-03 21:28 3443
// - InputScalarN()
1193b4be Poul Sander 2025-02-03 21:28 3444
// - InputFloat()
1193b4be Poul Sander 2025-02-03 21:28 3445
// - InputFloat2()
1193b4be Poul Sander 2025-02-03 21:28 3446
// - InputFloat3()
1193b4be Poul Sander 2025-02-03 21:28 3447
// - InputFloat4()
1193b4be Poul Sander 2025-02-03 21:28 3448
// - InputInt()
1193b4be Poul Sander 2025-02-03 21:28 3449
// - InputInt2()
1193b4be Poul Sander 2025-02-03 21:28 3450
// - InputInt3()
1193b4be Poul Sander 2025-02-03 21:28 3451
// - InputInt4()
1193b4be Poul Sander 2025-02-03 21:28 3452
// - InputDouble()
1193b4be Poul Sander 2025-02-03 21:28 3453
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3454
1193b4be Poul Sander 2025-02-03 21:28 3455
// We don't use strchr() because our strings are usually very short and often start with '%'
1193b4be Poul Sander 2025-02-03 21:28 3456
const char* ImParseFormatFindStart(const char* fmt)
1193b4be Poul Sander 2025-02-03 21:28 3457
{
1193b4be Poul Sander 2025-02-03 21:28 3458
    while (char c = fmt[0])
1193b4be Poul Sander 2025-02-03 21:28 3459
    {
1193b4be Poul Sander 2025-02-03 21:28 3460
        if (c == '%' && fmt[1] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3461
            return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3462
        else if (c == '%')
1193b4be Poul Sander 2025-02-03 21:28 3463
            fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3464
        fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3465
    }
1193b4be Poul Sander 2025-02-03 21:28 3466
    return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3467
}
1193b4be Poul Sander 2025-02-03 21:28 3468
1193b4be Poul Sander 2025-02-03 21:28 3469
const char* ImParseFormatFindEnd(const char* fmt)
1193b4be Poul Sander 2025-02-03 21:28 3470
{
1193b4be Poul Sander 2025-02-03 21:28 3471
    // 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 3472
    if (fmt[0] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3473
        return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3474
    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
1193b4be Poul Sander 2025-02-03 21:28 3475
    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 3476
    for (char c; (c = *fmt) != 0; fmt++)
1193b4be Poul Sander 2025-02-03 21:28 3477
    {
1193b4be Poul Sander 2025-02-03 21:28 3478
        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
1193b4be Poul Sander 2025-02-03 21:28 3479
            return fmt + 1;
1193b4be Poul Sander 2025-02-03 21:28 3480
        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
1193b4be Poul Sander 2025-02-03 21:28 3481
            return fmt + 1;
1193b4be Poul Sander 2025-02-03 21:28 3482
    }
1193b4be Poul Sander 2025-02-03 21:28 3483
    return fmt;
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
// Extract the format out of a format string with leading or trailing decorations
1193b4be Poul Sander 2025-02-03 21:28 3487
//  fmt = "blah blah"  -> return ""
1193b4be Poul Sander 2025-02-03 21:28 3488
//  fmt = "%.3f"       -> return fmt
1193b4be Poul Sander 2025-02-03 21:28 3489
//  fmt = "hello %.3f" -> return fmt + 6
1193b4be Poul Sander 2025-02-03 21:28 3490
//  fmt = "%.3f hello" -> return buf written with "%.3f"
1193b4be Poul Sander 2025-02-03 21:28 3491
const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
1193b4be Poul Sander 2025-02-03 21:28 3492
{
1193b4be Poul Sander 2025-02-03 21:28 3493
    const char* fmt_start = ImParseFormatFindStart(fmt);
1193b4be Poul Sander 2025-02-03 21:28 3494
    if (fmt_start[0] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3495
        return "";
1193b4be Poul Sander 2025-02-03 21:28 3496
    const char* fmt_end = ImParseFormatFindEnd(fmt_start);
1193b4be Poul Sander 2025-02-03 21:28 3497
    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 3498
        return fmt_start;
1193b4be Poul Sander 2025-02-03 21:28 3499
    ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
1193b4be Poul Sander 2025-02-03 21:28 3500
    return buf;
1193b4be Poul Sander 2025-02-03 21:28 3501
}
1193b4be Poul Sander 2025-02-03 21:28 3502
1193b4be Poul Sander 2025-02-03 21:28 3503
// Sanitize format
1193b4be Poul Sander 2025-02-03 21:28 3504
// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
1193b4be Poul Sander 2025-02-03 21:28 3505
// - 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 3506
void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
1193b4be Poul Sander 2025-02-03 21:28 3507
{
1193b4be Poul Sander 2025-02-03 21:28 3508
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
1193b4be Poul Sander 2025-02-03 21:28 3509
    IM_UNUSED(fmt_out_size);
1193b4be Poul Sander 2025-02-03 21:28 3510
    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 3511
    while (fmt_in < fmt_end)
1193b4be Poul Sander 2025-02-03 21:28 3512
    {
1193b4be Poul Sander 2025-02-03 21:28 3513
        char c = *fmt_in++;
1193b4be Poul Sander 2025-02-03 21:28 3514
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
1193b4be Poul Sander 2025-02-03 21:28 3515
            *(fmt_out++) = c;
1193b4be Poul Sander 2025-02-03 21:28 3516
    }
1193b4be Poul Sander 2025-02-03 21:28 3517
    *fmt_out = 0; // Zero-terminate
1193b4be Poul Sander 2025-02-03 21:28 3518
}
1193b4be Poul Sander 2025-02-03 21:28 3519
1193b4be Poul Sander 2025-02-03 21:28 3520
// - 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 3521
const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
1193b4be Poul Sander 2025-02-03 21:28 3522
{
1193b4be Poul Sander 2025-02-03 21:28 3523
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
1193b4be Poul Sander 2025-02-03 21:28 3524
    const char* fmt_out_begin = fmt_out;
1193b4be Poul Sander 2025-02-03 21:28 3525
    IM_UNUSED(fmt_out_size);
1193b4be Poul Sander 2025-02-03 21:28 3526
    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 3527
    bool has_type = false;
1193b4be Poul Sander 2025-02-03 21:28 3528
    while (fmt_in < fmt_end)
1193b4be Poul Sander 2025-02-03 21:28 3529
    {
1193b4be Poul Sander 2025-02-03 21:28 3530
        char c = *fmt_in++;
1193b4be Poul Sander 2025-02-03 21:28 3531
        if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))
1193b4be Poul Sander 2025-02-03 21:28 3532
            continue;
1193b4be Poul Sander 2025-02-03 21:28 3533
        has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
1193b4be Poul Sander 2025-02-03 21:28 3534
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
1193b4be Poul Sander 2025-02-03 21:28 3535
            *(fmt_out++) = c;
1193b4be Poul Sander 2025-02-03 21:28 3536
    }
1193b4be Poul Sander 2025-02-03 21:28 3537
    *fmt_out = 0; // Zero-terminate
1193b4be Poul Sander 2025-02-03 21:28 3538
    return fmt_out_begin;
1193b4be Poul Sander 2025-02-03 21:28 3539
}
1193b4be Poul Sander 2025-02-03 21:28 3540
1193b4be Poul Sander 2025-02-03 21:28 3541
template<typename TYPE>
1193b4be Poul Sander 2025-02-03 21:28 3542
static const char* ImAtoi(const char* src, TYPE* output)
1193b4be Poul Sander 2025-02-03 21:28 3543
{
1193b4be Poul Sander 2025-02-03 21:28 3544
    int negative = 0;
1193b4be Poul Sander 2025-02-03 21:28 3545
    if (*src == '-') { negative = 1; src++; }
1193b4be Poul Sander 2025-02-03 21:28 3546
    if (*src == '+') { src++; }
1193b4be Poul Sander 2025-02-03 21:28 3547
    TYPE v = 0;
1193b4be Poul Sander 2025-02-03 21:28 3548
    while (*src >= '0' && *src <= '9')
1193b4be Poul Sander 2025-02-03 21:28 3549
        v = (v * 10) + (*src++ - '0');
1193b4be Poul Sander 2025-02-03 21:28 3550
    *output = negative ? -v : v;
1193b4be Poul Sander 2025-02-03 21:28 3551
    return src;
1193b4be Poul Sander 2025-02-03 21:28 3552
}
1193b4be Poul Sander 2025-02-03 21:28 3553
1193b4be Poul Sander 2025-02-03 21:28 3554
// Parse display precision back from the display format string
1193b4be Poul Sander 2025-02-03 21:28 3555
// 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 3556
int ImParseFormatPrecision(const char* fmt, int default_precision)
1193b4be Poul Sander 2025-02-03 21:28 3557
{
1193b4be Poul Sander 2025-02-03 21:28 3558
    fmt = ImParseFormatFindStart(fmt);
1193b4be Poul Sander 2025-02-03 21:28 3559
    if (fmt[0] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3560
        return default_precision;
1193b4be Poul Sander 2025-02-03 21:28 3561
    fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3562
    while (*fmt >= '0' && *fmt <= '9')
1193b4be Poul Sander 2025-02-03 21:28 3563
        fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3564
    int precision = INT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 3565
    if (*fmt == '.')
1193b4be Poul Sander 2025-02-03 21:28 3566
    {
1193b4be Poul Sander 2025-02-03 21:28 3567
        fmt = ImAtoi<int>(fmt + 1, &precision);
1193b4be Poul Sander 2025-02-03 21:28 3568
        if (precision < 0 || precision > 99)
1193b4be Poul Sander 2025-02-03 21:28 3569
            precision = default_precision;
1193b4be Poul Sander 2025-02-03 21:28 3570
    }
1193b4be Poul Sander 2025-02-03 21:28 3571
    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
1193b4be Poul Sander 2025-02-03 21:28 3572
        precision = -1;
1193b4be Poul Sander 2025-02-03 21:28 3573
    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 3574
        precision = -1;
1193b4be Poul Sander 2025-02-03 21:28 3575
    return (precision == INT_MAX) ? default_precision : precision;
1193b4be Poul Sander 2025-02-03 21:28 3576
}
1193b4be Poul Sander 2025-02-03 21:28 3577
1193b4be Poul Sander 2025-02-03 21:28 3578
// 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 3579
// FIXME: Facilitate using this in variety of other situations.
1193b4be Poul Sander 2025-02-03 21:28 3580
// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but
1193b4be Poul Sander 2025-02-03 21:28 3581
// the expected relationship between TempInputXXX functions and LastItemData is a little fishy.
1193b4be Poul Sander 2025-02-03 21:28 3582
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 3583
{
1193b4be Poul Sander 2025-02-03 21:28 3584
    // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
1193b4be Poul Sander 2025-02-03 21:28 3585
    // We clear ActiveID on the first frame to allow the InputText() taking it back.
1193b4be Poul Sander 2025-02-03 21:28 3586
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3587
    const bool init = (g.TempInputId != id);
1193b4be Poul Sander 2025-02-03 21:28 3588
    if (init)
1193b4be Poul Sander 2025-02-03 21:28 3589
        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 3590
1193b4be Poul Sander 2025-02-03 21:28 3591
    g.CurrentWindow->DC.CursorPos = bb.Min;
1193b4be Poul Sander 2025-02-03 21:28 3592
    g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId;
1193b4be Poul Sander 2025-02-03 21:28 3593
    bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
1193b4be Poul Sander 2025-02-03 21:28 3594
    if (init)
1193b4be Poul Sander 2025-02-03 21:28 3595
    {
1193b4be Poul Sander 2025-02-03 21:28 3596
        // First frame we started displaying the InputText widget, we expect it to take the active id.
1193b4be Poul Sander 2025-02-03 21:28 3597
        IM_ASSERT(g.ActiveId == id);
1193b4be Poul Sander 2025-02-03 21:28 3598
        g.TempInputId = g.ActiveId;
1193b4be Poul Sander 2025-02-03 21:28 3599
    }
1193b4be Poul Sander 2025-02-03 21:28 3600
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3601
}
1193b4be Poul Sander 2025-02-03 21:28 3602
1193b4be Poul Sander 2025-02-03 21:28 3603
// 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 3604
// 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 3605
// 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 3606
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 3607
{
1193b4be Poul Sander 2025-02-03 21:28 3608
    // FIXME: May need to clarify display behavior if format doesn't contain %.
1193b4be Poul Sander 2025-02-03 21:28 3609
    // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405
1193b4be Poul Sander 2025-02-03 21:28 3610
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3611
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
1193b4be Poul Sander 2025-02-03 21:28 3612
    char fmt_buf[32];
1193b4be Poul Sander 2025-02-03 21:28 3613
    char data_buf[32];
1193b4be Poul Sander 2025-02-03 21:28 3614
    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
1193b4be Poul Sander 2025-02-03 21:28 3615
    if (format[0] == 0)
1193b4be Poul Sander 2025-02-03 21:28 3616
        format = type_info->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3617
    DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 3618
    ImStrTrimBlanks(data_buf);
1193b4be Poul Sander 2025-02-03 21:28 3619
1193b4be Poul Sander 2025-02-03 21:28 3620
    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
1193b4be Poul Sander 2025-02-03 21:28 3621
    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 3622
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3623
    if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
1193b4be Poul Sander 2025-02-03 21:28 3624
    {
1193b4be Poul Sander 2025-02-03 21:28 3625
        // Backup old value
1193b4be Poul Sander 2025-02-03 21:28 3626
        size_t data_type_size = type_info->Size;
1193b4be Poul Sander 2025-02-03 21:28 3627
        ImGuiDataTypeStorage data_backup;
1193b4be Poul Sander 2025-02-03 21:28 3628
        memcpy(&data_backup, p_data, data_type_size);
1193b4be Poul Sander 2025-02-03 21:28 3629
1193b4be Poul Sander 2025-02-03 21:28 3630
        // Apply new value (or operations) then clamp
1193b4be Poul Sander 2025-02-03 21:28 3631
        DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL);
1193b4be Poul Sander 2025-02-03 21:28 3632
        if (p_clamp_min || p_clamp_max)
1193b4be Poul Sander 2025-02-03 21:28 3633
        {
1193b4be Poul Sander 2025-02-03 21:28 3634
            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 3635
                ImSwap(p_clamp_min, p_clamp_max);
1193b4be Poul Sander 2025-02-03 21:28 3636
            DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
1193b4be Poul Sander 2025-02-03 21:28 3637
        }
1193b4be Poul Sander 2025-02-03 21:28 3638
1193b4be Poul Sander 2025-02-03 21:28 3639
        // Only mark as edited if new value is different
1193b4be Poul Sander 2025-02-03 21:28 3640
        g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
1193b4be Poul Sander 2025-02-03 21:28 3641
        value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
1193b4be Poul Sander 2025-02-03 21:28 3642
        if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3643
            MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 3644
    }
1193b4be Poul Sander 2025-02-03 21:28 3645
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3646
}
1193b4be Poul Sander 2025-02-03 21:28 3647
1193b4be Poul Sander 2025-02-03 21:28 3648
void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data)
1193b4be Poul Sander 2025-02-03 21:28 3649
{
1193b4be Poul Sander 2025-02-03 21:28 3650
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3651
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal;
1193b4be Poul Sander 2025-02-03 21:28 3652
    memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size);
1193b4be Poul Sander 2025-02-03 21:28 3653
}
1193b4be Poul Sander 2025-02-03 21:28 3654
1193b4be Poul Sander 2025-02-03 21:28 3655
// 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 3656
// 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 3657
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 3658
{
1193b4be Poul Sander 2025-02-03 21:28 3659
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3660
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3661
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3662
1193b4be Poul Sander 2025-02-03 21:28 3663
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3664
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3665
    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 3666
1193b4be Poul Sander 2025-02-03 21:28 3667
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3668
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3669
1193b4be Poul Sander 2025-02-03 21:28 3670
    void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue;
1193b4be Poul Sander 2025-02-03 21:28 3671
1193b4be Poul Sander 2025-02-03 21:28 3672
    char buf[64];
1193b4be Poul Sander 2025-02-03 21:28 3673
    if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)
1193b4be Poul Sander 2025-02-03 21:28 3674
        buf[0] = 0;
1193b4be Poul Sander 2025-02-03 21:28 3675
    else
1193b4be Poul Sander 2025-02-03 21:28 3676
        DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 3677
1193b4be Poul Sander 2025-02-03 21:28 3678
    // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.
1193b4be Poul Sander 2025-02-03 21:28 3679
    // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
1193b4be Poul Sander 2025-02-03 21:28 3680
    g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;
1193b4be Poul Sander 2025-02-03 21:28 3681
    flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
1193b4be Poul Sander 2025-02-03 21:28 3682
1193b4be Poul Sander 2025-02-03 21:28 3683
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3684
    if (p_step == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3685
    {
1193b4be Poul Sander 2025-02-03 21:28 3686
        if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
1193b4be Poul Sander 2025-02-03 21:28 3687
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
1193b4be Poul Sander 2025-02-03 21:28 3688
    }
1193b4be Poul Sander 2025-02-03 21:28 3689
    else
1193b4be Poul Sander 2025-02-03 21:28 3690
    {
1193b4be Poul Sander 2025-02-03 21:28 3691
        const float button_size = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 3692
1193b4be Poul Sander 2025-02-03 21:28 3693
        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 3694
        PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 3695
        SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
1193b4be Poul Sander 2025-02-03 21:28 3696
        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 3697
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
1193b4be Poul Sander 2025-02-03 21:28 3698
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
1193b4be Poul Sander 2025-02-03 21:28 3699
1193b4be Poul Sander 2025-02-03 21:28 3700
        // Step buttons
1193b4be Poul Sander 2025-02-03 21:28 3701
        const ImVec2 backup_frame_padding = style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 3702
        style.FramePadding.x = style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 3703
        if (flags & ImGuiInputTextFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 3704
            BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 3705
        PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
1193b4be Poul Sander 2025-02-03 21:28 3706
        SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3707
        if (ButtonEx("-", ImVec2(button_size, button_size)))
1193b4be Poul Sander 2025-02-03 21:28 3708
        {
1193b4be Poul Sander 2025-02-03 21:28 3709
            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 3710
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 3711
        }
1193b4be Poul Sander 2025-02-03 21:28 3712
        SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3713
        if (ButtonEx("+", ImVec2(button_size, button_size)))
1193b4be Poul Sander 2025-02-03 21:28 3714
        {
1193b4be Poul Sander 2025-02-03 21:28 3715
            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 3716
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 3717
        }
1193b4be Poul Sander 2025-02-03 21:28 3718
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 3719
        if (flags & ImGuiInputTextFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 3720
            EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 3721
1193b4be Poul Sander 2025-02-03 21:28 3722
        const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 3723
        if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 3724
        {
1193b4be Poul Sander 2025-02-03 21:28 3725
            SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3726
            TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 3727
        }
1193b4be Poul Sander 2025-02-03 21:28 3728
        style.FramePadding = backup_frame_padding;
1193b4be Poul Sander 2025-02-03 21:28 3729
1193b4be Poul Sander 2025-02-03 21:28 3730
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 3731
        EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 3732
    }
1193b4be Poul Sander 2025-02-03 21:28 3733
1193b4be Poul Sander 2025-02-03 21:28 3734
    g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
1193b4be Poul Sander 2025-02-03 21:28 3735
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3736
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 3737
1193b4be Poul Sander 2025-02-03 21:28 3738
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3739
}
1193b4be Poul Sander 2025-02-03 21:28 3740
1193b4be Poul Sander 2025-02-03 21:28 3741
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 3742
{
1193b4be Poul Sander 2025-02-03 21:28 3743
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3744
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3745
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3746
1193b4be Poul Sander 2025-02-03 21:28 3747
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3748
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3749
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 3750
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 3751
    PushMultiItemsWidths(components, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 3752
    size_t type_size = GDataTypeInfo[data_type].Size;
1193b4be Poul Sander 2025-02-03 21:28 3753
    for (int i = 0; i < components; i++)
1193b4be Poul Sander 2025-02-03 21:28 3754
    {
1193b4be Poul Sander 2025-02-03 21:28 3755
        PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 3756
        if (i > 0)
1193b4be Poul Sander 2025-02-03 21:28 3757
            SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3758
        value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3759
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 3760
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 3761
        p_data = (void*)((char*)p_data + type_size);
1193b4be Poul Sander 2025-02-03 21:28 3762
    }
1193b4be Poul Sander 2025-02-03 21:28 3763
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 3764
1193b4be Poul Sander 2025-02-03 21:28 3765
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 3766
    if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 3767
    {
1193b4be Poul Sander 2025-02-03 21:28 3768
        SameLine(0.0f, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3769
        TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 3770
    }
1193b4be Poul Sander 2025-02-03 21:28 3771
1193b4be Poul Sander 2025-02-03 21:28 3772
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 3773
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3774
}
1193b4be Poul Sander 2025-02-03 21:28 3775
1193b4be Poul Sander 2025-02-03 21:28 3776
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 3777
{
1193b4be Poul Sander 2025-02-03 21:28 3778
    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 3779
}
1193b4be Poul Sander 2025-02-03 21:28 3780
1193b4be Poul Sander 2025-02-03 21:28 3781
bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3782
{
1193b4be Poul Sander 2025-02-03 21:28 3783
    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3784
}
1193b4be Poul Sander 2025-02-03 21:28 3785
1193b4be Poul Sander 2025-02-03 21:28 3786
bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3787
{
1193b4be Poul Sander 2025-02-03 21:28 3788
    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3789
}
1193b4be Poul Sander 2025-02-03 21:28 3790
1193b4be Poul Sander 2025-02-03 21:28 3791
bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3792
{
1193b4be Poul Sander 2025-02-03 21:28 3793
    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3794
}
1193b4be Poul Sander 2025-02-03 21:28 3795
1193b4be Poul Sander 2025-02-03 21:28 3796
bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3797
{
1193b4be Poul Sander 2025-02-03 21:28 3798
    // 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 3799
    const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
1193b4be Poul Sander 2025-02-03 21:28 3800
    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 3801
}
1193b4be Poul Sander 2025-02-03 21:28 3802
1193b4be Poul Sander 2025-02-03 21:28 3803
bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3804
{
1193b4be Poul Sander 2025-02-03 21:28 3805
    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
1193b4be Poul Sander 2025-02-03 21:28 3806
}
1193b4be Poul Sander 2025-02-03 21:28 3807
1193b4be Poul Sander 2025-02-03 21:28 3808
bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3809
{
1193b4be Poul Sander 2025-02-03 21:28 3810
    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
1193b4be Poul Sander 2025-02-03 21:28 3811
}
1193b4be Poul Sander 2025-02-03 21:28 3812
1193b4be Poul Sander 2025-02-03 21:28 3813
bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3814
{
1193b4be Poul Sander 2025-02-03 21:28 3815
    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
1193b4be Poul Sander 2025-02-03 21:28 3816
}
1193b4be Poul Sander 2025-02-03 21:28 3817
1193b4be Poul Sander 2025-02-03 21:28 3818
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 3819
{
1193b4be Poul Sander 2025-02-03 21:28 3820
    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 3821
}
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
// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
1193b4be Poul Sander 2025-02-03 21:28 3825
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3826
// - imstb_textedit.h include
1193b4be Poul Sander 2025-02-03 21:28 3827
// - InputText()
1193b4be Poul Sander 2025-02-03 21:28 3828
// - InputTextWithHint()
1193b4be Poul Sander 2025-02-03 21:28 3829
// - InputTextMultiline()
1193b4be Poul Sander 2025-02-03 21:28 3830
// - InputTextGetCharInfo() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3831
// - InputTextReindexLines() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3832
// - InputTextReindexLinesRange() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3833
// - InputTextEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3834
// - DebugNodeInputTextState() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3835
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3836
1193b4be Poul Sander 2025-02-03 21:28 3837
namespace ImStb
1193b4be Poul Sander 2025-02-03 21:28 3838
{
1193b4be Poul Sander 2025-02-03 21:28 3839
#include "imstb_textedit.h"
1193b4be Poul Sander 2025-02-03 21:28 3840
}
1193b4be Poul Sander 2025-02-03 21:28 3841
1193b4be Poul Sander 2025-02-03 21:28 3842
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 3843
{
1193b4be Poul Sander 2025-02-03 21:28 3844
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
1193b4be Poul Sander 2025-02-03 21:28 3845
    return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
1193b4be Poul Sander 2025-02-03 21:28 3846
}
1193b4be Poul Sander 2025-02-03 21:28 3847
1193b4be Poul Sander 2025-02-03 21:28 3848
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 3849
{
1193b4be Poul Sander 2025-02-03 21:28 3850
    return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
1193b4be Poul Sander 2025-02-03 21:28 3851
}
1193b4be Poul Sander 2025-02-03 21:28 3852
1193b4be Poul Sander 2025-02-03 21:28 3853
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 3854
{
1193b4be Poul Sander 2025-02-03 21:28 3855
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or  InputTextEx() manually if you need multi-line + hint.
1193b4be Poul Sander 2025-02-03 21:28 3856
    return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
1193b4be Poul Sander 2025-02-03 21:28 3857
}
1193b4be Poul Sander 2025-02-03 21:28 3858
1193b4be Poul Sander 2025-02-03 21:28 3859
// This is only used in the path where the multiline widget is inactivate.
1193b4be Poul Sander 2025-02-03 21:28 3860
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
1193b4be Poul Sander 2025-02-03 21:28 3861
{
1193b4be Poul Sander 2025-02-03 21:28 3862
    int line_count = 0;
1193b4be Poul Sander 2025-02-03 21:28 3863
    const char* s = text_begin;
1193b4be Poul Sander 2025-02-03 21:28 3864
    while (true)
1193b4be Poul Sander 2025-02-03 21:28 3865
    {
1193b4be Poul Sander 2025-02-03 21:28 3866
        const char* s_eol = strchr(s, '\n');
1193b4be Poul Sander 2025-02-03 21:28 3867
        line_count++;
1193b4be Poul Sander 2025-02-03 21:28 3868
        if (s_eol == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3869
        {
1193b4be Poul Sander 2025-02-03 21:28 3870
            s = s + strlen(s);
1193b4be Poul Sander 2025-02-03 21:28 3871
            break;
1193b4be Poul Sander 2025-02-03 21:28 3872
        }
1193b4be Poul Sander 2025-02-03 21:28 3873
        s = s_eol + 1;
1193b4be Poul Sander 2025-02-03 21:28 3874
    }
1193b4be Poul Sander 2025-02-03 21:28 3875
    *out_text_end = s;
1193b4be Poul Sander 2025-02-03 21:28 3876
    return line_count;
1193b4be Poul Sander 2025-02-03 21:28 3877
}
1193b4be Poul Sander 2025-02-03 21:28 3878
1193b4be Poul Sander 2025-02-03 21:28 3879
// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA()
1193b4be Poul Sander 2025-02-03 21:28 3880
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line)
1193b4be Poul Sander 2025-02-03 21:28 3881
{
1193b4be Poul Sander 2025-02-03 21:28 3882
    ImGuiContext& g = *ctx;
1193b4be Poul Sander 2025-02-03 21:28 3883
    ImFont* font = g.Font;
1193b4be Poul Sander 2025-02-03 21:28 3884
    const float line_height = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 3885
    const float scale = line_height / font->FontSize;
1193b4be Poul Sander 2025-02-03 21:28 3886
1193b4be Poul Sander 2025-02-03 21:28 3887
    ImVec2 text_size = ImVec2(0, 0);
1193b4be Poul Sander 2025-02-03 21:28 3888
    float line_width = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3889
1193b4be Poul Sander 2025-02-03 21:28 3890
    const char* s = text_begin;
1193b4be Poul Sander 2025-02-03 21:28 3891
    while (s < text_end)
1193b4be Poul Sander 2025-02-03 21:28 3892
    {
1193b4be Poul Sander 2025-02-03 21:28 3893
        unsigned int c = (unsigned int)*s;
1193b4be Poul Sander 2025-02-03 21:28 3894
        if (c < 0x80)
1193b4be Poul Sander 2025-02-03 21:28 3895
            s += 1;
1193b4be Poul Sander 2025-02-03 21:28 3896
        else
1193b4be Poul Sander 2025-02-03 21:28 3897
            s += ImTextCharFromUtf8(&c, s, text_end);
1193b4be Poul Sander 2025-02-03 21:28 3898
1193b4be Poul Sander 2025-02-03 21:28 3899
        if (c == '\n')
1193b4be Poul Sander 2025-02-03 21:28 3900
        {
1193b4be Poul Sander 2025-02-03 21:28 3901
            text_size.x = ImMax(text_size.x, line_width);
1193b4be Poul Sander 2025-02-03 21:28 3902
            text_size.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 3903
            line_width = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3904
            if (stop_on_new_line)
1193b4be Poul Sander 2025-02-03 21:28 3905
                break;
1193b4be Poul Sander 2025-02-03 21:28 3906
            continue;
1193b4be Poul Sander 2025-02-03 21:28 3907
        }
1193b4be Poul Sander 2025-02-03 21:28 3908
        if (c == '\r')
1193b4be Poul Sander 2025-02-03 21:28 3909
            continue;
1193b4be Poul Sander 2025-02-03 21:28 3910
1193b4be Poul Sander 2025-02-03 21:28 3911
        const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale;
1193b4be Poul Sander 2025-02-03 21:28 3912
        line_width += char_width;
1193b4be Poul Sander 2025-02-03 21:28 3913
    }
1193b4be Poul Sander 2025-02-03 21:28 3914
1193b4be Poul Sander 2025-02-03 21:28 3915
    if (text_size.x < line_width)
1193b4be Poul Sander 2025-02-03 21:28 3916
        text_size.x = line_width;
1193b4be Poul Sander 2025-02-03 21:28 3917
1193b4be Poul Sander 2025-02-03 21:28 3918
    if (out_offset)
1193b4be Poul Sander 2025-02-03 21:28 3919
        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
1193b4be Poul Sander 2025-02-03 21:28 3920
1193b4be Poul Sander 2025-02-03 21:28 3921
    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
1193b4be Poul Sander 2025-02-03 21:28 3922
        text_size.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 3923
1193b4be Poul Sander 2025-02-03 21:28 3924
    if (remaining)
1193b4be Poul Sander 2025-02-03 21:28 3925
        *remaining = s;
1193b4be Poul Sander 2025-02-03 21:28 3926
1193b4be Poul Sander 2025-02-03 21:28 3927
    return text_size;
1193b4be Poul Sander 2025-02-03 21:28 3928
}
1193b4be Poul Sander 2025-02-03 21:28 3929
1193b4be Poul Sander 2025-02-03 21:28 3930
// 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 3931
// With our UTF-8 use of stb_textedit:
1193b4be Poul Sander 2025-02-03 21:28 3932
// - 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 3933
// - 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 3934
// - ...but we don't use that feature.
1193b4be Poul Sander 2025-02-03 21:28 3935
namespace ImStb
1193b4be Poul Sander 2025-02-03 21:28 3936
{
1193b4be Poul Sander 2025-02-03 21:28 3937
static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->CurLenA; }
1193b4be Poul Sander 2025-02-03 21:28 3938
static char    STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx <= obj->CurLenA); return obj->TextA[idx]; }
1193b4be Poul Sander 2025-02-03 21:28 3939
static float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx)  { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextA.Size); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
1193b4be Poul Sander 2025-02-03 21:28 3940
static char    STB_TEXTEDIT_NEWLINE = '\n';
1193b4be Poul Sander 2025-02-03 21:28 3941
static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
1193b4be Poul Sander 2025-02-03 21:28 3942
{
1193b4be Poul Sander 2025-02-03 21:28 3943
    const char* text = obj->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 3944
    const char* text_remaining = NULL;
1193b4be Poul Sander 2025-02-03 21:28 3945
    const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->CurLenA, &text_remaining, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 3946
    r->x0 = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3947
    r->x1 = size.x;
1193b4be Poul Sander 2025-02-03 21:28 3948
    r->baseline_y_delta = size.y;
1193b4be Poul Sander 2025-02-03 21:28 3949
    r->ymin = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3950
    r->ymax = size.y;
1193b4be Poul Sander 2025-02-03 21:28 3951
    r->num_chars = (int)(text_remaining - (text + line_start_idx));
1193b4be Poul Sander 2025-02-03 21:28 3952
}
1193b4be Poul Sander 2025-02-03 21:28 3953
1193b4be Poul Sander 2025-02-03 21:28 3954
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX  IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL
1193b4be Poul Sander 2025-02-03 21:28 3955
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX  IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL
1193b4be Poul Sander 2025-02-03 21:28 3956
1193b4be Poul Sander 2025-02-03 21:28 3957
static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 3958
{
1193b4be Poul Sander 2025-02-03 21:28 3959
    if (idx >= obj->CurLenA)
1193b4be Poul Sander 2025-02-03 21:28 3960
        return obj->CurLenA + 1;
1193b4be Poul Sander 2025-02-03 21:28 3961
    unsigned int c;
1193b4be Poul Sander 2025-02-03 21:28 3962
    return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextA.Size);
1193b4be Poul Sander 2025-02-03 21:28 3963
}
1193b4be Poul Sander 2025-02-03 21:28 3964
1193b4be Poul Sander 2025-02-03 21:28 3965
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 3966
{
1193b4be Poul Sander 2025-02-03 21:28 3967
    if (idx <= 0)
1193b4be Poul Sander 2025-02-03 21:28 3968
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 3969
    const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx);
1193b4be Poul Sander 2025-02-03 21:28 3970
    return (int)(p - obj->TextA.Data);
1193b4be Poul Sander 2025-02-03 21:28 3971
}
1193b4be Poul Sander 2025-02-03 21:28 3972
1193b4be Poul Sander 2025-02-03 21:28 3973
static bool ImCharIsSeparatorW(unsigned int c)
1193b4be Poul Sander 2025-02-03 21:28 3974
{
1193b4be Poul Sander 2025-02-03 21:28 3975
    static const unsigned int separator_list[] =
1193b4be Poul Sander 2025-02-03 21:28 3976
    {
1193b4be Poul Sander 2025-02-03 21:28 3977
        ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D,
1193b4be Poul Sander 2025-02-03 21:28 3978
        '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F,
1193b4be Poul Sander 2025-02-03 21:28 3979
        '\n', '\r',
1193b4be Poul Sander 2025-02-03 21:28 3980
    };
1193b4be Poul Sander 2025-02-03 21:28 3981
    for (unsigned int separator : separator_list)
1193b4be Poul Sander 2025-02-03 21:28 3982
        if (c == separator)
1193b4be Poul Sander 2025-02-03 21:28 3983
            return true;
1193b4be Poul Sander 2025-02-03 21:28 3984
    return false;
1193b4be Poul Sander 2025-02-03 21:28 3985
}
1193b4be Poul Sander 2025-02-03 21:28 3986
1193b4be Poul Sander 2025-02-03 21:28 3987
static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 3988
{
1193b4be Poul Sander 2025-02-03 21:28 3989
    // 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 3990
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
1193b4be Poul Sander 2025-02-03 21:28 3991
        return 0;
1193b4be Poul Sander 2025-02-03 21:28 3992
1193b4be Poul Sander 2025-02-03 21:28 3993
    const char* curr_p = obj->TextA.Data + idx;
1193b4be Poul Sander 2025-02-03 21:28 3994
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
1193b4be Poul Sander 2025-02-03 21:28 3995
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextA.Size);
1193b4be Poul Sander 2025-02-03 21:28 3996
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextA.Size);
1193b4be Poul Sander 2025-02-03 21:28 3997
1193b4be Poul Sander 2025-02-03 21:28 3998
    bool prev_white = ImCharIsBlankW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 3999
    bool prev_separ = ImCharIsSeparatorW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4000
    bool curr_white = ImCharIsBlankW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4001
    bool curr_separ = ImCharIsSeparatorW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4002
    return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
1193b4be Poul Sander 2025-02-03 21:28 4003
}
1193b4be Poul Sander 2025-02-03 21:28 4004
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4005
{
1193b4be Poul Sander 2025-02-03 21:28 4006
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
1193b4be Poul Sander 2025-02-03 21:28 4007
        return 0;
1193b4be Poul Sander 2025-02-03 21:28 4008
1193b4be Poul Sander 2025-02-03 21:28 4009
    const char* curr_p = obj->TextA.Data + idx;
1193b4be Poul Sander 2025-02-03 21:28 4010
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
1193b4be Poul Sander 2025-02-03 21:28 4011
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextA.Size);
1193b4be Poul Sander 2025-02-03 21:28 4012
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextA.Size);
1193b4be Poul Sander 2025-02-03 21:28 4013
1193b4be Poul Sander 2025-02-03 21:28 4014
    bool prev_white = ImCharIsBlankW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4015
    bool prev_separ = ImCharIsSeparatorW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4016
    bool curr_white = ImCharIsBlankW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4017
    bool curr_separ = ImCharIsSeparatorW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4018
    return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
1193b4be Poul Sander 2025-02-03 21:28 4019
}
1193b4be Poul Sander 2025-02-03 21:28 4020
static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4021
{
1193b4be Poul Sander 2025-02-03 21:28 4022
    idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4023
    while (idx >= 0 && !is_word_boundary_from_right(obj, idx))
1193b4be Poul Sander 2025-02-03 21:28 4024
        idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4025
    return idx < 0 ? 0 : idx;
1193b4be Poul Sander 2025-02-03 21:28 4026
}
1193b4be Poul Sander 2025-02-03 21:28 4027
static int  STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4028
{
1193b4be Poul Sander 2025-02-03 21:28 4029
    int len = obj->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 4030
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4031
    while (idx < len && !is_word_boundary_from_left(obj, idx))
1193b4be Poul Sander 2025-02-03 21:28 4032
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4033
    return idx > len ? len : idx;
1193b4be Poul Sander 2025-02-03 21:28 4034
}
1193b4be Poul Sander 2025-02-03 21:28 4035
static int  STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4036
{
1193b4be Poul Sander 2025-02-03 21:28 4037
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4038
    int len = obj->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 4039
    while (idx < len && !is_word_boundary_from_right(obj, idx))
1193b4be Poul Sander 2025-02-03 21:28 4040
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4041
    return idx > len ? len : idx;
1193b4be Poul Sander 2025-02-03 21:28 4042
}
1193b4be Poul Sander 2025-02-03 21:28 4043
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 4044
#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 4045
#define STB_TEXTEDIT_MOVEWORDRIGHT      STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
1193b4be Poul Sander 2025-02-03 21:28 4046
1193b4be Poul Sander 2025-02-03 21:28 4047
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
1193b4be Poul Sander 2025-02-03 21:28 4048
{
1193b4be Poul Sander 2025-02-03 21:28 4049
    char* dst = obj->TextA.Data + pos;
1193b4be Poul Sander 2025-02-03 21:28 4050
1193b4be Poul Sander 2025-02-03 21:28 4051
    obj->Edited = true;
1193b4be Poul Sander 2025-02-03 21:28 4052
    obj->CurLenA -= n;
1193b4be Poul Sander 2025-02-03 21:28 4053
1193b4be Poul Sander 2025-02-03 21:28 4054
    // Offset remaining text (FIXME-OPT: Use memmove)
1193b4be Poul Sander 2025-02-03 21:28 4055
    const char* src = obj->TextA.Data + pos + n;
1193b4be Poul Sander 2025-02-03 21:28 4056
    while (char c = *src++)
1193b4be Poul Sander 2025-02-03 21:28 4057
        *dst++ = c;
1193b4be Poul Sander 2025-02-03 21:28 4058
    *dst = '\0';
1193b4be Poul Sander 2025-02-03 21:28 4059
}
1193b4be Poul Sander 2025-02-03 21:28 4060
1193b4be Poul Sander 2025-02-03 21:28 4061
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 4062
{
1193b4be Poul Sander 2025-02-03 21:28 4063
    const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4064
    const int text_len = obj->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 4065
    IM_ASSERT(pos <= text_len);
1193b4be Poul Sander 2025-02-03 21:28 4066
1193b4be Poul Sander 2025-02-03 21:28 4067
    if (!is_resizable && (new_text_len + obj->CurLenA + 1 > obj->BufCapacityA))
1193b4be Poul Sander 2025-02-03 21:28 4068
        return false;
1193b4be Poul Sander 2025-02-03 21:28 4069
1193b4be Poul Sander 2025-02-03 21:28 4070
    // Grow internal buffer if needed
1193b4be Poul Sander 2025-02-03 21:28 4071
    if (new_text_len + text_len + 1 > obj->TextA.Size)
1193b4be Poul Sander 2025-02-03 21:28 4072
    {
1193b4be Poul Sander 2025-02-03 21:28 4073
        if (!is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 4074
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4075
        obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
1193b4be Poul Sander 2025-02-03 21:28 4076
    }
1193b4be Poul Sander 2025-02-03 21:28 4077
1193b4be Poul Sander 2025-02-03 21:28 4078
    char* text = obj->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 4079
    if (pos != text_len)
1193b4be Poul Sander 2025-02-03 21:28 4080
        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos));
1193b4be Poul Sander 2025-02-03 21:28 4081
    memcpy(text + pos, new_text, (size_t)new_text_len);
1193b4be Poul Sander 2025-02-03 21:28 4082
1193b4be Poul Sander 2025-02-03 21:28 4083
    obj->Edited = true;
1193b4be Poul Sander 2025-02-03 21:28 4084
    obj->CurLenA += new_text_len;
1193b4be Poul Sander 2025-02-03 21:28 4085
    obj->TextA[obj->CurLenA] = '\0';
1193b4be Poul Sander 2025-02-03 21:28 4086
1193b4be Poul Sander 2025-02-03 21:28 4087
    return true;
1193b4be Poul Sander 2025-02-03 21:28 4088
}
1193b4be Poul Sander 2025-02-03 21:28 4089
1193b4be Poul Sander 2025-02-03 21:28 4090
// 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 4091
#define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
1193b4be Poul Sander 2025-02-03 21:28 4092
#define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
1193b4be Poul Sander 2025-02-03 21:28 4093
#define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
1193b4be Poul Sander 2025-02-03 21:28 4094
#define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
1193b4be Poul Sander 2025-02-03 21:28 4095
#define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
1193b4be Poul Sander 2025-02-03 21:28 4096
#define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
1193b4be Poul Sander 2025-02-03 21:28 4097
#define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
1193b4be Poul Sander 2025-02-03 21:28 4098
#define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
1193b4be Poul Sander 2025-02-03 21:28 4099
#define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
1193b4be Poul Sander 2025-02-03 21:28 4100
#define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
1193b4be Poul Sander 2025-02-03 21:28 4101
#define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
1193b4be Poul Sander 2025-02-03 21:28 4102
#define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
1193b4be Poul Sander 2025-02-03 21:28 4103
#define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
1193b4be Poul Sander 2025-02-03 21:28 4104
#define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
1193b4be Poul Sander 2025-02-03 21:28 4105
#define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
1193b4be Poul Sander 2025-02-03 21:28 4106
#define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
1193b4be Poul Sander 2025-02-03 21:28 4107
#define STB_TEXTEDIT_K_SHIFT        0x400000
1193b4be Poul Sander 2025-02-03 21:28 4108
1193b4be Poul Sander 2025-02-03 21:28 4109
#define IMSTB_TEXTEDIT_IMPLEMENTATION
1193b4be Poul Sander 2025-02-03 21:28 4110
#define IMSTB_TEXTEDIT_memmove memmove
1193b4be Poul Sander 2025-02-03 21:28 4111
#include "imstb_textedit.h"
1193b4be Poul Sander 2025-02-03 21:28 4112
1193b4be Poul Sander 2025-02-03 21:28 4113
// 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 4114
// 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 4115
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 4116
{
1193b4be Poul Sander 2025-02-03 21:28 4117
    stb_text_makeundo_replace(str, state, 0, str->CurLenA, text_len);
1193b4be Poul Sander 2025-02-03 21:28 4118
    ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenA);
1193b4be Poul Sander 2025-02-03 21:28 4119
    state->cursor = state->select_start = state->select_end = 0;
1193b4be Poul Sander 2025-02-03 21:28 4120
    if (text_len <= 0)
1193b4be Poul Sander 2025-02-03 21:28 4121
        return;
1193b4be Poul Sander 2025-02-03 21:28 4122
    if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
1193b4be Poul Sander 2025-02-03 21:28 4123
    {
1193b4be Poul Sander 2025-02-03 21:28 4124
        state->cursor = state->select_start = state->select_end = text_len;
1193b4be Poul Sander 2025-02-03 21:28 4125
        state->has_preferred_x = 0;
1193b4be Poul Sander 2025-02-03 21:28 4126
        return;
1193b4be Poul Sander 2025-02-03 21:28 4127
    }
1193b4be Poul Sander 2025-02-03 21:28 4128
    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 4129
}
1193b4be Poul Sander 2025-02-03 21:28 4130
1193b4be Poul Sander 2025-02-03 21:28 4131
} // namespace ImStb
1193b4be Poul Sander 2025-02-03 21:28 4132
1193b4be Poul Sander 2025-02-03 21:28 4133
// 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 4134
ImGuiInputTextState::ImGuiInputTextState()
1193b4be Poul Sander 2025-02-03 21:28 4135
{
1193b4be Poul Sander 2025-02-03 21:28 4136
    memset(this, 0, sizeof(*this));
1193b4be Poul Sander 2025-02-03 21:28 4137
    Stb = IM_NEW(ImStbTexteditState);
1193b4be Poul Sander 2025-02-03 21:28 4138
    memset(Stb, 0, sizeof(*Stb));
1193b4be Poul Sander 2025-02-03 21:28 4139
}
1193b4be Poul Sander 2025-02-03 21:28 4140
1193b4be Poul Sander 2025-02-03 21:28 4141
ImGuiInputTextState::~ImGuiInputTextState()
1193b4be Poul Sander 2025-02-03 21:28 4142
{
1193b4be Poul Sander 2025-02-03 21:28 4143
    IM_DELETE(Stb);
1193b4be Poul Sander 2025-02-03 21:28 4144
}
1193b4be Poul Sander 2025-02-03 21:28 4145
1193b4be Poul Sander 2025-02-03 21:28 4146
void ImGuiInputTextState::OnKeyPressed(int key)
1193b4be Poul Sander 2025-02-03 21:28 4147
{
1193b4be Poul Sander 2025-02-03 21:28 4148
    stb_textedit_key(this, Stb, key);
1193b4be Poul Sander 2025-02-03 21:28 4149
    CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4150
    CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4151
}
1193b4be Poul Sander 2025-02-03 21:28 4152
1193b4be Poul Sander 2025-02-03 21:28 4153
void ImGuiInputTextState::OnCharPressed(unsigned int c)
1193b4be Poul Sander 2025-02-03 21:28 4154
{
1193b4be Poul Sander 2025-02-03 21:28 4155
    // Convert the key to a UTF8 byte sequence.
1193b4be Poul Sander 2025-02-03 21:28 4156
    // 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 4157
    char utf8[5];
1193b4be Poul Sander 2025-02-03 21:28 4158
    ImTextCharToUtf8(utf8, c);
1193b4be Poul Sander 2025-02-03 21:28 4159
    stb_textedit_text(this, Stb, utf8, (int)strlen(utf8));
1193b4be Poul Sander 2025-02-03 21:28 4160
    CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4161
    CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4162
}
1193b4be Poul Sander 2025-02-03 21:28 4163
1193b4be Poul Sander 2025-02-03 21:28 4164
// 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 4165
void ImGuiInputTextState::CursorAnimReset()                 { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking
1193b4be Poul Sander 2025-02-03 21:28 4166
void ImGuiInputTextState::CursorClamp()                     { Stb->cursor = ImMin(Stb->cursor, CurLenA); Stb->select_start = ImMin(Stb->select_start, CurLenA); Stb->select_end = ImMin(Stb->select_end, CurLenA); }
1193b4be Poul Sander 2025-02-03 21:28 4167
bool ImGuiInputTextState::HasSelection() const              { return Stb->select_start != Stb->select_end; }
1193b4be Poul Sander 2025-02-03 21:28 4168
void ImGuiInputTextState::ClearSelection()                  { Stb->select_start = Stb->select_end = Stb->cursor; }
1193b4be Poul Sander 2025-02-03 21:28 4169
int  ImGuiInputTextState::GetCursorPos() const              { return Stb->cursor; }
1193b4be Poul Sander 2025-02-03 21:28 4170
int  ImGuiInputTextState::GetSelectionStart() const         { return Stb->select_start; }
1193b4be Poul Sander 2025-02-03 21:28 4171
int  ImGuiInputTextState::GetSelectionEnd() const           { return Stb->select_end; }
1193b4be Poul Sander 2025-02-03 21:28 4172
void ImGuiInputTextState::SelectAll()                       { Stb->select_start = 0; Stb->cursor = Stb->select_end = CurLenA; Stb->has_preferred_x = 0; }
1193b4be Poul Sander 2025-02-03 21:28 4173
void ImGuiInputTextState::ReloadUserBufAndSelectAll()       { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
1193b4be Poul Sander 2025-02-03 21:28 4174
void ImGuiInputTextState::ReloadUserBufAndKeepSelection()   { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
1193b4be Poul Sander 2025-02-03 21:28 4175
void ImGuiInputTextState::ReloadUserBufAndMoveToEnd()       { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
1193b4be Poul Sander 2025-02-03 21:28 4176
1193b4be Poul Sander 2025-02-03 21:28 4177
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
1193b4be Poul Sander 2025-02-03 21:28 4178
{
1193b4be Poul Sander 2025-02-03 21:28 4179
    memset(this, 0, sizeof(*this));
1193b4be Poul Sander 2025-02-03 21:28 4180
}
1193b4be Poul Sander 2025-02-03 21:28 4181
1193b4be Poul Sander 2025-02-03 21:28 4182
// Public API to manipulate UTF-8 text
1193b4be Poul Sander 2025-02-03 21:28 4183
// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
1193b4be Poul Sander 2025-02-03 21:28 4184
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
1193b4be Poul Sander 2025-02-03 21:28 4185
void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
1193b4be Poul Sander 2025-02-03 21:28 4186
{
1193b4be Poul Sander 2025-02-03 21:28 4187
    IM_ASSERT(pos + bytes_count <= BufTextLen);
1193b4be Poul Sander 2025-02-03 21:28 4188
    char* dst = Buf + pos;
1193b4be Poul Sander 2025-02-03 21:28 4189
    const char* src = Buf + pos + bytes_count;
1193b4be Poul Sander 2025-02-03 21:28 4190
    while (char c = *src++)
1193b4be Poul Sander 2025-02-03 21:28 4191
        *dst++ = c;
1193b4be Poul Sander 2025-02-03 21:28 4192
    *dst = '\0';
1193b4be Poul Sander 2025-02-03 21:28 4193
1193b4be Poul Sander 2025-02-03 21:28 4194
    if (CursorPos >= pos + bytes_count)
1193b4be Poul Sander 2025-02-03 21:28 4195
        CursorPos -= bytes_count;
1193b4be Poul Sander 2025-02-03 21:28 4196
    else if (CursorPos >= pos)
1193b4be Poul Sander 2025-02-03 21:28 4197
        CursorPos = pos;
1193b4be Poul Sander 2025-02-03 21:28 4198
    SelectionStart = SelectionEnd = CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 4199
    BufDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 4200
    BufTextLen -= bytes_count;
1193b4be Poul Sander 2025-02-03 21:28 4201
}
1193b4be Poul Sander 2025-02-03 21:28 4202
1193b4be Poul Sander 2025-02-03 21:28 4203
void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
1193b4be Poul Sander 2025-02-03 21:28 4204
{
1193b4be Poul Sander 2025-02-03 21:28 4205
    // Accept null ranges
1193b4be Poul Sander 2025-02-03 21:28 4206
    if (new_text == new_text_end)
1193b4be Poul Sander 2025-02-03 21:28 4207
        return;
1193b4be Poul Sander 2025-02-03 21:28 4208
1193b4be Poul Sander 2025-02-03 21:28 4209
    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4210
    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
1193b4be Poul Sander 2025-02-03 21:28 4211
    if (new_text_len + BufTextLen >= BufSize)
1193b4be Poul Sander 2025-02-03 21:28 4212
    {
1193b4be Poul Sander 2025-02-03 21:28 4213
        if (!is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 4214
            return;
1193b4be Poul Sander 2025-02-03 21:28 4215
1193b4be Poul Sander 2025-02-03 21:28 4216
        // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
1193b4be Poul Sander 2025-02-03 21:28 4217
        ImGuiContext& g = *Ctx;
1193b4be Poul Sander 2025-02-03 21:28 4218
        ImGuiInputTextState* edit_state = &g.InputTextState;
1193b4be Poul Sander 2025-02-03 21:28 4219
        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
1193b4be Poul Sander 2025-02-03 21:28 4220
        IM_ASSERT(Buf == edit_state->TextA.Data);
1193b4be Poul Sander 2025-02-03 21:28 4221
        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
1193b4be Poul Sander 2025-02-03 21:28 4222
        edit_state->TextA.reserve(new_buf_size + 1);
1193b4be Poul Sander 2025-02-03 21:28 4223
        Buf = edit_state->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 4224
        BufSize = edit_state->BufCapacityA = new_buf_size;
1193b4be Poul Sander 2025-02-03 21:28 4225
    }
1193b4be Poul Sander 2025-02-03 21:28 4226
1193b4be Poul Sander 2025-02-03 21:28 4227
    if (BufTextLen != pos)
1193b4be Poul Sander 2025-02-03 21:28 4228
        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
1193b4be Poul Sander 2025-02-03 21:28 4229
    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
1193b4be Poul Sander 2025-02-03 21:28 4230
    Buf[BufTextLen + new_text_len] = '\0';
1193b4be Poul Sander 2025-02-03 21:28 4231
1193b4be Poul Sander 2025-02-03 21:28 4232
    if (CursorPos >= pos)
1193b4be Poul Sander 2025-02-03 21:28 4233
        CursorPos += new_text_len;
1193b4be Poul Sander 2025-02-03 21:28 4234
    SelectionStart = SelectionEnd = CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 4235
    BufDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 4236
    BufTextLen += new_text_len;
1193b4be Poul Sander 2025-02-03 21:28 4237
}
1193b4be Poul Sander 2025-02-03 21:28 4238
1193b4be Poul Sander 2025-02-03 21:28 4239
// Return false to discard a character.
1193b4be Poul Sander 2025-02-03 21:28 4240
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 4241
{
1193b4be Poul Sander 2025-02-03 21:28 4242
    unsigned int c = *p_char;
1193b4be Poul Sander 2025-02-03 21:28 4243
1193b4be Poul Sander 2025-02-03 21:28 4244
    // Filter non-printable (NB: isprint is unreliable! see #2467)
1193b4be Poul Sander 2025-02-03 21:28 4245
    bool apply_named_filters = true;
1193b4be Poul Sander 2025-02-03 21:28 4246
    if (c < 0x20)
1193b4be Poul Sander 2025-02-03 21:28 4247
    {
1193b4be Poul Sander 2025-02-03 21:28 4248
        bool pass = false;
1193b4be Poul Sander 2025-02-03 21:28 4249
        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)
1193b4be Poul Sander 2025-02-03 21:28 4250
        pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4251
        if (!pass)
1193b4be Poul Sander 2025-02-03 21:28 4252
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4253
        apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
1193b4be Poul Sander 2025-02-03 21:28 4254
    }
1193b4be Poul Sander 2025-02-03 21:28 4255
1193b4be Poul Sander 2025-02-03 21:28 4256
    if (input_source_is_clipboard == false)
1193b4be Poul Sander 2025-02-03 21:28 4257
    {
1193b4be Poul Sander 2025-02-03 21:28 4258
        // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
1193b4be Poul Sander 2025-02-03 21:28 4259
        if (c == 127)
1193b4be Poul Sander 2025-02-03 21:28 4260
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4261
1193b4be Poul Sander 2025-02-03 21:28 4262
        // 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 4263
        if (c >= 0xE000 && c <= 0xF8FF)
1193b4be Poul Sander 2025-02-03 21:28 4264
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4265
    }
1193b4be Poul Sander 2025-02-03 21:28 4266
1193b4be Poul Sander 2025-02-03 21:28 4267
    // Filter Unicode ranges we are not handling in this build
1193b4be Poul Sander 2025-02-03 21:28 4268
    if (c > IM_UNICODE_CODEPOINT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 4269
        return false;
1193b4be Poul Sander 2025-02-03 21:28 4270
1193b4be Poul Sander 2025-02-03 21:28 4271
    // Generic named filters
1193b4be Poul Sander 2025-02-03 21:28 4272
    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 4273
    {
1193b4be Poul Sander 2025-02-03 21:28 4274
        // 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 4275
        // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
1193b4be Poul Sander 2025-02-03 21:28 4276
        // 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 4277
        // Change the default decimal_point with:
1193b4be Poul Sander 2025-02-03 21:28 4278
        //   ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point;
1193b4be Poul Sander 2025-02-03 21:28 4279
        // 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 4280
        ImGuiContext& g = *ctx;
1193b4be Poul Sander 2025-02-03 21:28 4281
        const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint;
1193b4be Poul Sander 2025-02-03 21:28 4282
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
1193b4be Poul Sander 2025-02-03 21:28 4283
            if (c == '.' || c == ',')
1193b4be Poul Sander 2025-02-03 21:28 4284
                c = c_decimal_point;
1193b4be Poul Sander 2025-02-03 21:28 4285
1193b4be Poul Sander 2025-02-03 21:28 4286
        // 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 4287
        // 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 4288
        // 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 4289
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
1193b4be Poul Sander 2025-02-03 21:28 4290
            if (c >= 0xFF01 && c <= 0xFF5E)
1193b4be Poul Sander 2025-02-03 21:28 4291
                c = c - 0xFF01 + 0x21;
1193b4be Poul Sander 2025-02-03 21:28 4292
1193b4be Poul Sander 2025-02-03 21:28 4293
        // Allow 0-9 . - + * /
1193b4be Poul Sander 2025-02-03 21:28 4294
        if (flags & ImGuiInputTextFlags_CharsDecimal)
1193b4be Poul Sander 2025-02-03 21:28 4295
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
1193b4be Poul Sander 2025-02-03 21:28 4296
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4297
1193b4be Poul Sander 2025-02-03 21:28 4298
        // Allow 0-9 . - + * / e E
1193b4be Poul Sander 2025-02-03 21:28 4299
        if (flags & ImGuiInputTextFlags_CharsScientific)
1193b4be Poul Sander 2025-02-03 21:28 4300
            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 4301
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4302
1193b4be Poul Sander 2025-02-03 21:28 4303
        // Allow 0-9 a-F A-F
1193b4be Poul Sander 2025-02-03 21:28 4304
        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
1193b4be Poul Sander 2025-02-03 21:28 4305
            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
1193b4be Poul Sander 2025-02-03 21:28 4306
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4307
1193b4be Poul Sander 2025-02-03 21:28 4308
        // Turn a-z into A-Z
1193b4be Poul Sander 2025-02-03 21:28 4309
        if (flags & ImGuiInputTextFlags_CharsUppercase)
1193b4be Poul Sander 2025-02-03 21:28 4310
            if (c >= 'a' && c <= 'z')
1193b4be Poul Sander 2025-02-03 21:28 4311
                c += (unsigned int)('A' - 'a');
1193b4be Poul Sander 2025-02-03 21:28 4312
1193b4be Poul Sander 2025-02-03 21:28 4313
        if (flags & ImGuiInputTextFlags_CharsNoBlank)
1193b4be Poul Sander 2025-02-03 21:28 4314
            if (ImCharIsBlankW(c))
1193b4be Poul Sander 2025-02-03 21:28 4315
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4316
1193b4be Poul Sander 2025-02-03 21:28 4317
        *p_char = c;
1193b4be Poul Sander 2025-02-03 21:28 4318
    }
1193b4be Poul Sander 2025-02-03 21:28 4319
1193b4be Poul Sander 2025-02-03 21:28 4320
    // Custom callback filter
1193b4be Poul Sander 2025-02-03 21:28 4321
    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
1193b4be Poul Sander 2025-02-03 21:28 4322
    {
1193b4be Poul Sander 2025-02-03 21:28 4323
        ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 4324
        ImGuiInputTextCallbackData callback_data;
1193b4be Poul Sander 2025-02-03 21:28 4325
        callback_data.Ctx = &g;
1193b4be Poul Sander 2025-02-03 21:28 4326
        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
1193b4be Poul Sander 2025-02-03 21:28 4327
        callback_data.EventChar = (ImWchar)c;
1193b4be Poul Sander 2025-02-03 21:28 4328
        callback_data.Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 4329
        callback_data.UserData = user_data;
1193b4be Poul Sander 2025-02-03 21:28 4330
        if (callback(&callback_data) != 0)
1193b4be Poul Sander 2025-02-03 21:28 4331
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4332
        *p_char = callback_data.EventChar;
1193b4be Poul Sander 2025-02-03 21:28 4333
        if (!callback_data.EventChar)
1193b4be Poul Sander 2025-02-03 21:28 4334
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4335
    }
1193b4be Poul Sander 2025-02-03 21:28 4336
1193b4be Poul Sander 2025-02-03 21:28 4337
    return true;
1193b4be Poul Sander 2025-02-03 21:28 4338
}
1193b4be Poul Sander 2025-02-03 21:28 4339
1193b4be Poul Sander 2025-02-03 21:28 4340
// Find the shortest single replacement we can make to get the new text from the old text.
1193b4be Poul Sander 2025-02-03 21:28 4341
// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end.
1193b4be Poul Sander 2025-02-03 21:28 4342
// 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.
1193b4be Poul Sander 2025-02-03 21:28 4343
static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a)
1193b4be Poul Sander 2025-02-03 21:28 4344
{
1193b4be Poul Sander 2025-02-03 21:28 4345
    const char* old_buf = state->CallbackTextBackup.Data;
1193b4be Poul Sander 2025-02-03 21:28 4346
    const int old_length = state->CallbackTextBackup.Size - 1;
1193b4be Poul Sander 2025-02-03 21:28 4347
1193b4be Poul Sander 2025-02-03 21:28 4348
    const int shorter_length = ImMin(old_length, new_length_a);
1193b4be Poul Sander 2025-02-03 21:28 4349
    int first_diff;
1193b4be Poul Sander 2025-02-03 21:28 4350
    for (first_diff = 0; first_diff < shorter_length; first_diff++)
1193b4be Poul Sander 2025-02-03 21:28 4351
        if (old_buf[first_diff] != new_buf_a[first_diff])
1193b4be Poul Sander 2025-02-03 21:28 4352
            break;
1193b4be Poul Sander 2025-02-03 21:28 4353
    if (first_diff == old_length && first_diff == new_length_a)
1193b4be Poul Sander 2025-02-03 21:28 4354
        return;
1193b4be Poul Sander 2025-02-03 21:28 4355
1193b4be Poul Sander 2025-02-03 21:28 4356
    int old_last_diff = old_length   - 1;
1193b4be Poul Sander 2025-02-03 21:28 4357
    int new_last_diff = new_length_a - 1;
1193b4be Poul Sander 2025-02-03 21:28 4358
    for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
1193b4be Poul Sander 2025-02-03 21:28 4359
        if (old_buf[old_last_diff] != new_buf_a[new_last_diff])
1193b4be Poul Sander 2025-02-03 21:28 4360
            break;
1193b4be Poul Sander 2025-02-03 21:28 4361
1193b4be Poul Sander 2025-02-03 21:28 4362
    const int insert_len = new_last_diff - first_diff + 1;
1193b4be Poul Sander 2025-02-03 21:28 4363
    const int delete_len = old_last_diff - first_diff + 1;
1193b4be Poul Sander 2025-02-03 21:28 4364
    if (insert_len > 0 || delete_len > 0)
1193b4be Poul Sander 2025-02-03 21:28 4365
        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 4366
            for (int i = 0; i < delete_len; i++)
1193b4be Poul Sander 2025-02-03 21:28 4367
                p[i] = old_buf[first_diff + i];
1193b4be Poul Sander 2025-02-03 21:28 4368
}
1193b4be Poul Sander 2025-02-03 21:28 4369
1193b4be Poul Sander 2025-02-03 21:28 4370
// 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 4371
// 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 4372
// 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 4373
// but that more likely be attractive when we do have _NoLiveEdit flag available.
1193b4be Poul Sander 2025-02-03 21:28 4374
void ImGui::InputTextDeactivateHook(ImGuiID id)
1193b4be Poul Sander 2025-02-03 21:28 4375
{
1193b4be Poul Sander 2025-02-03 21:28 4376
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 4377
    ImGuiInputTextState* state = &g.InputTextState;
1193b4be Poul Sander 2025-02-03 21:28 4378
    if (id == 0 || state->ID != id)
1193b4be Poul Sander 2025-02-03 21:28 4379
        return;
1193b4be Poul Sander 2025-02-03 21:28 4380
    g.InputTextDeactivatedState.ID = state->ID;
1193b4be Poul Sander 2025-02-03 21:28 4381
    if (state->Flags & ImGuiInputTextFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 4382
    {
1193b4be Poul Sander 2025-02-03 21:28 4383
        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 4384
    }
1193b4be Poul Sander 2025-02-03 21:28 4385
    else
1193b4be Poul Sander 2025-02-03 21:28 4386
    {
1193b4be Poul Sander 2025-02-03 21:28 4387
        IM_ASSERT(state->TextA.Data != 0);
1193b4be Poul Sander 2025-02-03 21:28 4388
        g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1);
1193b4be Poul Sander 2025-02-03 21:28 4389
        memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1);
1193b4be Poul Sander 2025-02-03 21:28 4390
    }
1193b4be Poul Sander 2025-02-03 21:28 4391
}
1193b4be Poul Sander 2025-02-03 21:28 4392
1193b4be Poul Sander 2025-02-03 21:28 4393
// Edit a string of text
1193b4be Poul Sander 2025-02-03 21:28 4394
// - 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 4395
//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
1193b4be Poul Sander 2025-02-03 21:28 4396
//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
1193b4be Poul Sander 2025-02-03 21:28 4397
// - 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 4398
// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
1193b4be Poul Sander 2025-02-03 21:28 4399
// (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 4400
//  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 4401
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 4402
{
1193b4be Poul Sander 2025-02-03 21:28 4403
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 4404
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 4405
        return false;
1193b4be Poul Sander 2025-02-03 21:28 4406
1193b4be Poul Sander 2025-02-03 21:28 4407
    IM_ASSERT(buf != NULL && buf_size >= 0);
1193b4be Poul Sander 2025-02-03 21:28 4408
    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 4409
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
1193b4be Poul Sander 2025-02-03 21:28 4410
1193b4be Poul Sander 2025-02-03 21:28 4411
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 4412
    ImGuiIO& io = g.IO;
1193b4be Poul Sander 2025-02-03 21:28 4413
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 4414
1193b4be Poul Sander 2025-02-03 21:28 4415
    const bool RENDER_SELECTION_WHEN_INACTIVE = false;
1193b4be Poul Sander 2025-02-03 21:28 4416
    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4417
1193b4be Poul Sander 2025-02-03 21:28 4418
    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 4419
        BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 4420
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 4421
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 4422
    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 4423
    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 4424
1193b4be Poul Sander 2025-02-03 21:28 4425
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
1193b4be Poul Sander 2025-02-03 21:28 4426
    const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
1193b4be Poul Sander 2025-02-03 21:28 4427
1193b4be Poul Sander 2025-02-03 21:28 4428
    ImGuiWindow* draw_window = window;
1193b4be Poul Sander 2025-02-03 21:28 4429
    ImVec2 inner_size = frame_size;
1193b4be Poul Sander 2025-02-03 21:28 4430
    ImGuiLastItemData item_data_backup;
1193b4be Poul Sander 2025-02-03 21:28 4431
    if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4432
    {
1193b4be Poul Sander 2025-02-03 21:28 4433
        ImVec2 backup_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 4434
        ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 4435
        if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
1193b4be Poul Sander 2025-02-03 21:28 4436
        {
1193b4be Poul Sander 2025-02-03 21:28 4437
            EndGroup();
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
        item_data_backup = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 4441
        window->DC.CursorPos = backup_pos;
1193b4be Poul Sander 2025-02-03 21:28 4442
1193b4be Poul Sander 2025-02-03 21:28 4443
        // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
1193b4be Poul Sander 2025-02-03 21:28 4444
        if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
1193b4be Poul Sander 2025-02-03 21:28 4445
            g.NavActivateId = 0;
1193b4be Poul Sander 2025-02-03 21:28 4446
1193b4be Poul Sander 2025-02-03 21:28 4447
        // Prevent NavActivate reactivating in BeginChild() when we are already active.
1193b4be Poul Sander 2025-02-03 21:28 4448
        const ImGuiID backup_activate_id = g.NavActivateId;
1193b4be Poul Sander 2025-02-03 21:28 4449
        if (g.ActiveId == id) // Prevent reactivation
1193b4be Poul Sander 2025-02-03 21:28 4450
            g.NavActivateId = 0;
1193b4be Poul Sander 2025-02-03 21:28 4451
1193b4be Poul Sander 2025-02-03 21:28 4452
        // 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 4453
        PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
1193b4be Poul Sander 2025-02-03 21:28 4454
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 4455
        PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 4456
        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 4457
        bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);
1193b4be Poul Sander 2025-02-03 21:28 4458
        g.NavActivateId = backup_activate_id;
1193b4be Poul Sander 2025-02-03 21:28 4459
        PopStyleVar(3);
1193b4be Poul Sander 2025-02-03 21:28 4460
        PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 4461
        if (!child_visible)
1193b4be Poul Sander 2025-02-03 21:28 4462
        {
1193b4be Poul Sander 2025-02-03 21:28 4463
            EndChild();
1193b4be Poul Sander 2025-02-03 21:28 4464
            EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 4465
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4466
        }
1193b4be Poul Sander 2025-02-03 21:28 4467
        draw_window = g.CurrentWindow; // Child window
1193b4be Poul Sander 2025-02-03 21:28 4468
        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 4469
        draw_window->DC.CursorPos += style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 4470
        inner_size.x -= draw_window->ScrollbarSizes.x;
1193b4be Poul Sander 2025-02-03 21:28 4471
    }
1193b4be Poul Sander 2025-02-03 21:28 4472
    else
1193b4be Poul Sander 2025-02-03 21:28 4473
    {
1193b4be Poul Sander 2025-02-03 21:28 4474
        // 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 4475
        ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 4476
        if (!(flags & ImGuiInputTextFlags_MergedItem))
1193b4be Poul Sander 2025-02-03 21:28 4477
            if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
1193b4be Poul Sander 2025-02-03 21:28 4478
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4479
    }
1193b4be Poul Sander 2025-02-03 21:28 4480
1193b4be Poul Sander 2025-02-03 21:28 4481
    // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417)
1193b4be Poul Sander 2025-02-03 21:28 4482
    bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover);
1193b4be Poul Sander 2025-02-03 21:28 4483
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 4484
        SetMouseCursor(ImGuiMouseCursor_TextInput);
1193b4be Poul Sander 2025-02-03 21:28 4485
    if (hovered && g.NavHighlightItemUnderNav)
1193b4be Poul Sander 2025-02-03 21:28 4486
        hovered = false;
1193b4be Poul Sander 2025-02-03 21:28 4487
1193b4be Poul Sander 2025-02-03 21:28 4488
    // We are only allowed to access the state if we are already the active widget.
1193b4be Poul Sander 2025-02-03 21:28 4489
    ImGuiInputTextState* state = GetInputTextState(id);
1193b4be Poul Sander 2025-02-03 21:28 4490
1193b4be Poul Sander 2025-02-03 21:28 4491
    if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 4492
        flags |= ImGuiInputTextFlags_ReadOnly;
1193b4be Poul Sander 2025-02-03 21:28 4493
    const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4494
    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4495
    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
1193b4be Poul Sander 2025-02-03 21:28 4496
    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4497
    if (is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 4498
        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
1193b4be Poul Sander 2025-02-03 21:28 4499
1193b4be Poul Sander 2025-02-03 21:28 4500
    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 4501
1193b4be Poul Sander 2025-02-03 21:28 4502
    const bool user_clicked = hovered && io.MouseClicked[0];
1193b4be Poul Sander 2025-02-03 21:28 4503
    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 4504
    const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
1193b4be Poul Sander 2025-02-03 21:28 4505
    bool clear_active_id = false;
1193b4be Poul Sander 2025-02-03 21:28 4506
    bool select_all = false;
1193b4be Poul Sander 2025-02-03 21:28 4507
1193b4be Poul Sander 2025-02-03 21:28 4508
    float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 4509
1193b4be Poul Sander 2025-02-03 21:28 4510
    const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
1193b4be Poul Sander 2025-02-03 21:28 4511
    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 4512
    const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
1193b4be Poul Sander 2025-02-03 21:28 4513
    const bool init_state = (init_make_active || user_scroll_active);
1193b4be Poul Sander 2025-02-03 21:28 4514
    if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
1193b4be Poul Sander 2025-02-03 21:28 4515
    {
1193b4be Poul Sander 2025-02-03 21:28 4516
        // Access state even if we don't own it yet.
1193b4be Poul Sander 2025-02-03 21:28 4517
        state = &g.InputTextState;
1193b4be Poul Sander 2025-02-03 21:28 4518
        state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4519
        state->ReloadUserBuf = false;
1193b4be Poul Sander 2025-02-03 21:28 4520
1193b4be Poul Sander 2025-02-03 21:28 4521
        // 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 4522
        InputTextDeactivateHook(state->ID);
1193b4be Poul Sander 2025-02-03 21:28 4523
1193b4be Poul Sander 2025-02-03 21:28 4524
        // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
1193b4be Poul Sander 2025-02-03 21:28 4525
        const int buf_len = (int)strlen(buf);
1193b4be Poul Sander 2025-02-03 21:28 4526
        if (!init_reload_from_user_buf)
1193b4be Poul Sander 2025-02-03 21:28 4527
        {
1193b4be Poul Sander 2025-02-03 21:28 4528
            // Take a copy of the initial buffer value.
1193b4be Poul Sander 2025-02-03 21:28 4529
            state->InitialTextA.resize(buf_len + 1);    // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
1193b4be Poul Sander 2025-02-03 21:28 4530
            memcpy(state->InitialTextA.Data, buf, buf_len + 1);
1193b4be Poul Sander 2025-02-03 21:28 4531
        }
1193b4be Poul Sander 2025-02-03 21:28 4532
1193b4be Poul Sander 2025-02-03 21:28 4533
        // Preserve cursor position and undo/redo stack if we come back to same widget
1193b4be Poul Sander 2025-02-03 21:28 4534
        // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
1193b4be Poul Sander 2025-02-03 21:28 4535
        bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
1193b4be Poul Sander 2025-02-03 21:28 4536
        if (recycle_state && (state->CurLenA != buf_len || (strncmp(state->TextA.Data, buf, buf_len) != 0)))
1193b4be Poul Sander 2025-02-03 21:28 4537
            recycle_state = false;
1193b4be Poul Sander 2025-02-03 21:28 4538
1193b4be Poul Sander 2025-02-03 21:28 4539
        // Start edition
1193b4be Poul Sander 2025-02-03 21:28 4540
        state->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 4541
        state->TextA.resize(buf_size + 1);          // we use +1 to make sure that .Data is always pointing to at least an empty string.
1193b4be Poul Sander 2025-02-03 21:28 4542
        state->CurLenA = (int)strlen(buf);
1193b4be Poul Sander 2025-02-03 21:28 4543
        memcpy(state->TextA.Data, buf, state->CurLenA + 1);
1193b4be Poul Sander 2025-02-03 21:28 4544
1193b4be Poul Sander 2025-02-03 21:28 4545
        if (recycle_state)
1193b4be Poul Sander 2025-02-03 21:28 4546
        {
1193b4be Poul Sander 2025-02-03 21:28 4547
            // Recycle existing cursor/selection/undo stack but clamp position
1193b4be Poul Sander 2025-02-03 21:28 4548
            // 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 4549
            state->CursorClamp();
1193b4be Poul Sander 2025-02-03 21:28 4550
        }
1193b4be Poul Sander 2025-02-03 21:28 4551
        else
1193b4be Poul Sander 2025-02-03 21:28 4552
        {
1193b4be Poul Sander 2025-02-03 21:28 4553
            state->Scroll = ImVec2(0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 4554
            stb_textedit_initialize_state(state->Stb, !is_multiline);
1193b4be Poul Sander 2025-02-03 21:28 4555
        }
1193b4be Poul Sander 2025-02-03 21:28 4556
1193b4be Poul Sander 2025-02-03 21:28 4557
        if (init_reload_from_user_buf)
1193b4be Poul Sander 2025-02-03 21:28 4558
        {
1193b4be Poul Sander 2025-02-03 21:28 4559
            state->Stb->select_start = state->ReloadSelectionStart;
1193b4be Poul Sander 2025-02-03 21:28 4560
            state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd;
1193b4be Poul Sander 2025-02-03 21:28 4561
            state->CursorClamp();
1193b4be Poul Sander 2025-02-03 21:28 4562
        }
1193b4be Poul Sander 2025-02-03 21:28 4563
        else if (!is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4564
        {
1193b4be Poul Sander 2025-02-03 21:28 4565
            if (flags & ImGuiInputTextFlags_AutoSelectAll)
1193b4be Poul Sander 2025-02-03 21:28 4566
                select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 4567
            if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
1193b4be Poul Sander 2025-02-03 21:28 4568
                select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 4569
            if (user_clicked && io.KeyCtrl)
1193b4be Poul Sander 2025-02-03 21:28 4570
                select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 4571
        }
1193b4be Poul Sander 2025-02-03 21:28 4572
1193b4be Poul Sander 2025-02-03 21:28 4573
        if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
1193b4be Poul Sander 2025-02-03 21:28 4574
            state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863)
1193b4be Poul Sander 2025-02-03 21:28 4575
    }
1193b4be Poul Sander 2025-02-03 21:28 4576
1193b4be Poul Sander 2025-02-03 21:28 4577
    const bool is_osx = io.ConfigMacOSXBehaviors;
1193b4be Poul Sander 2025-02-03 21:28 4578
    if (g.ActiveId != id && init_make_active)
1193b4be Poul Sander 2025-02-03 21:28 4579
    {
1193b4be Poul Sander 2025-02-03 21:28 4580
        IM_ASSERT(state && state->ID == id);
1193b4be Poul Sander 2025-02-03 21:28 4581
        SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 4582
        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 4583
        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 4584
    }
1193b4be Poul Sander 2025-02-03 21:28 4585
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 4586
    {
1193b4be Poul Sander 2025-02-03 21:28 4587
        // Declare some inputs, the other are registered and polled via Shortcut() routing system.
1193b4be Poul Sander 2025-02-03 21:28 4588
        // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts.
1193b4be Poul Sander 2025-02-03 21:28 4589
        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 4590
        for (ImGuiKey key : always_owned_keys)
1193b4be Poul Sander 2025-02-03 21:28 4591
            SetKeyOwner(key, id);
1193b4be Poul Sander 2025-02-03 21:28 4592
        if (user_clicked)
1193b4be Poul Sander 2025-02-03 21:28 4593
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 4594
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 4595
        if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
1193b4be Poul Sander 2025-02-03 21:28 4596
        {
1193b4be Poul Sander 2025-02-03 21:28 4597
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
1193b4be Poul Sander 2025-02-03 21:28 4598
            SetKeyOwner(ImGuiKey_UpArrow, id);
1193b4be Poul Sander 2025-02-03 21:28 4599
            SetKeyOwner(ImGuiKey_DownArrow, id);
1193b4be Poul Sander 2025-02-03 21:28 4600
        }
1193b4be Poul Sander 2025-02-03 21:28 4601
        if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4602
        {
1193b4be Poul Sander 2025-02-03 21:28 4603
            SetKeyOwner(ImGuiKey_PageUp, id);
1193b4be Poul Sander 2025-02-03 21:28 4604
            SetKeyOwner(ImGuiKey_PageDown, id);
1193b4be Poul Sander 2025-02-03 21:28 4605
        }
1193b4be Poul Sander 2025-02-03 21:28 4606
        // 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 4607
        if (is_osx)
1193b4be Poul Sander 2025-02-03 21:28 4608
            SetKeyOwner(ImGuiMod_Alt, id);
1193b4be Poul Sander 2025-02-03 21:28 4609
1193b4be Poul Sander 2025-02-03 21:28 4610
        // Expose scroll in a manner that is agnostic to us using a child window
1193b4be Poul Sander 2025-02-03 21:28 4611
        if (is_multiline && state != NULL)
1193b4be Poul Sander 2025-02-03 21:28 4612
            state->Scroll.y = draw_window->Scroll.y;
1193b4be Poul Sander 2025-02-03 21:28 4613
    }
1193b4be Poul Sander 2025-02-03 21:28 4614
1193b4be Poul Sander 2025-02-03 21:28 4615
    // 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 4616
    if (g.ActiveId == id && state == NULL)
1193b4be Poul Sander 2025-02-03 21:28 4617
        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 4618
1193b4be Poul Sander 2025-02-03 21:28 4619
    // Release focus when we click outside
1193b4be Poul Sander 2025-02-03 21:28 4620
    if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
1193b4be Poul Sander 2025-02-03 21:28 4621
        clear_active_id = true;
1193b4be Poul Sander 2025-02-03 21:28 4622
1193b4be Poul Sander 2025-02-03 21:28 4623
    // 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 4624
    bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
1193b4be Poul Sander 2025-02-03 21:28 4625
    bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
1193b4be Poul Sander 2025-02-03 21:28 4626
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 4627
    bool validated = false;
1193b4be Poul Sander 2025-02-03 21:28 4628
1193b4be Poul Sander 2025-02-03 21:28 4629
    // Select the buffer to render.
1193b4be Poul Sander 2025-02-03 21:28 4630
    const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
1193b4be Poul Sander 2025-02-03 21:28 4631
    const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
1193b4be Poul Sander 2025-02-03 21:28 4632
1193b4be Poul Sander 2025-02-03 21:28 4633
    // Password pushes a temporary font with only a fallback glyph
1193b4be Poul Sander 2025-02-03 21:28 4634
    if (is_password && !is_displaying_hint)
1193b4be Poul Sander 2025-02-03 21:28 4635
    {
1193b4be Poul Sander 2025-02-03 21:28 4636
        const ImFontGlyph* glyph = g.Font->FindGlyph('*');
1193b4be Poul Sander 2025-02-03 21:28 4637
        ImFont* password_font = &g.InputTextPasswordFont;
1193b4be Poul Sander 2025-02-03 21:28 4638
        password_font->FontSize = g.Font->FontSize;
1193b4be Poul Sander 2025-02-03 21:28 4639
        password_font->Scale = g.Font->Scale;
1193b4be Poul Sander 2025-02-03 21:28 4640
        password_font->Ascent = g.Font->Ascent;
1193b4be Poul Sander 2025-02-03 21:28 4641
        password_font->Descent = g.Font->Descent;
1193b4be Poul Sander 2025-02-03 21:28 4642
        password_font->ContainerAtlas = g.Font->ContainerAtlas;
1193b4be Poul Sander 2025-02-03 21:28 4643
        password_font->FallbackGlyph = glyph;
1193b4be Poul Sander 2025-02-03 21:28 4644
        password_font->FallbackAdvanceX = glyph->AdvanceX;
1193b4be Poul Sander 2025-02-03 21:28 4645
        IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
1193b4be Poul Sander 2025-02-03 21:28 4646
        PushFont(password_font);
1193b4be Poul Sander 2025-02-03 21:28 4647
    }
1193b4be Poul Sander 2025-02-03 21:28 4648
1193b4be Poul Sander 2025-02-03 21:28 4649
    // Process mouse inputs and character inputs
1193b4be Poul Sander 2025-02-03 21:28 4650
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 4651
    {
1193b4be Poul Sander 2025-02-03 21:28 4652
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 4653
        state->Edited = false;
1193b4be Poul Sander 2025-02-03 21:28 4654
        state->BufCapacityA = buf_size;
1193b4be Poul Sander 2025-02-03 21:28 4655
        state->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 4656
1193b4be Poul Sander 2025-02-03 21:28 4657
        // 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 4658
        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
1193b4be Poul Sander 2025-02-03 21:28 4659
        g.ActiveIdAllowOverlap = !io.MouseDown[0];
1193b4be Poul Sander 2025-02-03 21:28 4660
1193b4be Poul Sander 2025-02-03 21:28 4661
        // Edit in progress
1193b4be Poul Sander 2025-02-03 21:28 4662
        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 4663
        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 4664
1193b4be Poul Sander 2025-02-03 21:28 4665
        if (select_all)
1193b4be Poul Sander 2025-02-03 21:28 4666
        {
1193b4be Poul Sander 2025-02-03 21:28 4667
            state->SelectAll();
1193b4be Poul Sander 2025-02-03 21:28 4668
            state->SelectedAllMouseLock = true;
1193b4be Poul Sander 2025-02-03 21:28 4669
        }
1193b4be Poul Sander 2025-02-03 21:28 4670
        else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 4671
        {
1193b4be Poul Sander 2025-02-03 21:28 4672
            stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4673
            const int multiclick_count = (io.MouseClickedCount[0] - 2);
1193b4be Poul Sander 2025-02-03 21:28 4674
            if ((multiclick_count % 2) == 0)
1193b4be Poul Sander 2025-02-03 21:28 4675
            {
1193b4be Poul Sander 2025-02-03 21:28 4676
                // Double-click: Select word
1193b4be Poul Sander 2025-02-03 21:28 4677
                // 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 4678
                // 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 4679
                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 4680
                if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol)
1193b4be Poul Sander 2025-02-03 21:28 4681
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
1193b4be Poul Sander 2025-02-03 21:28 4682
                //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4683
                if (!STB_TEXT_HAS_SELECTION(state->Stb))
1193b4be Poul Sander 2025-02-03 21:28 4684
                    ImStb::stb_textedit_prep_selection_at_cursor(state->Stb);
1193b4be Poul Sander 2025-02-03 21:28 4685
                state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor);
1193b4be Poul Sander 2025-02-03 21:28 4686
                state->Stb->select_end = state->Stb->cursor;
1193b4be Poul Sander 2025-02-03 21:28 4687
                ImStb::stb_textedit_clamp(state, state->Stb);
1193b4be Poul Sander 2025-02-03 21:28 4688
            }
1193b4be Poul Sander 2025-02-03 21:28 4689
            else
1193b4be Poul Sander 2025-02-03 21:28 4690
            {
1193b4be Poul Sander 2025-02-03 21:28 4691
                // Triple-click: Select line
1193b4be Poul Sander 2025-02-03 21:28 4692
                const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n';
1193b4be Poul Sander 2025-02-03 21:28 4693
                state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
1193b4be Poul Sander 2025-02-03 21:28 4694
                state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4695
                state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4696
                if (!is_eol && is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4697
                {
1193b4be Poul Sander 2025-02-03 21:28 4698
                    ImSwap(state->Stb->select_start, state->Stb->select_end);
1193b4be Poul Sander 2025-02-03 21:28 4699
                    state->Stb->cursor = state->Stb->select_end;
1193b4be Poul Sander 2025-02-03 21:28 4700
                }
1193b4be Poul Sander 2025-02-03 21:28 4701
                state->CursorFollow = false;
1193b4be Poul Sander 2025-02-03 21:28 4702
            }
1193b4be Poul Sander 2025-02-03 21:28 4703
            state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4704
        }
1193b4be Poul Sander 2025-02-03 21:28 4705
        else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
1193b4be Poul Sander 2025-02-03 21:28 4706
        {
1193b4be Poul Sander 2025-02-03 21:28 4707
            if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 4708
            {
1193b4be Poul Sander 2025-02-03 21:28 4709
                if (io.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 4710
                    stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4711
                else
1193b4be Poul Sander 2025-02-03 21:28 4712
                    stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4713
                state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4714
            }
1193b4be Poul Sander 2025-02-03 21:28 4715
        }
1193b4be Poul Sander 2025-02-03 21:28 4716
        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 4717
        {
1193b4be Poul Sander 2025-02-03 21:28 4718
            stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4719
            state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4720
            state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4721
        }
1193b4be Poul Sander 2025-02-03 21:28 4722
        if (state->SelectedAllMouseLock && !io.MouseDown[0])
1193b4be Poul Sander 2025-02-03 21:28 4723
            state->SelectedAllMouseLock = false;
1193b4be Poul Sander 2025-02-03 21:28 4724
1193b4be Poul Sander 2025-02-03 21:28 4725
        // 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 4726
        // (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 4727
        if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 4728
        {
1193b4be Poul Sander 2025-02-03 21:28 4729
            if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
1193b4be Poul Sander 2025-02-03 21:28 4730
            {
1193b4be Poul Sander 2025-02-03 21:28 4731
                unsigned int c = '\t'; // Insert TAB
1193b4be Poul Sander 2025-02-03 21:28 4732
                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
1193b4be Poul Sander 2025-02-03 21:28 4733
                    state->OnCharPressed(c);
1193b4be Poul Sander 2025-02-03 21:28 4734
            }
1193b4be Poul Sander 2025-02-03 21:28 4735
            // FIXME: Implement Shift+Tab
1193b4be Poul Sander 2025-02-03 21:28 4736
            /*
1193b4be Poul Sander 2025-02-03 21:28 4737
            if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id))
1193b4be Poul Sander 2025-02-03 21:28 4738
            {
1193b4be Poul Sander 2025-02-03 21:28 4739
            }
1193b4be Poul Sander 2025-02-03 21:28 4740
            */
1193b4be Poul Sander 2025-02-03 21:28 4741
        }
1193b4be Poul Sander 2025-02-03 21:28 4742
1193b4be Poul Sander 2025-02-03 21:28 4743
        // 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 4744
        // 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 4745
        const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);
1193b4be Poul Sander 2025-02-03 21:28 4746
        if (io.InputQueueCharacters.Size > 0)
1193b4be Poul Sander 2025-02-03 21:28 4747
        {
1193b4be Poul Sander 2025-02-03 21:28 4748
            if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
1193b4be Poul Sander 2025-02-03 21:28 4749
                for (int n = 0; n < io.InputQueueCharacters.Size; n++)
1193b4be Poul Sander 2025-02-03 21:28 4750
                {
1193b4be Poul Sander 2025-02-03 21:28 4751
                    // Insert character if they pass filtering
1193b4be Poul Sander 2025-02-03 21:28 4752
                    unsigned int c = (unsigned int)io.InputQueueCharacters[n];
1193b4be Poul Sander 2025-02-03 21:28 4753
                    if (c == '\t') // Skip Tab, see above.
1193b4be Poul Sander 2025-02-03 21:28 4754
                        continue;
1193b4be Poul Sander 2025-02-03 21:28 4755
                    if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
1193b4be Poul Sander 2025-02-03 21:28 4756
                        state->OnCharPressed(c);
1193b4be Poul Sander 2025-02-03 21:28 4757
                }
1193b4be Poul Sander 2025-02-03 21:28 4758
1193b4be Poul Sander 2025-02-03 21:28 4759
            // Consume characters
1193b4be Poul Sander 2025-02-03 21:28 4760
            io.InputQueueCharacters.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 4761
        }
1193b4be Poul Sander 2025-02-03 21:28 4762
    }
1193b4be Poul Sander 2025-02-03 21:28 4763
1193b4be Poul Sander 2025-02-03 21:28 4764
    // Process other shortcuts/key-presses
1193b4be Poul Sander 2025-02-03 21:28 4765
    bool revert_edit = false;
1193b4be Poul Sander 2025-02-03 21:28 4766
    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
1193b4be Poul Sander 2025-02-03 21:28 4767
    {
1193b4be Poul Sander 2025-02-03 21:28 4768
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 4769
1193b4be Poul Sander 2025-02-03 21:28 4770
        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 4771
        state->Stb->row_count_per_page = row_count_per_page;
1193b4be Poul Sander 2025-02-03 21:28 4772
1193b4be Poul Sander 2025-02-03 21:28 4773
        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
1193b4be Poul Sander 2025-02-03 21:28 4774
        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 4775
        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 4776
1193b4be Poul Sander 2025-02-03 21:28 4777
        // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText)
1193b4be Poul Sander 2025-02-03 21:28 4778
        // Otherwise we could simply assume that we own the keys as we are active.
1193b4be Poul Sander 2025-02-03 21:28 4779
        const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
1193b4be Poul Sander 2025-02-03 21:28 4780
        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 4781
        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 4782
        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 4783
        const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
1193b4be Poul Sander 2025-02-03 21:28 4784
        const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable;
1193b4be Poul Sander 2025-02-03 21:28 4785
        const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
1193b4be Poul Sander 2025-02-03 21:28 4786
1193b4be Poul Sander 2025-02-03 21:28 4787
        // 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 4788
        const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4789
        const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
1193b4be Poul Sander 2025-02-03 21:28 4790
        const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
1193b4be Poul Sander 2025-02-03 21:28 4791
        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 4792
1193b4be Poul Sander 2025-02-03 21:28 4793
        // 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 4794
        // 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 4795
        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 4796
        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 4797
        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 4798
        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 4799
        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 4800
        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 4801
        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 4802
        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 4803
        else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
1193b4be Poul Sander 2025-02-03 21:28 4804
        {
1193b4be Poul Sander 2025-02-03 21:28 4805
            if (!state->HasSelection())
1193b4be Poul Sander 2025-02-03 21:28 4806
            {
1193b4be Poul Sander 2025-02-03 21:28 4807
                // 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 4808
                if (is_wordmove_key_down)
1193b4be Poul Sander 2025-02-03 21:28 4809
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4810
            }
1193b4be Poul Sander 2025-02-03 21:28 4811
            state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
1193b4be Poul Sander 2025-02-03 21:28 4812
        }
1193b4be Poul Sander 2025-02-03 21:28 4813
        else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 4814
        {
1193b4be Poul Sander 2025-02-03 21:28 4815
            if (!state->HasSelection())
1193b4be Poul Sander 2025-02-03 21:28 4816
            {
1193b4be Poul Sander 2025-02-03 21:28 4817
                if (is_wordmove_key_down)
1193b4be Poul Sander 2025-02-03 21:28 4818
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4819
                else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)
1193b4be Poul Sander 2025-02-03 21:28 4820
                    state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4821
            }
1193b4be Poul Sander 2025-02-03 21:28 4822
            state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
1193b4be Poul Sander 2025-02-03 21:28 4823
        }
1193b4be Poul Sander 2025-02-03 21:28 4824
        else if (is_enter_pressed || is_gamepad_validate)
1193b4be Poul Sander 2025-02-03 21:28 4825
        {
1193b4be Poul Sander 2025-02-03 21:28 4826
            // Determine if we turn Enter into a \n character
1193b4be Poul Sander 2025-02-03 21:28 4827
            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4828
            if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
1193b4be Poul Sander 2025-02-03 21:28 4829
            {
1193b4be Poul Sander 2025-02-03 21:28 4830
                validated = true;
1193b4be Poul Sander 2025-02-03 21:28 4831
                if (io.ConfigInputTextEnterKeepActive && !is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4832
                    state->SelectAll(); // No need to scroll
1193b4be Poul Sander 2025-02-03 21:28 4833
                else
1193b4be Poul Sander 2025-02-03 21:28 4834
                    clear_active_id = true;
1193b4be Poul Sander 2025-02-03 21:28 4835
            }
1193b4be Poul Sander 2025-02-03 21:28 4836
            else if (!is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 4837
            {
1193b4be Poul Sander 2025-02-03 21:28 4838
                unsigned int c = '\n'; // Insert new line
1193b4be Poul Sander 2025-02-03 21:28 4839
                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
1193b4be Poul Sander 2025-02-03 21:28 4840
                    state->OnCharPressed(c);
1193b4be Poul Sander 2025-02-03 21:28 4841
            }
1193b4be Poul Sander 2025-02-03 21:28 4842
        }
1193b4be Poul Sander 2025-02-03 21:28 4843
        else if (is_cancel)
1193b4be Poul Sander 2025-02-03 21:28 4844
        {
1193b4be Poul Sander 2025-02-03 21:28 4845
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
1193b4be Poul Sander 2025-02-03 21:28 4846
            {
1193b4be Poul Sander 2025-02-03 21:28 4847
                if (buf[0] != 0)
1193b4be Poul Sander 2025-02-03 21:28 4848
                {
1193b4be Poul Sander 2025-02-03 21:28 4849
                    revert_edit = true;
1193b4be Poul Sander 2025-02-03 21:28 4850
                }
1193b4be Poul Sander 2025-02-03 21:28 4851
                else
1193b4be Poul Sander 2025-02-03 21:28 4852
                {
1193b4be Poul Sander 2025-02-03 21:28 4853
                    render_cursor = render_selection = false;
1193b4be Poul Sander 2025-02-03 21:28 4854
                    clear_active_id = true;
1193b4be Poul Sander 2025-02-03 21:28 4855
                }
1193b4be Poul Sander 2025-02-03 21:28 4856
            }
1193b4be Poul Sander 2025-02-03 21:28 4857
            else
1193b4be Poul Sander 2025-02-03 21:28 4858
            {
1193b4be Poul Sander 2025-02-03 21:28 4859
                clear_active_id = revert_edit = true;
1193b4be Poul Sander 2025-02-03 21:28 4860
                render_cursor = render_selection = false;
1193b4be Poul Sander 2025-02-03 21:28 4861
            }
1193b4be Poul Sander 2025-02-03 21:28 4862
        }
1193b4be Poul Sander 2025-02-03 21:28 4863
        else if (is_undo || is_redo)
1193b4be Poul Sander 2025-02-03 21:28 4864
        {
1193b4be Poul Sander 2025-02-03 21:28 4865
            state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
1193b4be Poul Sander 2025-02-03 21:28 4866
            state->ClearSelection();
1193b4be Poul Sander 2025-02-03 21:28 4867
        }
1193b4be Poul Sander 2025-02-03 21:28 4868
        else if (is_select_all)
1193b4be Poul Sander 2025-02-03 21:28 4869
        {
1193b4be Poul Sander 2025-02-03 21:28 4870
            state->SelectAll();
1193b4be Poul Sander 2025-02-03 21:28 4871
            state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4872
        }
1193b4be Poul Sander 2025-02-03 21:28 4873
        else if (is_cut || is_copy)
1193b4be Poul Sander 2025-02-03 21:28 4874
        {
1193b4be Poul Sander 2025-02-03 21:28 4875
            // Cut, Copy
1193b4be Poul Sander 2025-02-03 21:28 4876
            if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
1193b4be Poul Sander 2025-02-03 21:28 4877
            {
1193b4be Poul Sander 2025-02-03 21:28 4878
                const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;
1193b4be Poul Sander 2025-02-03 21:28 4879
                const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 4880
1193b4be Poul Sander 2025-02-03 21:28 4881
                char backup = state->TextA.Data[ie];
1193b4be Poul Sander 2025-02-03 21:28 4882
                state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings
1193b4be Poul Sander 2025-02-03 21:28 4883
                SetClipboardText(state->TextA.Data + ib);
1193b4be Poul Sander 2025-02-03 21:28 4884
                state->TextA.Data[ie] = backup;
1193b4be Poul Sander 2025-02-03 21:28 4885
            }
1193b4be Poul Sander 2025-02-03 21:28 4886
            if (is_cut)
1193b4be Poul Sander 2025-02-03 21:28 4887
            {
1193b4be Poul Sander 2025-02-03 21:28 4888
                if (!state->HasSelection())
1193b4be Poul Sander 2025-02-03 21:28 4889
                    state->SelectAll();
1193b4be Poul Sander 2025-02-03 21:28 4890
                state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4891
                stb_textedit_cut(state, state->Stb);
1193b4be Poul Sander 2025-02-03 21:28 4892
            }
1193b4be Poul Sander 2025-02-03 21:28 4893
        }
1193b4be Poul Sander 2025-02-03 21:28 4894
        else if (is_paste)
1193b4be Poul Sander 2025-02-03 21:28 4895
        {
1193b4be Poul Sander 2025-02-03 21:28 4896
            if (const char* clipboard = GetClipboardText())
1193b4be Poul Sander 2025-02-03 21:28 4897
            {
1193b4be Poul Sander 2025-02-03 21:28 4898
                // Filter pasted buffer
1193b4be Poul Sander 2025-02-03 21:28 4899
                const int clipboard_len = (int)strlen(clipboard);
1193b4be Poul Sander 2025-02-03 21:28 4900
                char* clipboard_filtered = (char*)IM_ALLOC(clipboard_len + 1);
1193b4be Poul Sander 2025-02-03 21:28 4901
                int clipboard_filtered_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 4902
                for (const char* s = clipboard; *s != 0; )
1193b4be Poul Sander 2025-02-03 21:28 4903
                {
1193b4be Poul Sander 2025-02-03 21:28 4904
                    unsigned int c;
1193b4be Poul Sander 2025-02-03 21:28 4905
                    int len = ImTextCharFromUtf8(&c, s, NULL);
1193b4be Poul Sander 2025-02-03 21:28 4906
                    s += len;
1193b4be Poul Sander 2025-02-03 21:28 4907
                    if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
1193b4be Poul Sander 2025-02-03 21:28 4908
                        continue;
1193b4be Poul Sander 2025-02-03 21:28 4909
                    memcpy(clipboard_filtered + clipboard_filtered_len, s - len, len);
1193b4be Poul Sander 2025-02-03 21:28 4910
                    clipboard_filtered_len += len;
1193b4be Poul Sander 2025-02-03 21:28 4911
                }
1193b4be Poul Sander 2025-02-03 21:28 4912
                clipboard_filtered[clipboard_filtered_len] = 0;
1193b4be Poul Sander 2025-02-03 21:28 4913
                if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
1193b4be Poul Sander 2025-02-03 21:28 4914
                {
1193b4be Poul Sander 2025-02-03 21:28 4915
                    stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len);
1193b4be Poul Sander 2025-02-03 21:28 4916
                    state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4917
                }
1193b4be Poul Sander 2025-02-03 21:28 4918
                MemFree(clipboard_filtered);
1193b4be Poul Sander 2025-02-03 21:28 4919
            }
1193b4be Poul Sander 2025-02-03 21:28 4920
        }
1193b4be Poul Sander 2025-02-03 21:28 4921
1193b4be Poul Sander 2025-02-03 21:28 4922
        // 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 4923
        render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
1193b4be Poul Sander 2025-02-03 21:28 4924
    }
1193b4be Poul Sander 2025-02-03 21:28 4925
1193b4be Poul Sander 2025-02-03 21:28 4926
    // Process callbacks and apply result back to user's buffer.
1193b4be Poul Sander 2025-02-03 21:28 4927
    const char* apply_new_text = NULL;
1193b4be Poul Sander 2025-02-03 21:28 4928
    int apply_new_text_length = 0;
1193b4be Poul Sander 2025-02-03 21:28 4929
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 4930
    {
1193b4be Poul Sander 2025-02-03 21:28 4931
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 4932
        if (revert_edit && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 4933
        {
1193b4be Poul Sander 2025-02-03 21:28 4934
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
1193b4be Poul Sander 2025-02-03 21:28 4935
            {
1193b4be Poul Sander 2025-02-03 21:28 4936
                // Clear input
1193b4be Poul Sander 2025-02-03 21:28 4937
                IM_ASSERT(buf[0] != 0);
1193b4be Poul Sander 2025-02-03 21:28 4938
                apply_new_text = "";
1193b4be Poul Sander 2025-02-03 21:28 4939
                apply_new_text_length = 0;
1193b4be Poul Sander 2025-02-03 21:28 4940
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 4941
                IMSTB_TEXTEDIT_CHARTYPE empty_string;
1193b4be Poul Sander 2025-02-03 21:28 4942
                stb_textedit_replace(state, state->Stb, &empty_string, 0);
1193b4be Poul Sander 2025-02-03 21:28 4943
            }
1193b4be Poul Sander 2025-02-03 21:28 4944
            else if (strcmp(buf, state->InitialTextA.Data) != 0)
1193b4be Poul Sander 2025-02-03 21:28 4945
            {
1193b4be Poul Sander 2025-02-03 21:28 4946
                apply_new_text = state->InitialTextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 4947
                apply_new_text_length = state->InitialTextA.Size - 1;
1193b4be Poul Sander 2025-02-03 21:28 4948
1193b4be Poul Sander 2025-02-03 21:28 4949
                // 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 4950
                // Push records into the undo stack so we can CTRL+Z the revert operation itself
1193b4be Poul Sander 2025-02-03 21:28 4951
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 4952
                stb_textedit_replace(state, state->Stb, state->InitialTextA.Data, state->InitialTextA.Size - 1);
1193b4be Poul Sander 2025-02-03 21:28 4953
            }
1193b4be Poul Sander 2025-02-03 21:28 4954
        }
1193b4be Poul Sander 2025-02-03 21:28 4955
1193b4be Poul Sander 2025-02-03 21:28 4956
        // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer
1193b4be Poul Sander 2025-02-03 21:28 4957
        // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
1193b4be Poul Sander 2025-02-03 21:28 4958
        // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
1193b4be Poul Sander 2025-02-03 21:28 4959
        // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage
1193b4be Poul Sander 2025-02-03 21:28 4960
        // (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 4961
        // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
1193b4be Poul Sander 2025-02-03 21:28 4962
        const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
1193b4be Poul Sander 2025-02-03 21:28 4963
        if (apply_edit_back_to_user_buffer)
1193b4be Poul Sander 2025-02-03 21:28 4964
        {
1193b4be Poul Sander 2025-02-03 21:28 4965
            // Apply new value immediately - copy modified buffer back
1193b4be Poul Sander 2025-02-03 21:28 4966
            // 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 4967
            // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
1193b4be Poul Sander 2025-02-03 21:28 4968
            // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
1193b4be Poul Sander 2025-02-03 21:28 4969
1193b4be Poul Sander 2025-02-03 21:28 4970
            // User callback
1193b4be Poul Sander 2025-02-03 21:28 4971
            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
1193b4be Poul Sander 2025-02-03 21:28 4972
            {
1193b4be Poul Sander 2025-02-03 21:28 4973
                IM_ASSERT(callback != NULL);
1193b4be Poul Sander 2025-02-03 21:28 4974
1193b4be Poul Sander 2025-02-03 21:28 4975
                // 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 4976
                ImGuiInputTextFlags event_flag = 0;
1193b4be Poul Sander 2025-02-03 21:28 4977
                ImGuiKey event_key = ImGuiKey_None;
1193b4be Poul Sander 2025-02-03 21:28 4978
                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
1193b4be Poul Sander 2025-02-03 21:28 4979
                {
1193b4be Poul Sander 2025-02-03 21:28 4980
                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
1193b4be Poul Sander 2025-02-03 21:28 4981
                    event_key = ImGuiKey_Tab;
1193b4be Poul Sander 2025-02-03 21:28 4982
                }
1193b4be Poul Sander 2025-02-03 21:28 4983
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
1193b4be Poul Sander 2025-02-03 21:28 4984
                {
1193b4be Poul Sander 2025-02-03 21:28 4985
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
1193b4be Poul Sander 2025-02-03 21:28 4986
                    event_key = ImGuiKey_UpArrow;
1193b4be Poul Sander 2025-02-03 21:28 4987
                }
1193b4be Poul Sander 2025-02-03 21:28 4988
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
1193b4be Poul Sander 2025-02-03 21:28 4989
                {
1193b4be Poul Sander 2025-02-03 21:28 4990
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
1193b4be Poul Sander 2025-02-03 21:28 4991
                    event_key = ImGuiKey_DownArrow;
1193b4be Poul Sander 2025-02-03 21:28 4992
                }
1193b4be Poul Sander 2025-02-03 21:28 4993
                else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
1193b4be Poul Sander 2025-02-03 21:28 4994
                {
1193b4be Poul Sander 2025-02-03 21:28 4995
                    event_flag = ImGuiInputTextFlags_CallbackEdit;
1193b4be Poul Sander 2025-02-03 21:28 4996
                }
1193b4be Poul Sander 2025-02-03 21:28 4997
                else if (flags & ImGuiInputTextFlags_CallbackAlways)
1193b4be Poul Sander 2025-02-03 21:28 4998
                {
1193b4be Poul Sander 2025-02-03 21:28 4999
                    event_flag = ImGuiInputTextFlags_CallbackAlways;
1193b4be Poul Sander 2025-02-03 21:28 5000
                }
1193b4be Poul Sander 2025-02-03 21:28 5001
1193b4be Poul Sander 2025-02-03 21:28 5002
                if (event_flag)
1193b4be Poul Sander 2025-02-03 21:28 5003
                {
1193b4be Poul Sander 2025-02-03 21:28 5004
                    ImGuiInputTextCallbackData callback_data;
1193b4be Poul Sander 2025-02-03 21:28 5005
                    callback_data.Ctx = &g;
1193b4be Poul Sander 2025-02-03 21:28 5006
                    callback_data.EventFlag = event_flag;
1193b4be Poul Sander 2025-02-03 21:28 5007
                    callback_data.Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 5008
                    callback_data.UserData = callback_user_data;
1193b4be Poul Sander 2025-02-03 21:28 5009
1193b4be Poul Sander 2025-02-03 21:28 5010
                    // 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 5011
                    state->CallbackTextBackup.resize(state->CurLenA + 1);
1193b4be Poul Sander 2025-02-03 21:28 5012
                    memcpy(state->CallbackTextBackup.Data, state->TextA.Data, state->CurLenA + 1);
1193b4be Poul Sander 2025-02-03 21:28 5013
1193b4be Poul Sander 2025-02-03 21:28 5014
                    char* callback_buf = is_readonly ? buf : state->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 5015
                    callback_data.EventKey = event_key;
1193b4be Poul Sander 2025-02-03 21:28 5016
                    callback_data.Buf = callback_buf;
1193b4be Poul Sander 2025-02-03 21:28 5017
                    callback_data.BufTextLen = state->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 5018
                    callback_data.BufSize = state->BufCapacityA;
1193b4be Poul Sander 2025-02-03 21:28 5019
                    callback_data.BufDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 5020
1193b4be Poul Sander 2025-02-03 21:28 5021
                    const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor;
1193b4be Poul Sander 2025-02-03 21:28 5022
                    const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start;
1193b4be Poul Sander 2025-02-03 21:28 5023
                    const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end;
1193b4be Poul Sander 2025-02-03 21:28 5024
1193b4be Poul Sander 2025-02-03 21:28 5025
                    // Call user code
1193b4be Poul Sander 2025-02-03 21:28 5026
                    callback(&callback_data);
1193b4be Poul Sander 2025-02-03 21:28 5027
1193b4be Poul Sander 2025-02-03 21:28 5028
                    // Read back what user may have modified
1193b4be Poul Sander 2025-02-03 21:28 5029
                    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 5030
                    IM_ASSERT(callback_data.Buf == callback_buf);         // Invalid to modify those fields
1193b4be Poul Sander 2025-02-03 21:28 5031
                    IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
1193b4be Poul Sander 2025-02-03 21:28 5032
                    IM_ASSERT(callback_data.Flags == flags);
1193b4be Poul Sander 2025-02-03 21:28 5033
                    const bool buf_dirty = callback_data.BufDirty;
1193b4be Poul Sander 2025-02-03 21:28 5034
                    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 5035
                    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 5036
                    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 5037
                    if (buf_dirty)
1193b4be Poul Sander 2025-02-03 21:28 5038
                    {
1193b4be Poul Sander 2025-02-03 21:28 5039
                        // Callback may update buffer and thus set buf_dirty even in read-only mode.
1193b4be Poul Sander 2025-02-03 21:28 5040
                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
1193b4be Poul Sander 2025-02-03 21:28 5041
                        InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ?
1193b4be Poul Sander 2025-02-03 21:28 5042
                        state->CurLenA = 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 5043
                        state->TextA.Size = state->CurLenA + 1;
1193b4be Poul Sander 2025-02-03 21:28 5044
                        state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 5045
                    }
1193b4be Poul Sander 2025-02-03 21:28 5046
                }
1193b4be Poul Sander 2025-02-03 21:28 5047
            }
1193b4be Poul Sander 2025-02-03 21:28 5048
1193b4be Poul Sander 2025-02-03 21:28 5049
            // Will copy result string if modified
1193b4be Poul Sander 2025-02-03 21:28 5050
            if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5051
            {
1193b4be Poul Sander 2025-02-03 21:28 5052
                apply_new_text = state->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 5053
                apply_new_text_length = state->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 5054
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5055
            }
1193b4be Poul Sander 2025-02-03 21:28 5056
        }
1193b4be Poul Sander 2025-02-03 21:28 5057
    }
1193b4be Poul Sander 2025-02-03 21:28 5058
1193b4be Poul Sander 2025-02-03 21:28 5059
    // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
1193b4be Poul Sander 2025-02-03 21:28 5060
    if (g.InputTextDeactivatedState.ID == id)
1193b4be Poul Sander 2025-02-03 21:28 5061
    {
1193b4be Poul Sander 2025-02-03 21:28 5062
        if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5063
        {
1193b4be Poul Sander 2025-02-03 21:28 5064
            apply_new_text = g.InputTextDeactivatedState.TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 5065
            apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
1193b4be Poul Sander 2025-02-03 21:28 5066
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5067
            //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 5068
        }
1193b4be Poul Sander 2025-02-03 21:28 5069
        g.InputTextDeactivatedState.ID = 0;
1193b4be Poul Sander 2025-02-03 21:28 5070
    }
1193b4be Poul Sander 2025-02-03 21:28 5071
1193b4be Poul Sander 2025-02-03 21:28 5072
    // Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 5073
    if (apply_new_text != NULL)
1193b4be Poul Sander 2025-02-03 21:28 5074
    {
1193b4be Poul Sander 2025-02-03 21:28 5075
        //// 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 5076
        //// 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 5077
        //// without any storage on user's side.
1193b4be Poul Sander 2025-02-03 21:28 5078
        IM_ASSERT(apply_new_text_length >= 0);
1193b4be Poul Sander 2025-02-03 21:28 5079
        if (is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 5080
        {
1193b4be Poul Sander 2025-02-03 21:28 5081
            ImGuiInputTextCallbackData callback_data;
1193b4be Poul Sander 2025-02-03 21:28 5082
            callback_data.Ctx = &g;
1193b4be Poul Sander 2025-02-03 21:28 5083
            callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
1193b4be Poul Sander 2025-02-03 21:28 5084
            callback_data.Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 5085
            callback_data.Buf = buf;
1193b4be Poul Sander 2025-02-03 21:28 5086
            callback_data.BufTextLen = apply_new_text_length;
1193b4be Poul Sander 2025-02-03 21:28 5087
            callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
1193b4be Poul Sander 2025-02-03 21:28 5088
            callback_data.UserData = callback_user_data;
1193b4be Poul Sander 2025-02-03 21:28 5089
            callback(&callback_data);
1193b4be Poul Sander 2025-02-03 21:28 5090
            buf = callback_data.Buf;
1193b4be Poul Sander 2025-02-03 21:28 5091
            buf_size = callback_data.BufSize;
1193b4be Poul Sander 2025-02-03 21:28 5092
            apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
1193b4be Poul Sander 2025-02-03 21:28 5093
            IM_ASSERT(apply_new_text_length <= buf_size);
1193b4be Poul Sander 2025-02-03 21:28 5094
        }
1193b4be Poul Sander 2025-02-03 21:28 5095
        //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
1193b4be Poul Sander 2025-02-03 21:28 5096
1193b4be Poul Sander 2025-02-03 21:28 5097
        // 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 5098
        ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
1193b4be Poul Sander 2025-02-03 21:28 5099
    }
1193b4be Poul Sander 2025-02-03 21:28 5100
1193b4be Poul Sander 2025-02-03 21:28 5101
    // 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 5102
    // Otherwise request text input ahead for next frame.
1193b4be Poul Sander 2025-02-03 21:28 5103
    if (g.ActiveId == id && clear_active_id)
1193b4be Poul Sander 2025-02-03 21:28 5104
        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 5105
    else if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 5106
        g.WantTextInputNextFrame = 1;
1193b4be Poul Sander 2025-02-03 21:28 5107
1193b4be Poul Sander 2025-02-03 21:28 5108
    // Render frame
1193b4be Poul Sander 2025-02-03 21:28 5109
    if (!is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5110
    {
1193b4be Poul Sander 2025-02-03 21:28 5111
        RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 5112
        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 5113
    }
1193b4be Poul Sander 2025-02-03 21:28 5114
1193b4be Poul Sander 2025-02-03 21:28 5115
    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
1193b4be Poul Sander 2025-02-03 21:28 5116
    ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 5117
    ImVec2 text_size(0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 5118
1193b4be Poul Sander 2025-02-03 21:28 5119
    // 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 5120
    // 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 5121
    // 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 5122
    const int buf_display_max_length = 2 * 1024 * 1024;
1193b4be Poul Sander 2025-02-03 21:28 5123
    const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
1193b4be Poul Sander 2025-02-03 21:28 5124
    const char* buf_display_end = NULL; // We have specialized paths below for setting the length
1193b4be Poul Sander 2025-02-03 21:28 5125
    if (is_displaying_hint)
1193b4be Poul Sander 2025-02-03 21:28 5126
    {
1193b4be Poul Sander 2025-02-03 21:28 5127
        buf_display = hint;
1193b4be Poul Sander 2025-02-03 21:28 5128
        buf_display_end = hint + strlen(hint);
1193b4be Poul Sander 2025-02-03 21:28 5129
    }
1193b4be Poul Sander 2025-02-03 21:28 5130
1193b4be Poul Sander 2025-02-03 21:28 5131
    // Render text. We currently only render selection when the widget is active or while scrolling.
1193b4be Poul Sander 2025-02-03 21:28 5132
    // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
1193b4be Poul Sander 2025-02-03 21:28 5133
    if (render_cursor || render_selection)
1193b4be Poul Sander 2025-02-03 21:28 5134
    {
1193b4be Poul Sander 2025-02-03 21:28 5135
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 5136
        if (!is_displaying_hint)
1193b4be Poul Sander 2025-02-03 21:28 5137
            buf_display_end = buf_display + state->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 5138
1193b4be Poul Sander 2025-02-03 21:28 5139
        // Render text (with cursor and selection)
1193b4be Poul Sander 2025-02-03 21:28 5140
        // This is going to be messy. We need to:
1193b4be Poul Sander 2025-02-03 21:28 5141
        // - Display the text (this alone can be more easily clipped)
1193b4be Poul Sander 2025-02-03 21:28 5142
        // - 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 5143
        // - Measure text height (for scrollbar)
1193b4be Poul Sander 2025-02-03 21:28 5144
        // 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 5145
        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
1193b4be Poul Sander 2025-02-03 21:28 5146
        const char* text_begin = state->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 5147
        const char* text_end = text_begin + state->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 5148
        ImVec2 cursor_offset, select_start_offset;
1193b4be Poul Sander 2025-02-03 21:28 5149
1193b4be Poul Sander 2025-02-03 21:28 5150
        {
1193b4be Poul Sander 2025-02-03 21:28 5151
            // Find lines numbers straddling cursor and selection min position
1193b4be Poul Sander 2025-02-03 21:28 5152
            int cursor_line_no = render_cursor ? -1 : -1000;
1193b4be Poul Sander 2025-02-03 21:28 5153
            int selmin_line_no = render_selection ? -1 : -1000;
1193b4be Poul Sander 2025-02-03 21:28 5154
            const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL;
1193b4be Poul Sander 2025-02-03 21:28 5155
            const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL;
1193b4be Poul Sander 2025-02-03 21:28 5156
1193b4be Poul Sander 2025-02-03 21:28 5157
            // Count lines and find line number for cursor and selection ends
1193b4be Poul Sander 2025-02-03 21:28 5158
            int line_count = 1;
1193b4be Poul Sander 2025-02-03 21:28 5159
            if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5160
            {
1193b4be Poul Sander 2025-02-03 21:28 5161
                for (const char* s = text_begin; (s = (const char*)memchr(s, '\n', (size_t)(text_end - s))) != NULL; s++)
1193b4be Poul Sander 2025-02-03 21:28 5162
                {
1193b4be Poul Sander 2025-02-03 21:28 5163
                    if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; }
1193b4be Poul Sander 2025-02-03 21:28 5164
                    if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; }
1193b4be Poul Sander 2025-02-03 21:28 5165
                    line_count++;
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
            if (cursor_line_no == -1)
1193b4be Poul Sander 2025-02-03 21:28 5169
                cursor_line_no = line_count;
1193b4be Poul Sander 2025-02-03 21:28 5170
            if (selmin_line_no == -1)
1193b4be Poul Sander 2025-02-03 21:28 5171
                selmin_line_no = line_count;
1193b4be Poul Sander 2025-02-03 21:28 5172
1193b4be Poul Sander 2025-02-03 21:28 5173
            // Calculate 2d position by finding the beginning of the line and measuring distance
1193b4be Poul Sander 2025-02-03 21:28 5174
            cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x;
1193b4be Poul Sander 2025-02-03 21:28 5175
            cursor_offset.y = cursor_line_no * g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 5176
            if (selmin_line_no >= 0)
1193b4be Poul Sander 2025-02-03 21:28 5177
            {
1193b4be Poul Sander 2025-02-03 21:28 5178
                select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x;
1193b4be Poul Sander 2025-02-03 21:28 5179
                select_start_offset.y = selmin_line_no * g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 5180
            }
1193b4be Poul Sander 2025-02-03 21:28 5181
1193b4be Poul Sander 2025-02-03 21:28 5182
            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
1193b4be Poul Sander 2025-02-03 21:28 5183
            if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5184
                text_size = ImVec2(inner_size.x, line_count * g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 5185
        }
1193b4be Poul Sander 2025-02-03 21:28 5186
1193b4be Poul Sander 2025-02-03 21:28 5187
        // Scroll
1193b4be Poul Sander 2025-02-03 21:28 5188
        if (render_cursor && state->CursorFollow)
1193b4be Poul Sander 2025-02-03 21:28 5189
        {
1193b4be Poul Sander 2025-02-03 21:28 5190
            // Horizontal scroll in chunks of quarter width
1193b4be Poul Sander 2025-02-03 21:28 5191
            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
1193b4be Poul Sander 2025-02-03 21:28 5192
            {
1193b4be Poul Sander 2025-02-03 21:28 5193
                const float scroll_increment_x = inner_size.x * 0.25f;
1193b4be Poul Sander 2025-02-03 21:28 5194
                const float visible_width = inner_size.x - style.FramePadding.x;
1193b4be Poul Sander 2025-02-03 21:28 5195
                if (cursor_offset.x < state->Scroll.x)
1193b4be Poul Sander 2025-02-03 21:28 5196
                    state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
1193b4be Poul Sander 2025-02-03 21:28 5197
                else if (cursor_offset.x - visible_width >= state->Scroll.x)
1193b4be Poul Sander 2025-02-03 21:28 5198
                    state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
1193b4be Poul Sander 2025-02-03 21:28 5199
            }
1193b4be Poul Sander 2025-02-03 21:28 5200
            else
1193b4be Poul Sander 2025-02-03 21:28 5201
            {
1193b4be Poul Sander 2025-02-03 21:28 5202
                state->Scroll.y = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 5203
            }
1193b4be Poul Sander 2025-02-03 21:28 5204
1193b4be Poul Sander 2025-02-03 21:28 5205
            // Vertical scroll
1193b4be Poul Sander 2025-02-03 21:28 5206
            if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5207
            {
1193b4be Poul Sander 2025-02-03 21:28 5208
                // Test if cursor is vertically visible
1193b4be Poul Sander 2025-02-03 21:28 5209
                if (cursor_offset.y - g.FontSize < scroll_y)
1193b4be Poul Sander 2025-02-03 21:28 5210
                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 5211
                else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
1193b4be Poul Sander 2025-02-03 21:28 5212
                    scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 5213
                const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 5214
                scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
1193b4be Poul Sander 2025-02-03 21:28 5215
                draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
1193b4be Poul Sander 2025-02-03 21:28 5216
                draw_window->Scroll.y = scroll_y;
1193b4be Poul Sander 2025-02-03 21:28 5217
            }
1193b4be Poul Sander 2025-02-03 21:28 5218
1193b4be Poul Sander 2025-02-03 21:28 5219
            state->CursorFollow = false;
1193b4be Poul Sander 2025-02-03 21:28 5220
        }
1193b4be Poul Sander 2025-02-03 21:28 5221
1193b4be Poul Sander 2025-02-03 21:28 5222
        // Draw selection
1193b4be Poul Sander 2025-02-03 21:28 5223
        const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 5224
        if (render_selection)
1193b4be Poul Sander 2025-02-03 21:28 5225
        {
1193b4be Poul Sander 2025-02-03 21:28 5226
            const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end);
1193b4be Poul Sander 2025-02-03 21:28 5227
            const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end);
1193b4be Poul Sander 2025-02-03 21:28 5228
1193b4be Poul Sander 2025-02-03 21:28 5229
            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.
1193b4be Poul Sander 2025-02-03 21:28 5230
            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.
1193b4be Poul Sander 2025-02-03 21:28 5231
            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 5232
            ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
1193b4be Poul Sander 2025-02-03 21:28 5233
            for (const char* p = text_selected_begin; p < text_selected_end; )
1193b4be Poul Sander 2025-02-03 21:28 5234
            {
1193b4be Poul Sander 2025-02-03 21:28 5235
                if (rect_pos.y > clip_rect.w + g.FontSize)
1193b4be Poul Sander 2025-02-03 21:28 5236
                    break;
1193b4be Poul Sander 2025-02-03 21:28 5237
                if (rect_pos.y < clip_rect.y)
1193b4be Poul Sander 2025-02-03 21:28 5238
                {
1193b4be Poul Sander 2025-02-03 21:28 5239
                    p = (const char*)memchr((void*)p, '\n', text_selected_end - p);
1193b4be Poul Sander 2025-02-03 21:28 5240
                    p = p ? p + 1 : text_selected_end;
1193b4be Poul Sander 2025-02-03 21:28 5241
                }
1193b4be Poul Sander 2025-02-03 21:28 5242
                else
1193b4be Poul Sander 2025-02-03 21:28 5243
                {
1193b4be Poul Sander 2025-02-03 21:28 5244
                    ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 5245
                    if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
1193b4be Poul Sander 2025-02-03 21:28 5246
                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
1193b4be Poul Sander 2025-02-03 21:28 5247
                    rect.ClipWith(clip_rect);
1193b4be Poul Sander 2025-02-03 21:28 5248
                    if (rect.Overlaps(clip_rect))
1193b4be Poul Sander 2025-02-03 21:28 5249
                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
1193b4be Poul Sander 2025-02-03 21:28 5250
                    rect_pos.x = draw_pos.x - draw_scroll.x;
1193b4be Poul Sander 2025-02-03 21:28 5251
                }
1193b4be Poul Sander 2025-02-03 21:28 5252
                rect_pos.y += g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 5253
            }
1193b4be Poul Sander 2025-02-03 21:28 5254
        }
1193b4be Poul Sander 2025-02-03 21:28 5255
1193b4be Poul Sander 2025-02-03 21:28 5256
        // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
1193b4be Poul Sander 2025-02-03 21:28 5257
        // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it.
1193b4be Poul Sander 2025-02-03 21:28 5258
        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
1193b4be Poul Sander 2025-02-03 21:28 5259
        {
1193b4be Poul Sander 2025-02-03 21:28 5260
            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 5261
            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
1193b4be Poul Sander 2025-02-03 21:28 5262
        }
1193b4be Poul Sander 2025-02-03 21:28 5263
1193b4be Poul Sander 2025-02-03 21:28 5264
        // Draw blinking cursor
1193b4be Poul Sander 2025-02-03 21:28 5265
        if (render_cursor)
1193b4be Poul Sander 2025-02-03 21:28 5266
        {
1193b4be Poul Sander 2025-02-03 21:28 5267
            state->CursorAnim += io.DeltaTime;
1193b4be Poul Sander 2025-02-03 21:28 5268
            bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
1193b4be Poul Sander 2025-02-03 21:28 5269
            ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
1193b4be Poul Sander 2025-02-03 21:28 5270
            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);
1193b4be Poul Sander 2025-02-03 21:28 5271
            if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
1193b4be Poul Sander 2025-02-03 21:28 5272
                draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
1193b4be Poul Sander 2025-02-03 21:28 5273
1193b4be Poul Sander 2025-02-03 21:28 5274
            // 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.)
1193b4be Poul Sander 2025-02-03 21:28 5275
            if (!is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5276
            {
1193b4be Poul Sander 2025-02-03 21:28 5277
                g.PlatformImeData.WantVisible = true;
1193b4be Poul Sander 2025-02-03 21:28 5278
                g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 5279
                g.PlatformImeData.InputLineHeight = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 5280
                g.PlatformImeViewport = window->Viewport->ID;
1193b4be Poul Sander 2025-02-03 21:28 5281
            }
1193b4be Poul Sander 2025-02-03 21:28 5282
        }
1193b4be Poul Sander 2025-02-03 21:28 5283
    }
1193b4be Poul Sander 2025-02-03 21:28 5284
    else
1193b4be Poul Sander 2025-02-03 21:28 5285
    {
1193b4be Poul Sander 2025-02-03 21:28 5286
        // Render text only (no selection, no cursor)
1193b4be Poul Sander 2025-02-03 21:28 5287
        if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5288
            text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
1193b4be Poul Sander 2025-02-03 21:28 5289
        else if (!is_displaying_hint && g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 5290
            buf_display_end = buf_display + state->CurLenA;
1193b4be Poul Sander 2025-02-03 21:28 5291
        else if (!is_displaying_hint)
1193b4be Poul Sander 2025-02-03 21:28 5292
            buf_display_end = buf_display + strlen(buf_display);
1193b4be Poul Sander 2025-02-03 21:28 5293
1193b4be Poul Sander 2025-02-03 21:28 5294
        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
1193b4be Poul Sander 2025-02-03 21:28 5295
        {
1193b4be Poul Sander 2025-02-03 21:28 5296
            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 5297
            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
1193b4be Poul Sander 2025-02-03 21:28 5298
        }
1193b4be Poul Sander 2025-02-03 21:28 5299
    }
1193b4be Poul Sander 2025-02-03 21:28 5300
1193b4be Poul Sander 2025-02-03 21:28 5301
    if (is_password && !is_displaying_hint)
1193b4be Poul Sander 2025-02-03 21:28 5302
        PopFont();
1193b4be Poul Sander 2025-02-03 21:28 5303
1193b4be Poul Sander 2025-02-03 21:28 5304
    if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5305
    {
1193b4be Poul Sander 2025-02-03 21:28 5306
        // 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 5307
        Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
1193b4be Poul Sander 2025-02-03 21:28 5308
        g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
1193b4be Poul Sander 2025-02-03 21:28 5309
        EndChild();
1193b4be Poul Sander 2025-02-03 21:28 5310
        item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
1193b4be Poul Sander 2025-02-03 21:28 5311
1193b4be Poul Sander 2025-02-03 21:28 5312
        // ...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 5313
        // FIXME: This quite messy/tricky, should attempt to get rid of the child window.
1193b4be Poul Sander 2025-02-03 21:28 5314
        EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 5315
        if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y))
1193b4be Poul Sander 2025-02-03 21:28 5316
        {
1193b4be Poul Sander 2025-02-03 21:28 5317
            g.LastItemData.ID = id;
1193b4be Poul Sander 2025-02-03 21:28 5318
            g.LastItemData.ItemFlags = item_data_backup.ItemFlags;
1193b4be Poul Sander 2025-02-03 21:28 5319
            g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
1193b4be Poul Sander 2025-02-03 21:28 5320
        }
1193b4be Poul Sander 2025-02-03 21:28 5321
    }
1193b4be Poul Sander 2025-02-03 21:28 5322
1193b4be Poul Sander 2025-02-03 21:28 5323
    // Log as text
1193b4be Poul Sander 2025-02-03 21:28 5324
    if (g.LogEnabled && (!is_password || is_displaying_hint))
1193b4be Poul Sander 2025-02-03 21:28 5325
    {
1193b4be Poul Sander 2025-02-03 21:28 5326
        LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 5327
        LogRenderedText(&draw_pos, buf_display, buf_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5328
    }
1193b4be Poul Sander 2025-02-03 21:28 5329
1193b4be Poul Sander 2025-02-03 21:28 5330
    if (label_size.x > 0)
1193b4be Poul Sander 2025-02-03 21:28 5331
        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 5332
1193b4be Poul Sander 2025-02-03 21:28 5333
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 5334
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 5335
1193b4be Poul Sander 2025-02-03 21:28 5336
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
1193b4be Poul Sander 2025-02-03 21:28 5337
    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5338
        return validated;
1193b4be Poul Sander 2025-02-03 21:28 5339
    else
1193b4be Poul Sander 2025-02-03 21:28 5340
        return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 5341
}
1193b4be Poul Sander 2025-02-03 21:28 5342
1193b4be Poul Sander 2025-02-03 21:28 5343
void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
1193b4be Poul Sander 2025-02-03 21:28 5344
{
1193b4be Poul Sander 2025-02-03 21:28 5345
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1193b4be Poul Sander 2025-02-03 21:28 5346
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5347
    ImStb::STB_TexteditState* stb_state = state->Stb;
1193b4be Poul Sander 2025-02-03 21:28 5348
    ImStb::StbUndoState* undo_state = &stb_state->undostate;
1193b4be Poul Sander 2025-02-03 21:28 5349
    Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
1193b4be Poul Sander 2025-02-03 21:28 5350
    DebugLocateItemOnHover(state->ID);
1193b4be Poul Sander 2025-02-03 21:28 5351
    Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end);
1193b4be Poul Sander 2025-02-03 21:28 5352
    Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
1193b4be Poul Sander 2025-02-03 21:28 5353
    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 5354
    if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state
1193b4be Poul Sander 2025-02-03 21:28 5355
    {
1193b4be Poul Sander 2025-02-03 21:28 5356
        PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
1193b4be Poul Sander 2025-02-03 21:28 5357
        for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)
1193b4be Poul Sander 2025-02-03 21:28 5358
        {
1193b4be Poul Sander 2025-02-03 21:28 5359
            ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
1193b4be Poul Sander 2025-02-03 21:28 5360
            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 5361
            if (undo_rec_type == ' ')
1193b4be Poul Sander 2025-02-03 21:28 5362
                BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 5363
            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 5364
            const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage;
1193b4be Poul Sander 2025-02-03 21:28 5365
            Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%.*s\"",
1193b4be Poul Sander 2025-02-03 21:28 5366
                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 5367
            if (undo_rec_type == ' ')
1193b4be Poul Sander 2025-02-03 21:28 5368
                EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 5369
        }
1193b4be Poul Sander 2025-02-03 21:28 5370
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 5371
    }
1193b4be Poul Sander 2025-02-03 21:28 5372
    EndChild();
1193b4be Poul Sander 2025-02-03 21:28 5373
#else
1193b4be Poul Sander 2025-02-03 21:28 5374
    IM_UNUSED(state);
1193b4be Poul Sander 2025-02-03 21:28 5375
#endif
1193b4be Poul Sander 2025-02-03 21:28 5376
}
1193b4be Poul Sander 2025-02-03 21:28 5377
1193b4be Poul Sander 2025-02-03 21:28 5378
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 5379
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
1193b4be Poul Sander 2025-02-03 21:28 5380
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 5381
// - ColorEdit3()
1193b4be Poul Sander 2025-02-03 21:28 5382
// - ColorEdit4()
1193b4be Poul Sander 2025-02-03 21:28 5383
// - ColorPicker3()
1193b4be Poul Sander 2025-02-03 21:28 5384
// - RenderColorRectWithAlphaCheckerboard() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5385
// - ColorPicker4()
1193b4be Poul Sander 2025-02-03 21:28 5386
// - ColorButton()
1193b4be Poul Sander 2025-02-03 21:28 5387
// - SetColorEditOptions()
1193b4be Poul Sander 2025-02-03 21:28 5388
// - ColorTooltip() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5389
// - ColorEditOptionsPopup() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5390
// - ColorPickerOptionsPopup() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5391
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 5392
1193b4be Poul Sander 2025-02-03 21:28 5393
bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 5394
{
1193b4be Poul Sander 2025-02-03 21:28 5395
    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
1193b4be Poul Sander 2025-02-03 21:28 5396
}
1193b4be Poul Sander 2025-02-03 21:28 5397
1193b4be Poul Sander 2025-02-03 21:28 5398
static void ColorEditRestoreH(const float* col, float* H)
1193b4be Poul Sander 2025-02-03 21:28 5399
{
1193b4be Poul Sander 2025-02-03 21:28 5400
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5401
    IM_ASSERT(g.ColorEditCurrentID != 0);
1193b4be Poul Sander 2025-02-03 21:28 5402
    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 5403
        return;
1193b4be Poul Sander 2025-02-03 21:28 5404
    *H = g.ColorEditSavedHue;
1193b4be Poul Sander 2025-02-03 21:28 5405
}
1193b4be Poul Sander 2025-02-03 21:28 5406
1193b4be Poul Sander 2025-02-03 21:28 5407
// 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 5408
// 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 5409
static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
1193b4be Poul Sander 2025-02-03 21:28 5410
{
1193b4be Poul Sander 2025-02-03 21:28 5411
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5412
    IM_ASSERT(g.ColorEditCurrentID != 0);
1193b4be Poul Sander 2025-02-03 21:28 5413
    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 5414
        return;
1193b4be Poul Sander 2025-02-03 21:28 5415
1193b4be Poul Sander 2025-02-03 21:28 5416
    // When S == 0, H is undefined.
1193b4be Poul Sander 2025-02-03 21:28 5417
    // When H == 1 it wraps around to 0.
1193b4be Poul Sander 2025-02-03 21:28 5418
    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
1193b4be Poul Sander 2025-02-03 21:28 5419
        *H = g.ColorEditSavedHue;
1193b4be Poul Sander 2025-02-03 21:28 5420
1193b4be Poul Sander 2025-02-03 21:28 5421
    // When V == 0, S is undefined.
1193b4be Poul Sander 2025-02-03 21:28 5422
    if (*V == 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 5423
        *S = g.ColorEditSavedSat;
1193b4be Poul Sander 2025-02-03 21:28 5424
}
1193b4be Poul Sander 2025-02-03 21:28 5425
1193b4be Poul Sander 2025-02-03 21:28 5426
// Edit colors components (each component in 0.0f..1.0f range).
1193b4be Poul Sander 2025-02-03 21:28 5427
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
1193b4be Poul Sander 2025-02-03 21:28 5428
// 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 5429
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 5430
{
1193b4be Poul Sander 2025-02-03 21:28 5431
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 5432
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 5433
        return false;
1193b4be Poul Sander 2025-02-03 21:28 5434
1193b4be Poul Sander 2025-02-03 21:28 5435
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5436
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 5437
    const float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 5438
    const char* label_display_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 5439
    float w_full = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 5440
    g.NextItemData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 5441
1193b4be Poul Sander 2025-02-03 21:28 5442
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 5443
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 5444
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
1193b4be Poul Sander 2025-02-03 21:28 5445
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 5446
        g.ColorEditCurrentID = window->IDStack.back();
1193b4be Poul Sander 2025-02-03 21:28 5447
1193b4be Poul Sander 2025-02-03 21:28 5448
    // If we're not showing any slider there's no point in doing any HSV conversions
1193b4be Poul Sander 2025-02-03 21:28 5449
    const ImGuiColorEditFlags flags_untouched = flags;
1193b4be Poul Sander 2025-02-03 21:28 5450
    if (flags & ImGuiColorEditFlags_NoInputs)
1193b4be Poul Sander 2025-02-03 21:28 5451
        flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
1193b4be Poul Sander 2025-02-03 21:28 5452
1193b4be Poul Sander 2025-02-03 21:28 5453
    // Context menu: display and modify options (before defaults are applied)
1193b4be Poul Sander 2025-02-03 21:28 5454
    if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5455
        ColorEditOptionsPopup(col, flags);
1193b4be Poul Sander 2025-02-03 21:28 5456
1193b4be Poul Sander 2025-02-03 21:28 5457
    // Read stored options
1193b4be Poul Sander 2025-02-03 21:28 5458
    if (!(flags & ImGuiColorEditFlags_DisplayMask_))
1193b4be Poul Sander 2025-02-03 21:28 5459
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
1193b4be Poul Sander 2025-02-03 21:28 5460
    if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
1193b4be Poul Sander 2025-02-03 21:28 5461
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
1193b4be Poul Sander 2025-02-03 21:28 5462
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
1193b4be Poul Sander 2025-02-03 21:28 5463
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
1193b4be Poul Sander 2025-02-03 21:28 5464
    if (!(flags & ImGuiColorEditFlags_InputMask_))
1193b4be Poul Sander 2025-02-03 21:28 5465
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
1193b4be Poul Sander 2025-02-03 21:28 5466
    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
1193b4be Poul Sander 2025-02-03 21:28 5467
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5468
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5469
1193b4be Poul Sander 2025-02-03 21:28 5470
    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
1193b4be Poul Sander 2025-02-03 21:28 5471
    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
1193b4be Poul Sander 2025-02-03 21:28 5472
    const int components = alpha ? 4 : 3;
1193b4be Poul Sander 2025-02-03 21:28 5473
    const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5474
    const float w_inputs = ImMax(w_full - w_button, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 5475
    w_full = w_inputs + w_button;
1193b4be Poul Sander 2025-02-03 21:28 5476
1193b4be Poul Sander 2025-02-03 21:28 5477
    // Convert to the formats we need
1193b4be Poul Sander 2025-02-03 21:28 5478
    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
1193b4be Poul Sander 2025-02-03 21:28 5479
    if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
1193b4be Poul Sander 2025-02-03 21:28 5480
        ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5481
    else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
1193b4be Poul Sander 2025-02-03 21:28 5482
    {
1193b4be Poul Sander 2025-02-03 21:28 5483
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
1193b4be Poul Sander 2025-02-03 21:28 5484
        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5485
        ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5486
    }
1193b4be Poul Sander 2025-02-03 21:28 5487
    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 5488
1193b4be Poul Sander 2025-02-03 21:28 5489
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 5490
    bool value_changed_as_float = false;
1193b4be Poul Sander 2025-02-03 21:28 5491
1193b4be Poul Sander 2025-02-03 21:28 5492
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 5493
    const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 5494
    window->DC.CursorPos.x = pos.x + inputs_offset_x;
1193b4be Poul Sander 2025-02-03 21:28 5495
1193b4be Poul Sander 2025-02-03 21:28 5496
    if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5497
    {
1193b4be Poul Sander 2025-02-03 21:28 5498
        // RGB/HSV 0..255 Sliders
1193b4be Poul Sander 2025-02-03 21:28 5499
        const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
1193b4be Poul Sander 2025-02-03 21:28 5500
1193b4be Poul Sander 2025-02-03 21:28 5501
        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 5502
        static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
1193b4be Poul Sander 2025-02-03 21:28 5503
        static const char* fmt_table_int[3][4] =
1193b4be Poul Sander 2025-02-03 21:28 5504
        {
1193b4be Poul Sander 2025-02-03 21:28 5505
            {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
1193b4be Poul Sander 2025-02-03 21:28 5506
            { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
1193b4be Poul Sander 2025-02-03 21:28 5507
            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
1193b4be Poul Sander 2025-02-03 21:28 5508
        };
1193b4be Poul Sander 2025-02-03 21:28 5509
        static const char* fmt_table_float[3][4] =
1193b4be Poul Sander 2025-02-03 21:28 5510
        {
1193b4be Poul Sander 2025-02-03 21:28 5511
            {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
1193b4be Poul Sander 2025-02-03 21:28 5512
            { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
1193b4be Poul Sander 2025-02-03 21:28 5513
            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
1193b4be Poul Sander 2025-02-03 21:28 5514
        };
1193b4be Poul Sander 2025-02-03 21:28 5515
        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
1193b4be Poul Sander 2025-02-03 21:28 5516
1193b4be Poul Sander 2025-02-03 21:28 5517
        float prev_split = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 5518
        for (int n = 0; n < components; n++)
1193b4be Poul Sander 2025-02-03 21:28 5519
        {
1193b4be Poul Sander 2025-02-03 21:28 5520
            if (n > 0)
1193b4be Poul Sander 2025-02-03 21:28 5521
                SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5522
            float next_split = IM_TRUNC(w_items * (n + 1) / components);
1193b4be Poul Sander 2025-02-03 21:28 5523
            SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
1193b4be Poul Sander 2025-02-03 21:28 5524
            prev_split = next_split;
1193b4be Poul Sander 2025-02-03 21:28 5525
1193b4be Poul Sander 2025-02-03 21:28 5526
            // 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 5527
            if (flags & ImGuiColorEditFlags_Float)
1193b4be Poul Sander 2025-02-03 21:28 5528
            {
1193b4be Poul Sander 2025-02-03 21:28 5529
                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 5530
                value_changed_as_float |= value_changed;
1193b4be Poul Sander 2025-02-03 21:28 5531
            }
1193b4be Poul Sander 2025-02-03 21:28 5532
            else
1193b4be Poul Sander 2025-02-03 21:28 5533
            {
1193b4be Poul Sander 2025-02-03 21:28 5534
                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 5535
            }
1193b4be Poul Sander 2025-02-03 21:28 5536
            if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5537
                OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5538
        }
1193b4be Poul Sander 2025-02-03 21:28 5539
    }
1193b4be Poul Sander 2025-02-03 21:28 5540
    else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5541
    {
1193b4be Poul Sander 2025-02-03 21:28 5542
        // RGB Hexadecimal Input
1193b4be Poul Sander 2025-02-03 21:28 5543
        char buf[64];
1193b4be Poul Sander 2025-02-03 21:28 5544
        if (alpha)
1193b4be Poul Sander 2025-02-03 21:28 5545
            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 5546
        else
1193b4be Poul Sander 2025-02-03 21:28 5547
            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 5548
        SetNextItemWidth(w_inputs);
1193b4be Poul Sander 2025-02-03 21:28 5549
        if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))
1193b4be Poul Sander 2025-02-03 21:28 5550
        {
1193b4be Poul Sander 2025-02-03 21:28 5551
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5552
            char* p = buf;
1193b4be Poul Sander 2025-02-03 21:28 5553
            while (*p == '#' || ImCharIsBlankA(*p))
1193b4be Poul Sander 2025-02-03 21:28 5554
                p++;
1193b4be Poul Sander 2025-02-03 21:28 5555
            i[0] = i[1] = i[2] = 0;
1193b4be Poul Sander 2025-02-03 21:28 5556
            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 5557
            int r;
1193b4be Poul Sander 2025-02-03 21:28 5558
            if (alpha)
1193b4be Poul Sander 2025-02-03 21:28 5559
                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 5560
            else
1193b4be Poul Sander 2025-02-03 21:28 5561
                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 5562
            IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
1193b4be Poul Sander 2025-02-03 21:28 5563
        }
1193b4be Poul Sander 2025-02-03 21:28 5564
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5565
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5566
    }
1193b4be Poul Sander 2025-02-03 21:28 5567
1193b4be Poul Sander 2025-02-03 21:28 5568
    ImGuiWindow* picker_active_window = NULL;
1193b4be Poul Sander 2025-02-03 21:28 5569
    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
1193b4be Poul Sander 2025-02-03 21:28 5570
    {
1193b4be Poul Sander 2025-02-03 21:28 5571
        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 5572
        window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
1193b4be Poul Sander 2025-02-03 21:28 5573
1193b4be Poul Sander 2025-02-03 21:28 5574
        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 5575
        if (ColorButton("##ColorButton", col_v4, flags))
1193b4be Poul Sander 2025-02-03 21:28 5576
        {
1193b4be Poul Sander 2025-02-03 21:28 5577
            if (!(flags & ImGuiColorEditFlags_NoPicker))
1193b4be Poul Sander 2025-02-03 21:28 5578
            {
1193b4be Poul Sander 2025-02-03 21:28 5579
                // Store current color and open a picker
1193b4be Poul Sander 2025-02-03 21:28 5580
                g.ColorPickerRef = col_v4;
1193b4be Poul Sander 2025-02-03 21:28 5581
                OpenPopup("picker");
1193b4be Poul Sander 2025-02-03 21:28 5582
                SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
1193b4be Poul Sander 2025-02-03 21:28 5583
            }
1193b4be Poul Sander 2025-02-03 21:28 5584
        }
1193b4be Poul Sander 2025-02-03 21:28 5585
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5586
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5587
1193b4be Poul Sander 2025-02-03 21:28 5588
        if (BeginPopup("picker"))
1193b4be Poul Sander 2025-02-03 21:28 5589
        {
1193b4be Poul Sander 2025-02-03 21:28 5590
            if (g.CurrentWindow->BeginCount == 1)
1193b4be Poul Sander 2025-02-03 21:28 5591
            {
1193b4be Poul Sander 2025-02-03 21:28 5592
                picker_active_window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 5593
                if (label != label_display_end)
1193b4be Poul Sander 2025-02-03 21:28 5594
                {
1193b4be Poul Sander 2025-02-03 21:28 5595
                    TextEx(label, label_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5596
                    Spacing();
1193b4be Poul Sander 2025-02-03 21:28 5597
                }
1193b4be Poul Sander 2025-02-03 21:28 5598
                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 5599
                ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
1193b4be Poul Sander 2025-02-03 21:28 5600
                SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
1193b4be Poul Sander 2025-02-03 21:28 5601
                value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
1193b4be Poul Sander 2025-02-03 21:28 5602
            }
1193b4be Poul Sander 2025-02-03 21:28 5603
            EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 5604
        }
1193b4be Poul Sander 2025-02-03 21:28 5605
    }
1193b4be Poul Sander 2025-02-03 21:28 5606
1193b4be Poul Sander 2025-02-03 21:28 5607
    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
1193b4be Poul Sander 2025-02-03 21:28 5608
    {
1193b4be Poul Sander 2025-02-03 21:28 5609
        // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),
1193b4be Poul Sander 2025-02-03 21:28 5610
        // 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 5611
        SameLine(0.0f, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5612
        window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5613
        TextEx(label, label_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5614
    }
1193b4be Poul Sander 2025-02-03 21:28 5615
1193b4be Poul Sander 2025-02-03 21:28 5616
    // Convert back
1193b4be Poul Sander 2025-02-03 21:28 5617
    if (value_changed && picker_active_window == NULL)
1193b4be Poul Sander 2025-02-03 21:28 5618
    {
1193b4be Poul Sander 2025-02-03 21:28 5619
        if (!value_changed_as_float)
1193b4be Poul Sander 2025-02-03 21:28 5620
            for (int n = 0; n < 4; n++)
1193b4be Poul Sander 2025-02-03 21:28 5621
                f[n] = i[n] / 255.0f;
1193b4be Poul Sander 2025-02-03 21:28 5622
        if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
1193b4be Poul Sander 2025-02-03 21:28 5623
        {
1193b4be Poul Sander 2025-02-03 21:28 5624
            g.ColorEditSavedHue = f[0];
1193b4be Poul Sander 2025-02-03 21:28 5625
            g.ColorEditSavedSat = f[1];
1193b4be Poul Sander 2025-02-03 21:28 5626
            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5627
            g.ColorEditSavedID = g.ColorEditCurrentID;
1193b4be Poul Sander 2025-02-03 21:28 5628
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
1193b4be Poul Sander 2025-02-03 21:28 5629
        }
1193b4be Poul Sander 2025-02-03 21:28 5630
        if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
1193b4be Poul Sander 2025-02-03 21:28 5631
            ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5632
1193b4be Poul Sander 2025-02-03 21:28 5633
        col[0] = f[0];
1193b4be Poul Sander 2025-02-03 21:28 5634
        col[1] = f[1];
1193b4be Poul Sander 2025-02-03 21:28 5635
        col[2] = f[2];
1193b4be Poul Sander 2025-02-03 21:28 5636
        if (alpha)
1193b4be Poul Sander 2025-02-03 21:28 5637
            col[3] = f[3];
1193b4be Poul Sander 2025-02-03 21:28 5638
    }
1193b4be Poul Sander 2025-02-03 21:28 5639
1193b4be Poul Sander 2025-02-03 21:28 5640
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 5641
        g.ColorEditCurrentID = 0;
1193b4be Poul Sander 2025-02-03 21:28 5642
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 5643
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 5644
1193b4be Poul Sander 2025-02-03 21:28 5645
    // Drag and Drop Target
1193b4be Poul Sander 2025-02-03 21:28 5646
    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
1193b4be Poul Sander 2025-02-03 21:28 5647
    if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
1193b4be Poul Sander 2025-02-03 21:28 5648
    {
1193b4be Poul Sander 2025-02-03 21:28 5649
        bool accepted_drag_drop = false;
1193b4be Poul Sander 2025-02-03 21:28 5650
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
1193b4be Poul Sander 2025-02-03 21:28 5651
        {
1193b4be Poul Sander 2025-02-03 21:28 5652
            memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
1193b4be Poul Sander 2025-02-03 21:28 5653
            value_changed = accepted_drag_drop = true;
1193b4be Poul Sander 2025-02-03 21:28 5654
        }
1193b4be Poul Sander 2025-02-03 21:28 5655
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
1193b4be Poul Sander 2025-02-03 21:28 5656
        {
1193b4be Poul Sander 2025-02-03 21:28 5657
            memcpy((float*)col, payload->Data, sizeof(float) * components);
1193b4be Poul Sander 2025-02-03 21:28 5658
            value_changed = accepted_drag_drop = true;
1193b4be Poul Sander 2025-02-03 21:28 5659
        }
1193b4be Poul Sander 2025-02-03 21:28 5660
1193b4be Poul Sander 2025-02-03 21:28 5661
        // Drag-drop payloads are always RGB
1193b4be Poul Sander 2025-02-03 21:28 5662
        if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
1193b4be Poul Sander 2025-02-03 21:28 5663
            ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 5664
        EndDragDropTarget();
1193b4be Poul Sander 2025-02-03 21:28 5665
    }
1193b4be Poul Sander 2025-02-03 21:28 5666
1193b4be Poul Sander 2025-02-03 21:28 5667
    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
1193b4be Poul Sander 2025-02-03 21:28 5668
    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
1193b4be Poul Sander 2025-02-03 21:28 5669
        g.LastItemData.ID = g.ActiveId;
1193b4be Poul Sander 2025-02-03 21:28 5670
1193b4be Poul Sander 2025-02-03 21:28 5671
    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 5672
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 5673
1193b4be Poul Sander 2025-02-03 21:28 5674
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 5675
}
1193b4be Poul Sander 2025-02-03 21:28 5676
1193b4be Poul Sander 2025-02-03 21:28 5677
bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 5678
{
1193b4be Poul Sander 2025-02-03 21:28 5679
    float col4[4] = { col[0], col[1], col[2], 1.0f };
1193b4be Poul Sander 2025-02-03 21:28 5680
    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
1193b4be Poul Sander 2025-02-03 21:28 5681
        return false;
1193b4be Poul Sander 2025-02-03 21:28 5682
    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
1193b4be Poul Sander 2025-02-03 21:28 5683
    return true;
1193b4be Poul Sander 2025-02-03 21:28 5684
}
1193b4be Poul Sander 2025-02-03 21:28 5685
1193b4be Poul Sander 2025-02-03 21:28 5686
// Helper for ColorPicker4()
1193b4be Poul Sander 2025-02-03 21:28 5687
static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
1193b4be Poul Sander 2025-02-03 21:28 5688
{
1193b4be Poul Sander 2025-02-03 21:28 5689
    ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
1193b4be Poul Sander 2025-02-03 21:28 5690
    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 5691
    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 5692
    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 5693
    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 5694
}
1193b4be Poul Sander 2025-02-03 21:28 5695
1193b4be Poul Sander 2025-02-03 21:28 5696
// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
1193b4be Poul Sander 2025-02-03 21:28 5697
// (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 5698
// 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 5699
// 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 5700
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
1193b4be Poul Sander 2025-02-03 21:28 5701
{
1193b4be Poul Sander 2025-02-03 21:28 5702
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5703
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 5704
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 5705
        return false;
1193b4be Poul Sander 2025-02-03 21:28 5706
1193b4be Poul Sander 2025-02-03 21:28 5707
    ImDrawList* draw_list = window->DrawList;
1193b4be Poul Sander 2025-02-03 21:28 5708
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 5709
    ImGuiIO& io = g.IO;
1193b4be Poul Sander 2025-02-03 21:28 5710
1193b4be Poul Sander 2025-02-03 21:28 5711
    const float width = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 5712
    const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
1193b4be Poul Sander 2025-02-03 21:28 5713
    g.NextItemData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 5714
1193b4be Poul Sander 2025-02-03 21:28 5715
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 5716
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
1193b4be Poul Sander 2025-02-03 21:28 5717
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 5718
        g.ColorEditCurrentID = window->IDStack.back();
1193b4be Poul Sander 2025-02-03 21:28 5719
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 5720
1193b4be Poul Sander 2025-02-03 21:28 5721
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 5722
        flags |= ImGuiColorEditFlags_NoSmallPreview;
1193b4be Poul Sander 2025-02-03 21:28 5723
1193b4be Poul Sander 2025-02-03 21:28 5724
    // Context menu: display and store options.
1193b4be Poul Sander 2025-02-03 21:28 5725
    if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5726
        ColorPickerOptionsPopup(col, flags);
1193b4be Poul Sander 2025-02-03 21:28 5727
1193b4be Poul Sander 2025-02-03 21:28 5728
    // Read stored options
1193b4be Poul Sander 2025-02-03 21:28 5729
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
1193b4be Poul Sander 2025-02-03 21:28 5730
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
1193b4be Poul Sander 2025-02-03 21:28 5731
    if (!(flags & ImGuiColorEditFlags_InputMask_))
1193b4be Poul Sander 2025-02-03 21:28 5732
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
1193b4be Poul Sander 2025-02-03 21:28 5733
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5734
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5735
    if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5736
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
1193b4be Poul Sander 2025-02-03 21:28 5737
1193b4be Poul Sander 2025-02-03 21:28 5738
    // Setup
1193b4be Poul Sander 2025-02-03 21:28 5739
    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
1193b4be Poul Sander 2025-02-03 21:28 5740
    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
1193b4be Poul Sander 2025-02-03 21:28 5741
    ImVec2 picker_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 5742
    float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 5743
    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
1193b4be Poul Sander 2025-02-03 21:28 5744
    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 5745
    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 5746
    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 5747
    float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);
1193b4be Poul Sander 2025-02-03 21:28 5748
1193b4be Poul Sander 2025-02-03 21:28 5749
    float backup_initial_col[4];
1193b4be Poul Sander 2025-02-03 21:28 5750
    memcpy(backup_initial_col, col, components * sizeof(float));
1193b4be Poul Sander 2025-02-03 21:28 5751
1193b4be Poul Sander 2025-02-03 21:28 5752
    float wheel_thickness = sv_picker_size * 0.08f;
1193b4be Poul Sander 2025-02-03 21:28 5753
    float wheel_r_outer = sv_picker_size * 0.50f;
1193b4be Poul Sander 2025-02-03 21:28 5754
    float wheel_r_inner = wheel_r_outer - wheel_thickness;
1193b4be Poul Sander 2025-02-03 21:28 5755
    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 5756
1193b4be Poul Sander 2025-02-03 21:28 5757
    // 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 5758
    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
1193b4be Poul Sander 2025-02-03 21:28 5759
    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
1193b4be Poul Sander 2025-02-03 21:28 5760
    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
1193b4be Poul Sander 2025-02-03 21:28 5761
    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
1193b4be Poul Sander 2025-02-03 21:28 5762
1193b4be Poul Sander 2025-02-03 21:28 5763
    float H = col[0], S = col[1], V = col[2];
1193b4be Poul Sander 2025-02-03 21:28 5764
    float R = col[0], G = col[1], B = col[2];
1193b4be Poul Sander 2025-02-03 21:28 5765
    if (flags & ImGuiColorEditFlags_InputRGB)
1193b4be Poul Sander 2025-02-03 21:28 5766
    {
1193b4be Poul Sander 2025-02-03 21:28 5767
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
1193b4be Poul Sander 2025-02-03 21:28 5768
        ColorConvertRGBtoHSV(R, G, B, H, S, V);
1193b4be Poul Sander 2025-02-03 21:28 5769
        ColorEditRestoreHS(col, &H, &S, &V);
1193b4be Poul Sander 2025-02-03 21:28 5770
    }
1193b4be Poul Sander 2025-02-03 21:28 5771
    else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 5772
    {
1193b4be Poul Sander 2025-02-03 21:28 5773
        ColorConvertHSVtoRGB(H, S, V, R, G, B);
1193b4be Poul Sander 2025-02-03 21:28 5774
    }
1193b4be Poul Sander 2025-02-03 21:28 5775
1193b4be Poul Sander 2025-02-03 21:28 5776
    bool value_changed = false, value_changed_h = false, value_changed_sv = false;
1193b4be Poul Sander 2025-02-03 21:28 5777
1193b4be Poul Sander 2025-02-03 21:28 5778
    PushItemFlag(ImGuiItemFlags_NoNav, true);
1193b4be Poul Sander 2025-02-03 21:28 5779
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
1193b4be Poul Sander 2025-02-03 21:28 5780
    {
1193b4be Poul Sander 2025-02-03 21:28 5781
        // Hue wheel + SV triangle logic
1193b4be Poul Sander 2025-02-03 21:28 5782
        InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 5783
        if (IsItemActive() && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5784
        {
1193b4be Poul Sander 2025-02-03 21:28 5785
            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
1193b4be Poul Sander 2025-02-03 21:28 5786
            ImVec2 current_off = g.IO.MousePos - wheel_center;
1193b4be Poul Sander 2025-02-03 21:28 5787
            float initial_dist2 = ImLengthSqr(initial_off);
1193b4be Poul Sander 2025-02-03 21:28 5788
            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 5789
            {
1193b4be Poul Sander 2025-02-03 21:28 5790
                // Interactive with Hue wheel
1193b4be Poul Sander 2025-02-03 21:28 5791
                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 5792
                if (H < 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 5793
                    H += 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 5794
                value_changed = value_changed_h = true;
1193b4be Poul Sander 2025-02-03 21:28 5795
            }
1193b4be Poul Sander 2025-02-03 21:28 5796
            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 5797
            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 5798
            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 5799
            {
1193b4be Poul Sander 2025-02-03 21:28 5800
                // Interacting with SV triangle
1193b4be Poul Sander 2025-02-03 21:28 5801
                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 5802
                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
1193b4be Poul Sander 2025-02-03 21:28 5803
                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
1193b4be Poul Sander 2025-02-03 21:28 5804
                float uu, vv, ww;
1193b4be Poul Sander 2025-02-03 21:28 5805
                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
1193b4be Poul Sander 2025-02-03 21:28 5806
                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 5807
                S = ImClamp(uu / V, 0.0001f, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 5808
                value_changed = value_changed_sv = true;
1193b4be Poul Sander 2025-02-03 21:28 5809
            }
1193b4be Poul Sander 2025-02-03 21:28 5810
        }
1193b4be Poul Sander 2025-02-03 21:28 5811
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5812
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5813
    }
1193b4be Poul Sander 2025-02-03 21:28 5814
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
1193b4be Poul Sander 2025-02-03 21:28 5815
    {
1193b4be Poul Sander 2025-02-03 21:28 5816
        // SV rectangle logic
1193b4be Poul Sander 2025-02-03 21:28 5817
        InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 5818
        if (IsItemActive() && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5819
        {
1193b4be Poul Sander 2025-02-03 21:28 5820
            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 5821
            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 5822
            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 5823
            value_changed = value_changed_sv = true;
1193b4be Poul Sander 2025-02-03 21:28 5824
        }
1193b4be Poul Sander 2025-02-03 21:28 5825
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5826
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5827
1193b4be Poul Sander 2025-02-03 21:28 5828
        // Hue bar logic
1193b4be Poul Sander 2025-02-03 21:28 5829
        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
1193b4be Poul Sander 2025-02-03 21:28 5830
        InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 5831
        if (IsItemActive() && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5832
        {
1193b4be Poul Sander 2025-02-03 21:28 5833
            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 5834
            value_changed = value_changed_h = true;
1193b4be Poul Sander 2025-02-03 21:28 5835
        }
1193b4be Poul Sander 2025-02-03 21:28 5836
    }
1193b4be Poul Sander 2025-02-03 21:28 5837
1193b4be Poul Sander 2025-02-03 21:28 5838
    // Alpha bar logic
1193b4be Poul Sander 2025-02-03 21:28 5839
    if (alpha_bar)
1193b4be Poul Sander 2025-02-03 21:28 5840
    {
1193b4be Poul Sander 2025-02-03 21:28 5841
        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
1193b4be Poul Sander 2025-02-03 21:28 5842
        InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 5843
        if (IsItemActive())
1193b4be Poul Sander 2025-02-03 21:28 5844
        {
1193b4be Poul Sander 2025-02-03 21:28 5845
            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 5846
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5847
        }
1193b4be Poul Sander 2025-02-03 21:28 5848
    }
1193b4be Poul Sander 2025-02-03 21:28 5849
    PopItemFlag(); // ImGuiItemFlags_NoNav
1193b4be Poul Sander 2025-02-03 21:28 5850
1193b4be Poul Sander 2025-02-03 21:28 5851
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 5852
    {
1193b4be Poul Sander 2025-02-03 21:28 5853
        SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5854
        BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 5855
    }
1193b4be Poul Sander 2025-02-03 21:28 5856
1193b4be Poul Sander 2025-02-03 21:28 5857
    if (!(flags & ImGuiColorEditFlags_NoLabel))
1193b4be Poul Sander 2025-02-03 21:28 5858
    {
1193b4be Poul Sander 2025-02-03 21:28 5859
        const char* label_display_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 5860
        if (label != label_display_end)
1193b4be Poul Sander 2025-02-03 21:28 5861
        {
1193b4be Poul Sander 2025-02-03 21:28 5862
            if ((flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 5863
                SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5864
            TextEx(label, label_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5865
        }
1193b4be Poul Sander 2025-02-03 21:28 5866
    }
1193b4be Poul Sander 2025-02-03 21:28 5867
1193b4be Poul Sander 2025-02-03 21:28 5868
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 5869
    {
1193b4be Poul Sander 2025-02-03 21:28 5870
        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
1193b4be Poul Sander 2025-02-03 21:28 5871
        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
1193b4be Poul Sander 2025-02-03 21:28 5872
        if ((flags & ImGuiColorEditFlags_NoLabel))
1193b4be Poul Sander 2025-02-03 21:28 5873
            Text("Current");
1193b4be Poul Sander 2025-02-03 21:28 5874
1193b4be Poul Sander 2025-02-03 21:28 5875
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
1193b4be Poul Sander 2025-02-03 21:28 5876
        ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
1193b4be Poul Sander 2025-02-03 21:28 5877
        if (ref_col != NULL)
1193b4be Poul Sander 2025-02-03 21:28 5878
        {
1193b4be Poul Sander 2025-02-03 21:28 5879
            Text("Original");
1193b4be Poul Sander 2025-02-03 21:28 5880
            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 5881
            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 5882
            {
1193b4be Poul Sander 2025-02-03 21:28 5883
                memcpy(col, ref_col, components * sizeof(float));
1193b4be Poul Sander 2025-02-03 21:28 5884
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5885
            }
1193b4be Poul Sander 2025-02-03 21:28 5886
        }
1193b4be Poul Sander 2025-02-03 21:28 5887
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 5888
        EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 5889
    }
1193b4be Poul Sander 2025-02-03 21:28 5890
1193b4be Poul Sander 2025-02-03 21:28 5891
    // Convert back color to RGB
1193b4be Poul Sander 2025-02-03 21:28 5892
    if (value_changed_h || value_changed_sv)
1193b4be Poul Sander 2025-02-03 21:28 5893
    {
1193b4be Poul Sander 2025-02-03 21:28 5894
        if (flags & ImGuiColorEditFlags_InputRGB)
1193b4be Poul Sander 2025-02-03 21:28 5895
        {
1193b4be Poul Sander 2025-02-03 21:28 5896
            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 5897
            g.ColorEditSavedHue = H;
1193b4be Poul Sander 2025-02-03 21:28 5898
            g.ColorEditSavedSat = S;
1193b4be Poul Sander 2025-02-03 21:28 5899
            g.ColorEditSavedID = g.ColorEditCurrentID;
1193b4be Poul Sander 2025-02-03 21:28 5900
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
1193b4be Poul Sander 2025-02-03 21:28 5901
        }
1193b4be Poul Sander 2025-02-03 21:28 5902
        else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 5903
        {
1193b4be Poul Sander 2025-02-03 21:28 5904
            col[0] = H;
1193b4be Poul Sander 2025-02-03 21:28 5905
            col[1] = S;
1193b4be Poul Sander 2025-02-03 21:28 5906
            col[2] = V;
1193b4be Poul Sander 2025-02-03 21:28 5907
        }
1193b4be Poul Sander 2025-02-03 21:28 5908
    }
1193b4be Poul Sander 2025-02-03 21:28 5909
1193b4be Poul Sander 2025-02-03 21:28 5910
    // R,G,B and H,S,V slider color editor
1193b4be Poul Sander 2025-02-03 21:28 5911
    bool value_changed_fix_hue_wrap = false;
1193b4be Poul Sander 2025-02-03 21:28 5912
    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5913
    {
1193b4be Poul Sander 2025-02-03 21:28 5914
        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
1193b4be Poul Sander 2025-02-03 21:28 5915
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
1193b4be Poul Sander 2025-02-03 21:28 5916
        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
1193b4be Poul Sander 2025-02-03 21:28 5917
        if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5918
            if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
1193b4be Poul Sander 2025-02-03 21:28 5919
            {
1193b4be Poul Sander 2025-02-03 21:28 5920
                // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
1193b4be Poul Sander 2025-02-03 21:28 5921
                // 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 5922
                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
1193b4be Poul Sander 2025-02-03 21:28 5923
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5924
            }
1193b4be Poul Sander 2025-02-03 21:28 5925
        if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5926
            value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
1193b4be Poul Sander 2025-02-03 21:28 5927
        if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5928
            value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
1193b4be Poul Sander 2025-02-03 21:28 5929
        PopItemWidth();
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
    // Try to cancel hue wrap (after ColorEdit4 call), if any
1193b4be Poul Sander 2025-02-03 21:28 5933
    if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
1193b4be Poul Sander 2025-02-03 21:28 5934
    {
1193b4be Poul Sander 2025-02-03 21:28 5935
        float new_H, new_S, new_V;
1193b4be Poul Sander 2025-02-03 21:28 5936
        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
1193b4be Poul Sander 2025-02-03 21:28 5937
        if (new_H <= 0 && H > 0)
1193b4be Poul Sander 2025-02-03 21:28 5938
        {
1193b4be Poul Sander 2025-02-03 21:28 5939
            if (new_V <= 0 && V != new_V)
1193b4be Poul Sander 2025-02-03 21:28 5940
                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 5941
            else if (new_S <= 0)
1193b4be Poul Sander 2025-02-03 21:28 5942
                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 5943
        }
1193b4be Poul Sander 2025-02-03 21:28 5944
    }
1193b4be Poul Sander 2025-02-03 21:28 5945
1193b4be Poul Sander 2025-02-03 21:28 5946
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 5947
    {
1193b4be Poul Sander 2025-02-03 21:28 5948
        if (flags & ImGuiColorEditFlags_InputRGB)
1193b4be Poul Sander 2025-02-03 21:28 5949
        {
1193b4be Poul Sander 2025-02-03 21:28 5950
            R = col[0];
1193b4be Poul Sander 2025-02-03 21:28 5951
            G = col[1];
1193b4be Poul Sander 2025-02-03 21:28 5952
            B = col[2];
1193b4be Poul Sander 2025-02-03 21:28 5953
            ColorConvertRGBtoHSV(R, G, B, H, S, V);
1193b4be Poul Sander 2025-02-03 21:28 5954
            ColorEditRestoreHS(col, &H, &S, &V);   // Fix local Hue as display below will use it immediately.
1193b4be Poul Sander 2025-02-03 21:28 5955
        }
1193b4be Poul Sander 2025-02-03 21:28 5956
        else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 5957
        {
1193b4be Poul Sander 2025-02-03 21:28 5958
            H = col[0];
1193b4be Poul Sander 2025-02-03 21:28 5959
            S = col[1];
1193b4be Poul Sander 2025-02-03 21:28 5960
            V = col[2];
1193b4be Poul Sander 2025-02-03 21:28 5961
            ColorConvertHSVtoRGB(H, S, V, R, G, B);
1193b4be Poul Sander 2025-02-03 21:28 5962
        }
1193b4be Poul Sander 2025-02-03 21:28 5963
    }
1193b4be Poul Sander 2025-02-03 21:28 5964
1193b4be Poul Sander 2025-02-03 21:28 5965
    const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
1193b4be Poul Sander 2025-02-03 21:28 5966
    const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
1193b4be Poul Sander 2025-02-03 21:28 5967
    const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
1193b4be Poul Sander 2025-02-03 21:28 5968
    const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
1193b4be Poul Sander 2025-02-03 21:28 5969
    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 5970
1193b4be Poul Sander 2025-02-03 21:28 5971
    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 5972
    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
1193b4be Poul Sander 2025-02-03 21:28 5973
    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 5974
1193b4be Poul Sander 2025-02-03 21:28 5975
    ImVec2 sv_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 5976
1193b4be Poul Sander 2025-02-03 21:28 5977
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
1193b4be Poul Sander 2025-02-03 21:28 5978
    {
1193b4be Poul Sander 2025-02-03 21:28 5979
        // Render Hue Wheel
1193b4be Poul Sander 2025-02-03 21:28 5980
        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 5981
        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
1193b4be Poul Sander 2025-02-03 21:28 5982
        for (int n = 0; n < 6; n++)
1193b4be Poul Sander 2025-02-03 21:28 5983
        {
1193b4be Poul Sander 2025-02-03 21:28 5984
            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
1193b4be Poul Sander 2025-02-03 21:28 5985
            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
1193b4be Poul Sander 2025-02-03 21:28 5986
            const int vert_start_idx = draw_list->VtxBuffer.Size;
1193b4be Poul Sander 2025-02-03 21:28 5987
            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 5988
            draw_list->PathStroke(col_white, 0, wheel_thickness);
1193b4be Poul Sander 2025-02-03 21:28 5989
            const int vert_end_idx = draw_list->VtxBuffer.Size;
1193b4be Poul Sander 2025-02-03 21:28 5990
1193b4be Poul Sander 2025-02-03 21:28 5991
            // Paint colors over existing vertices
1193b4be Poul Sander 2025-02-03 21:28 5992
            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 5993
            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 5994
            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 5995
        }
1193b4be Poul Sander 2025-02-03 21:28 5996
1193b4be Poul Sander 2025-02-03 21:28 5997
        // Render Cursor + preview on Hue Wheel
1193b4be Poul Sander 2025-02-03 21:28 5998
        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 5999
        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 6000
        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 6001
        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
1193b4be Poul Sander 2025-02-03 21:28 6002
        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 6003
        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6004
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6005
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6006
1193b4be Poul Sander 2025-02-03 21:28 6007
        // Render SV triangle (rotated according to hue)
1193b4be Poul Sander 2025-02-03 21:28 6008
        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6009
        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6010
        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6011
        ImVec2 uv_white = GetFontTexUvWhitePixel();
1193b4be Poul Sander 2025-02-03 21:28 6012
        draw_list->PrimReserve(3, 3);
1193b4be Poul Sander 2025-02-03 21:28 6013
        draw_list->PrimVtx(tra, uv_white, hue_color32);
1193b4be Poul Sander 2025-02-03 21:28 6014
        draw_list->PrimVtx(trb, uv_white, col_black);
1193b4be Poul Sander 2025-02-03 21:28 6015
        draw_list->PrimVtx(trc, uv_white, col_white);
1193b4be Poul Sander 2025-02-03 21:28 6016
        draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
1193b4be Poul Sander 2025-02-03 21:28 6017
        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
1193b4be Poul Sander 2025-02-03 21:28 6018
    }
1193b4be Poul Sander 2025-02-03 21:28 6019
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
1193b4be Poul Sander 2025-02-03 21:28 6020
    {
1193b4be Poul Sander 2025-02-03 21:28 6021
        // Render SV Square
1193b4be Poul Sander 2025-02-03 21:28 6022
        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 6023
        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 6024
        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6025
        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 6026
        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 6027
1193b4be Poul Sander 2025-02-03 21:28 6028
        // Render Hue Bar
1193b4be Poul Sander 2025-02-03 21:28 6029
        for (int i = 0; i < 6; ++i)
1193b4be Poul Sander 2025-02-03 21:28 6030
            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 6031
        float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
1193b4be Poul Sander 2025-02-03 21:28 6032
        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 6033
        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 6034
    }
1193b4be Poul Sander 2025-02-03 21:28 6035
1193b4be Poul Sander 2025-02-03 21:28 6036
    // 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 6037
    float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;
1193b4be Poul Sander 2025-02-03 21:28 6038
    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 6039
    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 6040
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6041
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6042
1193b4be Poul Sander 2025-02-03 21:28 6043
    // Render alpha bar
1193b4be Poul Sander 2025-02-03 21:28 6044
    if (alpha_bar)
1193b4be Poul Sander 2025-02-03 21:28 6045
    {
1193b4be Poul Sander 2025-02-03 21:28 6046
        float alpha = ImSaturate(col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6047
        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 6048
        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 6049
        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 6050
        float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
1193b4be Poul Sander 2025-02-03 21:28 6051
        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6052
        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 6053
    }
1193b4be Poul Sander 2025-02-03 21:28 6054
1193b4be Poul Sander 2025-02-03 21:28 6055
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 6056
1193b4be Poul Sander 2025-02-03 21:28 6057
    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6058
        value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 6059
    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 6060
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 6061
1193b4be Poul Sander 2025-02-03 21:28 6062
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 6063
        g.ColorEditCurrentID = 0;
1193b4be Poul Sander 2025-02-03 21:28 6064
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 6065
1193b4be Poul Sander 2025-02-03 21:28 6066
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 6067
}
1193b4be Poul Sander 2025-02-03 21:28 6068
1193b4be Poul Sander 2025-02-03 21:28 6069
// A little color square. Return true when clicked.
1193b4be Poul Sander 2025-02-03 21:28 6070
// 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 6071
// '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 6072
// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
1193b4be Poul Sander 2025-02-03 21:28 6073
bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 6074
{
1193b4be Poul Sander 2025-02-03 21:28 6075
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6076
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6077
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6078
1193b4be Poul Sander 2025-02-03 21:28 6079
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6080
    const ImGuiID id = window->GetID(desc_id);
1193b4be Poul Sander 2025-02-03 21:28 6081
    const float default_size = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 6082
    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 6083
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 6084
    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6085
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 6086
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6087
1193b4be Poul Sander 2025-02-03 21:28 6088
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 6089
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 6090
1193b4be Poul Sander 2025-02-03 21:28 6091
    if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6092
        flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
1193b4be Poul Sander 2025-02-03 21:28 6093
1193b4be Poul Sander 2025-02-03 21:28 6094
    ImVec4 col_rgb = col;
1193b4be Poul Sander 2025-02-03 21:28 6095
    if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6096
        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 6097
1193b4be Poul Sander 2025-02-03 21:28 6098
    ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 6099
    float grid_step = ImMin(size.x, size.y) / 2.99f;
1193b4be Poul Sander 2025-02-03 21:28 6100
    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 6101
    ImRect bb_inner = bb;
1193b4be Poul Sander 2025-02-03 21:28 6102
    float off = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 6103
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6104
    {
1193b4be Poul Sander 2025-02-03 21:28 6105
        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 6106
        bb_inner.Expand(off);
1193b4be Poul Sander 2025-02-03 21:28 6107
    }
1193b4be Poul Sander 2025-02-03 21:28 6108
    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 6109
    {
1193b4be Poul Sander 2025-02-03 21:28 6110
        float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 6111
        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);
1193b4be Poul Sander 2025-02-03 21:28 6112
        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 6113
    }
1193b4be Poul Sander 2025-02-03 21:28 6114
    else
1193b4be Poul Sander 2025-02-03 21:28 6115
    {
1193b4be Poul Sander 2025-02-03 21:28 6116
        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
1193b4be Poul Sander 2025-02-03 21:28 6117
        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
1193b4be Poul Sander 2025-02-03 21:28 6118
        if (col_source.w < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 6119
            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 6120
        else
1193b4be Poul Sander 2025-02-03 21:28 6121
            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
1193b4be Poul Sander 2025-02-03 21:28 6122
    }
1193b4be Poul Sander 2025-02-03 21:28 6123
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 6124
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6125
    {
1193b4be Poul Sander 2025-02-03 21:28 6126
        if (g.Style.FrameBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 6127
            RenderFrameBorder(bb.Min, bb.Max, rounding);
1193b4be Poul Sander 2025-02-03 21:28 6128
        else
1193b4be Poul Sander 2025-02-03 21:28 6129
            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
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
    // Drag and Drop Source
1193b4be Poul Sander 2025-02-03 21:28 6133
    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
1193b4be Poul Sander 2025-02-03 21:28 6134
    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
1193b4be Poul Sander 2025-02-03 21:28 6135
    {
1193b4be Poul Sander 2025-02-03 21:28 6136
        if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6137
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
1193b4be Poul Sander 2025-02-03 21:28 6138
        else
1193b4be Poul Sander 2025-02-03 21:28 6139
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
1193b4be Poul Sander 2025-02-03 21:28 6140
        ColorButton(desc_id, col, flags);
1193b4be Poul Sander 2025-02-03 21:28 6141
        SameLine();
1193b4be Poul Sander 2025-02-03 21:28 6142
        TextEx("Color");
1193b4be Poul Sander 2025-02-03 21:28 6143
        EndDragDropSource();
1193b4be Poul Sander 2025-02-03 21:28 6144
    }
1193b4be Poul Sander 2025-02-03 21:28 6145
1193b4be Poul Sander 2025-02-03 21:28 6146
    // Tooltip
1193b4be Poul Sander 2025-02-03 21:28 6147
    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))
1193b4be Poul Sander 2025-02-03 21:28 6148
        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
1193b4be Poul Sander 2025-02-03 21:28 6149
1193b4be Poul Sander 2025-02-03 21:28 6150
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 6151
}
1193b4be Poul Sander 2025-02-03 21:28 6152
1193b4be Poul Sander 2025-02-03 21:28 6153
// Initialize/override default color options
1193b4be Poul Sander 2025-02-03 21:28 6154
void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6155
{
1193b4be Poul Sander 2025-02-03 21:28 6156
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6157
    if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6158
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
1193b4be Poul Sander 2025-02-03 21:28 6159
    if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6160
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
1193b4be Poul Sander 2025-02-03 21:28 6161
    if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6162
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
1193b4be Poul Sander 2025-02-03 21:28 6163
    if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6164
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
1193b4be Poul Sander 2025-02-03 21:28 6165
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6166
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6167
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6168
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6169
    g.ColorEditOptions = flags;
1193b4be Poul Sander 2025-02-03 21:28 6170
}
1193b4be Poul Sander 2025-02-03 21:28 6171
1193b4be Poul Sander 2025-02-03 21:28 6172
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
1193b4be Poul Sander 2025-02-03 21:28 6173
void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6174
{
1193b4be Poul Sander 2025-02-03 21:28 6175
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6176
1193b4be Poul Sander 2025-02-03 21:28 6177
    if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 6178
        return;
1193b4be Poul Sander 2025-02-03 21:28 6179
    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
1193b4be Poul Sander 2025-02-03 21:28 6180
    if (text_end > text)
1193b4be Poul Sander 2025-02-03 21:28 6181
    {
1193b4be Poul Sander 2025-02-03 21:28 6182
        TextEx(text, text_end);
1193b4be Poul Sander 2025-02-03 21:28 6183
        Separator();
1193b4be Poul Sander 2025-02-03 21:28 6184
    }
1193b4be Poul Sander 2025-02-03 21:28 6185
1193b4be Poul Sander 2025-02-03 21:28 6186
    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 6187
    ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6188
    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 6189
    ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
1193b4be Poul Sander 2025-02-03 21:28 6190
    SameLine();
1193b4be Poul Sander 2025-02-03 21:28 6191
    if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
1193b4be Poul Sander 2025-02-03 21:28 6192
    {
1193b4be Poul Sander 2025-02-03 21:28 6193
        if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6194
            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 6195
        else
1193b4be Poul Sander 2025-02-03 21:28 6196
            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 6197
    }
1193b4be Poul Sander 2025-02-03 21:28 6198
    else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6199
    {
1193b4be Poul Sander 2025-02-03 21:28 6200
        if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6201
            Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 6202
        else
1193b4be Poul Sander 2025-02-03 21:28 6203
            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 6204
    }
1193b4be Poul Sander 2025-02-03 21:28 6205
    EndTooltip();
1193b4be Poul Sander 2025-02-03 21:28 6206
}
1193b4be Poul Sander 2025-02-03 21:28 6207
1193b4be Poul Sander 2025-02-03 21:28 6208
void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6209
{
1193b4be Poul Sander 2025-02-03 21:28 6210
    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
1193b4be Poul Sander 2025-02-03 21:28 6211
    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
1193b4be Poul Sander 2025-02-03 21:28 6212
    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
1193b4be Poul Sander 2025-02-03 21:28 6213
        return;
1193b4be Poul Sander 2025-02-03 21:28 6214
1193b4be Poul Sander 2025-02-03 21:28 6215
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6216
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
1193b4be Poul Sander 2025-02-03 21:28 6217
    ImGuiColorEditFlags opts = g.ColorEditOptions;
1193b4be Poul Sander 2025-02-03 21:28 6218
    if (allow_opt_inputs)
1193b4be Poul Sander 2025-02-03 21:28 6219
    {
1193b4be Poul Sander 2025-02-03 21:28 6220
        if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
1193b4be Poul Sander 2025-02-03 21:28 6221
        if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
1193b4be Poul Sander 2025-02-03 21:28 6222
        if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
1193b4be Poul Sander 2025-02-03 21:28 6223
    }
1193b4be Poul Sander 2025-02-03 21:28 6224
    if (allow_opt_datatype)
1193b4be Poul Sander 2025-02-03 21:28 6225
    {
1193b4be Poul Sander 2025-02-03 21:28 6226
        if (allow_opt_inputs) Separator();
1193b4be Poul Sander 2025-02-03 21:28 6227
        if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
1193b4be Poul Sander 2025-02-03 21:28 6228
        if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
1193b4be Poul Sander 2025-02-03 21:28 6229
    }
1193b4be Poul Sander 2025-02-03 21:28 6230
1193b4be Poul Sander 2025-02-03 21:28 6231
    if (allow_opt_inputs || allow_opt_datatype)
1193b4be Poul Sander 2025-02-03 21:28 6232
        Separator();
1193b4be Poul Sander 2025-02-03 21:28 6233
    if (Button("Copy as..", ImVec2(-1, 0)))
1193b4be Poul Sander 2025-02-03 21:28 6234
        OpenPopup("Copy");
1193b4be Poul Sander 2025-02-03 21:28 6235
    if (BeginPopup("Copy"))
1193b4be Poul Sander 2025-02-03 21:28 6236
    {
1193b4be Poul Sander 2025-02-03 21:28 6237
        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 6238
        char buf[64];
1193b4be Poul Sander 2025-02-03 21:28 6239
        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 6240
        if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6241
            SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6242
        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
1193b4be Poul Sander 2025-02-03 21:28 6243
        if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6244
            SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6245
        ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
1193b4be Poul Sander 2025-02-03 21:28 6246
        if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6247
            SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6248
        if (!(flags & ImGuiColorEditFlags_NoAlpha))
1193b4be Poul Sander 2025-02-03 21:28 6249
        {
1193b4be Poul Sander 2025-02-03 21:28 6250
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
1193b4be Poul Sander 2025-02-03 21:28 6251
            if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6252
                SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6253
        }
1193b4be Poul Sander 2025-02-03 21:28 6254
        EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 6255
    }
1193b4be Poul Sander 2025-02-03 21:28 6256
1193b4be Poul Sander 2025-02-03 21:28 6257
    g.ColorEditOptions = opts;
1193b4be Poul Sander 2025-02-03 21:28 6258
    PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 6259
    EndPopup();
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
void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6263
{
1193b4be Poul Sander 2025-02-03 21:28 6264
    bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
1193b4be Poul Sander 2025-02-03 21:28 6265
    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
1193b4be Poul Sander 2025-02-03 21:28 6266
    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
1193b4be Poul Sander 2025-02-03 21:28 6267
        return;
1193b4be Poul Sander 2025-02-03 21:28 6268
1193b4be Poul Sander 2025-02-03 21:28 6269
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6270
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
1193b4be Poul Sander 2025-02-03 21:28 6271
    if (allow_opt_picker)
1193b4be Poul Sander 2025-02-03 21:28 6272
    {
1193b4be Poul Sander 2025-02-03 21:28 6273
        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 6274
        PushItemWidth(picker_size.x);
1193b4be Poul Sander 2025-02-03 21:28 6275
        for (int picker_type = 0; picker_type < 2; picker_type++)
1193b4be Poul Sander 2025-02-03 21:28 6276
        {
1193b4be Poul Sander 2025-02-03 21:28 6277
            // Draw small/thumbnail version of each picker type (over an invisible button for selection)
1193b4be Poul Sander 2025-02-03 21:28 6278
            if (picker_type > 0) Separator();
1193b4be Poul Sander 2025-02-03 21:28 6279
            PushID(picker_type);
1193b4be Poul Sander 2025-02-03 21:28 6280
            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
1193b4be Poul Sander 2025-02-03 21:28 6281
            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
1193b4be Poul Sander 2025-02-03 21:28 6282
            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
1193b4be Poul Sander 2025-02-03 21:28 6283
            ImVec2 backup_pos = GetCursorScreenPos();
1193b4be Poul Sander 2025-02-03 21:28 6284
            if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
1193b4be Poul Sander 2025-02-03 21:28 6285
                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
1193b4be Poul Sander 2025-02-03 21:28 6286
            SetCursorScreenPos(backup_pos);
1193b4be Poul Sander 2025-02-03 21:28 6287
            ImVec4 previewing_ref_col;
1193b4be Poul Sander 2025-02-03 21:28 6288
            memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
1193b4be Poul Sander 2025-02-03 21:28 6289
            ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
1193b4be Poul Sander 2025-02-03 21:28 6290
            PopID();
1193b4be Poul Sander 2025-02-03 21:28 6291
        }
1193b4be Poul Sander 2025-02-03 21:28 6292
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 6293
    }
1193b4be Poul Sander 2025-02-03 21:28 6294
    if (allow_opt_alpha_bar)
1193b4be Poul Sander 2025-02-03 21:28 6295
    {
1193b4be Poul Sander 2025-02-03 21:28 6296
        if (allow_opt_picker) Separator();
1193b4be Poul Sander 2025-02-03 21:28 6297
        CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
1193b4be Poul Sander 2025-02-03 21:28 6298
    }
1193b4be Poul Sander 2025-02-03 21:28 6299
    PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 6300
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 6301
}
1193b4be Poul Sander 2025-02-03 21:28 6302
1193b4be Poul Sander 2025-02-03 21:28 6303
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6304
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
1193b4be Poul Sander 2025-02-03 21:28 6305
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6306
// - TreeNode()
1193b4be Poul Sander 2025-02-03 21:28 6307
// - TreeNodeV()
1193b4be Poul Sander 2025-02-03 21:28 6308
// - TreeNodeEx()
1193b4be Poul Sander 2025-02-03 21:28 6309
// - TreeNodeExV()
1193b4be Poul Sander 2025-02-03 21:28 6310
// - TreeNodeBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 6311
// - TreePush()
1193b4be Poul Sander 2025-02-03 21:28 6312
// - TreePop()
1193b4be Poul Sander 2025-02-03 21:28 6313
// - GetTreeNodeToLabelSpacing()
1193b4be Poul Sander 2025-02-03 21:28 6314
// - SetNextItemOpen()
1193b4be Poul Sander 2025-02-03 21:28 6315
// - CollapsingHeader()
1193b4be Poul Sander 2025-02-03 21:28 6316
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6317
1193b4be Poul Sander 2025-02-03 21:28 6318
bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6319
{
1193b4be Poul Sander 2025-02-03 21:28 6320
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6321
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6322
    bool is_open = TreeNodeExV(str_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6323
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6324
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6325
}
1193b4be Poul Sander 2025-02-03 21:28 6326
1193b4be Poul Sander 2025-02-03 21:28 6327
bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6328
{
1193b4be Poul Sander 2025-02-03 21:28 6329
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6330
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6331
    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6332
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6333
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6334
}
1193b4be Poul Sander 2025-02-03 21:28 6335
1193b4be Poul Sander 2025-02-03 21:28 6336
bool ImGui::TreeNode(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 6337
{
1193b4be Poul Sander 2025-02-03 21:28 6338
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6339
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6340
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6341
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6342
    return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL);
1193b4be Poul Sander 2025-02-03 21:28 6343
}
1193b4be Poul Sander 2025-02-03 21:28 6344
1193b4be Poul Sander 2025-02-03 21:28 6345
bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6346
{
1193b4be Poul Sander 2025-02-03 21:28 6347
    return TreeNodeExV(str_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6348
}
1193b4be Poul Sander 2025-02-03 21:28 6349
1193b4be Poul Sander 2025-02-03 21:28 6350
bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6351
{
1193b4be Poul Sander 2025-02-03 21:28 6352
    return TreeNodeExV(ptr_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6353
}
1193b4be Poul Sander 2025-02-03 21:28 6354
1193b4be Poul Sander 2025-02-03 21:28 6355
bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6356
{
1193b4be Poul Sander 2025-02-03 21:28 6357
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6358
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6359
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6360
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6361
    return TreeNodeBehavior(id, flags, label, NULL);
1193b4be Poul Sander 2025-02-03 21:28 6362
}
1193b4be Poul Sander 2025-02-03 21:28 6363
1193b4be Poul Sander 2025-02-03 21:28 6364
bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6365
{
1193b4be Poul Sander 2025-02-03 21:28 6366
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6367
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6368
    bool is_open = TreeNodeExV(str_id, flags, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6369
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6370
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6371
}
1193b4be Poul Sander 2025-02-03 21:28 6372
1193b4be Poul Sander 2025-02-03 21:28 6373
bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6374
{
1193b4be Poul Sander 2025-02-03 21:28 6375
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6376
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6377
    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6378
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6379
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6380
}
1193b4be Poul Sander 2025-02-03 21:28 6381
1193b4be Poul Sander 2025-02-03 21:28 6382
bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6383
{
1193b4be Poul Sander 2025-02-03 21:28 6384
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6385
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6386
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6387
1193b4be Poul Sander 2025-02-03 21:28 6388
    ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 6389
    const char* label, *label_end;
1193b4be Poul Sander 2025-02-03 21:28 6390
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6391
    return TreeNodeBehavior(id, flags, label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 6392
}
1193b4be Poul Sander 2025-02-03 21:28 6393
1193b4be Poul Sander 2025-02-03 21:28 6394
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6395
{
1193b4be Poul Sander 2025-02-03 21:28 6396
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6397
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6398
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6399
1193b4be Poul Sander 2025-02-03 21:28 6400
    ImGuiID id = window->GetID(ptr_id);
1193b4be Poul Sander 2025-02-03 21:28 6401
    const char* label, *label_end;
1193b4be Poul Sander 2025-02-03 21:28 6402
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6403
    return TreeNodeBehavior(id, flags, label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 6404
}
1193b4be Poul Sander 2025-02-03 21:28 6405
1193b4be Poul Sander 2025-02-03 21:28 6406
bool ImGui::TreeNodeGetOpen(ImGuiID storage_id)
1193b4be Poul Sander 2025-02-03 21:28 6407
{
1193b4be Poul Sander 2025-02-03 21:28 6408
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6409
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
1193b4be Poul Sander 2025-02-03 21:28 6410
    return storage->GetInt(storage_id, 0) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6411
}
1193b4be Poul Sander 2025-02-03 21:28 6412
1193b4be Poul Sander 2025-02-03 21:28 6413
void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open)
1193b4be Poul Sander 2025-02-03 21:28 6414
{
1193b4be Poul Sander 2025-02-03 21:28 6415
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6416
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
1193b4be Poul Sander 2025-02-03 21:28 6417
    storage->SetInt(storage_id, open ? 1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 6418
}
1193b4be Poul Sander 2025-02-03 21:28 6419
1193b4be Poul Sander 2025-02-03 21:28 6420
bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6421
{
1193b4be Poul Sander 2025-02-03 21:28 6422
    if (flags & ImGuiTreeNodeFlags_Leaf)
1193b4be Poul Sander 2025-02-03 21:28 6423
        return true;
1193b4be Poul Sander 2025-02-03 21:28 6424
1193b4be Poul Sander 2025-02-03 21:28 6425
    // 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 6426
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6427
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 6428
    ImGuiStorage* storage = window->DC.StateStorage;
1193b4be Poul Sander 2025-02-03 21:28 6429
1193b4be Poul Sander 2025-02-03 21:28 6430
    bool is_open;
1193b4be Poul Sander 2025-02-03 21:28 6431
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen)
1193b4be Poul Sander 2025-02-03 21:28 6432
    {
1193b4be Poul Sander 2025-02-03 21:28 6433
        if (g.NextItemData.OpenCond & ImGuiCond_Always)
1193b4be Poul Sander 2025-02-03 21:28 6434
        {
1193b4be Poul Sander 2025-02-03 21:28 6435
            is_open = g.NextItemData.OpenVal;
1193b4be Poul Sander 2025-02-03 21:28 6436
            TreeNodeSetOpen(storage_id, is_open);
1193b4be Poul Sander 2025-02-03 21:28 6437
        }
1193b4be Poul Sander 2025-02-03 21:28 6438
        else
1193b4be Poul Sander 2025-02-03 21:28 6439
        {
1193b4be Poul Sander 2025-02-03 21:28 6440
            // 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 6441
            const int stored_value = storage->GetInt(storage_id, -1);
1193b4be Poul Sander 2025-02-03 21:28 6442
            if (stored_value == -1)
1193b4be Poul Sander 2025-02-03 21:28 6443
            {
1193b4be Poul Sander 2025-02-03 21:28 6444
                is_open = g.NextItemData.OpenVal;
1193b4be Poul Sander 2025-02-03 21:28 6445
                TreeNodeSetOpen(storage_id, is_open);
1193b4be Poul Sander 2025-02-03 21:28 6446
            }
1193b4be Poul Sander 2025-02-03 21:28 6447
            else
1193b4be Poul Sander 2025-02-03 21:28 6448
            {
1193b4be Poul Sander 2025-02-03 21:28 6449
                is_open = stored_value != 0;
1193b4be Poul Sander 2025-02-03 21:28 6450
            }
1193b4be Poul Sander 2025-02-03 21:28 6451
        }
1193b4be Poul Sander 2025-02-03 21:28 6452
    }
1193b4be Poul Sander 2025-02-03 21:28 6453
    else
1193b4be Poul Sander 2025-02-03 21:28 6454
    {
1193b4be Poul Sander 2025-02-03 21:28 6455
        is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6456
    }
1193b4be Poul Sander 2025-02-03 21:28 6457
1193b4be Poul Sander 2025-02-03 21:28 6458
    // 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 6459
    // NB- If we are above max depth we still allow manually opened nodes to be logged.
1193b4be Poul Sander 2025-02-03 21:28 6460
    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
1193b4be Poul Sander 2025-02-03 21:28 6461
        is_open = true;
1193b4be Poul Sander 2025-02-03 21:28 6462
1193b4be Poul Sander 2025-02-03 21:28 6463
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6464
}
1193b4be Poul Sander 2025-02-03 21:28 6465
1193b4be Poul Sander 2025-02-03 21:28 6466
// Store ImGuiTreeNodeStackData for just submitted node.
1193b4be Poul Sander 2025-02-03 21:28 6467
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
1193b4be Poul Sander 2025-02-03 21:28 6468
static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6469
{
1193b4be Poul Sander 2025-02-03 21:28 6470
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6471
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 6472
1193b4be Poul Sander 2025-02-03 21:28 6473
    g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
1193b4be Poul Sander 2025-02-03 21:28 6474
    ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back();
1193b4be Poul Sander 2025-02-03 21:28 6475
    tree_node_data->ID = g.LastItemData.ID;
1193b4be Poul Sander 2025-02-03 21:28 6476
    tree_node_data->TreeFlags = flags;
1193b4be Poul Sander 2025-02-03 21:28 6477
    tree_node_data->ItemFlags = g.LastItemData.ItemFlags;
1193b4be Poul Sander 2025-02-03 21:28 6478
    tree_node_data->NavRect = g.LastItemData.NavRect;
1193b4be Poul Sander 2025-02-03 21:28 6479
    window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
1193b4be Poul Sander 2025-02-03 21:28 6480
}
1193b4be Poul Sander 2025-02-03 21:28 6481
1193b4be Poul Sander 2025-02-03 21:28 6482
// 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 6483
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
1193b4be Poul Sander 2025-02-03 21:28 6484
{
1193b4be Poul Sander 2025-02-03 21:28 6485
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6486
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6487
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6488
1193b4be Poul Sander 2025-02-03 21:28 6489
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6490
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 6491
    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6492
    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 6493
1193b4be Poul Sander 2025-02-03 21:28 6494
    if (!label_end)
1193b4be Poul Sander 2025-02-03 21:28 6495
        label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 6496
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1193b4be Poul Sander 2025-02-03 21:28 6497
1193b4be Poul Sander 2025-02-03 21:28 6498
    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 6499
    const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);            // Latch before ItemSize changes it
1193b4be Poul Sander 2025-02-03 21:28 6500
    const float text_width = g.FontSize + label_size.x + padding.x * 2;                         // Include collapsing arrow
1193b4be Poul Sander 2025-02-03 21:28 6501
1193b4be Poul Sander 2025-02-03 21:28 6502
    // We vertically grow up to current line height up the typical widget height.
1193b4be Poul Sander 2025-02-03 21:28 6503
    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 6504
    const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
1193b4be Poul Sander 2025-02-03 21:28 6505
    ImRect frame_bb;
1193b4be Poul Sander 2025-02-03 21:28 6506
    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 6507
    frame_bb.Min.y = window->DC.CursorPos.y;
1193b4be Poul Sander 2025-02-03 21:28 6508
    frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6509
    frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
1193b4be Poul Sander 2025-02-03 21:28 6510
    if (display_frame)
1193b4be Poul Sander 2025-02-03 21:28 6511
    {
1193b4be Poul Sander 2025-02-03 21:28 6512
        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 6513
        frame_bb.Min.x -= outer_extend;
1193b4be Poul Sander 2025-02-03 21:28 6514
        frame_bb.Max.x += outer_extend;
1193b4be Poul Sander 2025-02-03 21:28 6515
    }
1193b4be Poul Sander 2025-02-03 21:28 6516
1193b4be Poul Sander 2025-02-03 21:28 6517
    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 6518
    ItemSize(ImVec2(text_width, frame_height), padding.y);
1193b4be Poul Sander 2025-02-03 21:28 6519
1193b4be Poul Sander 2025-02-03 21:28 6520
    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
1193b4be Poul Sander 2025-02-03 21:28 6521
    ImRect interact_bb = frame_bb;
1193b4be Poul Sander 2025-02-03 21:28 6522
    if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6523
        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 6524
1193b4be Poul Sander 2025-02-03 21:28 6525
    // Compute open and multi-select states before ItemAdd() as it clear NextItem data.
1193b4be Poul Sander 2025-02-03 21:28 6526
    ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id;
1193b4be Poul Sander 2025-02-03 21:28 6527
    bool is_open = TreeNodeUpdateNextOpen(storage_id, flags);
1193b4be Poul Sander 2025-02-03 21:28 6528
1193b4be Poul Sander 2025-02-03 21:28 6529
    bool is_visible;
1193b4be Poul Sander 2025-02-03 21:28 6530
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 6531
    {
1193b4be Poul Sander 2025-02-03 21:28 6532
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
1193b4be Poul Sander 2025-02-03 21:28 6533
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 6534
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6535
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 6536
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6537
        is_visible = ItemAdd(interact_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 6538
        window->ClipRect.Min.x = backup_clip_rect_min_x;
1193b4be Poul Sander 2025-02-03 21:28 6539
        window->ClipRect.Max.x = backup_clip_rect_max_x;
1193b4be Poul Sander 2025-02-03 21:28 6540
    }
1193b4be Poul Sander 2025-02-03 21:28 6541
    else
1193b4be Poul Sander 2025-02-03 21:28 6542
    {
1193b4be Poul Sander 2025-02-03 21:28 6543
        is_visible = ItemAdd(interact_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 6544
    }
1193b4be Poul Sander 2025-02-03 21:28 6545
    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
1193b4be Poul Sander 2025-02-03 21:28 6546
    g.LastItemData.DisplayRect = frame_bb;
1193b4be Poul Sander 2025-02-03 21:28 6547
1193b4be Poul Sander 2025-02-03 21:28 6548
    // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled:
1193b4be Poul Sander 2025-02-03 21:28 6549
    // Store data for the current depth to allow returning to this node from any child item.
1193b4be Poul Sander 2025-02-03 21:28 6550
    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
1193b4be Poul Sander 2025-02-03 21:28 6551
    // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.
1193b4be Poul Sander 2025-02-03 21:28 6552
    bool store_tree_node_stack_data = false;
1193b4be Poul Sander 2025-02-03 21:28 6553
    if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
1193b4be Poul Sander 2025-02-03 21:28 6554
    {
1193b4be Poul Sander 2025-02-03 21:28 6555
        if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive)
1193b4be Poul Sander 2025-02-03 21:28 6556
            if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
1193b4be Poul Sander 2025-02-03 21:28 6557
                store_tree_node_stack_data = true;
1193b4be Poul Sander 2025-02-03 21:28 6558
    }
1193b4be Poul Sander 2025-02-03 21:28 6559
1193b4be Poul Sander 2025-02-03 21:28 6560
    const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6561
    if (!is_visible)
1193b4be Poul Sander 2025-02-03 21:28 6562
    {
1193b4be Poul Sander 2025-02-03 21:28 6563
        if (store_tree_node_stack_data && is_open)
1193b4be Poul Sander 2025-02-03 21:28 6564
            TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
1193b4be Poul Sander 2025-02-03 21:28 6565
        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
1193b4be Poul Sander 2025-02-03 21:28 6566
            TreePushOverrideID(id);
1193b4be Poul Sander 2025-02-03 21:28 6567
        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 6568
        return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6569
    }
1193b4be Poul Sander 2025-02-03 21:28 6570
1193b4be Poul Sander 2025-02-03 21:28 6571
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 6572
    {
1193b4be Poul Sander 2025-02-03 21:28 6573
        TablePushBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 6574
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
1193b4be Poul Sander 2025-02-03 21:28 6575
        g.LastItemData.ClipRect = window->ClipRect;
1193b4be Poul Sander 2025-02-03 21:28 6576
    }
1193b4be Poul Sander 2025-02-03 21:28 6577
1193b4be Poul Sander 2025-02-03 21:28 6578
    ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
1193b4be Poul Sander 2025-02-03 21:28 6579
    if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap))
1193b4be Poul Sander 2025-02-03 21:28 6580
        button_flags |= ImGuiButtonFlags_AllowOverlap;
1193b4be Poul Sander 2025-02-03 21:28 6581
    if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6582
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
1193b4be Poul Sander 2025-02-03 21:28 6583
1193b4be Poul Sander 2025-02-03 21:28 6584
    // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
1193b4be Poul Sander 2025-02-03 21:28 6585
    // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
1193b4be Poul Sander 2025-02-03 21:28 6586
    // When clicking on the rest of the tree node we always disallow keyboard modifiers.
1193b4be Poul Sander 2025-02-03 21:28 6587
    const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
1193b4be Poul Sander 2025-02-03 21:28 6588
    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 6589
    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 6590
1193b4be Poul Sander 2025-02-03 21:28 6591
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6592
    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 6593
        flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow;
1193b4be Poul Sander 2025-02-03 21:28 6594
1193b4be Poul Sander 2025-02-03 21:28 6595
    // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
1193b4be Poul Sander 2025-02-03 21:28 6596
    // 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 6597
    // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
1193b4be Poul Sander 2025-02-03 21:28 6598
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
1193b4be Poul Sander 2025-02-03 21:28 6599
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
1193b4be Poul Sander 2025-02-03 21:28 6600
    // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
1193b4be Poul Sander 2025-02-03 21:28 6601
    // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
1193b4be Poul Sander 2025-02-03 21:28 6602
    // It is rather standard that arrow click react on Down rather than Up.
1193b4be Poul Sander 2025-02-03 21:28 6603
    // 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 6604
    if (is_mouse_x_over_arrow)
1193b4be Poul Sander 2025-02-03 21:28 6605
        button_flags |= ImGuiButtonFlags_PressedOnClick;
1193b4be Poul Sander 2025-02-03 21:28 6606
    else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
1193b4be Poul Sander 2025-02-03 21:28 6607
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
1193b4be Poul Sander 2025-02-03 21:28 6608
    else
1193b4be Poul Sander 2025-02-03 21:28 6609
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 6610
1193b4be Poul Sander 2025-02-03 21:28 6611
    bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6612
    const bool was_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 6613
1193b4be Poul Sander 2025-02-03 21:28 6614
    // Multi-selection support (header)
1193b4be Poul Sander 2025-02-03 21:28 6615
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6616
    {
1193b4be Poul Sander 2025-02-03 21:28 6617
        // Handle multi-select + alter button flags for it
1193b4be Poul Sander 2025-02-03 21:28 6618
        MultiSelectItemHeader(id, &selected, &button_flags);
1193b4be Poul Sander 2025-02-03 21:28 6619
        if (is_mouse_x_over_arrow)
1193b4be Poul Sander 2025-02-03 21:28 6620
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 6621
    }
1193b4be Poul Sander 2025-02-03 21:28 6622
    else
1193b4be Poul Sander 2025-02-03 21:28 6623
    {
1193b4be Poul Sander 2025-02-03 21:28 6624
        if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
1193b4be Poul Sander 2025-02-03 21:28 6625
            button_flags |= ImGuiButtonFlags_NoKeyModsAllowed;
1193b4be Poul Sander 2025-02-03 21:28 6626
    }
1193b4be Poul Sander 2025-02-03 21:28 6627
1193b4be Poul Sander 2025-02-03 21:28 6628
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 6629
    bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 6630
    bool toggled = false;
1193b4be Poul Sander 2025-02-03 21:28 6631
    if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6632
    {
1193b4be Poul Sander 2025-02-03 21:28 6633
        if (pressed && g.DragDropHoldJustPressedId != id)
1193b4be Poul Sander 2025-02-03 21:28 6634
        {
1193b4be Poul Sander 2025-02-03 21:28 6635
            if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select))
1193b4be Poul Sander 2025-02-03 21:28 6636
                toggled = true; // Single click
1193b4be Poul Sander 2025-02-03 21:28 6637
            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
1193b4be Poul Sander 2025-02-03 21:28 6638
                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 6639
            if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)
1193b4be Poul Sander 2025-02-03 21:28 6640
                toggled = true; // Double click
1193b4be Poul Sander 2025-02-03 21:28 6641
        }
1193b4be Poul Sander 2025-02-03 21:28 6642
        else if (pressed && g.DragDropHoldJustPressedId == id)
1193b4be Poul Sander 2025-02-03 21:28 6643
        {
1193b4be Poul Sander 2025-02-03 21:28 6644
            IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
1193b4be Poul Sander 2025-02-03 21:28 6645
            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 6646
                toggled = true;
1193b4be Poul Sander 2025-02-03 21:28 6647
            else
1193b4be Poul Sander 2025-02-03 21:28 6648
                pressed = false; // Cancel press so it doesn't trigger selection.
1193b4be Poul Sander 2025-02-03 21:28 6649
        }
1193b4be Poul Sander 2025-02-03 21:28 6650
1193b4be Poul Sander 2025-02-03 21:28 6651
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
1193b4be Poul Sander 2025-02-03 21:28 6652
        {
1193b4be Poul Sander 2025-02-03 21:28 6653
            toggled = true;
1193b4be Poul Sander 2025-02-03 21:28 6654
            NavClearPreferredPosForAxis(ImGuiAxis_X);
1193b4be Poul Sander 2025-02-03 21:28 6655
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 6656
        }
1193b4be Poul Sander 2025-02-03 21:28 6657
        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 6658
        {
1193b4be Poul Sander 2025-02-03 21:28 6659
            toggled = true;
1193b4be Poul Sander 2025-02-03 21:28 6660
            NavClearPreferredPosForAxis(ImGuiAxis_X);
1193b4be Poul Sander 2025-02-03 21:28 6661
            NavMoveRequestCancel();
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
        if (toggled)
1193b4be Poul Sander 2025-02-03 21:28 6665
        {
1193b4be Poul Sander 2025-02-03 21:28 6666
            is_open = !is_open;
1193b4be Poul Sander 2025-02-03 21:28 6667
            window->DC.StateStorage->SetInt(storage_id, is_open);
1193b4be Poul Sander 2025-02-03 21:28 6668
            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
1193b4be Poul Sander 2025-02-03 21:28 6669
        }
1193b4be Poul Sander 2025-02-03 21:28 6670
    }
1193b4be Poul Sander 2025-02-03 21:28 6671
1193b4be Poul Sander 2025-02-03 21:28 6672
    // Multi-selection support (footer)
1193b4be Poul Sander 2025-02-03 21:28 6673
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6674
    {
1193b4be Poul Sander 2025-02-03 21:28 6675
        bool pressed_copy = pressed && !toggled;
1193b4be Poul Sander 2025-02-03 21:28 6676
        MultiSelectItemFooter(id, &selected, &pressed_copy);
1193b4be Poul Sander 2025-02-03 21:28 6677
        if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 6678
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
1193b4be Poul Sander 2025-02-03 21:28 6679
    }
1193b4be Poul Sander 2025-02-03 21:28 6680
1193b4be Poul Sander 2025-02-03 21:28 6681
    if (selected != was_selected)
1193b4be Poul Sander 2025-02-03 21:28 6682
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
1193b4be Poul Sander 2025-02-03 21:28 6683
1193b4be Poul Sander 2025-02-03 21:28 6684
    // Render
1193b4be Poul Sander 2025-02-03 21:28 6685
    {
1193b4be Poul Sander 2025-02-03 21:28 6686
        const ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 6687
        ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact;
1193b4be Poul Sander 2025-02-03 21:28 6688
        if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6689
            nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
1193b4be Poul Sander 2025-02-03 21:28 6690
        if (display_frame)
1193b4be Poul Sander 2025-02-03 21:28 6691
        {
1193b4be Poul Sander 2025-02-03 21:28 6692
            // Framed type
1193b4be Poul Sander 2025-02-03 21:28 6693
            const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
1193b4be Poul Sander 2025-02-03 21:28 6694
            RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 6695
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
1193b4be Poul Sander 2025-02-03 21:28 6696
            if (flags & ImGuiTreeNodeFlags_Bullet)
1193b4be Poul Sander 2025-02-03 21:28 6697
                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 6698
            else if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6699
                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 6700
            else // Leaf without bullet, left-adjusted text
1193b4be Poul Sander 2025-02-03 21:28 6701
                text_pos.x -= text_offset_x - padding.x;
1193b4be Poul Sander 2025-02-03 21:28 6702
            if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
1193b4be Poul Sander 2025-02-03 21:28 6703
                frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
1193b4be Poul Sander 2025-02-03 21:28 6704
            if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 6705
                LogSetNextTextDecoration("###", "###");
1193b4be Poul Sander 2025-02-03 21:28 6706
        }
1193b4be Poul Sander 2025-02-03 21:28 6707
        else
1193b4be Poul Sander 2025-02-03 21:28 6708
        {
1193b4be Poul Sander 2025-02-03 21:28 6709
            // Unframed typed for tree nodes
1193b4be Poul Sander 2025-02-03 21:28 6710
            if (hovered || selected)
1193b4be Poul Sander 2025-02-03 21:28 6711
            {
1193b4be Poul Sander 2025-02-03 21:28 6712
                const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
1193b4be Poul Sander 2025-02-03 21:28 6713
                RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
1193b4be Poul Sander 2025-02-03 21:28 6714
            }
1193b4be Poul Sander 2025-02-03 21:28 6715
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
1193b4be Poul Sander 2025-02-03 21:28 6716
            if (flags & ImGuiTreeNodeFlags_Bullet)
1193b4be Poul Sander 2025-02-03 21:28 6717
                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 6718
            else if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6719
                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 6720
            if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 6721
                LogSetNextTextDecoration(">", NULL);
1193b4be Poul Sander 2025-02-03 21:28 6722
        }
1193b4be Poul Sander 2025-02-03 21:28 6723
1193b4be Poul Sander 2025-02-03 21:28 6724
        if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 6725
            TablePopBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 6726
1193b4be Poul Sander 2025-02-03 21:28 6727
        // Label
1193b4be Poul Sander 2025-02-03 21:28 6728
        if (display_frame)
1193b4be Poul Sander 2025-02-03 21:28 6729
            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
1193b4be Poul Sander 2025-02-03 21:28 6730
        else
1193b4be Poul Sander 2025-02-03 21:28 6731
            RenderText(text_pos, label, label_end, false);
1193b4be Poul Sander 2025-02-03 21:28 6732
    }
1193b4be Poul Sander 2025-02-03 21:28 6733
1193b4be Poul Sander 2025-02-03 21:28 6734
    if (store_tree_node_stack_data && is_open)
1193b4be Poul Sander 2025-02-03 21:28 6735
        TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
1193b4be Poul Sander 2025-02-03 21:28 6736
    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
1193b4be Poul Sander 2025-02-03 21:28 6737
        TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice
1193b4be Poul Sander 2025-02-03 21:28 6738
1193b4be Poul Sander 2025-02-03 21:28 6739
    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 6740
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6741
}
1193b4be Poul Sander 2025-02-03 21:28 6742
1193b4be Poul Sander 2025-02-03 21:28 6743
void ImGui::TreePush(const char* str_id)
1193b4be Poul Sander 2025-02-03 21:28 6744
{
1193b4be Poul Sander 2025-02-03 21:28 6745
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6746
    Indent();
1193b4be Poul Sander 2025-02-03 21:28 6747
    window->DC.TreeDepth++;
1193b4be Poul Sander 2025-02-03 21:28 6748
    PushID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 6749
}
1193b4be Poul Sander 2025-02-03 21:28 6750
1193b4be Poul Sander 2025-02-03 21:28 6751
void ImGui::TreePush(const void* ptr_id)
1193b4be Poul Sander 2025-02-03 21:28 6752
{
1193b4be Poul Sander 2025-02-03 21:28 6753
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6754
    Indent();
1193b4be Poul Sander 2025-02-03 21:28 6755
    window->DC.TreeDepth++;
1193b4be Poul Sander 2025-02-03 21:28 6756
    PushID(ptr_id);
1193b4be Poul Sander 2025-02-03 21:28 6757
}
1193b4be Poul Sander 2025-02-03 21:28 6758
1193b4be Poul Sander 2025-02-03 21:28 6759
void ImGui::TreePushOverrideID(ImGuiID id)
1193b4be Poul Sander 2025-02-03 21:28 6760
{
1193b4be Poul Sander 2025-02-03 21:28 6761
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6762
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 6763
    Indent();
1193b4be Poul Sander 2025-02-03 21:28 6764
    window->DC.TreeDepth++;
1193b4be Poul Sander 2025-02-03 21:28 6765
    PushOverrideID(id);
1193b4be Poul Sander 2025-02-03 21:28 6766
}
1193b4be Poul Sander 2025-02-03 21:28 6767
1193b4be Poul Sander 2025-02-03 21:28 6768
void ImGui::TreePop()
1193b4be Poul Sander 2025-02-03 21:28 6769
{
1193b4be Poul Sander 2025-02-03 21:28 6770
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6771
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 6772
    Unindent();
1193b4be Poul Sander 2025-02-03 21:28 6773
1193b4be Poul Sander 2025-02-03 21:28 6774
    window->DC.TreeDepth--;
1193b4be Poul Sander 2025-02-03 21:28 6775
    ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
1193b4be Poul Sander 2025-02-03 21:28 6776
1193b4be Poul Sander 2025-02-03 21:28 6777
    if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request
1193b4be Poul Sander 2025-02-03 21:28 6778
    {
1193b4be Poul Sander 2025-02-03 21:28 6779
        ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back();
1193b4be Poul Sander 2025-02-03 21:28 6780
        IM_ASSERT(data->ID == window->IDStack.back());
1193b4be Poul Sander 2025-02-03 21:28 6781
        if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere)
1193b4be Poul Sander 2025-02-03 21:28 6782
        {
1193b4be Poul Sander 2025-02-03 21:28 6783
            // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
1193b4be Poul Sander 2025-02-03 21:28 6784
            if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
1193b4be Poul Sander 2025-02-03 21:28 6785
                NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
1193b4be Poul Sander 2025-02-03 21:28 6786
        }
1193b4be Poul Sander 2025-02-03 21:28 6787
        g.TreeNodeStack.pop_back();
1193b4be Poul Sander 2025-02-03 21:28 6788
        window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
1193b4be Poul Sander 2025-02-03 21:28 6789
    }
1193b4be Poul Sander 2025-02-03 21:28 6790
1193b4be Poul Sander 2025-02-03 21:28 6791
    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 6792
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 6793
}
1193b4be Poul Sander 2025-02-03 21:28 6794
1193b4be Poul Sander 2025-02-03 21:28 6795
// Horizontal distance preceding label when using TreeNode() or Bullet()
1193b4be Poul Sander 2025-02-03 21:28 6796
float ImGui::GetTreeNodeToLabelSpacing()
1193b4be Poul Sander 2025-02-03 21:28 6797
{
1193b4be Poul Sander 2025-02-03 21:28 6798
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6799
    return g.FontSize + (g.Style.FramePadding.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 6800
}
1193b4be Poul Sander 2025-02-03 21:28 6801
1193b4be Poul Sander 2025-02-03 21:28 6802
// Set next TreeNode/CollapsingHeader open state.
1193b4be Poul Sander 2025-02-03 21:28 6803
void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
1193b4be Poul Sander 2025-02-03 21:28 6804
{
1193b4be Poul Sander 2025-02-03 21:28 6805
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6806
    if (g.CurrentWindow->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6807
        return;
1193b4be Poul Sander 2025-02-03 21:28 6808
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen;
1193b4be Poul Sander 2025-02-03 21:28 6809
    g.NextItemData.OpenVal = is_open;
1193b4be Poul Sander 2025-02-03 21:28 6810
    g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always);
1193b4be Poul Sander 2025-02-03 21:28 6811
}
1193b4be Poul Sander 2025-02-03 21:28 6812
1193b4be Poul Sander 2025-02-03 21:28 6813
// Set next TreeNode/CollapsingHeader storage id.
1193b4be Poul Sander 2025-02-03 21:28 6814
void ImGui::SetNextItemStorageID(ImGuiID storage_id)
1193b4be Poul Sander 2025-02-03 21:28 6815
{
1193b4be Poul Sander 2025-02-03 21:28 6816
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6817
    if (g.CurrentWindow->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6818
        return;
1193b4be Poul Sander 2025-02-03 21:28 6819
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID;
1193b4be Poul Sander 2025-02-03 21:28 6820
    g.NextItemData.StorageId = storage_id;
1193b4be Poul Sander 2025-02-03 21:28 6821
}
1193b4be Poul Sander 2025-02-03 21:28 6822
1193b4be Poul Sander 2025-02-03 21:28 6823
// 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 6824
// 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 6825
bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6826
{
1193b4be Poul Sander 2025-02-03 21:28 6827
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6828
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6829
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6830
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6831
    return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
1193b4be Poul Sander 2025-02-03 21:28 6832
}
1193b4be Poul Sander 2025-02-03 21:28 6833
1193b4be Poul Sander 2025-02-03 21:28 6834
// p_visible == NULL                        : regular collapsing header
1193b4be Poul Sander 2025-02-03 21:28 6835
// 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 6836
// p_visible != NULL && *p_visible == false : do not show the header at all
1193b4be Poul Sander 2025-02-03 21:28 6837
// 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 6838
bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6839
{
1193b4be Poul Sander 2025-02-03 21:28 6840
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6841
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6842
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6843
1193b4be Poul Sander 2025-02-03 21:28 6844
    if (p_visible && !*p_visible)
1193b4be Poul Sander 2025-02-03 21:28 6845
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6846
1193b4be Poul Sander 2025-02-03 21:28 6847
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6848
    flags |= ImGuiTreeNodeFlags_CollapsingHeader;
1193b4be Poul Sander 2025-02-03 21:28 6849
    if (p_visible)
1193b4be Poul Sander 2025-02-03 21:28 6850
        flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
1193b4be Poul Sander 2025-02-03 21:28 6851
    bool is_open = TreeNodeBehavior(id, flags, label);
1193b4be Poul Sander 2025-02-03 21:28 6852
    if (p_visible != NULL)
1193b4be Poul Sander 2025-02-03 21:28 6853
    {
1193b4be Poul Sander 2025-02-03 21:28 6854
        // Create a small overlapping close button
1193b4be Poul Sander 2025-02-03 21:28 6855
        // 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 6856
        // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
1193b4be Poul Sander 2025-02-03 21:28 6857
        ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6858
        ImGuiLastItemData last_item_backup = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 6859
        float button_size = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 6860
        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 6861
        float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 6862
        ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
1193b4be Poul Sander 2025-02-03 21:28 6863
        if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
1193b4be Poul Sander 2025-02-03 21:28 6864
            *p_visible = false;
1193b4be Poul Sander 2025-02-03 21:28 6865
        g.LastItemData = last_item_backup;
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
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6869
}
1193b4be Poul Sander 2025-02-03 21:28 6870
1193b4be Poul Sander 2025-02-03 21:28 6871
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6872
// [SECTION] Widgets: Selectable
1193b4be Poul Sander 2025-02-03 21:28 6873
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6874
// - Selectable()
1193b4be Poul Sander 2025-02-03 21:28 6875
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6876
1193b4be Poul Sander 2025-02-03 21:28 6877
// 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 6878
// 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 6879
// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
1193b4be Poul Sander 2025-02-03 21:28 6880
// 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 6881
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 6882
{
1193b4be Poul Sander 2025-02-03 21:28 6883
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6884
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6885
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6886
1193b4be Poul Sander 2025-02-03 21:28 6887
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6888
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 6889
1193b4be Poul Sander 2025-02-03 21:28 6890
    // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
1193b4be Poul Sander 2025-02-03 21:28 6891
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6892
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 6893
    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 6894
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 6895
    pos.y += window->DC.CurrLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 6896
    ItemSize(size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6897
1193b4be Poul Sander 2025-02-03 21:28 6898
    // Fill horizontal space
1193b4be Poul Sander 2025-02-03 21:28 6899
    // 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 6900
    const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6901
    const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
1193b4be Poul Sander 2025-02-03 21:28 6902
    const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6903
    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
1193b4be Poul Sander 2025-02-03 21:28 6904
        size.x = ImMax(label_size.x, max_x - min_x);
1193b4be Poul Sander 2025-02-03 21:28 6905
1193b4be Poul Sander 2025-02-03 21:28 6906
    // Text stays at the submission position, but bounding box may be extended on both sides
1193b4be Poul Sander 2025-02-03 21:28 6907
    const ImVec2 text_min = pos;
1193b4be Poul Sander 2025-02-03 21:28 6908
    const ImVec2 text_max(min_x + size.x, pos.y + size.y);
1193b4be Poul Sander 2025-02-03 21:28 6909
1193b4be Poul Sander 2025-02-03 21:28 6910
    // 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 6911
    // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos.
1193b4be Poul Sander 2025-02-03 21:28 6912
    ImRect bb(min_x, pos.y, text_max.x, text_max.y);
1193b4be Poul Sander 2025-02-03 21:28 6913
    if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6914
    {
1193b4be Poul Sander 2025-02-03 21:28 6915
        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 6916
        const float spacing_y = style.ItemSpacing.y;
1193b4be Poul Sander 2025-02-03 21:28 6917
        const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
1193b4be Poul Sander 2025-02-03 21:28 6918
        const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
1193b4be Poul Sander 2025-02-03 21:28 6919
        bb.Min.x -= spacing_L;
1193b4be Poul Sander 2025-02-03 21:28 6920
        bb.Min.y -= spacing_U;
1193b4be Poul Sander 2025-02-03 21:28 6921
        bb.Max.x += (spacing_x - spacing_L);
1193b4be Poul Sander 2025-02-03 21:28 6922
        bb.Max.y += (spacing_y - spacing_U);
1193b4be Poul Sander 2025-02-03 21:28 6923
    }
1193b4be Poul Sander 2025-02-03 21:28 6924
    //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
1193b4be Poul Sander 2025-02-03 21:28 6925
1193b4be Poul Sander 2025-02-03 21:28 6926
    const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6927
    const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None;
1193b4be Poul Sander 2025-02-03 21:28 6928
    bool is_visible;
1193b4be Poul Sander 2025-02-03 21:28 6929
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 6930
    {
1193b4be Poul Sander 2025-02-03 21:28 6931
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
1193b4be Poul Sander 2025-02-03 21:28 6932
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 6933
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6934
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 6935
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6936
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
1193b4be Poul Sander 2025-02-03 21:28 6937
        window->ClipRect.Min.x = backup_clip_rect_min_x;
1193b4be Poul Sander 2025-02-03 21:28 6938
        window->ClipRect.Max.x = backup_clip_rect_max_x;
1193b4be Poul Sander 2025-02-03 21:28 6939
    }
1193b4be Poul Sander 2025-02-03 21:28 6940
    else
1193b4be Poul Sander 2025-02-03 21:28 6941
    {
1193b4be Poul Sander 2025-02-03 21:28 6942
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
1193b4be Poul Sander 2025-02-03 21:28 6943
    }
1193b4be Poul Sander 2025-02-03 21:28 6944
1193b4be Poul Sander 2025-02-03 21:28 6945
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6946
    if (!is_visible)
1193b4be Poul Sander 2025-02-03 21:28 6947
        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 6948
            return false;
1193b4be Poul Sander 2025-02-03 21:28 6949
1193b4be Poul Sander 2025-02-03 21:28 6950
    const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6951
    if (disabled_item && !disabled_global) // Only testing this as an optimization
1193b4be Poul Sander 2025-02-03 21:28 6952
        BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 6953
1193b4be Poul Sander 2025-02-03 21:28 6954
    // 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 6955
    // which would be advantageous since most selectable are not selected.
1193b4be Poul Sander 2025-02-03 21:28 6956
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 6957
    {
1193b4be Poul Sander 2025-02-03 21:28 6958
        if (g.CurrentTable)
1193b4be Poul Sander 2025-02-03 21:28 6959
            TablePushBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 6960
        else if (window->DC.CurrentColumns)
1193b4be Poul Sander 2025-02-03 21:28 6961
            PushColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 6962
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
1193b4be Poul Sander 2025-02-03 21:28 6963
        g.LastItemData.ClipRect = window->ClipRect;
1193b4be Poul Sander 2025-02-03 21:28 6964
    }
1193b4be Poul Sander 2025-02-03 21:28 6965
1193b4be Poul Sander 2025-02-03 21:28 6966
    // 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 6967
    ImGuiButtonFlags button_flags = 0;
1193b4be Poul Sander 2025-02-03 21:28 6968
    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
1193b4be Poul Sander 2025-02-03 21:28 6969
    if (flags & ImGuiSelectableFlags_NoSetKeyOwner)     { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }
1193b4be Poul Sander 2025-02-03 21:28 6970
    if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
1193b4be Poul Sander 2025-02-03 21:28 6971
    if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
1193b4be Poul Sander 2025-02-03 21:28 6972
    if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
1193b4be Poul Sander 2025-02-03 21:28 6973
    if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
1193b4be Poul Sander 2025-02-03 21:28 6974
1193b4be Poul Sander 2025-02-03 21:28 6975
    // Multi-selection support (header)
1193b4be Poul Sander 2025-02-03 21:28 6976
    const bool was_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 6977
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6978
    {
1193b4be Poul Sander 2025-02-03 21:28 6979
        // Handle multi-select + alter button flags for it
1193b4be Poul Sander 2025-02-03 21:28 6980
        MultiSelectItemHeader(id, &selected, &button_flags);
1193b4be Poul Sander 2025-02-03 21:28 6981
    }
1193b4be Poul Sander 2025-02-03 21:28 6982
1193b4be Poul Sander 2025-02-03 21:28 6983
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 6984
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 6985
1193b4be Poul Sander 2025-02-03 21:28 6986
    // Multi-selection support (footer)
1193b4be Poul Sander 2025-02-03 21:28 6987
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6988
    {
1193b4be Poul Sander 2025-02-03 21:28 6989
        MultiSelectItemFooter(id, &selected, &pressed);
1193b4be Poul Sander 2025-02-03 21:28 6990
    }
1193b4be Poul Sander 2025-02-03 21:28 6991
    else
1193b4be Poul Sander 2025-02-03 21:28 6992
    {
1193b4be Poul Sander 2025-02-03 21:28 6993
        // Auto-select when moved into
1193b4be Poul Sander 2025-02-03 21:28 6994
        // - This will be more fully fleshed in the range-select branch
1193b4be Poul Sander 2025-02-03 21:28 6995
        // - 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 6996
        // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
1193b4be Poul Sander 2025-02-03 21:28 6997
        //   - (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 6998
        //   - (2) usage will fail with clipped items
1193b4be Poul Sander 2025-02-03 21:28 6999
        //   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 7000
        if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
1193b4be Poul Sander 2025-02-03 21:28 7001
            if (g.NavJustMovedToId == id)
1193b4be Poul Sander 2025-02-03 21:28 7002
                selected = pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 7003
    }
1193b4be Poul Sander 2025-02-03 21:28 7004
1193b4be Poul Sander 2025-02-03 21:28 7005
    // 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 7006
    if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
1193b4be Poul Sander 2025-02-03 21:28 7007
    {
1193b4be Poul Sander 2025-02-03 21:28 7008
        if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
1193b4be Poul Sander 2025-02-03 21:28 7009
        {
1193b4be Poul Sander 2025-02-03 21:28 7010
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
1193b4be Poul Sander 2025-02-03 21:28 7011
            if (g.IO.ConfigNavCursorVisibleAuto)
1193b4be Poul Sander 2025-02-03 21:28 7012
                g.NavCursorVisible = false;
1193b4be Poul Sander 2025-02-03 21:28 7013
        }
1193b4be Poul Sander 2025-02-03 21:28 7014
    }
1193b4be Poul Sander 2025-02-03 21:28 7015
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 7016
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 7017
1193b4be Poul Sander 2025-02-03 21:28 7018
    if (selected != was_selected)
1193b4be Poul Sander 2025-02-03 21:28 7019
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
1193b4be Poul Sander 2025-02-03 21:28 7020
1193b4be Poul Sander 2025-02-03 21:28 7021
    // Render
1193b4be Poul Sander 2025-02-03 21:28 7022
    if (is_visible)
1193b4be Poul Sander 2025-02-03 21:28 7023
    {
1193b4be Poul Sander 2025-02-03 21:28 7024
        const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight);
1193b4be Poul Sander 2025-02-03 21:28 7025
        if (highlighted || selected)
1193b4be Poul Sander 2025-02-03 21:28 7026
        {
1193b4be Poul Sander 2025-02-03 21:28 7027
            // FIXME-MULTISELECT: Styling: Color for 'selected' elements? ImGuiCol_HeaderSelected
1193b4be Poul Sander 2025-02-03 21:28 7028
            ImU32 col;
1193b4be Poul Sander 2025-02-03 21:28 7029
            if (selected && !highlighted)
1193b4be Poul Sander 2025-02-03 21:28 7030
                col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f));
1193b4be Poul Sander 2025-02-03 21:28 7031
            else
1193b4be Poul Sander 2025-02-03 21:28 7032
                col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
1193b4be Poul Sander 2025-02-03 21:28 7033
            RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 7034
        }
1193b4be Poul Sander 2025-02-03 21:28 7035
        if (g.NavId == id)
1193b4be Poul Sander 2025-02-03 21:28 7036
        {
1193b4be Poul Sander 2025-02-03 21:28 7037
            ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding;
1193b4be Poul Sander 2025-02-03 21:28 7038
            if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 7039
                nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
1193b4be Poul Sander 2025-02-03 21:28 7040
            RenderNavCursor(bb, id, nav_render_cursor_flags);
1193b4be Poul Sander 2025-02-03 21:28 7041
        }
1193b4be Poul Sander 2025-02-03 21:28 7042
    }
1193b4be Poul Sander 2025-02-03 21:28 7043
1193b4be Poul Sander 2025-02-03 21:28 7044
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 7045
    {
1193b4be Poul Sander 2025-02-03 21:28 7046
        if (g.CurrentTable)
1193b4be Poul Sander 2025-02-03 21:28 7047
            TablePopBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 7048
        else if (window->DC.CurrentColumns)
1193b4be Poul Sander 2025-02-03 21:28 7049
            PopColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 7050
    }
1193b4be Poul Sander 2025-02-03 21:28 7051
1193b4be Poul Sander 2025-02-03 21:28 7052
    if (is_visible)
1193b4be Poul Sander 2025-02-03 21:28 7053
        RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
1193b4be Poul Sander 2025-02-03 21:28 7054
1193b4be Poul Sander 2025-02-03 21:28 7055
    // Automatically close popups
1193b4be Poul Sander 2025-02-03 21:28 7056
    if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
1193b4be Poul Sander 2025-02-03 21:28 7057
        CloseCurrentPopup();
1193b4be Poul Sander 2025-02-03 21:28 7058
1193b4be Poul Sander 2025-02-03 21:28 7059
    if (disabled_item && !disabled_global)
1193b4be Poul Sander 2025-02-03 21:28 7060
        EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 7061
1193b4be Poul Sander 2025-02-03 21:28 7062
    // Selectable() always returns a pressed state!
1193b4be Poul Sander 2025-02-03 21:28 7063
    // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve
1193b4be Poul Sander 2025-02-03 21:28 7064
    // 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 7065
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 7066
    return pressed; //-V1020
1193b4be Poul Sander 2025-02-03 21:28 7067
}
1193b4be Poul Sander 2025-02-03 21:28 7068
1193b4be Poul Sander 2025-02-03 21:28 7069
bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 7070
{
1193b4be Poul Sander 2025-02-03 21:28 7071
    if (Selectable(label, *p_selected, flags, size_arg))
1193b4be Poul Sander 2025-02-03 21:28 7072
    {
1193b4be Poul Sander 2025-02-03 21:28 7073
        *p_selected = !*p_selected;
1193b4be Poul Sander 2025-02-03 21:28 7074
        return true;
1193b4be Poul Sander 2025-02-03 21:28 7075
    }
1193b4be Poul Sander 2025-02-03 21:28 7076
    return false;
1193b4be Poul Sander 2025-02-03 21:28 7077
}
1193b4be Poul Sander 2025-02-03 21:28 7078
1193b4be Poul Sander 2025-02-03 21:28 7079
1193b4be Poul Sander 2025-02-03 21:28 7080
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7081
// [SECTION] Widgets: Typing-Select support
1193b4be Poul Sander 2025-02-03 21:28 7082
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7083
1193b4be Poul Sander 2025-02-03 21:28 7084
// [Experimental] Currently not exposed in public API.
1193b4be Poul Sander 2025-02-03 21:28 7085
// Consume character inputs and return search request, if any.
1193b4be Poul Sander 2025-02-03 21:28 7086
// 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 7087
//   if (ImGui::IsWindowFocused(...))
1193b4be Poul Sander 2025-02-03 21:28 7088
//       if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())
1193b4be Poul Sander 2025-02-03 21:28 7089
//           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 7090
// 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 7091
ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 7092
{
1193b4be Poul Sander 2025-02-03 21:28 7093
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7094
    ImGuiTypingSelectState* data = &g.TypingSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7095
    ImGuiTypingSelectRequest* out_request = &data->Request;
1193b4be Poul Sander 2025-02-03 21:28 7096
1193b4be Poul Sander 2025-02-03 21:28 7097
    // Clear buffer
1193b4be Poul Sander 2025-02-03 21:28 7098
    const float TYPING_SELECT_RESET_TIMER = 1.80f;          // FIXME: Potentially move to IO config.
1193b4be Poul Sander 2025-02-03 21:28 7099
    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 7100
    if (data->SearchBuffer[0] != 0)
1193b4be Poul Sander 2025-02-03 21:28 7101
    {
1193b4be Poul Sander 2025-02-03 21:28 7102
        bool clear_buffer = false;
1193b4be Poul Sander 2025-02-03 21:28 7103
        clear_buffer |= (g.NavFocusScopeId != data->FocusScope);
1193b4be Poul Sander 2025-02-03 21:28 7104
        clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);
1193b4be Poul Sander 2025-02-03 21:28 7105
        clear_buffer |= g.NavAnyRequest;
1193b4be Poul Sander 2025-02-03 21:28 7106
        clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere
1193b4be Poul Sander 2025-02-03 21:28 7107
        clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);
1193b4be Poul Sander 2025-02-03 21:28 7108
        clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;
1193b4be Poul Sander 2025-02-03 21:28 7109
        //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); }
1193b4be Poul Sander 2025-02-03 21:28 7110
        if (clear_buffer)
1193b4be Poul Sander 2025-02-03 21:28 7111
            data->Clear();
1193b4be Poul Sander 2025-02-03 21:28 7112
    }
1193b4be Poul Sander 2025-02-03 21:28 7113
1193b4be Poul Sander 2025-02-03 21:28 7114
    // Append to buffer
1193b4be Poul Sander 2025-02-03 21:28 7115
    const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
1193b4be Poul Sander 2025-02-03 21:28 7116
    int buffer_len = (int)strlen(data->SearchBuffer);
1193b4be Poul Sander 2025-02-03 21:28 7117
    bool select_request = false;
1193b4be Poul Sander 2025-02-03 21:28 7118
    for (ImWchar w : g.IO.InputQueueCharacters)
1193b4be Poul Sander 2025-02-03 21:28 7119
    {
1193b4be Poul Sander 2025-02-03 21:28 7120
        const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);
1193b4be Poul Sander 2025-02-03 21:28 7121
        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 7122
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7123
        char w_buf[5];
1193b4be Poul Sander 2025-02-03 21:28 7124
        ImTextCharToUtf8(w_buf, (unsigned int)w);
1193b4be Poul Sander 2025-02-03 21:28 7125
        if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7126
        {
1193b4be Poul Sander 2025-02-03 21:28 7127
            select_request = true; // Same character: don't need to append to buffer.
1193b4be Poul Sander 2025-02-03 21:28 7128
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7129
        }
1193b4be Poul Sander 2025-02-03 21:28 7130
        if (data->SingleCharModeLock)
1193b4be Poul Sander 2025-02-03 21:28 7131
        {
1193b4be Poul Sander 2025-02-03 21:28 7132
            data->Clear(); // Different character: clear
1193b4be Poul Sander 2025-02-03 21:28 7133
            buffer_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 7134
        }
1193b4be Poul Sander 2025-02-03 21:28 7135
        memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append
1193b4be Poul Sander 2025-02-03 21:28 7136
        buffer_len += w_len;
1193b4be Poul Sander 2025-02-03 21:28 7137
        select_request = true;
1193b4be Poul Sander 2025-02-03 21:28 7138
    }
1193b4be Poul Sander 2025-02-03 21:28 7139
    g.IO.InputQueueCharacters.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7140
1193b4be Poul Sander 2025-02-03 21:28 7141
    // Handle backspace
1193b4be Poul Sander 2025-02-03 21:28 7142
    if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat))
1193b4be Poul Sander 2025-02-03 21:28 7143
    {
1193b4be Poul Sander 2025-02-03 21:28 7144
        char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);
1193b4be Poul Sander 2025-02-03 21:28 7145
        *p = 0;
1193b4be Poul Sander 2025-02-03 21:28 7146
        buffer_len = (int)(p - data->SearchBuffer);
1193b4be Poul Sander 2025-02-03 21:28 7147
    }
1193b4be Poul Sander 2025-02-03 21:28 7148
1193b4be Poul Sander 2025-02-03 21:28 7149
    // Return request if any
1193b4be Poul Sander 2025-02-03 21:28 7150
    if (buffer_len == 0)
1193b4be Poul Sander 2025-02-03 21:28 7151
        return NULL;
1193b4be Poul Sander 2025-02-03 21:28 7152
    if (select_request)
1193b4be Poul Sander 2025-02-03 21:28 7153
    {
1193b4be Poul Sander 2025-02-03 21:28 7154
        data->FocusScope = g.NavFocusScopeId;
1193b4be Poul Sander 2025-02-03 21:28 7155
        data->LastRequestFrame = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 7156
        data->LastRequestTime = (float)g.Time;
1193b4be Poul Sander 2025-02-03 21:28 7157
    }
1193b4be Poul Sander 2025-02-03 21:28 7158
    out_request->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 7159
    out_request->SearchBufferLen = buffer_len;
1193b4be Poul Sander 2025-02-03 21:28 7160
    out_request->SearchBuffer = data->SearchBuffer;
1193b4be Poul Sander 2025-02-03 21:28 7161
    out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 7162
    out_request->SingleCharMode = false;
1193b4be Poul Sander 2025-02-03 21:28 7163
    out_request->SingleCharSize = 0;
1193b4be Poul Sander 2025-02-03 21:28 7164
1193b4be Poul Sander 2025-02-03 21:28 7165
    // Calculate if buffer contains the same character repeated.
1193b4be Poul Sander 2025-02-03 21:28 7166
    // - This can be used to implement a special search mode on first character.
1193b4be Poul Sander 2025-02-03 21:28 7167
    // - Performed on UTF-8 codepoint for correctness.
1193b4be Poul Sander 2025-02-03 21:28 7168
    // - SingleCharMode is always set for first input character, because it usually leads to a "next".
1193b4be Poul Sander 2025-02-03 21:28 7169
    if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)
1193b4be Poul Sander 2025-02-03 21:28 7170
    {
1193b4be Poul Sander 2025-02-03 21:28 7171
        const char* buf_begin = out_request->SearchBuffer;
1193b4be Poul Sander 2025-02-03 21:28 7172
        const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;
1193b4be Poul Sander 2025-02-03 21:28 7173
        const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);
1193b4be Poul Sander 2025-02-03 21:28 7174
        const char* p = buf_begin + c0_len;
1193b4be Poul Sander 2025-02-03 21:28 7175
        for (; p < buf_end; p += c0_len)
1193b4be Poul Sander 2025-02-03 21:28 7176
            if (memcmp(buf_begin, p, (size_t)c0_len) != 0)
1193b4be Poul Sander 2025-02-03 21:28 7177
                break;
1193b4be Poul Sander 2025-02-03 21:28 7178
        const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;
1193b4be Poul Sander 2025-02-03 21:28 7179
        out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);
1193b4be Poul Sander 2025-02-03 21:28 7180
        out_request->SingleCharSize = (ImS8)c0_len;
1193b4be Poul Sander 2025-02-03 21:28 7181
        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 7182
    }
1193b4be Poul Sander 2025-02-03 21:28 7183
1193b4be Poul Sander 2025-02-03 21:28 7184
    return out_request;
1193b4be Poul Sander 2025-02-03 21:28 7185
}
1193b4be Poul Sander 2025-02-03 21:28 7186
1193b4be Poul Sander 2025-02-03 21:28 7187
static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)
1193b4be Poul Sander 2025-02-03 21:28 7188
{
1193b4be Poul Sander 2025-02-03 21:28 7189
    int match_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 7190
    while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))
1193b4be Poul Sander 2025-02-03 21:28 7191
        match_len++;
1193b4be Poul Sander 2025-02-03 21:28 7192
    return match_len;
1193b4be Poul Sander 2025-02-03 21:28 7193
}
1193b4be Poul Sander 2025-02-03 21:28 7194
1193b4be Poul Sander 2025-02-03 21:28 7195
// Default handler for finding a result for typing-select. You may implement your own.
1193b4be Poul Sander 2025-02-03 21:28 7196
// You might want to display a tooltip to visualize the current request SearchBuffer
1193b4be Poul Sander 2025-02-03 21:28 7197
// When SingleCharMode is set:
1193b4be Poul Sander 2025-02-03 21:28 7198
// - it is better to NOT display a tooltip of other on-screen display indicator.
1193b4be Poul Sander 2025-02-03 21:28 7199
// - the index of the currently focused item is required.
1193b4be Poul Sander 2025-02-03 21:28 7200
//   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 7201
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 7202
{
1193b4be Poul Sander 2025-02-03 21:28 7203
    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 7204
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 7205
    int idx = -1;
1193b4be Poul Sander 2025-02-03 21:28 7206
    if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))
1193b4be Poul Sander 2025-02-03 21:28 7207
        idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);
1193b4be Poul Sander 2025-02-03 21:28 7208
    else
1193b4be Poul Sander 2025-02-03 21:28 7209
        idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);
1193b4be Poul Sander 2025-02-03 21:28 7210
    if (idx != -1)
1193b4be Poul Sander 2025-02-03 21:28 7211
        SetNavCursorVisibleAfterMove();
1193b4be Poul Sander 2025-02-03 21:28 7212
    return idx;
1193b4be Poul Sander 2025-02-03 21:28 7213
}
1193b4be Poul Sander 2025-02-03 21:28 7214
1193b4be Poul Sander 2025-02-03 21:28 7215
// 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 7216
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 7217
{
1193b4be Poul Sander 2025-02-03 21:28 7218
    // FIXME: Assume selection user data is index. Would be extremely practical.
1193b4be Poul Sander 2025-02-03 21:28 7219
    //if (nav_item_idx == -1)
1193b4be Poul Sander 2025-02-03 21:28 7220
    //    nav_item_idx = (int)g.NavLastValidSelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 7221
1193b4be Poul Sander 2025-02-03 21:28 7222
    int first_match_idx = -1;
1193b4be Poul Sander 2025-02-03 21:28 7223
    bool return_next_match = false;
1193b4be Poul Sander 2025-02-03 21:28 7224
    for (int idx = 0; idx < items_count; idx++)
1193b4be Poul Sander 2025-02-03 21:28 7225
    {
1193b4be Poul Sander 2025-02-03 21:28 7226
        const char* item_name = get_item_name_func(user_data, idx);
1193b4be Poul Sander 2025-02-03 21:28 7227
        if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)
1193b4be Poul Sander 2025-02-03 21:28 7228
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7229
        if (return_next_match)                           // Return next matching item after current item.
1193b4be Poul Sander 2025-02-03 21:28 7230
            return idx;
1193b4be Poul Sander 2025-02-03 21:28 7231
        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 7232
            return idx;
1193b4be Poul Sander 2025-02-03 21:28 7233
        if (first_match_idx == -1)                       // Record first match for wrapping.
1193b4be Poul Sander 2025-02-03 21:28 7234
            first_match_idx = idx;
1193b4be Poul Sander 2025-02-03 21:28 7235
        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 7236
            return_next_match = true;
1193b4be Poul Sander 2025-02-03 21:28 7237
    }
1193b4be Poul Sander 2025-02-03 21:28 7238
    return first_match_idx; // First result
1193b4be Poul Sander 2025-02-03 21:28 7239
}
1193b4be Poul Sander 2025-02-03 21:28 7240
1193b4be Poul Sander 2025-02-03 21:28 7241
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 7242
{
1193b4be Poul Sander 2025-02-03 21:28 7243
    int longest_match_idx = -1;
1193b4be Poul Sander 2025-02-03 21:28 7244
    int longest_match_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 7245
    for (int idx = 0; idx < items_count; idx++)
1193b4be Poul Sander 2025-02-03 21:28 7246
    {
1193b4be Poul Sander 2025-02-03 21:28 7247
        const char* item_name = get_item_name_func(user_data, idx);
1193b4be Poul Sander 2025-02-03 21:28 7248
        const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);
1193b4be Poul Sander 2025-02-03 21:28 7249
        if (match_len <= longest_match_len)
1193b4be Poul Sander 2025-02-03 21:28 7250
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7251
        longest_match_idx = idx;
1193b4be Poul Sander 2025-02-03 21:28 7252
        longest_match_len = match_len;
1193b4be Poul Sander 2025-02-03 21:28 7253
        if (match_len == req->SearchBufferLen)
1193b4be Poul Sander 2025-02-03 21:28 7254
            break;
1193b4be Poul Sander 2025-02-03 21:28 7255
    }
1193b4be Poul Sander 2025-02-03 21:28 7256
    return longest_match_idx;
1193b4be Poul Sander 2025-02-03 21:28 7257
}
1193b4be Poul Sander 2025-02-03 21:28 7258
1193b4be Poul Sander 2025-02-03 21:28 7259
void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
1193b4be Poul Sander 2025-02-03 21:28 7260
{
1193b4be Poul Sander 2025-02-03 21:28 7261
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1193b4be Poul Sander 2025-02-03 21:28 7262
    Text("SearchBuffer = \"%s\"", data->SearchBuffer);
1193b4be Poul Sander 2025-02-03 21:28 7263
    Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);
1193b4be Poul Sander 2025-02-03 21:28 7264
    Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame);
1193b4be Poul Sander 2025-02-03 21:28 7265
#else
1193b4be Poul Sander 2025-02-03 21:28 7266
    IM_UNUSED(data);
1193b4be Poul Sander 2025-02-03 21:28 7267
#endif
1193b4be Poul Sander 2025-02-03 21:28 7268
}
1193b4be Poul Sander 2025-02-03 21:28 7269
1193b4be Poul Sander 2025-02-03 21:28 7270
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7271
// [SECTION] Widgets: Box-Select support
1193b4be Poul Sander 2025-02-03 21:28 7272
// 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 7273
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7274
// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step()
1193b4be Poul Sander 2025-02-03 21:28 7275
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7276
// - BoxSelectPreStartDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7277
// - BoxSelectActivateDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7278
// - BoxSelectDeactivateDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7279
// - BoxSelectScrollWithMouseDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7280
// - BeginBoxSelect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7281
// - EndBoxSelect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7282
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7283
1193b4be Poul Sander 2025-02-03 21:28 7284
// Call on the initial click.
1193b4be Poul Sander 2025-02-03 21:28 7285
static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)
1193b4be Poul Sander 2025-02-03 21:28 7286
{
1193b4be Poul Sander 2025-02-03 21:28 7287
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7288
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7289
    bs->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 7290
    bs->IsStarting = true; // Consider starting box-select.
1193b4be Poul Sander 2025-02-03 21:28 7291
    bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid);
1193b4be Poul Sander 2025-02-03 21:28 7292
    bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid;
1193b4be Poul Sander 2025-02-03 21:28 7293
    bs->KeyMods = g.IO.KeyMods;
1193b4be Poul Sander 2025-02-03 21:28 7294
    bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 7295
    bs->ScrollAccum = ImVec2(0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 7296
}
1193b4be Poul Sander 2025-02-03 21:28 7297
1193b4be Poul Sander 2025-02-03 21:28 7298
static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 7299
{
1193b4be Poul Sander 2025-02-03 21:28 7300
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7301
    IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID);
1193b4be Poul Sander 2025-02-03 21:28 7302
    bs->IsActive = true;
1193b4be Poul Sander 2025-02-03 21:28 7303
    bs->Window = window;
1193b4be Poul Sander 2025-02-03 21:28 7304
    bs->IsStarting = false;
1193b4be Poul Sander 2025-02-03 21:28 7305
    ImGui::SetActiveID(bs->ID, window);
1193b4be Poul Sander 2025-02-03 21:28 7306
    ImGui::SetActiveIdUsingAllKeyboardKeys();
1193b4be Poul Sander 2025-02-03 21:28 7307
    if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7308
        bs->RequestClear = true;
1193b4be Poul Sander 2025-02-03 21:28 7309
}
1193b4be Poul Sander 2025-02-03 21:28 7310
1193b4be Poul Sander 2025-02-03 21:28 7311
static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs)
1193b4be Poul Sander 2025-02-03 21:28 7312
{
1193b4be Poul Sander 2025-02-03 21:28 7313
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7314
    bs->IsActive = bs->IsStarting = false;
1193b4be Poul Sander 2025-02-03 21:28 7315
    if (g.ActiveId == bs->ID)
1193b4be Poul Sander 2025-02-03 21:28 7316
    {
1193b4be Poul Sander 2025-02-03 21:28 7317
        IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID);
1193b4be Poul Sander 2025-02-03 21:28 7318
        ImGui::ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 7319
    }
1193b4be Poul Sander 2025-02-03 21:28 7320
    bs->ID = 0;
1193b4be Poul Sander 2025-02-03 21:28 7321
}
1193b4be Poul Sander 2025-02-03 21:28 7322
1193b4be Poul Sander 2025-02-03 21:28 7323
static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r)
1193b4be Poul Sander 2025-02-03 21:28 7324
{
1193b4be Poul Sander 2025-02-03 21:28 7325
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7326
    IM_ASSERT(bs->Window == window);
1193b4be Poul Sander 2025-02-03 21:28 7327
    for (int n = 0; n < 2; n++) // each axis
1193b4be Poul Sander 2025-02-03 21:28 7328
    {
1193b4be Poul Sander 2025-02-03 21:28 7329
        const float mouse_pos = g.IO.MousePos[n];
1193b4be Poul Sander 2025-02-03 21:28 7330
        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 7331
        const float scroll_curr = window->Scroll[n];
1193b4be Poul Sander 2025-02-03 21:28 7332
        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 7333
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7334
1193b4be Poul Sander 2025-02-03 21:28 7335
        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 7336
        const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime;
1193b4be Poul Sander 2025-02-03 21:28 7337
        bs->ScrollAccum[n] += scroll_step;
1193b4be Poul Sander 2025-02-03 21:28 7338
1193b4be Poul Sander 2025-02-03 21:28 7339
        // Accumulate into a stored value so we can handle high-framerate
1193b4be Poul Sander 2025-02-03 21:28 7340
        const float scroll_step_i = ImFloor(bs->ScrollAccum[n]);
1193b4be Poul Sander 2025-02-03 21:28 7341
        if (scroll_step_i == 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 7342
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7343
        if (n == 0)
1193b4be Poul Sander 2025-02-03 21:28 7344
            ImGui::SetScrollX(window, scroll_curr + scroll_step_i);
1193b4be Poul Sander 2025-02-03 21:28 7345
        else
1193b4be Poul Sander 2025-02-03 21:28 7346
            ImGui::SetScrollY(window, scroll_curr + scroll_step_i);
1193b4be Poul Sander 2025-02-03 21:28 7347
        bs->ScrollAccum[n] -= scroll_step_i;
1193b4be Poul Sander 2025-02-03 21:28 7348
    }
1193b4be Poul Sander 2025-02-03 21:28 7349
}
1193b4be Poul Sander 2025-02-03 21:28 7350
1193b4be Poul Sander 2025-02-03 21:28 7351
bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)
1193b4be Poul Sander 2025-02-03 21:28 7352
{
1193b4be Poul Sander 2025-02-03 21:28 7353
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7354
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7355
    KeepAliveID(box_select_id);
1193b4be Poul Sander 2025-02-03 21:28 7356
    if (bs->ID != box_select_id)
1193b4be Poul Sander 2025-02-03 21:28 7357
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7358
1193b4be Poul Sander 2025-02-03 21:28 7359
    // 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 7360
    bs->UnclipMode = false;
1193b4be Poul Sander 2025-02-03 21:28 7361
    bs->RequestClear = false;
1193b4be Poul Sander 2025-02-03 21:28 7362
    if (bs->IsStarting && IsMouseDragPastThreshold(0))
1193b4be Poul Sander 2025-02-03 21:28 7363
        BoxSelectActivateDrag(bs, window);
1193b4be Poul Sander 2025-02-03 21:28 7364
    else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
1193b4be Poul Sander 2025-02-03 21:28 7365
        BoxSelectDeactivateDrag(bs);
1193b4be Poul Sander 2025-02-03 21:28 7366
    if (!bs->IsActive)
1193b4be Poul Sander 2025-02-03 21:28 7367
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7368
1193b4be Poul Sander 2025-02-03 21:28 7369
    // Current frame absolute prev/current rectangles are used to toggle selection.
1193b4be Poul Sander 2025-02-03 21:28 7370
    // They are derived from positions relative to scrolling space.
1193b4be Poul Sander 2025-02-03 21:28 7371
    ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
1193b4be Poul Sander 2025-02-03 21:28 7372
    ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
1193b4be Poul Sander 2025-02-03 21:28 7373
    ImVec2 curr_end_pos_abs = g.IO.MousePos;
1193b4be Poul Sander 2025-02-03 21:28 7374
    if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow
1193b4be Poul Sander 2025-02-03 21:28 7375
        curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);
1193b4be Poul Sander 2025-02-03 21:28 7376
    bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7377
    bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7378
    bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7379
    bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7380
1193b4be Poul Sander 2025-02-03 21:28 7381
    // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
1193b4be Poul Sander 2025-02-03 21:28 7382
    // Storing an extra rect used by widgets supporting box-select.
1193b4be Poul Sander 2025-02-03 21:28 7383
    if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
1193b4be Poul Sander 2025-02-03 21:28 7384
        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 7385
        {
1193b4be Poul Sander 2025-02-03 21:28 7386
            bs->UnclipMode = true;
1193b4be Poul Sander 2025-02-03 21:28 7387
            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 7388
            bs->UnclipRect.Add(bs->BoxSelectRectCurr);
1193b4be Poul Sander 2025-02-03 21:28 7389
        }
1193b4be Poul Sander 2025-02-03 21:28 7390
1193b4be Poul Sander 2025-02-03 21:28 7391
    //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 7392
    //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 7393
    //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 7394
    return true;
1193b4be Poul Sander 2025-02-03 21:28 7395
}
1193b4be Poul Sander 2025-02-03 21:28 7396
1193b4be Poul Sander 2025-02-03 21:28 7397
void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags)
1193b4be Poul Sander 2025-02-03 21:28 7398
{
1193b4be Poul Sander 2025-02-03 21:28 7399
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7400
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7401
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7402
    IM_ASSERT(bs->IsActive);
1193b4be Poul Sander 2025-02-03 21:28 7403
    bs->UnclipMode = false;
1193b4be Poul Sander 2025-02-03 21:28 7404
1193b4be Poul Sander 2025-02-03 21:28 7405
    // Render selection rectangle
1193b4be Poul Sander 2025-02-03 21:28 7406
    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 7407
    ImRect box_select_r = bs->BoxSelectRectCurr;
1193b4be Poul Sander 2025-02-03 21:28 7408
    box_select_r.ClipWith(scope_rect);
1193b4be Poul Sander 2025-02-03 21:28 7409
    window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
1193b4be Poul Sander 2025-02-03 21:28 7410
    window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling
1193b4be Poul Sander 2025-02-03 21:28 7411
1193b4be Poul Sander 2025-02-03 21:28 7412
    // Scroll
1193b4be Poul Sander 2025-02-03 21:28 7413
    const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
1193b4be Poul Sander 2025-02-03 21:28 7414
    if (enable_scroll)
1193b4be Poul Sander 2025-02-03 21:28 7415
    {
1193b4be Poul Sander 2025-02-03 21:28 7416
        ImRect scroll_r = scope_rect;
1193b4be Poul Sander 2025-02-03 21:28 7417
        scroll_r.Expand(-g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 7418
        //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
1193b4be Poul Sander 2025-02-03 21:28 7419
        if (!scroll_r.Contains(g.IO.MousePos))
1193b4be Poul Sander 2025-02-03 21:28 7420
            BoxSelectScrollWithMouseDrag(bs, window, scroll_r);
1193b4be Poul Sander 2025-02-03 21:28 7421
    }
1193b4be Poul Sander 2025-02-03 21:28 7422
}
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
// [SECTION] Widgets: Multi-Select support
1193b4be Poul Sander 2025-02-03 21:28 7426
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7427
// - DebugLogMultiSelectRequests() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7428
// - CalcScopeRect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7429
// - BeginMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7430
// - EndMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7431
// - SetNextItemSelectionUserData()
1193b4be Poul Sander 2025-02-03 21:28 7432
// - MultiSelectItemHeader() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7433
// - MultiSelectItemFooter() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7434
// - DebugNodeMultiSelectState() [Internal]
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
static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)
1193b4be Poul Sander 2025-02-03 21:28 7438
{
1193b4be Poul Sander 2025-02-03 21:28 7439
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7440
    IM_UNUSED(function);
1193b4be Poul Sander 2025-02-03 21:28 7441
    for (const ImGuiSelectionRequest& req : io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 7442
    {
1193b4be Poul Sander 2025-02-03 21:28 7443
        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 7444
        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 7445
    }
1193b4be Poul Sander 2025-02-03 21:28 7446
}
1193b4be Poul Sander 2025-02-03 21:28 7447
1193b4be Poul Sander 2025-02-03 21:28 7448
static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 7449
{
1193b4be Poul Sander 2025-02-03 21:28 7450
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7451
    if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
1193b4be Poul Sander 2025-02-03 21:28 7452
    {
1193b4be Poul Sander 2025-02-03 21:28 7453
        // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only
1193b4be Poul Sander 2025-02-03 21:28 7454
        return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));
1193b4be Poul Sander 2025-02-03 21:28 7455
    }
1193b4be Poul Sander 2025-02-03 21:28 7456
    else
1193b4be Poul Sander 2025-02-03 21:28 7457
    {
1193b4be Poul Sander 2025-02-03 21:28 7458
        // 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 7459
        ImRect scope_rect = window->InnerClipRect;
1193b4be Poul Sander 2025-02-03 21:28 7460
        if (g.CurrentTable != NULL)
1193b4be Poul Sander 2025-02-03 21:28 7461
            scope_rect = g.CurrentTable->HostClipRect;
1193b4be Poul Sander 2025-02-03 21:28 7462
1193b4be Poul Sander 2025-02-03 21:28 7463
        // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?
1193b4be Poul Sander 2025-02-03 21:28 7464
        scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);
1193b4be Poul Sander 2025-02-03 21:28 7465
        return scope_rect;
1193b4be Poul Sander 2025-02-03 21:28 7466
    }
1193b4be Poul Sander 2025-02-03 21:28 7467
}
1193b4be Poul Sander 2025-02-03 21:28 7468
1193b4be Poul Sander 2025-02-03 21:28 7469
// Return ImGuiMultiSelectIO structure.
1193b4be Poul Sander 2025-02-03 21:28 7470
// 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 7471
// Passing 'selection_size' and 'items_count' parameters is currently optional.
1193b4be Poul Sander 2025-02-03 21:28 7472
// - '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 7473
//    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 7474
// - '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 7475
// - 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 7476
//   - 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 7477
ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count)
1193b4be Poul Sander 2025-02-03 21:28 7478
{
1193b4be Poul Sander 2025-02-03 21:28 7479
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7480
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7481
1193b4be Poul Sander 2025-02-03 21:28 7482
    if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size)
1193b4be Poul Sander 2025-02-03 21:28 7483
        g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData());
1193b4be Poul Sander 2025-02-03 21:28 7484
    ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1];
1193b4be Poul Sander 2025-02-03 21:28 7485
    IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that.
1193b4be Poul Sander 2025-02-03 21:28 7486
    g.CurrentMultiSelect = ms;
1193b4be Poul Sander 2025-02-03 21:28 7487
    if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7488
        flags |= ImGuiMultiSelectFlags_ScopeWindow;
1193b4be Poul Sander 2025-02-03 21:28 7489
    if (flags & ImGuiMultiSelectFlags_SingleSelect)
1193b4be Poul Sander 2025-02-03 21:28 7490
        flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d);
1193b4be Poul Sander 2025-02-03 21:28 7491
    if (flags & ImGuiMultiSelectFlags_BoxSelect2d)
1193b4be Poul Sander 2025-02-03 21:28 7492
        flags &= ~ImGuiMultiSelectFlags_BoxSelect1d;
1193b4be Poul Sander 2025-02-03 21:28 7493
1193b4be Poul Sander 2025-02-03 21:28 7494
    // FIXME: BeginFocusScope()
1193b4be Poul Sander 2025-02-03 21:28 7495
    const ImGuiID id = window->IDStack.back();
1193b4be Poul Sander 2025-02-03 21:28 7496
    ms->Clear();
1193b4be Poul Sander 2025-02-03 21:28 7497
    ms->FocusScopeId = id;
1193b4be Poul Sander 2025-02-03 21:28 7498
    ms->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 7499
    ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId);
1193b4be Poul Sander 2025-02-03 21:28 7500
    ms->BackupCursorMaxPos = window->DC.CursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 7501
    ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 7502
    PushFocusScope(ms->FocusScopeId);
1193b4be Poul Sander 2025-02-03 21:28 7503
    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 7504
        window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;
1193b4be Poul Sander 2025-02-03 21:28 7505
1193b4be Poul Sander 2025-02-03 21:28 7506
    // 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 7507
    ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods;
1193b4be Poul Sander 2025-02-03 21:28 7508
    if (flags & ImGuiMultiSelectFlags_NoRangeSelect)
1193b4be Poul Sander 2025-02-03 21:28 7509
        ms->KeyMods &= ~ImGuiMod_Shift;
1193b4be Poul Sander 2025-02-03 21:28 7510
1193b4be Poul Sander 2025-02-03 21:28 7511
    // Bind storage
1193b4be Poul Sander 2025-02-03 21:28 7512
    ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id);
1193b4be Poul Sander 2025-02-03 21:28 7513
    storage->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 7514
    storage->LastFrameActive = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 7515
    storage->LastSelectionSize = selection_size;
1193b4be Poul Sander 2025-02-03 21:28 7516
    storage->Window = window;
1193b4be Poul Sander 2025-02-03 21:28 7517
    ms->Storage = storage;
1193b4be Poul Sander 2025-02-03 21:28 7518
1193b4be Poul Sander 2025-02-03 21:28 7519
    // Output to user
1193b4be Poul Sander 2025-02-03 21:28 7520
    ms->IO.Requests.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7521
    ms->IO.RangeSrcItem = storage->RangeSrcItem;
1193b4be Poul Sander 2025-02-03 21:28 7522
    ms->IO.NavIdItem = storage->NavIdItem;
1193b4be Poul Sander 2025-02-03 21:28 7523
    ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;
1193b4be Poul Sander 2025-02-03 21:28 7524
    ms->IO.ItemsCount = items_count;
1193b4be Poul Sander 2025-02-03 21:28 7525
1193b4be Poul Sander 2025-02-03 21:28 7526
    // Clear when using Navigation to move within the scope
1193b4be Poul Sander 2025-02-03 21:28 7527
    // (we compare FocusScopeId so it possible to use multiple selections inside a same window)
1193b4be Poul Sander 2025-02-03 21:28 7528
    bool request_clear = false;
1193b4be Poul Sander 2025-02-03 21:28 7529
    bool request_select_all = false;
1193b4be Poul Sander 2025-02-03 21:28 7530
    if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)
1193b4be Poul Sander 2025-02-03 21:28 7531
    {
1193b4be Poul Sander 2025-02-03 21:28 7532
        if (ms->KeyMods & ImGuiMod_Shift)
1193b4be Poul Sander 2025-02-03 21:28 7533
            ms->IsKeyboardSetRange = true;
1193b4be Poul Sander 2025-02-03 21:28 7534
        if (ms->IsKeyboardSetRange)
1193b4be Poul Sander 2025-02-03 21:28 7535
            IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?
1193b4be Poul Sander 2025-02-03 21:28 7536
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7537
            request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7538
    }
1193b4be Poul Sander 2025-02-03 21:28 7539
    else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
1193b4be Poul Sander 2025-02-03 21:28 7540
    {
1193b4be Poul Sander 2025-02-03 21:28 7541
        // Also clear on leaving scope (may be optional?)
1193b4be Poul Sander 2025-02-03 21:28 7542
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7543
            request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7544
    }
1193b4be Poul Sander 2025-02-03 21:28 7545
1193b4be Poul Sander 2025-02-03 21:28 7546
    // Box-select handling: update active state.
1193b4be Poul Sander 2025-02-03 21:28 7547
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7548
    if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
1193b4be Poul Sander 2025-02-03 21:28 7549
    {
1193b4be Poul Sander 2025-02-03 21:28 7550
        ms->BoxSelectId = GetID("##BoxSelect");
1193b4be Poul Sander 2025-02-03 21:28 7551
        if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags))
1193b4be Poul Sander 2025-02-03 21:28 7552
            request_clear |= bs->RequestClear;
1193b4be Poul Sander 2025-02-03 21:28 7553
    }
1193b4be Poul Sander 2025-02-03 21:28 7554
1193b4be Poul Sander 2025-02-03 21:28 7555
    if (ms->IsFocused)
1193b4be Poul Sander 2025-02-03 21:28 7556
    {
1193b4be Poul Sander 2025-02-03 21:28 7557
        // Shortcut: Clear selection (Escape)
1193b4be Poul Sander 2025-02-03 21:28 7558
        // - 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 7559
        // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock.
1193b4be Poul Sander 2025-02-03 21:28 7560
        if (flags & ImGuiMultiSelectFlags_ClearOnEscape)
1193b4be Poul Sander 2025-02-03 21:28 7561
        {
1193b4be Poul Sander 2025-02-03 21:28 7562
            if (selection_size != 0 || bs->IsActive)
1193b4be Poul Sander 2025-02-03 21:28 7563
                if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0))
1193b4be Poul Sander 2025-02-03 21:28 7564
                {
1193b4be Poul Sander 2025-02-03 21:28 7565
                    request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7566
                    if (bs->IsActive)
1193b4be Poul Sander 2025-02-03 21:28 7567
                        BoxSelectDeactivateDrag(bs);
1193b4be Poul Sander 2025-02-03 21:28 7568
                }
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
        // Shortcut: Select all (CTRL+A)
1193b4be Poul Sander 2025-02-03 21:28 7572
        if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
1193b4be Poul Sander 2025-02-03 21:28 7573
            if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))
1193b4be Poul Sander 2025-02-03 21:28 7574
                request_select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 7575
    }
1193b4be Poul Sander 2025-02-03 21:28 7576
1193b4be Poul Sander 2025-02-03 21:28 7577
    if (request_clear || request_select_all)
1193b4be Poul Sander 2025-02-03 21:28 7578
    {
1193b4be Poul Sander 2025-02-03 21:28 7579
        MultiSelectAddSetAll(ms, request_select_all);
1193b4be Poul Sander 2025-02-03 21:28 7580
        if (!request_select_all)
1193b4be Poul Sander 2025-02-03 21:28 7581
            storage->LastSelectionSize = 0;
1193b4be Poul Sander 2025-02-03 21:28 7582
    }
1193b4be Poul Sander 2025-02-03 21:28 7583
    ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;
1193b4be Poul Sander 2025-02-03 21:28 7584
    ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid;
1193b4be Poul Sander 2025-02-03 21:28 7585
1193b4be Poul Sander 2025-02-03 21:28 7586
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
1193b4be Poul Sander 2025-02-03 21:28 7587
        DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO);
1193b4be Poul Sander 2025-02-03 21:28 7588
1193b4be Poul Sander 2025-02-03 21:28 7589
    return &ms->IO;
1193b4be Poul Sander 2025-02-03 21:28 7590
}
1193b4be Poul Sander 2025-02-03 21:28 7591
1193b4be Poul Sander 2025-02-03 21:28 7592
// Return updated ImGuiMultiSelectIO structure.
1193b4be Poul Sander 2025-02-03 21:28 7593
// 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 7594
ImGuiMultiSelectIO* ImGui::EndMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7595
{
1193b4be Poul Sander 2025-02-03 21:28 7596
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7597
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 7598
    ImGuiMultiSelectState* storage = ms->Storage;
1193b4be Poul Sander 2025-02-03 21:28 7599
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7600
    IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!");
1193b4be Poul Sander 2025-02-03 21:28 7601
    IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);
1193b4be Poul Sander 2025-02-03 21:28 7602
    IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);
1193b4be Poul Sander 2025-02-03 21:28 7603
1193b4be Poul Sander 2025-02-03 21:28 7604
    ImRect scope_rect = CalcScopeRect(ms, window);
1193b4be Poul Sander 2025-02-03 21:28 7605
    if (ms->IsFocused)
1193b4be Poul Sander 2025-02-03 21:28 7606
    {
1193b4be Poul Sander 2025-02-03 21:28 7607
        // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.
1193b4be Poul Sander 2025-02-03 21:28 7608
        if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure)
1193b4be Poul Sander 2025-02-03 21:28 7609
        {
1193b4be Poul Sander 2025-02-03 21:28 7610
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId.
1193b4be Poul Sander 2025-02-03 21:28 7611
            storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;
1193b4be Poul Sander 2025-02-03 21:28 7612
        }
1193b4be Poul Sander 2025-02-03 21:28 7613
        if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid)
1193b4be Poul Sander 2025-02-03 21:28 7614
        {
1193b4be Poul Sander 2025-02-03 21:28 7615
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n");
1193b4be Poul Sander 2025-02-03 21:28 7616
            storage->NavIdItem = ImGuiSelectionUserData_Invalid;
1193b4be Poul Sander 2025-02-03 21:28 7617
            storage->NavIdSelected = -1;
1193b4be Poul Sander 2025-02-03 21:28 7618
        }
1193b4be Poul Sander 2025-02-03 21:28 7619
1193b4be Poul Sander 2025-02-03 21:28 7620
        if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId))
1193b4be Poul Sander 2025-02-03 21:28 7621
            EndBoxSelect(scope_rect, ms->Flags);
1193b4be Poul Sander 2025-02-03 21:28 7622
    }
1193b4be Poul Sander 2025-02-03 21:28 7623
1193b4be Poul Sander 2025-02-03 21:28 7624
    if (ms->IsEndIO == false)
1193b4be Poul Sander 2025-02-03 21:28 7625
        ms->IO.Requests.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7626
1193b4be Poul Sander 2025-02-03 21:28 7627
    // Clear selection when clicking void?
1193b4be Poul Sander 2025-02-03 21:28 7628
    // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!
1193b4be Poul Sander 2025-02-03 21:28 7629
    // The InnerRect test is necessary for non-child/decorated windows.
1193b4be Poul Sander 2025-02-03 21:28 7630
    bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 7631
    if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))
1193b4be Poul Sander 2025-02-03 21:28 7632
        scope_hovered &= scope_rect.Contains(g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 7633
    if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)
1193b4be Poul Sander 2025-02-03 21:28 7634
    {
1193b4be Poul Sander 2025-02-03 21:28 7635
        if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
1193b4be Poul Sander 2025-02-03 21:28 7636
        {
1193b4be Poul Sander 2025-02-03 21:28 7637
            if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)
1193b4be Poul Sander 2025-02-03 21:28 7638
            {
1193b4be Poul Sander 2025-02-03 21:28 7639
                BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);
1193b4be Poul Sander 2025-02-03 21:28 7640
                FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);
1193b4be Poul Sander 2025-02-03 21:28 7641
                SetHoveredID(ms->BoxSelectId);
1193b4be Poul Sander 2025-02-03 21:28 7642
                if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
1193b4be Poul Sander 2025-02-03 21:28 7643
                    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 7644
            }
1193b4be Poul Sander 2025-02-03 21:28 7645
        }
1193b4be Poul Sander 2025-02-03 21:28 7646
1193b4be Poul Sander 2025-02-03 21:28 7647
        if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)
1193b4be Poul Sander 2025-02-03 21:28 7648
            if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
1193b4be Poul Sander 2025-02-03 21:28 7649
                MultiSelectAddSetAll(ms, false);
1193b4be Poul Sander 2025-02-03 21:28 7650
    }
1193b4be Poul Sander 2025-02-03 21:28 7651
1193b4be Poul Sander 2025-02-03 21:28 7652
    // Courtesy nav wrapping helper flag
1193b4be Poul Sander 2025-02-03 21:28 7653
    if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)
1193b4be Poul Sander 2025-02-03 21:28 7654
    {
1193b4be Poul Sander 2025-02-03 21:28 7655
        IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope
1193b4be Poul Sander 2025-02-03 21:28 7656
        ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
1193b4be Poul Sander 2025-02-03 21:28 7657
    }
1193b4be Poul Sander 2025-02-03 21:28 7658
1193b4be Poul Sander 2025-02-03 21:28 7659
    // Unwind
1193b4be Poul Sander 2025-02-03 21:28 7660
    window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);
1193b4be Poul Sander 2025-02-03 21:28 7661
    PopFocusScope();
1193b4be Poul Sander 2025-02-03 21:28 7662
1193b4be Poul Sander 2025-02-03 21:28 7663
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
1193b4be Poul Sander 2025-02-03 21:28 7664
        DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO);
1193b4be Poul Sander 2025-02-03 21:28 7665
1193b4be Poul Sander 2025-02-03 21:28 7666
    ms->FocusScopeId = 0;
1193b4be Poul Sander 2025-02-03 21:28 7667
    ms->Flags = ImGuiMultiSelectFlags_None;
1193b4be Poul Sander 2025-02-03 21:28 7668
    g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL;
1193b4be Poul Sander 2025-02-03 21:28 7669
1193b4be Poul Sander 2025-02-03 21:28 7670
    return &ms->IO;
1193b4be Poul Sander 2025-02-03 21:28 7671
}
1193b4be Poul Sander 2025-02-03 21:28 7672
1193b4be Poul Sander 2025-02-03 21:28 7673
void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
1193b4be Poul Sander 2025-02-03 21:28 7674
{
1193b4be Poul Sander 2025-02-03 21:28 7675
    // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
1193b4be Poul Sander 2025-02-03 21:28 7676
    // 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 7677
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7678
    g.NextItemData.SelectionUserData = selection_user_data;
1193b4be Poul Sander 2025-02-03 21:28 7679
    g.NextItemData.FocusScopeId = g.CurrentFocusScopeId;
1193b4be Poul Sander 2025-02-03 21:28 7680
1193b4be Poul Sander 2025-02-03 21:28 7681
    if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
1193b4be Poul Sander 2025-02-03 21:28 7682
    {
1193b4be Poul Sander 2025-02-03 21:28 7683
        // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)
1193b4be Poul Sander 2025-02-03 21:28 7684
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 7685
        if (ms->IO.RangeSrcItem == selection_user_data)
1193b4be Poul Sander 2025-02-03 21:28 7686
            ms->RangeSrcPassedBy = true;
1193b4be Poul Sander 2025-02-03 21:28 7687
    }
1193b4be Poul Sander 2025-02-03 21:28 7688
    else
1193b4be Poul Sander 2025-02-03 21:28 7689
    {
1193b4be Poul Sander 2025-02-03 21:28 7690
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 7691
    }
1193b4be Poul Sander 2025-02-03 21:28 7692
}
1193b4be Poul Sander 2025-02-03 21:28 7693
1193b4be Poul Sander 2025-02-03 21:28 7694
// In charge of:
1193b4be Poul Sander 2025-02-03 21:28 7695
// - Applying SetAll for submitted items.
1193b4be Poul Sander 2025-02-03 21:28 7696
// - Applying SetRange for submitted items and record end points.
1193b4be Poul Sander 2025-02-03 21:28 7697
// - Altering button behavior flags to facilitate use with drag and drop.
1193b4be Poul Sander 2025-02-03 21:28 7698
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags)
1193b4be Poul Sander 2025-02-03 21:28 7699
{
1193b4be Poul Sander 2025-02-03 21:28 7700
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7701
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 7702
1193b4be Poul Sander 2025-02-03 21:28 7703
    bool selected = *p_selected;
1193b4be Poul Sander 2025-02-03 21:28 7704
    if (ms->IsFocused)
1193b4be Poul Sander 2025-02-03 21:28 7705
    {
1193b4be Poul Sander 2025-02-03 21:28 7706
        ImGuiMultiSelectState* storage = ms->Storage;
1193b4be Poul Sander 2025-02-03 21:28 7707
        ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 7708
        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 7709
1193b4be Poul Sander 2025-02-03 21:28 7710
        // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 7711
        // 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 7712
        // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7713
        if (ms->LoopRequestSetAll != -1)
1193b4be Poul Sander 2025-02-03 21:28 7714
            selected = (ms->LoopRequestSetAll == 1);
1193b4be Poul Sander 2025-02-03 21:28 7715
1193b4be Poul Sander 2025-02-03 21:28 7716
        // 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 7717
        // 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 7718
        if (ms->IsKeyboardSetRange)
1193b4be Poul Sander 2025-02-03 21:28 7719
        {
1193b4be Poul Sander 2025-02-03 21:28 7720
            IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0);
1193b4be Poul Sander 2025-02-03 21:28 7721
            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 7722
            if (is_range_dst)
1193b4be Poul Sander 2025-02-03 21:28 7723
                ms->RangeDstPassedBy = true;
1193b4be Poul Sander 2025-02-03 21:28 7724
            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 7725
            {
1193b4be Poul Sander 2025-02-03 21:28 7726
                storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 7727
                storage->RangeSelected = selected ? 1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 7728
            }
1193b4be Poul Sander 2025-02-03 21:28 7729
            const bool is_range_src = storage->RangeSrcItem == item_data;
1193b4be Poul Sander 2025-02-03 21:28 7730
            if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy)
1193b4be Poul Sander 2025-02-03 21:28 7731
            {
1193b4be Poul Sander 2025-02-03 21:28 7732
                // Apply range-select value to visible items
1193b4be Poul Sander 2025-02-03 21:28 7733
                IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1);
1193b4be Poul Sander 2025-02-03 21:28 7734
                selected = (storage->RangeSelected != 0);
1193b4be Poul Sander 2025-02-03 21:28 7735
            }
1193b4be Poul Sander 2025-02-03 21:28 7736
            else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7737
            {
1193b4be Poul Sander 2025-02-03 21:28 7738
                // Clear other items
1193b4be Poul Sander 2025-02-03 21:28 7739
                selected = false;
1193b4be Poul Sander 2025-02-03 21:28 7740
            }
1193b4be Poul Sander 2025-02-03 21:28 7741
        }
1193b4be Poul Sander 2025-02-03 21:28 7742
        *p_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 7743
    }
1193b4be Poul Sander 2025-02-03 21:28 7744
1193b4be Poul Sander 2025-02-03 21:28 7745
    // Alter button behavior flags
1193b4be Poul Sander 2025-02-03 21:28 7746
    // To handle drag and drop of multiple items we need to avoid clearing selection on click.
1193b4be Poul Sander 2025-02-03 21:28 7747
    // 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 7748
    if (p_button_flags != NULL)
1193b4be Poul Sander 2025-02-03 21:28 7749
    {
1193b4be Poul Sander 2025-02-03 21:28 7750
        ImGuiButtonFlags button_flags = *p_button_flags;
1193b4be Poul Sander 2025-02-03 21:28 7751
        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
1193b4be Poul Sander 2025-02-03 21:28 7752
        if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
1193b4be Poul Sander 2025-02-03 21:28 7753
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 7754
        else
1193b4be Poul Sander 2025-02-03 21:28 7755
            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 7756
        *p_button_flags = button_flags;
1193b4be Poul Sander 2025-02-03 21:28 7757
    }
1193b4be Poul Sander 2025-02-03 21:28 7758
}
1193b4be Poul Sander 2025-02-03 21:28 7759
1193b4be Poul Sander 2025-02-03 21:28 7760
// In charge of:
1193b4be Poul Sander 2025-02-03 21:28 7761
// - Auto-select on navigation.
1193b4be Poul Sander 2025-02-03 21:28 7762
// - Box-select toggle handling.
1193b4be Poul Sander 2025-02-03 21:28 7763
// - Right-click handling.
1193b4be Poul Sander 2025-02-03 21:28 7764
// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse.
1193b4be Poul Sander 2025-02-03 21:28 7765
// - Record current selection state for RangeSrc
1193b4be Poul Sander 2025-02-03 21:28 7766
// 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 7767
void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
1193b4be Poul Sander 2025-02-03 21:28 7768
{
1193b4be Poul Sander 2025-02-03 21:28 7769
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7770
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7771
1193b4be Poul Sander 2025-02-03 21:28 7772
    bool selected = *p_selected;
1193b4be Poul Sander 2025-02-03 21:28 7773
    bool pressed = *p_pressed;
1193b4be Poul Sander 2025-02-03 21:28 7774
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 7775
    ImGuiMultiSelectState* storage = ms->Storage;
1193b4be Poul Sander 2025-02-03 21:28 7776
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 7777
        ms->IsFocused = true;
1193b4be Poul Sander 2025-02-03 21:28 7778
1193b4be Poul Sander 2025-02-03 21:28 7779
    bool hovered = false;
1193b4be Poul Sander 2025-02-03 21:28 7780
    if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)
1193b4be Poul Sander 2025-02-03 21:28 7781
        hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
1193b4be Poul Sander 2025-02-03 21:28 7782
    if (!ms->IsFocused && !hovered)
1193b4be Poul Sander 2025-02-03 21:28 7783
        return;
1193b4be Poul Sander 2025-02-03 21:28 7784
1193b4be Poul Sander 2025-02-03 21:28 7785
    ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 7786
1193b4be Poul Sander 2025-02-03 21:28 7787
    ImGuiMultiSelectFlags flags = ms->Flags;
1193b4be Poul Sander 2025-02-03 21:28 7788
    const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7789
    bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7790
    bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7791
1193b4be Poul Sander 2025-02-03 21:28 7792
    bool apply_to_range_src = false;
1193b4be Poul Sander 2025-02-03 21:28 7793
1193b4be Poul Sander 2025-02-03 21:28 7794
    if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
1193b4be Poul Sander 2025-02-03 21:28 7795
        apply_to_range_src = true;
1193b4be Poul Sander 2025-02-03 21:28 7796
    if (ms->IsEndIO == false)
1193b4be Poul Sander 2025-02-03 21:28 7797
    {
1193b4be Poul Sander 2025-02-03 21:28 7798
        ms->IO.Requests.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7799
        ms->IsEndIO = true;
1193b4be Poul Sander 2025-02-03 21:28 7800
    }
1193b4be Poul Sander 2025-02-03 21:28 7801
1193b4be Poul Sander 2025-02-03 21:28 7802
    // Auto-select as you navigate a list
1193b4be Poul Sander 2025-02-03 21:28 7803
    if (g.NavJustMovedToId == id)
1193b4be Poul Sander 2025-02-03 21:28 7804
    {
1193b4be Poul Sander 2025-02-03 21:28 7805
        if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7806
        {
1193b4be Poul Sander 2025-02-03 21:28 7807
            if (is_ctrl && is_shift)
1193b4be Poul Sander 2025-02-03 21:28 7808
                pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 7809
            else if (!is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 7810
                selected = pressed = true;
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
            // With NoAutoSelect, using Shift+keyboard performs a write/copy
1193b4be Poul Sander 2025-02-03 21:28 7815
            if (is_shift)
1193b4be Poul Sander 2025-02-03 21:28 7816
                pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 7817
            else if (!is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 7818
                apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this
1193b4be Poul Sander 2025-02-03 21:28 7819
        }
1193b4be Poul Sander 2025-02-03 21:28 7820
    }
1193b4be Poul Sander 2025-02-03 21:28 7821
1193b4be Poul Sander 2025-02-03 21:28 7822
    if (apply_to_range_src)
1193b4be Poul Sander 2025-02-03 21:28 7823
    {
1193b4be Poul Sander 2025-02-03 21:28 7824
        storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 7825
        storage->RangeSelected = selected; // Will be updated at the end of this function anyway.
1193b4be Poul Sander 2025-02-03 21:28 7826
    }
1193b4be Poul Sander 2025-02-03 21:28 7827
1193b4be Poul Sander 2025-02-03 21:28 7828
    // Box-select toggle handling
1193b4be Poul Sander 2025-02-03 21:28 7829
    if (ms->BoxSelectId != 0)
1193b4be Poul Sander 2025-02-03 21:28 7830
        if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
1193b4be Poul Sander 2025-02-03 21:28 7831
        {
1193b4be Poul Sander 2025-02-03 21:28 7832
            const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);
1193b4be Poul Sander 2025-02-03 21:28 7833
            const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect);
1193b4be Poul Sander 2025-02-03 21:28 7834
            if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))
1193b4be Poul Sander 2025-02-03 21:28 7835
            {
1193b4be Poul Sander 2025-02-03 21:28 7836
                if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)
1193b4be Poul Sander 2025-02-03 21:28 7837
                {
1193b4be Poul Sander 2025-02-03 21:28 7838
                    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 7839
                    bs->IsStartedSetNavIdOnce = false;
1193b4be Poul Sander 2025-02-03 21:28 7840
                }
1193b4be Poul Sander 2025-02-03 21:28 7841
                else
1193b4be Poul Sander 2025-02-03 21:28 7842
                {
1193b4be Poul Sander 2025-02-03 21:28 7843
                    selected = !selected;
1193b4be Poul Sander 2025-02-03 21:28 7844
                    MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);
1193b4be Poul Sander 2025-02-03 21:28 7845
                }
1193b4be Poul Sander 2025-02-03 21:28 7846
                storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);
1193b4be Poul Sander 2025-02-03 21:28 7847
            }
1193b4be Poul Sander 2025-02-03 21:28 7848
        }
1193b4be Poul Sander 2025-02-03 21:28 7849
1193b4be Poul Sander 2025-02-03 21:28 7850
    // Right-click handling.
1193b4be Poul Sander 2025-02-03 21:28 7851
    // 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 7852
    if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7853
    {
1193b4be Poul Sander 2025-02-03 21:28 7854
        if (g.ActiveId != 0 && g.ActiveId != id)
1193b4be Poul Sander 2025-02-03 21:28 7855
            ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 7856
        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 7857
        if (!pressed && !selected)
1193b4be Poul Sander 2025-02-03 21:28 7858
        {
1193b4be Poul Sander 2025-02-03 21:28 7859
            pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 7860
            is_ctrl = is_shift = false;
1193b4be Poul Sander 2025-02-03 21:28 7861
        }
1193b4be Poul Sander 2025-02-03 21:28 7862
    }
1193b4be Poul Sander 2025-02-03 21:28 7863
1193b4be Poul Sander 2025-02-03 21:28 7864
    // 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 7865
    // 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 7866
    // (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 7867
    const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput);
1193b4be Poul Sander 2025-02-03 21:28 7868
1193b4be Poul Sander 2025-02-03 21:28 7869
    // Alter selection
1193b4be Poul Sander 2025-02-03 21:28 7870
    if (pressed && (!enter_pressed || !selected))
1193b4be Poul Sander 2025-02-03 21:28 7871
    {
1193b4be Poul Sander 2025-02-03 21:28 7872
        // Box-select
1193b4be Poul Sander 2025-02-03 21:28 7873
        ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
1193b4be Poul Sander 2025-02-03 21:28 7874
        if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
1193b4be Poul Sander 2025-02-03 21:28 7875
            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 7876
                BoxSelectPreStartDrag(ms->BoxSelectId, item_data);
1193b4be Poul Sander 2025-02-03 21:28 7877
1193b4be Poul Sander 2025-02-03 21:28 7878
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7879
        // ACTION                      | Begin  | Pressed/Activated  | End
1193b4be Poul Sander 2025-02-03 21:28 7880
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7881
        // Keys Navigated:             | Clear  | Src=item, Sel=1               SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 7882
        // Keys Navigated: Ctrl        | n/a    | n/a
1193b4be Poul Sander 2025-02-03 21:28 7883
        // Keys Navigated:      Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 7884
        // Keys Navigated: Ctrl+Shift  | n/a    | Dst=item, Sel=Src  => Clear + SetRange Src-Dst
1193b4be Poul Sander 2025-02-03 21:28 7885
        // Keys Activated:             | n/a    | Src=item, Sel=1    => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 7886
        // Keys Activated: Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetSange 1
1193b4be Poul Sander 2025-02-03 21:28 7887
        // Keys Activated:      Shift  | n/a    | Dst=item, Sel=1    => Clear + SetSange 1
1193b4be Poul Sander 2025-02-03 21:28 7888
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7889
        // Mouse Pressed:              | n/a    | Src=item, Sel=1,   => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 7890
        // Mouse Pressed:  Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 7891
        // Mouse Pressed:       Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 7892
        // Mouse Pressed:  Ctrl+Shift  | n/a    | Dst=item, Sel=!Sel =>         SetRange Src-Dst
1193b4be Poul Sander 2025-02-03 21:28 7893
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7894
1193b4be Poul Sander 2025-02-03 21:28 7895
        if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7896
        {
1193b4be Poul Sander 2025-02-03 21:28 7897
            bool request_clear = false;
1193b4be Poul Sander 2025-02-03 21:28 7898
            if (is_singleselect)
1193b4be Poul Sander 2025-02-03 21:28 7899
                request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7900
            else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 7901
                request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true;
1193b4be Poul Sander 2025-02-03 21:28 7902
            else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 7903
                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 7904
            if (request_clear)
1193b4be Poul Sander 2025-02-03 21:28 7905
                MultiSelectAddSetAll(ms, false);
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
        int range_direction;
1193b4be Poul Sander 2025-02-03 21:28 7909
        bool range_selected;
1193b4be Poul Sander 2025-02-03 21:28 7910
        if (is_shift && !is_singleselect)
1193b4be Poul Sander 2025-02-03 21:28 7911
        {
1193b4be Poul Sander 2025-02-03 21:28 7912
            //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);
1193b4be Poul Sander 2025-02-03 21:28 7913
            if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
1193b4be Poul Sander 2025-02-03 21:28 7914
                storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 7915
            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7916
            {
1193b4be Poul Sander 2025-02-03 21:28 7917
                // Shift+Arrow always select
1193b4be Poul Sander 2025-02-03 21:28 7918
                // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected)
1193b4be Poul Sander 2025-02-03 21:28 7919
                range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
1193b4be Poul Sander 2025-02-03 21:28 7920
            }
1193b4be Poul Sander 2025-02-03 21:28 7921
            else
1193b4be Poul Sander 2025-02-03 21:28 7922
            {
1193b4be Poul Sander 2025-02-03 21:28 7923
                // Shift+Arrow copy source selection state
1193b4be Poul Sander 2025-02-03 21:28 7924
                // Shift+Click always copy from target selection state
1193b4be Poul Sander 2025-02-03 21:28 7925
                if (ms->IsKeyboardSetRange)
1193b4be Poul Sander 2025-02-03 21:28 7926
                    range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
1193b4be Poul Sander 2025-02-03 21:28 7927
                else
1193b4be Poul Sander 2025-02-03 21:28 7928
                    range_selected = !selected;
1193b4be Poul Sander 2025-02-03 21:28 7929
            }
1193b4be Poul Sander 2025-02-03 21:28 7930
            range_direction = ms->RangeSrcPassedBy ? +1 : -1;
1193b4be Poul Sander 2025-02-03 21:28 7931
        }
1193b4be Poul Sander 2025-02-03 21:28 7932
        else
1193b4be Poul Sander 2025-02-03 21:28 7933
        {
1193b4be Poul Sander 2025-02-03 21:28 7934
            // Ctrl inverts selection, otherwise always select
1193b4be Poul Sander 2025-02-03 21:28 7935
            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7936
                selected = is_ctrl ? !selected : true;
1193b4be Poul Sander 2025-02-03 21:28 7937
            else
1193b4be Poul Sander 2025-02-03 21:28 7938
                selected = !selected;
1193b4be Poul Sander 2025-02-03 21:28 7939
            storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 7940
            range_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 7941
            range_direction = +1;
1193b4be Poul Sander 2025-02-03 21:28 7942
        }
1193b4be Poul Sander 2025-02-03 21:28 7943
        MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data);
1193b4be Poul Sander 2025-02-03 21:28 7944
    }
1193b4be Poul Sander 2025-02-03 21:28 7945
1193b4be Poul Sander 2025-02-03 21:28 7946
    // 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 7947
    if (storage->RangeSrcItem == item_data)
1193b4be Poul Sander 2025-02-03 21:28 7948
        storage->RangeSelected = selected ? 1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 7949
1193b4be Poul Sander 2025-02-03 21:28 7950
    // Update/store the selection state of focused item
1193b4be Poul Sander 2025-02-03 21:28 7951
    if (g.NavId == id)
1193b4be Poul Sander 2025-02-03 21:28 7952
    {
1193b4be Poul Sander 2025-02-03 21:28 7953
        storage->NavIdItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 7954
        storage->NavIdSelected = selected ? 1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 7955
    }
1193b4be Poul Sander 2025-02-03 21:28 7956
    if (storage->NavIdItem == item_data)
1193b4be Poul Sander 2025-02-03 21:28 7957
        ms->NavIdPassedBy = true;
1193b4be Poul Sander 2025-02-03 21:28 7958
    ms->LastSubmittedItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 7959
1193b4be Poul Sander 2025-02-03 21:28 7960
    *p_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 7961
    *p_pressed = pressed;
1193b4be Poul Sander 2025-02-03 21:28 7962
}
1193b4be Poul Sander 2025-02-03 21:28 7963
1193b4be Poul Sander 2025-02-03 21:28 7964
void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)
1193b4be Poul Sander 2025-02-03 21:28 7965
{
1193b4be Poul Sander 2025-02-03 21:28 7966
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid };
1193b4be Poul Sander 2025-02-03 21:28 7967
    ms->IO.Requests.resize(0);      // Can always clear previous requests
1193b4be Poul Sander 2025-02-03 21:28 7968
    ms->IO.Requests.push_back(req); // Add new request
1193b4be Poul Sander 2025-02-03 21:28 7969
}
1193b4be Poul Sander 2025-02-03 21:28 7970
1193b4be Poul Sander 2025-02-03 21:28 7971
void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)
1193b4be Poul Sander 2025-02-03 21:28 7972
{
1193b4be Poul Sander 2025-02-03 21:28 7973
    // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)
1193b4be Poul Sander 2025-02-03 21:28 7974
    if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7975
    {
1193b4be Poul Sander 2025-02-03 21:28 7976
        ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];
1193b4be Poul Sander 2025-02-03 21:28 7977
        if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected)
1193b4be Poul Sander 2025-02-03 21:28 7978
        {
1193b4be Poul Sander 2025-02-03 21:28 7979
            prev->RangeLastItem = last_item;
1193b4be Poul Sander 2025-02-03 21:28 7980
            return;
1193b4be Poul Sander 2025-02-03 21:28 7981
        }
1193b4be Poul Sander 2025-02-03 21:28 7982
    }
1193b4be Poul Sander 2025-02-03 21:28 7983
1193b4be Poul Sander 2025-02-03 21:28 7984
    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 7985
    ms->IO.Requests.push_back(req); // Add new request
1193b4be Poul Sander 2025-02-03 21:28 7986
}
1193b4be Poul Sander 2025-02-03 21:28 7987
1193b4be Poul Sander 2025-02-03 21:28 7988
void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)
1193b4be Poul Sander 2025-02-03 21:28 7989
{
1193b4be Poul Sander 2025-02-03 21:28 7990
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1193b4be Poul Sander 2025-02-03 21:28 7991
    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 7992
    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
1193b4be Poul Sander 2025-02-03 21:28 7993
    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 7994
    if (!is_active) { PopStyleColor(); }
1193b4be Poul Sander 2025-02-03 21:28 7995
    if (!open)
1193b4be Poul Sander 2025-02-03 21:28 7996
        return;
1193b4be Poul Sander 2025-02-03 21:28 7997
    Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected);
1193b4be Poul Sander 2025-02-03 21:28 7998
    Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected);
1193b4be Poul Sander 2025-02-03 21:28 7999
    Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user
1193b4be Poul Sander 2025-02-03 21:28 8000
    TreePop();
1193b4be Poul Sander 2025-02-03 21:28 8001
#else
1193b4be Poul Sander 2025-02-03 21:28 8002
    IM_UNUSED(storage);
1193b4be Poul Sander 2025-02-03 21:28 8003
#endif
1193b4be Poul Sander 2025-02-03 21:28 8004
}
1193b4be Poul Sander 2025-02-03 21:28 8005
1193b4be Poul Sander 2025-02-03 21:28 8006
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8007
// [SECTION] Widgets: Multi-Select helpers
1193b4be Poul Sander 2025-02-03 21:28 8008
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8009
// - ImGuiSelectionBasicStorage
1193b4be Poul Sander 2025-02-03 21:28 8010
// - ImGuiSelectionExternalStorage
1193b4be Poul Sander 2025-02-03 21:28 8011
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8012
1193b4be Poul Sander 2025-02-03 21:28 8013
ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage()
1193b4be Poul Sander 2025-02-03 21:28 8014
{
1193b4be Poul Sander 2025-02-03 21:28 8015
    Size = 0;
1193b4be Poul Sander 2025-02-03 21:28 8016
    PreserveOrder = false;
1193b4be Poul Sander 2025-02-03 21:28 8017
    UserData = NULL;
1193b4be Poul Sander 2025-02-03 21:28 8018
    AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; };
1193b4be Poul Sander 2025-02-03 21:28 8019
    _SelectionOrder = 1; // Always >0
1193b4be Poul Sander 2025-02-03 21:28 8020
}
1193b4be Poul Sander 2025-02-03 21:28 8021
1193b4be Poul Sander 2025-02-03 21:28 8022
void ImGuiSelectionBasicStorage::Clear()
1193b4be Poul Sander 2025-02-03 21:28 8023
{
1193b4be Poul Sander 2025-02-03 21:28 8024
    Size = 0;
1193b4be Poul Sander 2025-02-03 21:28 8025
    _SelectionOrder = 1; // Always >0
1193b4be Poul Sander 2025-02-03 21:28 8026
    _Storage.Data.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 8027
}
1193b4be Poul Sander 2025-02-03 21:28 8028
1193b4be Poul Sander 2025-02-03 21:28 8029
void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r)
1193b4be Poul Sander 2025-02-03 21:28 8030
{
1193b4be Poul Sander 2025-02-03 21:28 8031
    ImSwap(Size, r.Size);
1193b4be Poul Sander 2025-02-03 21:28 8032
    ImSwap(_SelectionOrder, r._SelectionOrder);
1193b4be Poul Sander 2025-02-03 21:28 8033
    _Storage.Data.swap(r._Storage.Data);
1193b4be Poul Sander 2025-02-03 21:28 8034
}
1193b4be Poul Sander 2025-02-03 21:28 8035
1193b4be Poul Sander 2025-02-03 21:28 8036
bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const
1193b4be Poul Sander 2025-02-03 21:28 8037
{
1193b4be Poul Sander 2025-02-03 21:28 8038
    return _Storage.GetInt(id, 0) != 0;
1193b4be Poul Sander 2025-02-03 21:28 8039
}
1193b4be Poul Sander 2025-02-03 21:28 8040
1193b4be Poul Sander 2025-02-03 21:28 8041
static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 8042
{
1193b4be Poul Sander 2025-02-03 21:28 8043
    int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i;
1193b4be Poul Sander 2025-02-03 21:28 8044
    int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i;
1193b4be Poul Sander 2025-02-03 21:28 8045
    return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8046
}
1193b4be Poul Sander 2025-02-03 21:28 8047
1193b4be Poul Sander 2025-02-03 21:28 8048
// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user.
1193b4be Poul Sander 2025-02-03 21:28 8049
// (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 8050
bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id)
1193b4be Poul Sander 2025-02-03 21:28 8051
{
1193b4be Poul Sander 2025-02-03 21:28 8052
    ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it;
1193b4be Poul Sander 2025-02-03 21:28 8053
    ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size;
1193b4be Poul Sander 2025-02-03 21:28 8054
    if (PreserveOrder && it == NULL && it_end != NULL)
1193b4be Poul Sander 2025-02-03 21:28 8055
        ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt()
1193b4be Poul Sander 2025-02-03 21:28 8056
    if (it == NULL)
1193b4be Poul Sander 2025-02-03 21:28 8057
        it = _Storage.Data.Data;
1193b4be Poul Sander 2025-02-03 21:28 8058
    IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
1193b4be Poul Sander 2025-02-03 21:28 8059
    if (it != it_end)
1193b4be Poul Sander 2025-02-03 21:28 8060
        while (it->val_i == 0 && it < it_end)
1193b4be Poul Sander 2025-02-03 21:28 8061
            it++;
1193b4be Poul Sander 2025-02-03 21:28 8062
    const bool has_more = (it != it_end);
1193b4be Poul Sander 2025-02-03 21:28 8063
    *opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
1193b4be Poul Sander 2025-02-03 21:28 8064
    *out_id = has_more ? it->key : 0;
1193b4be Poul Sander 2025-02-03 21:28 8065
    if (PreserveOrder && !has_more)
1193b4be Poul Sander 2025-02-03 21:28 8066
        _Storage.BuildSortByKey();
1193b4be Poul Sander 2025-02-03 21:28 8067
    return has_more;
1193b4be Poul Sander 2025-02-03 21:28 8068
}
1193b4be Poul Sander 2025-02-03 21:28 8069
1193b4be Poul Sander 2025-02-03 21:28 8070
void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)
1193b4be Poul Sander 2025-02-03 21:28 8071
{
1193b4be Poul Sander 2025-02-03 21:28 8072
    int* p_int = _Storage.GetIntRef(id, 0);
1193b4be Poul Sander 2025-02-03 21:28 8073
    if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; }
1193b4be Poul Sander 2025-02-03 21:28 8074
    else if (!selected && *p_int != 0) { *p_int = 0; Size--; }
1193b4be Poul Sander 2025-02-03 21:28 8075
}
1193b4be Poul Sander 2025-02-03 21:28 8076
1193b4be Poul Sander 2025-02-03 21:28 8077
// Optimized for batch edits (with same value of 'selected')
1193b4be Poul Sander 2025-02-03 21:28 8078
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 8079
{
1193b4be Poul Sander 2025-02-03 21:28 8080
    ImGuiStorage* storage = &selection->_Storage;
1193b4be Poul Sander 2025-02-03 21:28 8081
    ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);
1193b4be Poul Sander 2025-02-03 21:28 8082
    const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id);
1193b4be Poul Sander 2025-02-03 21:28 8083
    if (selected == (is_contained && it->val_i != 0))
1193b4be Poul Sander 2025-02-03 21:28 8084
        return;
1193b4be Poul Sander 2025-02-03 21:28 8085
    if (selected && !is_contained)
1193b4be Poul Sander 2025-02-03 21:28 8086
        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 8087
    else if (is_contained)
1193b4be Poul Sander 2025-02-03 21:28 8088
        it->val_i = selected ? selection_order : 0; // Modify in-place.
1193b4be Poul Sander 2025-02-03 21:28 8089
    selection->Size += selected ? +1 : -1;
1193b4be Poul Sander 2025-02-03 21:28 8090
}
1193b4be Poul Sander 2025-02-03 21:28 8091
1193b4be Poul Sander 2025-02-03 21:28 8092
static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)
1193b4be Poul Sander 2025-02-03 21:28 8093
{
1193b4be Poul Sander 2025-02-03 21:28 8094
    ImGuiStorage* storage = &selection->_Storage;
1193b4be Poul Sander 2025-02-03 21:28 8095
    if (selected && selection->Size != size_before_amends)
1193b4be Poul Sander 2025-02-03 21:28 8096
        storage->BuildSortByKey(); // When done selecting: sort everything
1193b4be Poul Sander 2025-02-03 21:28 8097
}
1193b4be Poul Sander 2025-02-03 21:28 8098
1193b4be Poul Sander 2025-02-03 21:28 8099
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8100
// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
1193b4be Poul Sander 2025-02-03 21:28 8101
// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
1193b4be Poul Sander 2025-02-03 21:28 8102
//   - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.
1193b4be Poul Sander 2025-02-03 21:28 8103
//   - 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 8104
//     a lookup in order to have some way to iterate/interpolate between two items.
1193b4be Poul Sander 2025-02-03 21:28 8105
// - 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 8106
//   and constructing a view index <> object id/ptr data structure anyway.
1193b4be Poul Sander 2025-02-03 21:28 8107
// 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 8108
// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
1193b4be Poul Sander 2025-02-03 21:28 8109
// The most simple implementation (using indices everywhere) would look like:
1193b4be Poul Sander 2025-02-03 21:28 8110
//   for (ImGuiSelectionRequest& req : ms_io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 8111
//   {
1193b4be Poul Sander 2025-02-03 21:28 8112
//      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 8113
//      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 8114
//   }
1193b4be Poul Sander 2025-02-03 21:28 8115
void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
1193b4be Poul Sander 2025-02-03 21:28 8116
{
1193b4be Poul Sander 2025-02-03 21:28 8117
    // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional.
1193b4be Poul Sander 2025-02-03 21:28 8118
    // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8119
    // Other scheme may handle SetAll differently.
1193b4be Poul Sander 2025-02-03 21:28 8120
    IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
1193b4be Poul Sander 2025-02-03 21:28 8121
    IM_ASSERT(AdapterIndexToStorageId != NULL);
1193b4be Poul Sander 2025-02-03 21:28 8122
1193b4be Poul Sander 2025-02-03 21:28 8123
    // This is optimized/specialized to cope with very large selections (e.g. 100k+ items)
1193b4be Poul Sander 2025-02-03 21:28 8124
    // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().
1193b4be Poul Sander 2025-02-03 21:28 8125
    // - 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 8126
    // - 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 8127
    // - 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
1193b4be Poul Sander 2025-02-03 21:28 8128
    //   left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.)
1193b4be Poul Sander 2025-02-03 21:28 8129
    // FIXME-OPT: For each block of consecutive SetRange request:
1193b4be Poul Sander 2025-02-03 21:28 8130
    // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage.
1193b4be Poul Sander 2025-02-03 21:28 8131
    // - rewrite sorted storage a single time.
1193b4be Poul Sander 2025-02-03 21:28 8132
    for (ImGuiSelectionRequest& req : ms_io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 8133
    {
1193b4be Poul Sander 2025-02-03 21:28 8134
        if (req.Type == ImGuiSelectionRequestType_SetAll)
1193b4be Poul Sander 2025-02-03 21:28 8135
        {
1193b4be Poul Sander 2025-02-03 21:28 8136
            Clear();
1193b4be Poul Sander 2025-02-03 21:28 8137
            if (req.Selected)
1193b4be Poul Sander 2025-02-03 21:28 8138
            {
1193b4be Poul Sander 2025-02-03 21:28 8139
                _Storage.Data.reserve(ms_io->ItemsCount);
1193b4be Poul Sander 2025-02-03 21:28 8140
                const int size_before_amends = _Storage.Data.Size;
1193b4be Poul Sander 2025-02-03 21:28 8141
                for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++)
1193b4be Poul Sander 2025-02-03 21:28 8142
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder);
1193b4be Poul Sander 2025-02-03 21:28 8143
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
1193b4be Poul Sander 2025-02-03 21:28 8144
            }
1193b4be Poul Sander 2025-02-03 21:28 8145
        }
1193b4be Poul Sander 2025-02-03 21:28 8146
        else if (req.Type == ImGuiSelectionRequestType_SetRange)
1193b4be Poul Sander 2025-02-03 21:28 8147
        {
1193b4be Poul Sander 2025-02-03 21:28 8148
            const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1;
1193b4be Poul Sander 2025-02-03 21:28 8149
            //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 8150
            if (selection_changes == 1 || (selection_changes < Size / 100))
1193b4be Poul Sander 2025-02-03 21:28 8151
            {
1193b4be Poul Sander 2025-02-03 21:28 8152
                // Multiple sorted insertion + copy likely to be faster.
1193b4be Poul Sander 2025-02-03 21:28 8153
                // Technically we could do a single copy with a little more work (sort sequential SetRange requests)
1193b4be Poul Sander 2025-02-03 21:28 8154
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
1193b4be Poul Sander 2025-02-03 21:28 8155
                    SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);
1193b4be Poul Sander 2025-02-03 21:28 8156
            }
1193b4be Poul Sander 2025-02-03 21:28 8157
            else
1193b4be Poul Sander 2025-02-03 21:28 8158
            {
1193b4be Poul Sander 2025-02-03 21:28 8159
                // Append insertion + single sort likely be faster.
1193b4be Poul Sander 2025-02-03 21:28 8160
                // 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 8161
                const int size_before_amends = _Storage.Data.Size;
1193b4be Poul Sander 2025-02-03 21:28 8162
                int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8163
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection)
1193b4be Poul Sander 2025-02-03 21:28 8164
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order);
1193b4be Poul Sander 2025-02-03 21:28 8165
                if (req.Selected)
1193b4be Poul Sander 2025-02-03 21:28 8166
                    _SelectionOrder += selection_changes;
1193b4be Poul Sander 2025-02-03 21:28 8167
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
1193b4be Poul Sander 2025-02-03 21:28 8168
            }
1193b4be Poul Sander 2025-02-03 21:28 8169
        }
1193b4be Poul Sander 2025-02-03 21:28 8170
    }
1193b4be Poul Sander 2025-02-03 21:28 8171
}
1193b4be Poul Sander 2025-02-03 21:28 8172
1193b4be Poul Sander 2025-02-03 21:28 8173
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8174
1193b4be Poul Sander 2025-02-03 21:28 8175
ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage()
1193b4be Poul Sander 2025-02-03 21:28 8176
{
1193b4be Poul Sander 2025-02-03 21:28 8177
    UserData = NULL;
1193b4be Poul Sander 2025-02-03 21:28 8178
    AdapterSetItemSelected = NULL;
1193b4be Poul Sander 2025-02-03 21:28 8179
}
1193b4be Poul Sander 2025-02-03 21:28 8180
1193b4be Poul Sander 2025-02-03 21:28 8181
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8182
// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage
1193b4be Poul Sander 2025-02-03 21:28 8183
// This makes no assumption about underlying storage.
1193b4be Poul Sander 2025-02-03 21:28 8184
void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
1193b4be Poul Sander 2025-02-03 21:28 8185
{
1193b4be Poul Sander 2025-02-03 21:28 8186
    IM_ASSERT(AdapterSetItemSelected);
1193b4be Poul Sander 2025-02-03 21:28 8187
    for (ImGuiSelectionRequest& req : ms_io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 8188
    {
1193b4be Poul Sander 2025-02-03 21:28 8189
        if (req.Type == ImGuiSelectionRequestType_SetAll)
1193b4be Poul Sander 2025-02-03 21:28 8190
            for (int idx = 0; idx < ms_io->ItemsCount; idx++)
1193b4be Poul Sander 2025-02-03 21:28 8191
                AdapterSetItemSelected(this, idx, req.Selected);
1193b4be Poul Sander 2025-02-03 21:28 8192
        if (req.Type == ImGuiSelectionRequestType_SetRange)
1193b4be Poul Sander 2025-02-03 21:28 8193
            for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
1193b4be Poul Sander 2025-02-03 21:28 8194
                AdapterSetItemSelected(this, idx, req.Selected);
1193b4be Poul Sander 2025-02-03 21:28 8195
    }
1193b4be Poul Sander 2025-02-03 21:28 8196
}
1193b4be Poul Sander 2025-02-03 21:28 8197
1193b4be Poul Sander 2025-02-03 21:28 8198
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8199
// [SECTION] Widgets: ListBox
1193b4be Poul Sander 2025-02-03 21:28 8200
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8201
// - BeginListBox()
1193b4be Poul Sander 2025-02-03 21:28 8202
// - EndListBox()
1193b4be Poul Sander 2025-02-03 21:28 8203
// - ListBox()
1193b4be Poul Sander 2025-02-03 21:28 8204
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8205
1193b4be Poul Sander 2025-02-03 21:28 8206
// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
1193b4be Poul Sander 2025-02-03 21:28 8207
// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as:
1193b4be Poul Sander 2025-02-03 21:28 8208
//    if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle))
1193b4be Poul Sander 2025-02-03 21:28 8209
//        { .... }
1193b4be Poul Sander 2025-02-03 21:28 8210
//    ImGui::EndChild();
1193b4be Poul Sander 2025-02-03 21:28 8211
//    ImGui::SameLine();
1193b4be Poul Sander 2025-02-03 21:28 8212
//    ImGui::AlignTextToFramePadding();
1193b4be Poul Sander 2025-02-03 21:28 8213
//    ImGui::Text("Label");
1193b4be Poul Sander 2025-02-03 21:28 8214
// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
1193b4be Poul Sander 2025-02-03 21:28 8215
// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height).
1193b4be Poul Sander 2025-02-03 21:28 8216
bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 8217
{
1193b4be Poul Sander 2025-02-03 21:28 8218
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8219
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8220
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8221
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8222
1193b4be Poul Sander 2025-02-03 21:28 8223
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 8224
    const ImGuiID id = GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 8225
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 8226
1193b4be Poul Sander 2025-02-03 21:28 8227
    // Size default to hold ~7.25 items.
1193b4be Poul Sander 2025-02-03 21:28 8228
    // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
1193b4be Poul Sander 2025-02-03 21:28 8229
    ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 8230
    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
1193b4be Poul Sander 2025-02-03 21:28 8231
    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
1193b4be Poul Sander 2025-02-03 21:28 8232
    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 8233
    g.NextItemData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 8234
1193b4be Poul Sander 2025-02-03 21:28 8235
    if (!IsRectVisible(bb.Min, bb.Max))
1193b4be Poul Sander 2025-02-03 21:28 8236
    {
1193b4be Poul Sander 2025-02-03 21:28 8237
        ItemSize(bb.GetSize(), style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 8238
        ItemAdd(bb, 0, &frame_bb);
1193b4be Poul Sander 2025-02-03 21:28 8239
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 8240
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8241
    }
1193b4be Poul Sander 2025-02-03 21:28 8242
1193b4be Poul Sander 2025-02-03 21:28 8243
    // 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 8244
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 8245
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 8246
    {
1193b4be Poul Sander 2025-02-03 21:28 8247
        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 8248
        RenderText(label_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 8249
        window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
1193b4be Poul Sander 2025-02-03 21:28 8250
        AlignTextToFramePadding();
1193b4be Poul Sander 2025-02-03 21:28 8251
    }
1193b4be Poul Sander 2025-02-03 21:28 8252
1193b4be Poul Sander 2025-02-03 21:28 8253
    BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
1193b4be Poul Sander 2025-02-03 21:28 8254
    return true;
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
void ImGui::EndListBox()
1193b4be Poul Sander 2025-02-03 21:28 8258
{
1193b4be Poul Sander 2025-02-03 21:28 8259
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8260
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8261
    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 8262
    IM_UNUSED(window);
1193b4be Poul Sander 2025-02-03 21:28 8263
1193b4be Poul Sander 2025-02-03 21:28 8264
    EndChild();
1193b4be Poul Sander 2025-02-03 21:28 8265
    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 8266
}
1193b4be Poul Sander 2025-02-03 21:28 8267
1193b4be Poul Sander 2025-02-03 21:28 8268
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 8269
{
1193b4be Poul Sander 2025-02-03 21:28 8270
    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
1193b4be Poul Sander 2025-02-03 21:28 8271
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 8272
}
1193b4be Poul Sander 2025-02-03 21:28 8273
1193b4be Poul Sander 2025-02-03 21:28 8274
// This is merely a helper around BeginListBox(), EndListBox().
1193b4be Poul Sander 2025-02-03 21:28 8275
// Considering using those directly to submit custom data or store selection differently.
1193b4be Poul Sander 2025-02-03 21:28 8276
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 8277
{
1193b4be Poul Sander 2025-02-03 21:28 8278
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8279
1193b4be Poul Sander 2025-02-03 21:28 8280
    // Calculate size from "height_in_items"
1193b4be Poul Sander 2025-02-03 21:28 8281
    if (height_in_items < 0)
1193b4be Poul Sander 2025-02-03 21:28 8282
        height_in_items = ImMin(items_count, 7);
1193b4be Poul Sander 2025-02-03 21:28 8283
    float height_in_items_f = height_in_items + 0.25f;
1193b4be Poul Sander 2025-02-03 21:28 8284
    ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 8285
1193b4be Poul Sander 2025-02-03 21:28 8286
    if (!BeginListBox(label, size))
1193b4be Poul Sander 2025-02-03 21:28 8287
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8288
1193b4be Poul Sander 2025-02-03 21:28 8289
    // 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 8290
    // you can create a custom version of ListBox() in your code without using the clipper.
1193b4be Poul Sander 2025-02-03 21:28 8291
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 8292
    ImGuiListClipper clipper;
1193b4be Poul Sander 2025-02-03 21:28 8293
    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 8294
    clipper.IncludeItemByIndex(*current_item);
1193b4be Poul Sander 2025-02-03 21:28 8295
    while (clipper.Step())
1193b4be Poul Sander 2025-02-03 21:28 8296
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
1193b4be Poul Sander 2025-02-03 21:28 8297
        {
1193b4be Poul Sander 2025-02-03 21:28 8298
            const char* item_text = getter(user_data, i);
1193b4be Poul Sander 2025-02-03 21:28 8299
            if (item_text == NULL)
1193b4be Poul Sander 2025-02-03 21:28 8300
                item_text = "*Unknown item*";
1193b4be Poul Sander 2025-02-03 21:28 8301
1193b4be Poul Sander 2025-02-03 21:28 8302
            PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 8303
            const bool item_selected = (i == *current_item);
1193b4be Poul Sander 2025-02-03 21:28 8304
            if (Selectable(item_text, item_selected))
1193b4be Poul Sander 2025-02-03 21:28 8305
            {
1193b4be Poul Sander 2025-02-03 21:28 8306
                *current_item = i;
1193b4be Poul Sander 2025-02-03 21:28 8307
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 8308
            }
1193b4be Poul Sander 2025-02-03 21:28 8309
            if (item_selected)
1193b4be Poul Sander 2025-02-03 21:28 8310
                SetItemDefaultFocus();
1193b4be Poul Sander 2025-02-03 21:28 8311
            PopID();
1193b4be Poul Sander 2025-02-03 21:28 8312
        }
1193b4be Poul Sander 2025-02-03 21:28 8313
    EndListBox();
1193b4be Poul Sander 2025-02-03 21:28 8314
1193b4be Poul Sander 2025-02-03 21:28 8315
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 8316
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 8317
1193b4be Poul Sander 2025-02-03 21:28 8318
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 8319
}
1193b4be Poul Sander 2025-02-03 21:28 8320
1193b4be Poul Sander 2025-02-03 21:28 8321
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8322
// [SECTION] Widgets: PlotLines, PlotHistogram
1193b4be Poul Sander 2025-02-03 21:28 8323
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8324
// - PlotEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 8325
// - PlotLines()
1193b4be Poul Sander 2025-02-03 21:28 8326
// - PlotHistogram()
1193b4be Poul Sander 2025-02-03 21:28 8327
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8328
// Plot/Graph widgets are not very good.
1193b4be Poul Sander 2025-02-03 21:28 8329
// Consider writing your own, or using a third-party one, see:
1193b4be Poul Sander 2025-02-03 21:28 8330
// - ImPlot https://github.com/epezent/implot
1193b4be Poul Sander 2025-02-03 21:28 8331
// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
1193b4be Poul Sander 2025-02-03 21:28 8332
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8333
1193b4be Poul Sander 2025-02-03 21:28 8334
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 8335
{
1193b4be Poul Sander 2025-02-03 21:28 8336
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8337
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8338
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8339
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 8340
1193b4be Poul Sander 2025-02-03 21:28 8341
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 8342
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 8343
1193b4be Poul Sander 2025-02-03 21:28 8344
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 8345
    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 8346
1193b4be Poul Sander 2025-02-03 21:28 8347
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
1193b4be Poul Sander 2025-02-03 21:28 8348
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
1193b4be Poul Sander 2025-02-03 21:28 8349
    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 8350
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 8351
    if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav))
1193b4be Poul Sander 2025-02-03 21:28 8352
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 8353
    bool hovered;
1193b4be Poul Sander 2025-02-03 21:28 8354
    ButtonBehavior(frame_bb, id, &hovered, NULL);
1193b4be Poul Sander 2025-02-03 21:28 8355
1193b4be Poul Sander 2025-02-03 21:28 8356
    // Determine scale from values if not specified
1193b4be Poul Sander 2025-02-03 21:28 8357
    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 8358
    {
1193b4be Poul Sander 2025-02-03 21:28 8359
        float v_min = FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 8360
        float v_max = -FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 8361
        for (int i = 0; i < values_count; i++)
1193b4be Poul Sander 2025-02-03 21:28 8362
        {
1193b4be Poul Sander 2025-02-03 21:28 8363
            const float v = values_getter(data, i);
1193b4be Poul Sander 2025-02-03 21:28 8364
            if (v != v) // Ignore NaN values
1193b4be Poul Sander 2025-02-03 21:28 8365
                continue;
1193b4be Poul Sander 2025-02-03 21:28 8366
            v_min = ImMin(v_min, v);
1193b4be Poul Sander 2025-02-03 21:28 8367
            v_max = ImMax(v_max, v);
1193b4be Poul Sander 2025-02-03 21:28 8368
        }
1193b4be Poul Sander 2025-02-03 21:28 8369
        if (scale_min == FLT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 8370
            scale_min = v_min;
1193b4be Poul Sander 2025-02-03 21:28 8371
        if (scale_max == FLT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 8372
            scale_max = v_max;
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
    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 8376
1193b4be Poul Sander 2025-02-03 21:28 8377
    const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
1193b4be Poul Sander 2025-02-03 21:28 8378
    int idx_hovered = -1;
1193b4be Poul Sander 2025-02-03 21:28 8379
    if (values_count >= values_count_min)
1193b4be Poul Sander 2025-02-03 21:28 8380
    {
1193b4be Poul Sander 2025-02-03 21:28 8381
        int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8382
        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8383
1193b4be Poul Sander 2025-02-03 21:28 8384
        // Tooltip on hover
1193b4be Poul Sander 2025-02-03 21:28 8385
        if (hovered && inner_bb.Contains(g.IO.MousePos))
1193b4be Poul Sander 2025-02-03 21:28 8386
        {
1193b4be Poul Sander 2025-02-03 21:28 8387
            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 8388
            const int v_idx = (int)(t * item_count);
1193b4be Poul Sander 2025-02-03 21:28 8389
            IM_ASSERT(v_idx >= 0 && v_idx < values_count);
1193b4be Poul Sander 2025-02-03 21:28 8390
1193b4be Poul Sander 2025-02-03 21:28 8391
            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8392
            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8393
            if (plot_type == ImGuiPlotType_Lines)
1193b4be Poul Sander 2025-02-03 21:28 8394
                SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
1193b4be Poul Sander 2025-02-03 21:28 8395
            else if (plot_type == ImGuiPlotType_Histogram)
1193b4be Poul Sander 2025-02-03 21:28 8396
                SetTooltip("%d: %8.4g", v_idx, v0);
1193b4be Poul Sander 2025-02-03 21:28 8397
            idx_hovered = v_idx;
1193b4be Poul Sander 2025-02-03 21:28 8398
        }
1193b4be Poul Sander 2025-02-03 21:28 8399
1193b4be Poul Sander 2025-02-03 21:28 8400
        const float t_step = 1.0f / (float)res_w;
1193b4be Poul Sander 2025-02-03 21:28 8401
        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
1193b4be Poul Sander 2025-02-03 21:28 8402
1193b4be Poul Sander 2025-02-03 21:28 8403
        float v0 = values_getter(data, (0 + values_offset) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8404
        float t0 = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 8405
        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 8406
        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 8407
1193b4be Poul Sander 2025-02-03 21:28 8408
        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
1193b4be Poul Sander 2025-02-03 21:28 8409
        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
1193b4be Poul Sander 2025-02-03 21:28 8410
1193b4be Poul Sander 2025-02-03 21:28 8411
        for (int n = 0; n < res_w; n++)
1193b4be Poul Sander 2025-02-03 21:28 8412
        {
1193b4be Poul Sander 2025-02-03 21:28 8413
            const float t1 = t0 + t_step;
1193b4be Poul Sander 2025-02-03 21:28 8414
            const int v1_idx = (int)(t0 * item_count + 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 8415
            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
1193b4be Poul Sander 2025-02-03 21:28 8416
            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8417
            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
1193b4be Poul Sander 2025-02-03 21:28 8418
1193b4be Poul Sander 2025-02-03 21:28 8419
            // 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 8420
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
1193b4be Poul Sander 2025-02-03 21:28 8421
            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 8422
            if (plot_type == ImGuiPlotType_Lines)
1193b4be Poul Sander 2025-02-03 21:28 8423
            {
1193b4be Poul Sander 2025-02-03 21:28 8424
                window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
1193b4be Poul Sander 2025-02-03 21:28 8425
            }
1193b4be Poul Sander 2025-02-03 21:28 8426
            else if (plot_type == ImGuiPlotType_Histogram)
1193b4be Poul Sander 2025-02-03 21:28 8427
            {
1193b4be Poul Sander 2025-02-03 21:28 8428
                if (pos1.x >= pos0.x + 2.0f)
1193b4be Poul Sander 2025-02-03 21:28 8429
                    pos1.x -= 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 8430
                window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
1193b4be Poul Sander 2025-02-03 21:28 8431
            }
1193b4be Poul Sander 2025-02-03 21:28 8432
1193b4be Poul Sander 2025-02-03 21:28 8433
            t0 = t1;
1193b4be Poul Sander 2025-02-03 21:28 8434
            tp0 = tp1;
1193b4be Poul Sander 2025-02-03 21:28 8435
        }
1193b4be Poul Sander 2025-02-03 21:28 8436
    }
1193b4be Poul Sander 2025-02-03 21:28 8437
1193b4be Poul Sander 2025-02-03 21:28 8438
    // Text overlay
1193b4be Poul Sander 2025-02-03 21:28 8439
    if (overlay_text)
1193b4be Poul Sander 2025-02-03 21:28 8440
        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 8441
1193b4be Poul Sander 2025-02-03 21:28 8442
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 8443
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
1193b4be Poul Sander 2025-02-03 21:28 8444
1193b4be Poul Sander 2025-02-03 21:28 8445
    // Return hovered index or -1 if none are hovered.
1193b4be Poul Sander 2025-02-03 21:28 8446
    // 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 8447
    return idx_hovered;
1193b4be Poul Sander 2025-02-03 21:28 8448
}
1193b4be Poul Sander 2025-02-03 21:28 8449
1193b4be Poul Sander 2025-02-03 21:28 8450
struct ImGuiPlotArrayGetterData
1193b4be Poul Sander 2025-02-03 21:28 8451
{
1193b4be Poul Sander 2025-02-03 21:28 8452
    const float* Values;
1193b4be Poul Sander 2025-02-03 21:28 8453
    int Stride;
1193b4be Poul Sander 2025-02-03 21:28 8454
1193b4be Poul Sander 2025-02-03 21:28 8455
    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
1193b4be Poul Sander 2025-02-03 21:28 8456
};
1193b4be Poul Sander 2025-02-03 21:28 8457
1193b4be Poul Sander 2025-02-03 21:28 8458
static float Plot_ArrayGetter(void* data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 8459
{
1193b4be Poul Sander 2025-02-03 21:28 8460
    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
1193b4be Poul Sander 2025-02-03 21:28 8461
    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 8462
    return v;
1193b4be Poul Sander 2025-02-03 21:28 8463
}
1193b4be Poul Sander 2025-02-03 21:28 8464
1193b4be Poul Sander 2025-02-03 21:28 8465
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 8466
{
1193b4be Poul Sander 2025-02-03 21:28 8467
    ImGuiPlotArrayGetterData data(values, stride);
1193b4be Poul Sander 2025-02-03 21:28 8468
    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 8469
}
1193b4be Poul Sander 2025-02-03 21:28 8470
1193b4be Poul Sander 2025-02-03 21:28 8471
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 8472
{
1193b4be Poul Sander 2025-02-03 21:28 8473
    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 8474
}
1193b4be Poul Sander 2025-02-03 21:28 8475
1193b4be Poul Sander 2025-02-03 21:28 8476
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 8477
{
1193b4be Poul Sander 2025-02-03 21:28 8478
    ImGuiPlotArrayGetterData data(values, stride);
1193b4be Poul Sander 2025-02-03 21:28 8479
    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 8480
}
1193b4be Poul Sander 2025-02-03 21:28 8481
1193b4be Poul Sander 2025-02-03 21:28 8482
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 8483
{
1193b4be Poul Sander 2025-02-03 21:28 8484
    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 8485
}
1193b4be Poul Sander 2025-02-03 21:28 8486
1193b4be Poul Sander 2025-02-03 21:28 8487
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8488
// [SECTION] Widgets: Value helpers
1193b4be Poul Sander 2025-02-03 21:28 8489
// Those is not very useful, legacy API.
1193b4be Poul Sander 2025-02-03 21:28 8490
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8491
// - Value()
1193b4be Poul Sander 2025-02-03 21:28 8492
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8493
1193b4be Poul Sander 2025-02-03 21:28 8494
void ImGui::Value(const char* prefix, bool b)
1193b4be Poul Sander 2025-02-03 21:28 8495
{
1193b4be Poul Sander 2025-02-03 21:28 8496
    Text("%s: %s", prefix, (b ? "true" : "false"));
1193b4be Poul Sander 2025-02-03 21:28 8497
}
1193b4be Poul Sander 2025-02-03 21:28 8498
1193b4be Poul Sander 2025-02-03 21:28 8499
void ImGui::Value(const char* prefix, int v)
1193b4be Poul Sander 2025-02-03 21:28 8500
{
1193b4be Poul Sander 2025-02-03 21:28 8501
    Text("%s: %d", prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8502
}
1193b4be Poul Sander 2025-02-03 21:28 8503
1193b4be Poul Sander 2025-02-03 21:28 8504
void ImGui::Value(const char* prefix, unsigned int v)
1193b4be Poul Sander 2025-02-03 21:28 8505
{
1193b4be Poul Sander 2025-02-03 21:28 8506
    Text("%s: %d", prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8507
}
1193b4be Poul Sander 2025-02-03 21:28 8508
1193b4be Poul Sander 2025-02-03 21:28 8509
void ImGui::Value(const char* prefix, float v, const char* float_format)
1193b4be Poul Sander 2025-02-03 21:28 8510
{
1193b4be Poul Sander 2025-02-03 21:28 8511
    if (float_format)
1193b4be Poul Sander 2025-02-03 21:28 8512
    {
1193b4be Poul Sander 2025-02-03 21:28 8513
        char fmt[64];
1193b4be Poul Sander 2025-02-03 21:28 8514
        ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
1193b4be Poul Sander 2025-02-03 21:28 8515
        Text(fmt, prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8516
    }
1193b4be Poul Sander 2025-02-03 21:28 8517
    else
1193b4be Poul Sander 2025-02-03 21:28 8518
    {
1193b4be Poul Sander 2025-02-03 21:28 8519
        Text("%s: %.3f", prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8520
    }
1193b4be Poul Sander 2025-02-03 21:28 8521
}
1193b4be Poul Sander 2025-02-03 21:28 8522
1193b4be Poul Sander 2025-02-03 21:28 8523
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8524
// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
1193b4be Poul Sander 2025-02-03 21:28 8525
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8526
// - ImGuiMenuColumns [Internal]
1193b4be Poul Sander 2025-02-03 21:28 8527
// - BeginMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8528
// - EndMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8529
// - BeginMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8530
// - EndMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8531
// - BeginMenu()
1193b4be Poul Sander 2025-02-03 21:28 8532
// - EndMenu()
1193b4be Poul Sander 2025-02-03 21:28 8533
// - MenuItemEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 8534
// - MenuItem()
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
// Helpers for internal use
1193b4be Poul Sander 2025-02-03 21:28 8538
void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
1193b4be Poul Sander 2025-02-03 21:28 8539
{
1193b4be Poul Sander 2025-02-03 21:28 8540
    if (window_reappearing)
1193b4be Poul Sander 2025-02-03 21:28 8541
        memset(Widths, 0, sizeof(Widths));
1193b4be Poul Sander 2025-02-03 21:28 8542
    Spacing = (ImU16)spacing;
1193b4be Poul Sander 2025-02-03 21:28 8543
    CalcNextTotalWidth(true);
1193b4be Poul Sander 2025-02-03 21:28 8544
    memset(Widths, 0, sizeof(Widths));
1193b4be Poul Sander 2025-02-03 21:28 8545
    TotalWidth = NextTotalWidth;
1193b4be Poul Sander 2025-02-03 21:28 8546
    NextTotalWidth = 0;
1193b4be Poul Sander 2025-02-03 21:28 8547
}
1193b4be Poul Sander 2025-02-03 21:28 8548
1193b4be Poul Sander 2025-02-03 21:28 8549
void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
1193b4be Poul Sander 2025-02-03 21:28 8550
{
1193b4be Poul Sander 2025-02-03 21:28 8551
    ImU16 offset = 0;
1193b4be Poul Sander 2025-02-03 21:28 8552
    bool want_spacing = false;
1193b4be Poul Sander 2025-02-03 21:28 8553
    for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
1193b4be Poul Sander 2025-02-03 21:28 8554
    {
1193b4be Poul Sander 2025-02-03 21:28 8555
        ImU16 width = Widths[i];
1193b4be Poul Sander 2025-02-03 21:28 8556
        if (want_spacing && width > 0)
1193b4be Poul Sander 2025-02-03 21:28 8557
            offset += Spacing;
1193b4be Poul Sander 2025-02-03 21:28 8558
        want_spacing |= (width > 0);
1193b4be Poul Sander 2025-02-03 21:28 8559
        if (update_offsets)
1193b4be Poul Sander 2025-02-03 21:28 8560
        {
1193b4be Poul Sander 2025-02-03 21:28 8561
            if (i == 1) { OffsetLabel = offset; }
1193b4be Poul Sander 2025-02-03 21:28 8562
            if (i == 2) { OffsetShortcut = offset; }
1193b4be Poul Sander 2025-02-03 21:28 8563
            if (i == 3) { OffsetMark = offset; }
1193b4be Poul Sander 2025-02-03 21:28 8564
        }
1193b4be Poul Sander 2025-02-03 21:28 8565
        offset += width;
1193b4be Poul Sander 2025-02-03 21:28 8566
    }
1193b4be Poul Sander 2025-02-03 21:28 8567
    NextTotalWidth = offset;
1193b4be Poul Sander 2025-02-03 21:28 8568
}
1193b4be Poul Sander 2025-02-03 21:28 8569
1193b4be Poul Sander 2025-02-03 21:28 8570
float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
1193b4be Poul Sander 2025-02-03 21:28 8571
{
1193b4be Poul Sander 2025-02-03 21:28 8572
    Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
1193b4be Poul Sander 2025-02-03 21:28 8573
    Widths[1] = ImMax(Widths[1], (ImU16)w_label);
1193b4be Poul Sander 2025-02-03 21:28 8574
    Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
1193b4be Poul Sander 2025-02-03 21:28 8575
    Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
1193b4be Poul Sander 2025-02-03 21:28 8576
    CalcNextTotalWidth(false);
1193b4be Poul Sander 2025-02-03 21:28 8577
    return (float)ImMax(TotalWidth, NextTotalWidth);
1193b4be Poul Sander 2025-02-03 21:28 8578
}
1193b4be Poul Sander 2025-02-03 21:28 8579
1193b4be Poul Sander 2025-02-03 21:28 8580
// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
1193b4be Poul Sander 2025-02-03 21:28 8581
// 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 8582
// 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 8583
// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
1193b4be Poul Sander 2025-02-03 21:28 8584
bool ImGui::BeginMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8585
{
1193b4be Poul Sander 2025-02-03 21:28 8586
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8587
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8588
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8589
    if (!(window->Flags & ImGuiWindowFlags_MenuBar))
1193b4be Poul Sander 2025-02-03 21:28 8590
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8591
1193b4be Poul Sander 2025-02-03 21:28 8592
    IM_ASSERT(!window->DC.MenuBarAppending);
1193b4be Poul Sander 2025-02-03 21:28 8593
    BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
1193b4be Poul Sander 2025-02-03 21:28 8594
    PushID("##menubar");
1193b4be Poul Sander 2025-02-03 21:28 8595
1193b4be Poul Sander 2025-02-03 21:28 8596
    // 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 8597
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 8598
    ImRect bar_rect = window->MenuBarRect();
1193b4be Poul Sander 2025-02-03 21:28 8599
    ImRect clip_rect(ImFloor(bar_rect.Min.x + window->WindowBorderSize), ImFloor(bar_rect.Min.y + window->WindowBorderSize), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), ImFloor(bar_rect.Max.y));
1193b4be Poul Sander 2025-02-03 21:28 8600
    clip_rect.ClipWith(window->OuterRectClipped);
1193b4be Poul Sander 2025-02-03 21:28 8601
    PushClipRect(clip_rect.Min, clip_rect.Max, false);
1193b4be Poul Sander 2025-02-03 21:28 8602
1193b4be Poul Sander 2025-02-03 21:28 8603
    // 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 8604
    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 8605
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1193b4be Poul Sander 2025-02-03 21:28 8606
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 8607
    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
1193b4be Poul Sander 2025-02-03 21:28 8608
    window->DC.MenuBarAppending = true;
1193b4be Poul Sander 2025-02-03 21:28 8609
    AlignTextToFramePadding();
1193b4be Poul Sander 2025-02-03 21:28 8610
    return true;
1193b4be Poul Sander 2025-02-03 21:28 8611
}
1193b4be Poul Sander 2025-02-03 21:28 8612
1193b4be Poul Sander 2025-02-03 21:28 8613
void ImGui::EndMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8614
{
1193b4be Poul Sander 2025-02-03 21:28 8615
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8616
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8617
        return;
1193b4be Poul Sander 2025-02-03 21:28 8618
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8619
1193b4be Poul Sander 2025-02-03 21:28 8620
    // 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 8621
    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 8622
    {
1193b4be Poul Sander 2025-02-03 21:28 8623
        // Try to find out if the request is for one of our child menu
1193b4be Poul Sander 2025-02-03 21:28 8624
        ImGuiWindow* nav_earliest_child = g.NavWindow;
1193b4be Poul Sander 2025-02-03 21:28 8625
        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 8626
            nav_earliest_child = nav_earliest_child->ParentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8627
        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 8628
        {
1193b4be Poul Sander 2025-02-03 21:28 8629
            // 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 8630
            // 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 8631
            const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
1193b4be Poul Sander 2025-02-03 21:28 8632
            IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)
1193b4be Poul Sander 2025-02-03 21:28 8633
            FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 8634
            SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
1193b4be Poul Sander 2025-02-03 21:28 8635
            // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto?
1193b4be Poul Sander 2025-02-03 21:28 8636
            if (g.NavCursorVisible)
1193b4be Poul Sander 2025-02-03 21:28 8637
            {
1193b4be Poul Sander 2025-02-03 21:28 8638
                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 8639
                g.NavCursorHideFrames = 2;
1193b4be Poul Sander 2025-02-03 21:28 8640
            }
1193b4be Poul Sander 2025-02-03 21:28 8641
            g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 8642
            NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
1193b4be Poul Sander 2025-02-03 21:28 8643
        }
1193b4be Poul Sander 2025-02-03 21:28 8644
    }
1193b4be Poul Sander 2025-02-03 21:28 8645
1193b4be Poul Sander 2025-02-03 21:28 8646
    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
1193b4be Poul Sander 2025-02-03 21:28 8647
    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
1193b4be Poul Sander 2025-02-03 21:28 8648
    IM_ASSERT(window->DC.MenuBarAppending);
1193b4be Poul Sander 2025-02-03 21:28 8649
    PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 8650
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 8651
    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 8652
1193b4be Poul Sander 2025-02-03 21:28 8653
    // 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 8654
    ImGuiGroupData& group_data = g.GroupStack.back();
1193b4be Poul Sander 2025-02-03 21:28 8655
    group_data.EmitItem = false;
1193b4be Poul Sander 2025-02-03 21:28 8656
    ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 8657
    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 8658
    EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore
1193b4be Poul Sander 2025-02-03 21:28 8659
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
1193b4be Poul Sander 2025-02-03 21:28 8660
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 8661
    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
1193b4be Poul Sander 2025-02-03 21:28 8662
    window->DC.MenuBarAppending = false;
1193b4be Poul Sander 2025-02-03 21:28 8663
    window->DC.CursorMaxPos = restore_cursor_max_pos;
1193b4be Poul Sander 2025-02-03 21:28 8664
}
1193b4be Poul Sander 2025-02-03 21:28 8665
1193b4be Poul Sander 2025-02-03 21:28 8666
// Important: calling order matters!
1193b4be Poul Sander 2025-02-03 21:28 8667
// FIXME: Somehow overlapping with docking tech.
1193b4be Poul Sander 2025-02-03 21:28 8668
// 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 8669
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 8670
{
1193b4be Poul Sander 2025-02-03 21:28 8671
    IM_ASSERT(dir != ImGuiDir_None);
1193b4be Poul Sander 2025-02-03 21:28 8672
1193b4be Poul Sander 2025-02-03 21:28 8673
    ImGuiWindow* bar_window = FindWindowByName(name);
1193b4be Poul Sander 2025-02-03 21:28 8674
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
1193b4be Poul Sander 2025-02-03 21:28 8675
    if (bar_window == NULL || bar_window->BeginCount == 0)
1193b4be Poul Sander 2025-02-03 21:28 8676
    {
1193b4be Poul Sander 2025-02-03 21:28 8677
        // Calculate and set window size/position
1193b4be Poul Sander 2025-02-03 21:28 8678
        ImRect avail_rect = viewport->GetBuildWorkRect();
1193b4be Poul Sander 2025-02-03 21:28 8679
        ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
1193b4be Poul Sander 2025-02-03 21:28 8680
        ImVec2 pos = avail_rect.Min;
1193b4be Poul Sander 2025-02-03 21:28 8681
        if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
1193b4be Poul Sander 2025-02-03 21:28 8682
            pos[axis] = avail_rect.Max[axis] - axis_size;
1193b4be Poul Sander 2025-02-03 21:28 8683
        ImVec2 size = avail_rect.GetSize();
1193b4be Poul Sander 2025-02-03 21:28 8684
        size[axis] = axis_size;
1193b4be Poul Sander 2025-02-03 21:28 8685
        SetNextWindowPos(pos);
1193b4be Poul Sander 2025-02-03 21:28 8686
        SetNextWindowSize(size);
1193b4be Poul Sander 2025-02-03 21:28 8687
1193b4be Poul Sander 2025-02-03 21:28 8688
        // Report our size into work area (for next frame) using actual window size
1193b4be Poul Sander 2025-02-03 21:28 8689
        if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
1193b4be Poul Sander 2025-02-03 21:28 8690
            viewport->BuildWorkInsetMin[axis] += axis_size;
1193b4be Poul Sander 2025-02-03 21:28 8691
        else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
1193b4be Poul Sander 2025-02-03 21:28 8692
            viewport->BuildWorkInsetMax[axis] += axis_size;
1193b4be Poul Sander 2025-02-03 21:28 8693
    }
1193b4be Poul Sander 2025-02-03 21:28 8694
1193b4be Poul Sander 2025-02-03 21:28 8695
    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
1193b4be Poul Sander 2025-02-03 21:28 8696
    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 8697
    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 8698
    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
1193b4be Poul Sander 2025-02-03 21:28 8699
    bool is_open = Begin(name, NULL, window_flags);
1193b4be Poul Sander 2025-02-03 21:28 8700
    PopStyleVar(2);
1193b4be Poul Sander 2025-02-03 21:28 8701
1193b4be Poul Sander 2025-02-03 21:28 8702
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 8703
}
1193b4be Poul Sander 2025-02-03 21:28 8704
1193b4be Poul Sander 2025-02-03 21:28 8705
bool ImGui::BeginMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8706
{
1193b4be Poul Sander 2025-02-03 21:28 8707
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8708
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
1193b4be Poul Sander 2025-02-03 21:28 8709
1193b4be Poul Sander 2025-02-03 21:28 8710
    // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
1193b4be Poul Sander 2025-02-03 21:28 8711
    SetCurrentViewport(NULL, viewport);
1193b4be Poul Sander 2025-02-03 21:28 8712
1193b4be Poul Sander 2025-02-03 21:28 8713
    // 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 8714
    // 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 8715
    // 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 8716
    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 8717
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
1193b4be Poul Sander 2025-02-03 21:28 8718
    float height = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 8719
    bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
1193b4be Poul Sander 2025-02-03 21:28 8720
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 8721
1193b4be Poul Sander 2025-02-03 21:28 8722
    if (is_open)
1193b4be Poul Sander 2025-02-03 21:28 8723
        BeginMenuBar();
1193b4be Poul Sander 2025-02-03 21:28 8724
    else
1193b4be Poul Sander 2025-02-03 21:28 8725
        End();
1193b4be Poul Sander 2025-02-03 21:28 8726
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 8727
}
1193b4be Poul Sander 2025-02-03 21:28 8728
1193b4be Poul Sander 2025-02-03 21:28 8729
void ImGui::EndMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8730
{
1193b4be Poul Sander 2025-02-03 21:28 8731
    EndMenuBar();
1193b4be Poul Sander 2025-02-03 21:28 8732
1193b4be Poul Sander 2025-02-03 21:28 8733
    // 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 8734
    // FIXME: With this strategy we won't be able to restore a NULL focus.
1193b4be Poul Sander 2025-02-03 21:28 8735
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8736
    if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
1193b4be Poul Sander 2025-02-03 21:28 8737
        FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
1193b4be Poul Sander 2025-02-03 21:28 8738
1193b4be Poul Sander 2025-02-03 21:28 8739
    End();
1193b4be Poul Sander 2025-02-03 21:28 8740
}
1193b4be Poul Sander 2025-02-03 21:28 8741
1193b4be Poul Sander 2025-02-03 21:28 8742
static bool IsRootOfOpenMenuSet()
1193b4be Poul Sander 2025-02-03 21:28 8743
{
1193b4be Poul Sander 2025-02-03 21:28 8744
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8745
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8746
    if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 8747
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8748
1193b4be Poul Sander 2025-02-03 21:28 8749
    // 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 8750
    // (e.g. inside menu bar vs loose menu items) based on parent ID.
1193b4be Poul Sander 2025-02-03 21:28 8751
    // This would however prevent the use of e.g. PushID() user code submitting menus.
1193b4be Poul Sander 2025-02-03 21:28 8752
    // 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 8753
    // 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 8754
    // 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 8755
    // 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 8756
    // 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 8757
    // 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 8758
    // 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 8759
    // it likely won't be a problem anyone runs into.
1193b4be Poul Sander 2025-02-03 21:28 8760
    const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
1193b4be Poul Sander 2025-02-03 21:28 8761
    if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)
1193b4be Poul Sander 2025-02-03 21:28 8762
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8763
    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 8764
}
1193b4be Poul Sander 2025-02-03 21:28 8765
1193b4be Poul Sander 2025-02-03 21:28 8766
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 8767
{
1193b4be Poul Sander 2025-02-03 21:28 8768
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8769
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8770
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8771
1193b4be Poul Sander 2025-02-03 21:28 8772
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8773
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 8774
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 8775
    bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 8776
1193b4be Poul Sander 2025-02-03 21:28 8777
    // 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 8778
    // 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 8779
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
1193b4be Poul Sander 2025-02-03 21:28 8780
    if (window->Flags & ImGuiWindowFlags_ChildMenu)
1193b4be Poul Sander 2025-02-03 21:28 8781
        window_flags |= ImGuiWindowFlags_ChildWindow;
1193b4be Poul Sander 2025-02-03 21:28 8782
1193b4be Poul Sander 2025-02-03 21:28 8783
    // 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 8784
    // 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 8785
    // 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 8786
    if (g.MenusIdSubmittedThisFrame.contains(id))
1193b4be Poul Sander 2025-02-03 21:28 8787
    {
1193b4be Poul Sander 2025-02-03 21:28 8788
        if (menu_is_open)
1193b4be Poul Sander 2025-02-03 21:28 8789
            menu_is_open = BeginPopupEx(id, 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 8790
        else
1193b4be Poul Sander 2025-02-03 21:28 8791
            g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 8792
        return menu_is_open;
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
    // 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 8796
    g.MenusIdSubmittedThisFrame.push_back(id);
1193b4be Poul Sander 2025-02-03 21:28 8797
1193b4be Poul Sander 2025-02-03 21:28 8798
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 8799
1193b4be Poul Sander 2025-02-03 21:28 8800
    // 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 8801
    // This is only done for items for the menu set and not the full parent window.
1193b4be Poul Sander 2025-02-03 21:28 8802
    const bool menuset_is_open = IsRootOfOpenMenuSet();
1193b4be Poul Sander 2025-02-03 21:28 8803
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 8804
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
1193b4be Poul Sander 2025-02-03 21:28 8805
1193b4be Poul Sander 2025-02-03 21:28 8806
    // 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 8807
    // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
1193b4be Poul Sander 2025-02-03 21:28 8808
    // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
1193b4be Poul Sander 2025-02-03 21:28 8809
    ImVec2 popup_pos, pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 8810
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 8811
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 8812
        BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 8813
    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
1193b4be Poul Sander 2025-02-03 21:28 8814
    bool pressed;
1193b4be Poul Sander 2025-02-03 21:28 8815
1193b4be Poul Sander 2025-02-03 21:28 8816
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
1193b4be Poul Sander 2025-02-03 21:28 8817
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
1193b4be Poul Sander 2025-02-03 21:28 8818
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
1193b4be Poul Sander 2025-02-03 21:28 8819
    {
1193b4be Poul Sander 2025-02-03 21:28 8820
        // Menu inside an horizontal menu bar
1193b4be Poul Sander 2025-02-03 21:28 8821
        // Selectable extend their highlight by half ItemSpacing in each direction.
1193b4be Poul Sander 2025-02-03 21:28 8822
        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
1193b4be Poul Sander 2025-02-03 21:28 8823
        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 8824
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 8825
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 8826
        float w = label_size.x;
1193b4be Poul Sander 2025-02-03 21:28 8827
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 8828
        pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y));
1193b4be Poul Sander 2025-02-03 21:28 8829
        RenderText(text_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 8830
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 8831
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
1193b4be Poul Sander 2025-02-03 21:28 8832
    }
1193b4be Poul Sander 2025-02-03 21:28 8833
    else
1193b4be Poul Sander 2025-02-03 21:28 8834
    {
1193b4be Poul Sander 2025-02-03 21:28 8835
        // Menu inside a regular/vertical menu
1193b4be Poul Sander 2025-02-03 21:28 8836
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
1193b4be Poul Sander 2025-02-03 21:28 8837
        //  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 8838
        popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
1193b4be Poul Sander 2025-02-03 21:28 8839
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 8840
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
1193b4be Poul Sander 2025-02-03 21:28 8841
        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 8842
        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
1193b4be Poul Sander 2025-02-03 21:28 8843
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 8844
        pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
1193b4be Poul Sander 2025-02-03 21:28 8845
        RenderText(text_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 8846
        if (icon_w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 8847
            RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
1193b4be Poul Sander 2025-02-03 21:28 8848
        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 8849
    }
1193b4be Poul Sander 2025-02-03 21:28 8850
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 8851
        EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 8852
1193b4be Poul Sander 2025-02-03 21:28 8853
    const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;
1193b4be Poul Sander 2025-02-03 21:28 8854
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 8855
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 8856
1193b4be Poul Sander 2025-02-03 21:28 8857
    bool want_open = false;
1193b4be Poul Sander 2025-02-03 21:28 8858
    bool want_open_nav_init = false;
1193b4be Poul Sander 2025-02-03 21:28 8859
    bool want_close = false;
1193b4be Poul Sander 2025-02-03 21:28 8860
    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 8861
    {
1193b4be Poul Sander 2025-02-03 21:28 8862
        // 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 8863
        // 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 8864
        bool moving_toward_child_menu = false;
1193b4be Poul Sander 2025-02-03 21:28 8865
        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 8866
        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 8867
        if (g.HoveredWindow == window && child_menu_window != NULL)
1193b4be Poul Sander 2025-02-03 21:28 8868
        {
1193b4be Poul Sander 2025-02-03 21:28 8869
            const float ref_unit = g.FontSize; // FIXME-DPI
1193b4be Poul Sander 2025-02-03 21:28 8870
            const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
1193b4be Poul Sander 2025-02-03 21:28 8871
            const ImRect next_window_rect = child_menu_window->Rect();
1193b4be Poul Sander 2025-02-03 21:28 8872
            ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
1193b4be Poul Sander 2025-02-03 21:28 8873
            ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
1193b4be Poul Sander 2025-02-03 21:28 8874
            ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();
1193b4be Poul Sander 2025-02-03 21:28 8875
            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 8876
            ta.x += child_dir * -0.5f;
1193b4be Poul Sander 2025-02-03 21:28 8877
            tb.x += child_dir * ref_unit;
1193b4be Poul Sander 2025-02-03 21:28 8878
            tc.x += child_dir * ref_unit;
1193b4be Poul Sander 2025-02-03 21:28 8879
            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 8880
            tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);
1193b4be Poul Sander 2025-02-03 21:28 8881
            moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 8882
            //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 8883
        }
1193b4be Poul Sander 2025-02-03 21:28 8884
1193b4be Poul Sander 2025-02-03 21:28 8885
        // 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 8886
        // 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 8887
        // (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 8888
        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 8889
            want_close = true;
1193b4be Poul Sander 2025-02-03 21:28 8890
1193b4be Poul Sander 2025-02-03 21:28 8891
        // Open
1193b4be Poul Sander 2025-02-03 21:28 8892
        // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)
1193b4be Poul Sander 2025-02-03 21:28 8893
        if (!menu_is_open && pressed) // Click/activate to open
1193b4be Poul Sander 2025-02-03 21:28 8894
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 8895
        else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
1193b4be Poul Sander 2025-02-03 21:28 8896
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 8897
        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 8898
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 8899
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
1193b4be Poul Sander 2025-02-03 21:28 8900
        {
1193b4be Poul Sander 2025-02-03 21:28 8901
            want_open = want_open_nav_init = true;
1193b4be Poul Sander 2025-02-03 21:28 8902
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 8903
            SetNavCursorVisibleAfterMove();
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
    else
1193b4be Poul Sander 2025-02-03 21:28 8907
    {
1193b4be Poul Sander 2025-02-03 21:28 8908
        // Menu bar
1193b4be Poul Sander 2025-02-03 21:28 8909
        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
1193b4be Poul Sander 2025-02-03 21:28 8910
        {
1193b4be Poul Sander 2025-02-03 21:28 8911
            want_close = true;
1193b4be Poul Sander 2025-02-03 21:28 8912
            want_open = menu_is_open = false;
1193b4be Poul Sander 2025-02-03 21:28 8913
        }
1193b4be Poul Sander 2025-02-03 21:28 8914
        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 8915
        {
1193b4be Poul Sander 2025-02-03 21:28 8916
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 8917
        }
1193b4be Poul Sander 2025-02-03 21:28 8918
        else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
1193b4be Poul Sander 2025-02-03 21:28 8919
        {
1193b4be Poul Sander 2025-02-03 21:28 8920
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 8921
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 8922
        }
1193b4be Poul Sander 2025-02-03 21:28 8923
    }
1193b4be Poul Sander 2025-02-03 21:28 8924
1193b4be Poul Sander 2025-02-03 21:28 8925
    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 8926
        want_close = true;
1193b4be Poul Sander 2025-02-03 21:28 8927
    if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 8928
        ClosePopupToLevel(g.BeginPopupStack.Size, true);
1193b4be Poul Sander 2025-02-03 21:28 8929
1193b4be Poul Sander 2025-02-03 21:28 8930
    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 8931
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 8932
1193b4be Poul Sander 2025-02-03 21:28 8933
    if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
1193b4be Poul Sander 2025-02-03 21:28 8934
    {
1193b4be Poul Sander 2025-02-03 21:28 8935
        // 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 8936
        OpenPopup(label);
1193b4be Poul Sander 2025-02-03 21:28 8937
    }
1193b4be Poul Sander 2025-02-03 21:28 8938
    else if (want_open)
1193b4be Poul Sander 2025-02-03 21:28 8939
    {
1193b4be Poul Sander 2025-02-03 21:28 8940
        menu_is_open = true;
1193b4be Poul Sander 2025-02-03 21:28 8941
        OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
1193b4be Poul Sander 2025-02-03 21:28 8942
    }
1193b4be Poul Sander 2025-02-03 21:28 8943
1193b4be Poul Sander 2025-02-03 21:28 8944
    if (menu_is_open)
1193b4be Poul Sander 2025-02-03 21:28 8945
    {
1193b4be Poul Sander 2025-02-03 21:28 8946
        ImGuiLastItemData last_item_in_parent = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 8947
        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 8948
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
1193b4be Poul Sander 2025-02-03 21:28 8949
        menu_is_open = BeginPopupEx(id, 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 8950
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 8951
        if (menu_is_open)
1193b4be Poul Sander 2025-02-03 21:28 8952
        {
1193b4be Poul Sander 2025-02-03 21:28 8953
            // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
1193b4be Poul Sander 2025-02-03 21:28 8954
            // 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 8955
            if (want_open && want_open_nav_init && !g.NavInitRequest)
1193b4be Poul Sander 2025-02-03 21:28 8956
            {
1193b4be Poul Sander 2025-02-03 21:28 8957
                FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
1193b4be Poul Sander 2025-02-03 21:28 8958
                NavInitWindow(g.CurrentWindow, false);
1193b4be Poul Sander 2025-02-03 21:28 8959
            }
1193b4be Poul Sander 2025-02-03 21:28 8960
1193b4be Poul Sander 2025-02-03 21:28 8961
            // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
1193b4be Poul Sander 2025-02-03 21:28 8962
            // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
1193b4be Poul Sander 2025-02-03 21:28 8963
            g.LastItemData = last_item_in_parent;
1193b4be Poul Sander 2025-02-03 21:28 8964
            if (g.HoveredWindow == window)
1193b4be Poul Sander 2025-02-03 21:28 8965
                g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
1193b4be Poul Sander 2025-02-03 21:28 8966
        }
1193b4be Poul Sander 2025-02-03 21:28 8967
    }
1193b4be Poul Sander 2025-02-03 21:28 8968
    else
1193b4be Poul Sander 2025-02-03 21:28 8969
    {
1193b4be Poul Sander 2025-02-03 21:28 8970
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 8971
    }
1193b4be Poul Sander 2025-02-03 21:28 8972
1193b4be Poul Sander 2025-02-03 21:28 8973
    return menu_is_open;
1193b4be Poul Sander 2025-02-03 21:28 8974
}
1193b4be Poul Sander 2025-02-03 21:28 8975
1193b4be Poul Sander 2025-02-03 21:28 8976
bool ImGui::BeginMenu(const char* label, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 8977
{
1193b4be Poul Sander 2025-02-03 21:28 8978
    return BeginMenuEx(label, NULL, enabled);
1193b4be Poul Sander 2025-02-03 21:28 8979
}
1193b4be Poul Sander 2025-02-03 21:28 8980
1193b4be Poul Sander 2025-02-03 21:28 8981
void ImGui::EndMenu()
1193b4be Poul Sander 2025-02-03 21:28 8982
{
1193b4be Poul Sander 2025-02-03 21:28 8983
    // Nav: When a left move request our menu failed, close ourselves.
1193b4be Poul Sander 2025-02-03 21:28 8984
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8985
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8986
    IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginMenu()/EndMenu() calls
1193b4be Poul Sander 2025-02-03 21:28 8987
    ImGuiWindow* parent_window = window->ParentWindow;  // Should always be != NULL is we passed assert.
1193b4be Poul Sander 2025-02-03 21:28 8988
    if (window->BeginCount == window->BeginCountPreviousFrame)
1193b4be Poul Sander 2025-02-03 21:28 8989
        if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
1193b4be Poul Sander 2025-02-03 21:28 8990
            if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
1193b4be Poul Sander 2025-02-03 21:28 8991
            {
1193b4be Poul Sander 2025-02-03 21:28 8992
                ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
1193b4be Poul Sander 2025-02-03 21:28 8993
                NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 8994
            }
1193b4be Poul Sander 2025-02-03 21:28 8995
1193b4be Poul Sander 2025-02-03 21:28 8996
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 8997
}
1193b4be Poul Sander 2025-02-03 21:28 8998
1193b4be Poul Sander 2025-02-03 21:28 8999
bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9000
{
1193b4be Poul Sander 2025-02-03 21:28 9001
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 9002
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9003
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9004
1193b4be Poul Sander 2025-02-03 21:28 9005
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9006
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 9007
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9008
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 9009
1193b4be Poul Sander 2025-02-03 21:28 9010
    // See BeginMenuEx() for comments about this.
1193b4be Poul Sander 2025-02-03 21:28 9011
    const bool menuset_is_open = IsRootOfOpenMenuSet();
1193b4be Poul Sander 2025-02-03 21:28 9012
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9013
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
1193b4be Poul Sander 2025-02-03 21:28 9014
1193b4be Poul Sander 2025-02-03 21:28 9015
    // 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 9016
    // 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 9017
    bool pressed;
1193b4be Poul Sander 2025-02-03 21:28 9018
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 9019
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 9020
        BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 9021
1193b4be Poul Sander 2025-02-03 21:28 9022
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
1193b4be Poul Sander 2025-02-03 21:28 9023
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;
1193b4be Poul Sander 2025-02-03 21:28 9024
    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
1193b4be Poul Sander 2025-02-03 21:28 9025
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
1193b4be Poul Sander 2025-02-03 21:28 9026
    {
1193b4be Poul Sander 2025-02-03 21:28 9027
        // 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 9028
        // 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 9029
        float w = label_size.x;
1193b4be Poul Sander 2025-02-03 21:28 9030
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 9031
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 9032
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 9033
        pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 9034
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 9035
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
1193b4be Poul Sander 2025-02-03 21:28 9036
            RenderText(text_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 9037
        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 9038
    }
1193b4be Poul Sander 2025-02-03 21:28 9039
    else
1193b4be Poul Sander 2025-02-03 21:28 9040
    {
1193b4be Poul Sander 2025-02-03 21:28 9041
        // Menu item inside a vertical menu
1193b4be Poul Sander 2025-02-03 21:28 9042
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
1193b4be Poul Sander 2025-02-03 21:28 9043
        //  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 9044
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9045
        float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9046
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
1193b4be Poul Sander 2025-02-03 21:28 9047
        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 9048
        float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
1193b4be Poul Sander 2025-02-03 21:28 9049
        pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
1193b4be Poul Sander 2025-02-03 21:28 9050
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
1193b4be Poul Sander 2025-02-03 21:28 9051
        {
1193b4be Poul Sander 2025-02-03 21:28 9052
            RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
1193b4be Poul Sander 2025-02-03 21:28 9053
            if (icon_w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9054
                RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
1193b4be Poul Sander 2025-02-03 21:28 9055
            if (shortcut_w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9056
            {
1193b4be Poul Sander 2025-02-03 21:28 9057
                PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
1193b4be Poul Sander 2025-02-03 21:28 9058
                RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
1193b4be Poul Sander 2025-02-03 21:28 9059
                PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 9060
            }
1193b4be Poul Sander 2025-02-03 21:28 9061
            if (selected)
1193b4be Poul Sander 2025-02-03 21:28 9062
                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 9063
        }
1193b4be Poul Sander 2025-02-03 21:28 9064
    }
1193b4be Poul Sander 2025-02-03 21:28 9065
    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 9066
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 9067
        EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 9068
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 9069
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9070
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 9071
1193b4be Poul Sander 2025-02-03 21:28 9072
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 9073
}
1193b4be Poul Sander 2025-02-03 21:28 9074
1193b4be Poul Sander 2025-02-03 21:28 9075
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9076
{
1193b4be Poul Sander 2025-02-03 21:28 9077
    return MenuItemEx(label, NULL, shortcut, selected, enabled);
1193b4be Poul Sander 2025-02-03 21:28 9078
}
1193b4be Poul Sander 2025-02-03 21:28 9079
1193b4be Poul Sander 2025-02-03 21:28 9080
bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9081
{
1193b4be Poul Sander 2025-02-03 21:28 9082
    if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
1193b4be Poul Sander 2025-02-03 21:28 9083
    {
1193b4be Poul Sander 2025-02-03 21:28 9084
        if (p_selected)
1193b4be Poul Sander 2025-02-03 21:28 9085
            *p_selected = !*p_selected;
1193b4be Poul Sander 2025-02-03 21:28 9086
        return true;
1193b4be Poul Sander 2025-02-03 21:28 9087
    }
1193b4be Poul Sander 2025-02-03 21:28 9088
    return false;
1193b4be Poul Sander 2025-02-03 21:28 9089
}
1193b4be Poul Sander 2025-02-03 21:28 9090
1193b4be Poul Sander 2025-02-03 21:28 9091
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9092
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
1193b4be Poul Sander 2025-02-03 21:28 9093
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9094
// - BeginTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9095
// - BeginTabBarEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9096
// - EndTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9097
// - TabBarLayout() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9098
// - TabBarCalcTabID() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9099
// - TabBarCalcMaxTabWidth() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9100
// - TabBarFindTabById() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9101
// - TabBarFindTabByOrder() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9102
// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9103
// - TabBarGetCurrentTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9104
// - TabBarGetTabName() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9105
// - TabBarAddTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9106
// - TabBarRemoveTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9107
// - TabBarCloseTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9108
// - TabBarScrollClamp() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9109
// - TabBarScrollToTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9110
// - TabBarQueueFocus() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9111
// - TabBarQueueReorder() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9112
// - TabBarProcessReorderFromMousePos() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9113
// - TabBarProcessReorder() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9114
// - TabBarScrollingButtons() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9115
// - TabBarTabListPopupButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9116
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9117
1193b4be Poul Sander 2025-02-03 21:28 9118
struct ImGuiTabBarSection
1193b4be Poul Sander 2025-02-03 21:28 9119
{
1193b4be Poul Sander 2025-02-03 21:28 9120
    int                 TabCount;               // Number of tabs in this section.
1193b4be Poul Sander 2025-02-03 21:28 9121
    float               Width;                  // Sum of width of tabs in this section (after shrinking down)
1193b4be Poul Sander 2025-02-03 21:28 9122
    float               Spacing;                // Horizontal spacing at the end of the section.
1193b4be Poul Sander 2025-02-03 21:28 9123
1193b4be Poul Sander 2025-02-03 21:28 9124
    ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
1193b4be Poul Sander 2025-02-03 21:28 9125
};
1193b4be Poul Sander 2025-02-03 21:28 9126
1193b4be Poul Sander 2025-02-03 21:28 9127
namespace ImGui
1193b4be Poul Sander 2025-02-03 21:28 9128
{
1193b4be Poul Sander 2025-02-03 21:28 9129
    static void             TabBarLayout(ImGuiTabBar* tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9130
    static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
1193b4be Poul Sander 2025-02-03 21:28 9131
    static float            TabBarCalcMaxTabWidth();
1193b4be Poul Sander 2025-02-03 21:28 9132
    static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
1193b4be Poul Sander 2025-02-03 21:28 9133
    static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
1193b4be Poul Sander 2025-02-03 21:28 9134
    static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9135
    static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
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
ImGuiTabBar::ImGuiTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9139
{
1193b4be Poul Sander 2025-02-03 21:28 9140
    memset(this, 0, sizeof(*this));
1193b4be Poul Sander 2025-02-03 21:28 9141
    CurrFrameVisible = PrevFrameVisible = -1;
1193b4be Poul Sander 2025-02-03 21:28 9142
    LastTabItemIdx = -1;
1193b4be Poul Sander 2025-02-03 21:28 9143
}
1193b4be Poul Sander 2025-02-03 21:28 9144
1193b4be Poul Sander 2025-02-03 21:28 9145
static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 9146
{
1193b4be Poul Sander 2025-02-03 21:28 9147
    return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
1193b4be Poul Sander 2025-02-03 21:28 9148
}
1193b4be Poul Sander 2025-02-03 21:28 9149
1193b4be Poul Sander 2025-02-03 21:28 9150
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 9151
{
1193b4be Poul Sander 2025-02-03 21:28 9152
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
1193b4be Poul Sander 2025-02-03 21:28 9153
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
1193b4be Poul Sander 2025-02-03 21:28 9154
    const int a_section = TabItemGetSectionIdx(a);
1193b4be Poul Sander 2025-02-03 21:28 9155
    const int b_section = TabItemGetSectionIdx(b);
1193b4be Poul Sander 2025-02-03 21:28 9156
    if (a_section != b_section)
1193b4be Poul Sander 2025-02-03 21:28 9157
        return a_section - b_section;
1193b4be Poul Sander 2025-02-03 21:28 9158
    return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
1193b4be Poul Sander 2025-02-03 21:28 9159
}
1193b4be Poul Sander 2025-02-03 21:28 9160
1193b4be Poul Sander 2025-02-03 21:28 9161
static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 9162
{
1193b4be Poul Sander 2025-02-03 21:28 9163
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
1193b4be Poul Sander 2025-02-03 21:28 9164
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
1193b4be Poul Sander 2025-02-03 21:28 9165
    return (int)(a->BeginOrder - b->BeginOrder);
1193b4be Poul Sander 2025-02-03 21:28 9166
}
1193b4be Poul Sander 2025-02-03 21:28 9167
1193b4be Poul Sander 2025-02-03 21:28 9168
static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
1193b4be Poul Sander 2025-02-03 21:28 9169
{
1193b4be Poul Sander 2025-02-03 21:28 9170
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9171
    return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
1193b4be Poul Sander 2025-02-03 21:28 9172
}
1193b4be Poul Sander 2025-02-03 21:28 9173
1193b4be Poul Sander 2025-02-03 21:28 9174
static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9175
{
1193b4be Poul Sander 2025-02-03 21:28 9176
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9177
    if (g.TabBars.Contains(tab_bar))
1193b4be Poul Sander 2025-02-03 21:28 9178
        return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
1193b4be Poul Sander 2025-02-03 21:28 9179
    return ImGuiPtrOrIndex(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9180
}
1193b4be Poul Sander 2025-02-03 21:28 9181
1193b4be Poul Sander 2025-02-03 21:28 9182
bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 9183
{
1193b4be Poul Sander 2025-02-03 21:28 9184
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9185
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9186
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9187
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9188
1193b4be Poul Sander 2025-02-03 21:28 9189
    ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 9190
    ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
1193b4be Poul Sander 2025-02-03 21:28 9191
    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 9192
    tab_bar->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 9193
    tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 9194
    tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 9195
    //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false))
1193b4be Poul Sander 2025-02-03 21:28 9196
    flags |= ImGuiTabBarFlags_IsFocused;
1193b4be Poul Sander 2025-02-03 21:28 9197
    return BeginTabBarEx(tab_bar, tab_bar_bb, flags);
1193b4be Poul Sander 2025-02-03 21:28 9198
}
1193b4be Poul Sander 2025-02-03 21:28 9199
1193b4be Poul Sander 2025-02-03 21:28 9200
bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 9201
{
1193b4be Poul Sander 2025-02-03 21:28 9202
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9203
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9204
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9205
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9206
1193b4be Poul Sander 2025-02-03 21:28 9207
    IM_ASSERT(tab_bar->ID != 0);
1193b4be Poul Sander 2025-02-03 21:28 9208
    if ((flags & ImGuiTabBarFlags_DockNode) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9209
        PushOverrideID(tab_bar->ID);
1193b4be Poul Sander 2025-02-03 21:28 9210
1193b4be Poul Sander 2025-02-03 21:28 9211
    // Add to stack
1193b4be Poul Sander 2025-02-03 21:28 9212
    g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
1193b4be Poul Sander 2025-02-03 21:28 9213
    g.CurrentTabBar = tab_bar;
1193b4be Poul Sander 2025-02-03 21:28 9214
    tab_bar->Window = window;
1193b4be Poul Sander 2025-02-03 21:28 9215
1193b4be Poul Sander 2025-02-03 21:28 9216
    // Append with multiple BeginTabBar()/EndTabBar() pairs.
1193b4be Poul Sander 2025-02-03 21:28 9217
    tab_bar->BackupCursorPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9218
    if (tab_bar->CurrFrameVisible == g.FrameCount)
1193b4be Poul Sander 2025-02-03 21:28 9219
    {
1193b4be Poul Sander 2025-02-03 21:28 9220
        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 9221
        tab_bar->BeginCount++;
1193b4be Poul Sander 2025-02-03 21:28 9222
        return true;
1193b4be Poul Sander 2025-02-03 21:28 9223
    }
1193b4be Poul Sander 2025-02-03 21:28 9224
1193b4be Poul Sander 2025-02-03 21:28 9225
    // 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 9226
    if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
1193b4be Poul Sander 2025-02-03 21:28 9227
        if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
1193b4be Poul Sander 2025-02-03 21:28 9228
            ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
1193b4be Poul Sander 2025-02-03 21:28 9229
    tab_bar->TabsAddedNew = false;
1193b4be Poul Sander 2025-02-03 21:28 9230
1193b4be Poul Sander 2025-02-03 21:28 9231
    // Flags
1193b4be Poul Sander 2025-02-03 21:28 9232
    if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9233
        flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
1193b4be Poul Sander 2025-02-03 21:28 9234
1193b4be Poul Sander 2025-02-03 21:28 9235
    tab_bar->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 9236
    tab_bar->BarRect = tab_bar_bb;
1193b4be Poul Sander 2025-02-03 21:28 9237
    tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
1193b4be Poul Sander 2025-02-03 21:28 9238
    tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
1193b4be Poul Sander 2025-02-03 21:28 9239
    tab_bar->CurrFrameVisible = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 9240
    tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
1193b4be Poul Sander 2025-02-03 21:28 9241
    tab_bar->CurrTabsContentsHeight = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9242
    tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
1193b4be Poul Sander 2025-02-03 21:28 9243
    tab_bar->FramePadding = g.Style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 9244
    tab_bar->TabsActiveCount = 0;
1193b4be Poul Sander 2025-02-03 21:28 9245
    tab_bar->LastTabItemIdx = -1;
1193b4be Poul Sander 2025-02-03 21:28 9246
    tab_bar->BeginCount = 1;
1193b4be Poul Sander 2025-02-03 21:28 9247
1193b4be Poul Sander 2025-02-03 21:28 9248
    // 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 9249
    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 9250
1193b4be Poul Sander 2025-02-03 21:28 9251
    // Draw separator
1193b4be Poul Sander 2025-02-03 21:28 9252
    // (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 9253
    const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected);
1193b4be Poul Sander 2025-02-03 21:28 9254
    if (g.Style.TabBarBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9255
    {
1193b4be Poul Sander 2025-02-03 21:28 9256
        const float y = tab_bar->BarRect.Max.y;
1193b4be Poul Sander 2025-02-03 21:28 9257
        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 9258
    }
1193b4be Poul Sander 2025-02-03 21:28 9259
    return true;
1193b4be Poul Sander 2025-02-03 21:28 9260
}
1193b4be Poul Sander 2025-02-03 21:28 9261
1193b4be Poul Sander 2025-02-03 21:28 9262
void    ImGui::EndTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9263
{
1193b4be Poul Sander 2025-02-03 21:28 9264
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9265
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9266
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9267
        return;
1193b4be Poul Sander 2025-02-03 21:28 9268
1193b4be Poul Sander 2025-02-03 21:28 9269
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 9270
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9271
    {
1193b4be Poul Sander 2025-02-03 21:28 9272
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 9273
        return;
1193b4be Poul Sander 2025-02-03 21:28 9274
    }
1193b4be Poul Sander 2025-02-03 21:28 9275
1193b4be Poul Sander 2025-02-03 21:28 9276
    // Fallback in case no TabItem have been submitted
1193b4be Poul Sander 2025-02-03 21:28 9277
    if (tab_bar->WantLayout)
1193b4be Poul Sander 2025-02-03 21:28 9278
        TabBarLayout(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9279
1193b4be Poul Sander 2025-02-03 21:28 9280
    // 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 9281
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 9282
    if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
1193b4be Poul Sander 2025-02-03 21:28 9283
    {
1193b4be Poul Sander 2025-02-03 21:28 9284
        tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
1193b4be Poul Sander 2025-02-03 21:28 9285
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
1193b4be Poul Sander 2025-02-03 21:28 9286
    }
1193b4be Poul Sander 2025-02-03 21:28 9287
    else
1193b4be Poul Sander 2025-02-03 21:28 9288
    {
1193b4be Poul Sander 2025-02-03 21:28 9289
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
1193b4be Poul Sander 2025-02-03 21:28 9290
    }
1193b4be Poul Sander 2025-02-03 21:28 9291
    if (tab_bar->BeginCount > 1)
1193b4be Poul Sander 2025-02-03 21:28 9292
        window->DC.CursorPos = tab_bar->BackupCursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9293
1193b4be Poul Sander 2025-02-03 21:28 9294
    tab_bar->LastTabItemIdx = -1;
1193b4be Poul Sander 2025-02-03 21:28 9295
    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9296
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 9297
1193b4be Poul Sander 2025-02-03 21:28 9298
    g.CurrentTabBarStack.pop_back();
1193b4be Poul Sander 2025-02-03 21:28 9299
    g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
1193b4be Poul Sander 2025-02-03 21:28 9300
}
1193b4be Poul Sander 2025-02-03 21:28 9301
1193b4be Poul Sander 2025-02-03 21:28 9302
// Scrolling happens only in the central section (leading/trailing sections are not scrolling)
1193b4be Poul Sander 2025-02-03 21:28 9303
static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)
1193b4be Poul Sander 2025-02-03 21:28 9304
{
1193b4be Poul Sander 2025-02-03 21:28 9305
    return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9306
}
1193b4be Poul Sander 2025-02-03 21:28 9307
1193b4be Poul Sander 2025-02-03 21:28 9308
// This is called only once a frame before by the first call to ItemTab()
1193b4be Poul Sander 2025-02-03 21:28 9309
// 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 9310
static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9311
{
1193b4be Poul Sander 2025-02-03 21:28 9312
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9313
    tab_bar->WantLayout = false;
1193b4be Poul Sander 2025-02-03 21:28 9314
1193b4be Poul Sander 2025-02-03 21:28 9315
    // Garbage collect by compacting list
1193b4be Poul Sander 2025-02-03 21:28 9316
    // 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 9317
    int tab_dst_n = 0;
1193b4be Poul Sander 2025-02-03 21:28 9318
    bool need_sort_by_section = false;
1193b4be Poul Sander 2025-02-03 21:28 9319
    ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
1193b4be Poul Sander 2025-02-03 21:28 9320
    for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
1193b4be Poul Sander 2025-02-03 21:28 9321
    {
1193b4be Poul Sander 2025-02-03 21:28 9322
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
1193b4be Poul Sander 2025-02-03 21:28 9323
        if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
1193b4be Poul Sander 2025-02-03 21:28 9324
        {
1193b4be Poul Sander 2025-02-03 21:28 9325
            // Remove tab
1193b4be Poul Sander 2025-02-03 21:28 9326
            if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9327
            if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9328
            if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9329
            continue;
1193b4be Poul Sander 2025-02-03 21:28 9330
        }
1193b4be Poul Sander 2025-02-03 21:28 9331
        if (tab_dst_n != tab_src_n)
1193b4be Poul Sander 2025-02-03 21:28 9332
            tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
1193b4be Poul Sander 2025-02-03 21:28 9333
1193b4be Poul Sander 2025-02-03 21:28 9334
        tab = &tab_bar->Tabs[tab_dst_n];
1193b4be Poul Sander 2025-02-03 21:28 9335
        tab->IndexDuringLayout = (ImS16)tab_dst_n;
1193b4be Poul Sander 2025-02-03 21:28 9336
1193b4be Poul Sander 2025-02-03 21:28 9337
        // 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 9338
        int curr_tab_section_n = TabItemGetSectionIdx(tab);
1193b4be Poul Sander 2025-02-03 21:28 9339
        if (tab_dst_n > 0)
1193b4be Poul Sander 2025-02-03 21:28 9340
        {
1193b4be Poul Sander 2025-02-03 21:28 9341
            ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
1193b4be Poul Sander 2025-02-03 21:28 9342
            int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
1193b4be Poul Sander 2025-02-03 21:28 9343
            if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
1193b4be Poul Sander 2025-02-03 21:28 9344
                need_sort_by_section = true;
1193b4be Poul Sander 2025-02-03 21:28 9345
            if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
1193b4be Poul Sander 2025-02-03 21:28 9346
                need_sort_by_section = true;
1193b4be Poul Sander 2025-02-03 21:28 9347
        }
1193b4be Poul Sander 2025-02-03 21:28 9348
1193b4be Poul Sander 2025-02-03 21:28 9349
        sections[curr_tab_section_n].TabCount++;
1193b4be Poul Sander 2025-02-03 21:28 9350
        tab_dst_n++;
1193b4be Poul Sander 2025-02-03 21:28 9351
    }
1193b4be Poul Sander 2025-02-03 21:28 9352
    if (tab_bar->Tabs.Size != tab_dst_n)
1193b4be Poul Sander 2025-02-03 21:28 9353
        tab_bar->Tabs.resize(tab_dst_n);
1193b4be Poul Sander 2025-02-03 21:28 9354
1193b4be Poul Sander 2025-02-03 21:28 9355
    if (need_sort_by_section)
1193b4be Poul Sander 2025-02-03 21:28 9356
        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
1193b4be Poul Sander 2025-02-03 21:28 9357
1193b4be Poul Sander 2025-02-03 21:28 9358
    // Calculate spacing between sections
1193b4be Poul Sander 2025-02-03 21:28 9359
    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 9360
    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 9361
1193b4be Poul Sander 2025-02-03 21:28 9362
    // Setup next selected tab
1193b4be Poul Sander 2025-02-03 21:28 9363
    ImGuiID scroll_to_tab_id = 0;
1193b4be Poul Sander 2025-02-03 21:28 9364
    if (tab_bar->NextSelectedTabId)
1193b4be Poul Sander 2025-02-03 21:28 9365
    {
1193b4be Poul Sander 2025-02-03 21:28 9366
        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9367
        tab_bar->NextSelectedTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9368
        scroll_to_tab_id = tab_bar->SelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9369
    }
1193b4be Poul Sander 2025-02-03 21:28 9370
1193b4be Poul Sander 2025-02-03 21:28 9371
    // 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 9372
    if (tab_bar->ReorderRequestTabId != 0)
1193b4be Poul Sander 2025-02-03 21:28 9373
    {
1193b4be Poul Sander 2025-02-03 21:28 9374
        if (TabBarProcessReorder(tab_bar))
1193b4be Poul Sander 2025-02-03 21:28 9375
            if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
1193b4be Poul Sander 2025-02-03 21:28 9376
                scroll_to_tab_id = tab_bar->ReorderRequestTabId;
1193b4be Poul Sander 2025-02-03 21:28 9377
        tab_bar->ReorderRequestTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9378
    }
1193b4be Poul Sander 2025-02-03 21:28 9379
1193b4be Poul Sander 2025-02-03 21:28 9380
    // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
1193b4be Poul Sander 2025-02-03 21:28 9381
    const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
1193b4be Poul Sander 2025-02-03 21:28 9382
    if (tab_list_popup_button)
1193b4be Poul Sander 2025-02-03 21:28 9383
        if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
1193b4be Poul Sander 2025-02-03 21:28 9384
            scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
1193b4be Poul Sander 2025-02-03 21:28 9385
1193b4be Poul Sander 2025-02-03 21:28 9386
    // 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 9387
    // (whereas our tabs are stored as: leading, central, trailing)
1193b4be Poul Sander 2025-02-03 21:28 9388
    int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
1193b4be Poul Sander 2025-02-03 21:28 9389
    g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
1193b4be Poul Sander 2025-02-03 21:28 9390
1193b4be Poul Sander 2025-02-03 21:28 9391
    // Compute ideal tabs widths + store them into shrink buffer
1193b4be Poul Sander 2025-02-03 21:28 9392
    ImGuiTabItem* most_recently_selected_tab = NULL;
1193b4be Poul Sander 2025-02-03 21:28 9393
    int curr_section_n = -1;
1193b4be Poul Sander 2025-02-03 21:28 9394
    bool found_selected_tab_id = false;
1193b4be Poul Sander 2025-02-03 21:28 9395
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9396
    {
1193b4be Poul Sander 2025-02-03 21:28 9397
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
1193b4be Poul Sander 2025-02-03 21:28 9398
        IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
1193b4be Poul Sander 2025-02-03 21:28 9399
1193b4be Poul Sander 2025-02-03 21:28 9400
        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 9401
            most_recently_selected_tab = tab;
1193b4be Poul Sander 2025-02-03 21:28 9402
        if (tab->ID == tab_bar->SelectedTabId)
1193b4be Poul Sander 2025-02-03 21:28 9403
            found_selected_tab_id = true;
1193b4be Poul Sander 2025-02-03 21:28 9404
        if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
1193b4be Poul Sander 2025-02-03 21:28 9405
            scroll_to_tab_id = tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9406
1193b4be Poul Sander 2025-02-03 21:28 9407
        // 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 9408
        // 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 9409
        // 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 9410
        const char* tab_name = TabBarGetTabName(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 9411
        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 9412
        tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
1193b4be Poul Sander 2025-02-03 21:28 9413
1193b4be Poul Sander 2025-02-03 21:28 9414
        int section_n = TabItemGetSectionIdx(tab);
1193b4be Poul Sander 2025-02-03 21:28 9415
        ImGuiTabBarSection* section = &sections[section_n];
1193b4be Poul Sander 2025-02-03 21:28 9416
        section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9417
        curr_section_n = section_n;
1193b4be Poul Sander 2025-02-03 21:28 9418
1193b4be Poul Sander 2025-02-03 21:28 9419
        // 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 9420
        IM_MSVC_WARNING_SUPPRESS(6385);
1193b4be Poul Sander 2025-02-03 21:28 9421
        ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
1193b4be Poul Sander 2025-02-03 21:28 9422
        shrink_width_item->Index = tab_n;
1193b4be Poul Sander 2025-02-03 21:28 9423
        shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
1193b4be Poul Sander 2025-02-03 21:28 9424
        tab->Width = ImMax(tab->ContentWidth, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 9425
    }
1193b4be Poul Sander 2025-02-03 21:28 9426
1193b4be Poul Sander 2025-02-03 21:28 9427
    // Compute total ideal width (used for e.g. auto-resizing a window)
1193b4be Poul Sander 2025-02-03 21:28 9428
    tab_bar->WidthAllTabsIdeal = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9429
    for (int section_n = 0; section_n < 3; section_n++)
1193b4be Poul Sander 2025-02-03 21:28 9430
        tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9431
1193b4be Poul Sander 2025-02-03 21:28 9432
    // Horizontal scrolling buttons
1193b4be Poul Sander 2025-02-03 21:28 9433
    // (note that TabBarScrollButtons() will alter BarRect.Max.x)
1193b4be Poul Sander 2025-02-03 21:28 9434
    if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
1193b4be Poul Sander 2025-02-03 21:28 9435
        if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
1193b4be Poul Sander 2025-02-03 21:28 9436
        {
1193b4be Poul Sander 2025-02-03 21:28 9437
            scroll_to_tab_id = scroll_and_select_tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9438
            if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9439
                tab_bar->SelectedTabId = scroll_to_tab_id;
1193b4be Poul Sander 2025-02-03 21:28 9440
        }
1193b4be Poul Sander 2025-02-03 21:28 9441
1193b4be Poul Sander 2025-02-03 21:28 9442
    // Shrink widths if full tabs don't fit in their allocated space
1193b4be Poul Sander 2025-02-03 21:28 9443
    float section_0_w = sections[0].Width + sections[0].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9444
    float section_1_w = sections[1].Width + sections[1].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9445
    float section_2_w = sections[2].Width + sections[2].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9446
    bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 9447
    float width_excess;
1193b4be Poul Sander 2025-02-03 21:28 9448
    if (central_section_is_visible)
1193b4be Poul Sander 2025-02-03 21:28 9449
        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 9450
    else
1193b4be Poul Sander 2025-02-03 21:28 9451
        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 9452
1193b4be Poul Sander 2025-02-03 21:28 9453
    // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
1193b4be Poul Sander 2025-02-03 21:28 9454
    if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
1193b4be Poul Sander 2025-02-03 21:28 9455
    {
1193b4be Poul Sander 2025-02-03 21:28 9456
        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 9457
        int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
1193b4be Poul Sander 2025-02-03 21:28 9458
        ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
1193b4be Poul Sander 2025-02-03 21:28 9459
1193b4be Poul Sander 2025-02-03 21:28 9460
        // Apply shrunk values into tabs and sections
1193b4be Poul Sander 2025-02-03 21:28 9461
        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 9462
        {
1193b4be Poul Sander 2025-02-03 21:28 9463
            ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
1193b4be Poul Sander 2025-02-03 21:28 9464
            float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);
1193b4be Poul Sander 2025-02-03 21:28 9465
            if (shrinked_width < 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9466
                continue;
1193b4be Poul Sander 2025-02-03 21:28 9467
1193b4be Poul Sander 2025-02-03 21:28 9468
            shrinked_width = ImMax(1.0f, shrinked_width);
1193b4be Poul Sander 2025-02-03 21:28 9469
            int section_n = TabItemGetSectionIdx(tab);
1193b4be Poul Sander 2025-02-03 21:28 9470
            sections[section_n].Width -= (tab->Width - shrinked_width);
1193b4be Poul Sander 2025-02-03 21:28 9471
            tab->Width = shrinked_width;
1193b4be Poul Sander 2025-02-03 21:28 9472
        }
1193b4be Poul Sander 2025-02-03 21:28 9473
    }
1193b4be Poul Sander 2025-02-03 21:28 9474
1193b4be Poul Sander 2025-02-03 21:28 9475
    // Layout all active tabs
1193b4be Poul Sander 2025-02-03 21:28 9476
    int section_tab_index = 0;
1193b4be Poul Sander 2025-02-03 21:28 9477
    float tab_offset = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9478
    tab_bar->WidthAllTabs = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9479
    for (int section_n = 0; section_n < 3; section_n++)
1193b4be Poul Sander 2025-02-03 21:28 9480
    {
1193b4be Poul Sander 2025-02-03 21:28 9481
        ImGuiTabBarSection* section = &sections[section_n];
1193b4be Poul Sander 2025-02-03 21:28 9482
        if (section_n == 2)
1193b4be Poul Sander 2025-02-03 21:28 9483
            tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
1193b4be Poul Sander 2025-02-03 21:28 9484
1193b4be Poul Sander 2025-02-03 21:28 9485
        for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9486
        {
1193b4be Poul Sander 2025-02-03 21:28 9487
            ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
1193b4be Poul Sander 2025-02-03 21:28 9488
            tab->Offset = tab_offset;
1193b4be Poul Sander 2025-02-03 21:28 9489
            tab->NameOffset = -1;
1193b4be Poul Sander 2025-02-03 21:28 9490
            tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9491
        }
1193b4be Poul Sander 2025-02-03 21:28 9492
        tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9493
        tab_offset += section->Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9494
        section_tab_index += section->TabCount;
1193b4be Poul Sander 2025-02-03 21:28 9495
    }
1193b4be Poul Sander 2025-02-03 21:28 9496
1193b4be Poul Sander 2025-02-03 21:28 9497
    // Clear name buffers
1193b4be Poul Sander 2025-02-03 21:28 9498
    tab_bar->TabsNames.Buf.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 9499
1193b4be Poul Sander 2025-02-03 21:28 9500
    // If we have lost the selected tab, select the next most recently active one
1193b4be Poul Sander 2025-02-03 21:28 9501
    if (found_selected_tab_id == false)
1193b4be Poul Sander 2025-02-03 21:28 9502
        tab_bar->SelectedTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9503
    if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
1193b4be Poul Sander 2025-02-03 21:28 9504
        scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9505
1193b4be Poul Sander 2025-02-03 21:28 9506
    // Lock in visible tab
1193b4be Poul Sander 2025-02-03 21:28 9507
    tab_bar->VisibleTabId = tab_bar->SelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9508
    tab_bar->VisibleTabWasSubmitted = false;
1193b4be Poul Sander 2025-02-03 21:28 9509
1193b4be Poul Sander 2025-02-03 21:28 9510
    // CTRL+TAB can override visible tab temporarily
1193b4be Poul Sander 2025-02-03 21:28 9511
    if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9512
        tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId;
1193b4be Poul Sander 2025-02-03 21:28 9513
1193b4be Poul Sander 2025-02-03 21:28 9514
    // Apply request requests
1193b4be Poul Sander 2025-02-03 21:28 9515
    if (scroll_to_tab_id != 0)
1193b4be Poul Sander 2025-02-03 21:28 9516
        TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
1193b4be Poul Sander 2025-02-03 21:28 9517
    else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
1193b4be Poul Sander 2025-02-03 21:28 9518
    {
1193b4be Poul Sander 2025-02-03 21:28 9519
        const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
1193b4be Poul Sander 2025-02-03 21:28 9520
        const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;
1193b4be Poul Sander 2025-02-03 21:28 9521
        if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9522
        {
1193b4be Poul Sander 2025-02-03 21:28 9523
            const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;
1193b4be Poul Sander 2025-02-03 21:28 9524
            tab_bar->ScrollingTargetDistToVisibility = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9525
            tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);
1193b4be Poul Sander 2025-02-03 21:28 9526
        }
1193b4be Poul Sander 2025-02-03 21:28 9527
        SetKeyOwner(wheel_key, tab_bar->ID);
1193b4be Poul Sander 2025-02-03 21:28 9528
    }
1193b4be Poul Sander 2025-02-03 21:28 9529
1193b4be Poul Sander 2025-02-03 21:28 9530
    // Update scrolling
1193b4be Poul Sander 2025-02-03 21:28 9531
    tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
1193b4be Poul Sander 2025-02-03 21:28 9532
    tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
1193b4be Poul Sander 2025-02-03 21:28 9533
    if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
1193b4be Poul Sander 2025-02-03 21:28 9534
    {
1193b4be Poul Sander 2025-02-03 21:28 9535
        // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
1193b4be Poul Sander 2025-02-03 21:28 9536
        // Teleport if we are aiming far off the visible line
1193b4be Poul Sander 2025-02-03 21:28 9537
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 9538
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
1193b4be Poul Sander 2025-02-03 21:28 9539
        const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 9540
        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 9541
    }
1193b4be Poul Sander 2025-02-03 21:28 9542
    else
1193b4be Poul Sander 2025-02-03 21:28 9543
    {
1193b4be Poul Sander 2025-02-03 21:28 9544
        tab_bar->ScrollingSpeed = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9545
    }
1193b4be Poul Sander 2025-02-03 21:28 9546
    tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9547
    tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9548
1193b4be Poul Sander 2025-02-03 21:28 9549
    // 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 9550
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9551
    window->DC.CursorPos = tab_bar->BarRect.Min;
1193b4be Poul Sander 2025-02-03 21:28 9552
    ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 9553
    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 9554
}
1193b4be Poul Sander 2025-02-03 21:28 9555
1193b4be Poul Sander 2025-02-03 21:28 9556
// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.
1193b4be Poul Sander 2025-02-03 21:28 9557
static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
1193b4be Poul Sander 2025-02-03 21:28 9558
{
1193b4be Poul Sander 2025-02-03 21:28 9559
    if (docked_window != NULL)
1193b4be Poul Sander 2025-02-03 21:28 9560
    {
1193b4be Poul Sander 2025-02-03 21:28 9561
        IM_UNUSED(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9562
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
1193b4be Poul Sander 2025-02-03 21:28 9563
        ImGuiID id = docked_window->TabId;
1193b4be Poul Sander 2025-02-03 21:28 9564
        KeepAliveID(id);
1193b4be Poul Sander 2025-02-03 21:28 9565
        return id;
1193b4be Poul Sander 2025-02-03 21:28 9566
    }
1193b4be Poul Sander 2025-02-03 21:28 9567
    else
1193b4be Poul Sander 2025-02-03 21:28 9568
    {
1193b4be Poul Sander 2025-02-03 21:28 9569
        ImGuiWindow* window = GImGui->CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9570
        return window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 9571
    }
1193b4be Poul Sander 2025-02-03 21:28 9572
}
1193b4be Poul Sander 2025-02-03 21:28 9573
1193b4be Poul Sander 2025-02-03 21:28 9574
static float ImGui::TabBarCalcMaxTabWidth()
1193b4be Poul Sander 2025-02-03 21:28 9575
{
1193b4be Poul Sander 2025-02-03 21:28 9576
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9577
    return g.FontSize * 20.0f;
1193b4be Poul Sander 2025-02-03 21:28 9578
}
1193b4be Poul Sander 2025-02-03 21:28 9579
1193b4be Poul Sander 2025-02-03 21:28 9580
ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
1193b4be Poul Sander 2025-02-03 21:28 9581
{
1193b4be Poul Sander 2025-02-03 21:28 9582
    if (tab_id != 0)
1193b4be Poul Sander 2025-02-03 21:28 9583
        for (int n = 0; n < tab_bar->Tabs.Size; n++)
1193b4be Poul Sander 2025-02-03 21:28 9584
            if (tab_bar->Tabs[n].ID == tab_id)
1193b4be Poul Sander 2025-02-03 21:28 9585
                return &tab_bar->Tabs[n];
1193b4be Poul Sander 2025-02-03 21:28 9586
    return NULL;
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
// Order = visible order, not submission order! (which is tab->BeginOrder)
1193b4be Poul Sander 2025-02-03 21:28 9590
ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)
1193b4be Poul Sander 2025-02-03 21:28 9591
{
1193b4be Poul Sander 2025-02-03 21:28 9592
    if (order < 0 || order >= tab_bar->Tabs.Size)
1193b4be Poul Sander 2025-02-03 21:28 9593
        return NULL;
1193b4be Poul Sander 2025-02-03 21:28 9594
    return &tab_bar->Tabs[order];
1193b4be Poul Sander 2025-02-03 21:28 9595
}
1193b4be Poul Sander 2025-02-03 21:28 9596
1193b4be Poul Sander 2025-02-03 21:28 9597
// FIXME: See references to #2304 in TODO.txt
1193b4be Poul Sander 2025-02-03 21:28 9598
ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9599
{
1193b4be Poul Sander 2025-02-03 21:28 9600
    ImGuiTabItem* most_recently_selected_tab = NULL;
1193b4be Poul Sander 2025-02-03 21:28 9601
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9602
    {
1193b4be Poul Sander 2025-02-03 21:28 9603
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
1193b4be Poul Sander 2025-02-03 21:28 9604
        if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
1193b4be Poul Sander 2025-02-03 21:28 9605
            if (tab->Window && tab->Window->WasActive)
1193b4be Poul Sander 2025-02-03 21:28 9606
                most_recently_selected_tab = tab;
1193b4be Poul Sander 2025-02-03 21:28 9607
    }
1193b4be Poul Sander 2025-02-03 21:28 9608
    return most_recently_selected_tab;
1193b4be Poul Sander 2025-02-03 21:28 9609
}
1193b4be Poul Sander 2025-02-03 21:28 9610
1193b4be Poul Sander 2025-02-03 21:28 9611
ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9612
{
1193b4be Poul Sander 2025-02-03 21:28 9613
    if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)
1193b4be Poul Sander 2025-02-03 21:28 9614
        return NULL;
1193b4be Poul Sander 2025-02-03 21:28 9615
    return &tab_bar->Tabs[tab_bar->LastTabItemIdx];
1193b4be Poul Sander 2025-02-03 21:28 9616
}
1193b4be Poul Sander 2025-02-03 21:28 9617
1193b4be Poul Sander 2025-02-03 21:28 9618
const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 9619
{
1193b4be Poul Sander 2025-02-03 21:28 9620
    if (tab->Window)
1193b4be Poul Sander 2025-02-03 21:28 9621
        return tab->Window->Name;
1193b4be Poul Sander 2025-02-03 21:28 9622
    if (tab->NameOffset == -1)
1193b4be Poul Sander 2025-02-03 21:28 9623
        return "N/A";
1193b4be Poul Sander 2025-02-03 21:28 9624
    IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);
1193b4be Poul Sander 2025-02-03 21:28 9625
    return tab_bar->TabsNames.Buf.Data + tab->NameOffset;
1193b4be Poul Sander 2025-02-03 21:28 9626
}
1193b4be Poul Sander 2025-02-03 21:28 9627
1193b4be Poul Sander 2025-02-03 21:28 9628
// 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 9629
// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
1193b4be Poul Sander 2025-02-03 21:28 9630
void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 9631
{
1193b4be Poul Sander 2025-02-03 21:28 9632
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9633
    IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL);
1193b4be Poul Sander 2025-02-03 21:28 9634
    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 9635
1193b4be Poul Sander 2025-02-03 21:28 9636
    if (!window->HasCloseButton)
1193b4be Poul Sander 2025-02-03 21:28 9637
        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 9638
1193b4be Poul Sander 2025-02-03 21:28 9639
    ImGuiTabItem new_tab;
1193b4be Poul Sander 2025-02-03 21:28 9640
    new_tab.ID = window->TabId;
1193b4be Poul Sander 2025-02-03 21:28 9641
    new_tab.Flags = tab_flags;
1193b4be Poul Sander 2025-02-03 21:28 9642
    new_tab.LastFrameVisible = tab_bar->CurrFrameVisible;   // Required so BeginTabBar() doesn't ditch the tab
1193b4be Poul Sander 2025-02-03 21:28 9643
    if (new_tab.LastFrameVisible == -1)
1193b4be Poul Sander 2025-02-03 21:28 9644
        new_tab.LastFrameVisible = g.FrameCount - 1;
1193b4be Poul Sander 2025-02-03 21:28 9645
    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 9646
    tab_bar->Tabs.push_back(new_tab);
1193b4be Poul Sander 2025-02-03 21:28 9647
}
1193b4be Poul Sander 2025-02-03 21:28 9648
1193b4be Poul Sander 2025-02-03 21:28 9649
// 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 9650
void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
1193b4be Poul Sander 2025-02-03 21:28 9651
{
1193b4be Poul Sander 2025-02-03 21:28 9652
    if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
1193b4be Poul Sander 2025-02-03 21:28 9653
        tab_bar->Tabs.erase(tab);
1193b4be Poul Sander 2025-02-03 21:28 9654
    if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9655
    if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9656
    if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9657
}
1193b4be Poul Sander 2025-02-03 21:28 9658
1193b4be Poul Sander 2025-02-03 21:28 9659
// Called on manual closure attempt
1193b4be Poul Sander 2025-02-03 21:28 9660
void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 9661
{
1193b4be Poul Sander 2025-02-03 21:28 9662
    if (tab->Flags & ImGuiTabItemFlags_Button)
1193b4be Poul Sander 2025-02-03 21:28 9663
        return; // A button appended with TabItemButton().
1193b4be Poul Sander 2025-02-03 21:28 9664
1193b4be Poul Sander 2025-02-03 21:28 9665
    if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9666
    {
1193b4be Poul Sander 2025-02-03 21:28 9667
        // This will remove a frame of lag for selecting another tab on closure.
1193b4be Poul Sander 2025-02-03 21:28 9668
        // 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 9669
        tab->WantClose = true;
1193b4be Poul Sander 2025-02-03 21:28 9670
        if (tab_bar->VisibleTabId == tab->ID)
1193b4be Poul Sander 2025-02-03 21:28 9671
        {
1193b4be Poul Sander 2025-02-03 21:28 9672
            tab->LastFrameVisible = -1;
1193b4be Poul Sander 2025-02-03 21:28 9673
            tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9674
        }
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
        // 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 9679
        if (tab_bar->VisibleTabId != tab->ID)
1193b4be Poul Sander 2025-02-03 21:28 9680
            TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 9681
    }
1193b4be Poul Sander 2025-02-03 21:28 9682
}
1193b4be Poul Sander 2025-02-03 21:28 9683
1193b4be Poul Sander 2025-02-03 21:28 9684
static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
1193b4be Poul Sander 2025-02-03 21:28 9685
{
1193b4be Poul Sander 2025-02-03 21:28 9686
    scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
1193b4be Poul Sander 2025-02-03 21:28 9687
    return ImMax(scrolling, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9688
}
1193b4be Poul Sander 2025-02-03 21:28 9689
1193b4be Poul Sander 2025-02-03 21:28 9690
// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
1193b4be Poul Sander 2025-02-03 21:28 9691
static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
1193b4be Poul Sander 2025-02-03 21:28 9692
{
1193b4be Poul Sander 2025-02-03 21:28 9693
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
1193b4be Poul Sander 2025-02-03 21:28 9694
    if (tab == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9695
        return;
1193b4be Poul Sander 2025-02-03 21:28 9696
    if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
1193b4be Poul Sander 2025-02-03 21:28 9697
        return;
1193b4be Poul Sander 2025-02-03 21:28 9698
1193b4be Poul Sander 2025-02-03 21:28 9699
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9700
    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 9701
    int order = TabBarGetTabOrder(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 9702
1193b4be Poul Sander 2025-02-03 21:28 9703
    // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
1193b4be Poul Sander 2025-02-03 21:28 9704
    float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);
1193b4be Poul Sander 2025-02-03 21:28 9705
1193b4be Poul Sander 2025-02-03 21:28 9706
    // We make all tabs positions all relative Sections[0].Width to make code simpler
1193b4be Poul Sander 2025-02-03 21:28 9707
    float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9708
    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 9709
    tab_bar->ScrollingTargetDistToVisibility = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9710
    if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
1193b4be Poul Sander 2025-02-03 21:28 9711
    {
1193b4be Poul Sander 2025-02-03 21:28 9712
        // Scroll to the left
1193b4be Poul Sander 2025-02-03 21:28 9713
        tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9714
        tab_bar->ScrollingTarget = tab_x1;
1193b4be Poul Sander 2025-02-03 21:28 9715
    }
1193b4be Poul Sander 2025-02-03 21:28 9716
    else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
1193b4be Poul Sander 2025-02-03 21:28 9717
    {
1193b4be Poul Sander 2025-02-03 21:28 9718
        // Scroll to the right
1193b4be Poul Sander 2025-02-03 21:28 9719
        tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9720
        tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
1193b4be Poul Sander 2025-02-03 21:28 9721
    }
1193b4be Poul Sander 2025-02-03 21:28 9722
}
1193b4be Poul Sander 2025-02-03 21:28 9723
1193b4be Poul Sander 2025-02-03 21:28 9724
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 9725
{
1193b4be Poul Sander 2025-02-03 21:28 9726
    tab_bar->NextSelectedTabId = tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9727
}
1193b4be Poul Sander 2025-02-03 21:28 9728
1193b4be Poul Sander 2025-02-03 21:28 9729
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name)
1193b4be Poul Sander 2025-02-03 21:28 9730
{
1193b4be Poul Sander 2025-02-03 21:28 9731
    IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars
1193b4be Poul Sander 2025-02-03 21:28 9732
    ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL);
1193b4be Poul Sander 2025-02-03 21:28 9733
    tab_bar->NextSelectedTabId = tab_id;
1193b4be Poul Sander 2025-02-03 21:28 9734
}
1193b4be Poul Sander 2025-02-03 21:28 9735
1193b4be Poul Sander 2025-02-03 21:28 9736
void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)
1193b4be Poul Sander 2025-02-03 21:28 9737
{
1193b4be Poul Sander 2025-02-03 21:28 9738
    IM_ASSERT(offset != 0);
1193b4be Poul Sander 2025-02-03 21:28 9739
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
1193b4be Poul Sander 2025-02-03 21:28 9740
    tab_bar->ReorderRequestTabId = tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9741
    tab_bar->ReorderRequestOffset = (ImS16)offset;
1193b4be Poul Sander 2025-02-03 21:28 9742
}
1193b4be Poul Sander 2025-02-03 21:28 9743
1193b4be Poul Sander 2025-02-03 21:28 9744
void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)
1193b4be Poul Sander 2025-02-03 21:28 9745
{
1193b4be Poul Sander 2025-02-03 21:28 9746
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9747
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
1193b4be Poul Sander 2025-02-03 21:28 9748
    if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9749
        return;
1193b4be Poul Sander 2025-02-03 21:28 9750
1193b4be Poul Sander 2025-02-03 21:28 9751
    const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
1193b4be Poul Sander 2025-02-03 21:28 9752
    const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
1193b4be Poul Sander 2025-02-03 21:28 9753
1193b4be Poul Sander 2025-02-03 21:28 9754
    // Count number of contiguous tabs we are crossing over
1193b4be Poul Sander 2025-02-03 21:28 9755
    const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
1193b4be Poul Sander 2025-02-03 21:28 9756
    const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
1193b4be Poul Sander 2025-02-03 21:28 9757
    int dst_idx = src_idx;
1193b4be Poul Sander 2025-02-03 21:28 9758
    for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
1193b4be Poul Sander 2025-02-03 21:28 9759
    {
1193b4be Poul Sander 2025-02-03 21:28 9760
        // Reordered tabs must share the same section
1193b4be Poul Sander 2025-02-03 21:28 9761
        const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
1193b4be Poul Sander 2025-02-03 21:28 9762
        if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
1193b4be Poul Sander 2025-02-03 21:28 9763
            break;
1193b4be Poul Sander 2025-02-03 21:28 9764
        if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
1193b4be Poul Sander 2025-02-03 21:28 9765
            break;
1193b4be Poul Sander 2025-02-03 21:28 9766
        dst_idx = i;
1193b4be Poul Sander 2025-02-03 21:28 9767
1193b4be Poul Sander 2025-02-03 21:28 9768
        // 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 9769
        const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 9770
        const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 9771
        //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 9772
        if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
1193b4be Poul Sander 2025-02-03 21:28 9773
            break;
1193b4be Poul Sander 2025-02-03 21:28 9774
    }
1193b4be Poul Sander 2025-02-03 21:28 9775
1193b4be Poul Sander 2025-02-03 21:28 9776
    if (dst_idx != src_idx)
1193b4be Poul Sander 2025-02-03 21:28 9777
        TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
1193b4be Poul Sander 2025-02-03 21:28 9778
}
1193b4be Poul Sander 2025-02-03 21:28 9779
1193b4be Poul Sander 2025-02-03 21:28 9780
bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9781
{
1193b4be Poul Sander 2025-02-03 21:28 9782
    ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
1193b4be Poul Sander 2025-02-03 21:28 9783
    if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
1193b4be Poul Sander 2025-02-03 21:28 9784
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9785
1193b4be Poul Sander 2025-02-03 21:28 9786
    //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
1193b4be Poul Sander 2025-02-03 21:28 9787
    int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;
1193b4be Poul Sander 2025-02-03 21:28 9788
    if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
1193b4be Poul Sander 2025-02-03 21:28 9789
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9790
1193b4be Poul Sander 2025-02-03 21:28 9791
    // Reordered tabs must share the same section
1193b4be Poul Sander 2025-02-03 21:28 9792
    // (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 9793
    ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
1193b4be Poul Sander 2025-02-03 21:28 9794
    if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
1193b4be Poul Sander 2025-02-03 21:28 9795
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9796
    if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
1193b4be Poul Sander 2025-02-03 21:28 9797
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9798
1193b4be Poul Sander 2025-02-03 21:28 9799
    ImGuiTabItem item_tmp = *tab1;
1193b4be Poul Sander 2025-02-03 21:28 9800
    ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
1193b4be Poul Sander 2025-02-03 21:28 9801
    ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
1193b4be Poul Sander 2025-02-03 21:28 9802
    const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
1193b4be Poul Sander 2025-02-03 21:28 9803
    memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
1193b4be Poul Sander 2025-02-03 21:28 9804
    *tab2 = item_tmp;
1193b4be Poul Sander 2025-02-03 21:28 9805
1193b4be Poul Sander 2025-02-03 21:28 9806
    if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
1193b4be Poul Sander 2025-02-03 21:28 9807
        MarkIniSettingsDirty();
1193b4be Poul Sander 2025-02-03 21:28 9808
    return true;
1193b4be Poul Sander 2025-02-03 21:28 9809
}
1193b4be Poul Sander 2025-02-03 21:28 9810
1193b4be Poul Sander 2025-02-03 21:28 9811
static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9812
{
1193b4be Poul Sander 2025-02-03 21:28 9813
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9814
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9815
1193b4be Poul Sander 2025-02-03 21:28 9816
    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 9817
    const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 9818
1193b4be Poul Sander 2025-02-03 21:28 9819
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9820
    //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 9821
1193b4be Poul Sander 2025-02-03 21:28 9822
    int select_dir = 0;
1193b4be Poul Sander 2025-02-03 21:28 9823
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
1193b4be Poul Sander 2025-02-03 21:28 9824
    arrow_col.w *= 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 9825
1193b4be Poul Sander 2025-02-03 21:28 9826
    PushStyleColor(ImGuiCol_Text, arrow_col);
1193b4be Poul Sander 2025-02-03 21:28 9827
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
1193b4be Poul Sander 2025-02-03 21:28 9828
    PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
1193b4be Poul Sander 2025-02-03 21:28 9829
    const float backup_repeat_delay = g.IO.KeyRepeatDelay;
1193b4be Poul Sander 2025-02-03 21:28 9830
    const float backup_repeat_rate = g.IO.KeyRepeatRate;
1193b4be Poul Sander 2025-02-03 21:28 9831
    g.IO.KeyRepeatDelay = 0.250f;
1193b4be Poul Sander 2025-02-03 21:28 9832
    g.IO.KeyRepeatRate = 0.200f;
1193b4be Poul Sander 2025-02-03 21:28 9833
    float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
1193b4be Poul Sander 2025-02-03 21:28 9834
    window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
1193b4be Poul Sander 2025-02-03 21:28 9835
    if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
1193b4be Poul Sander 2025-02-03 21:28 9836
        select_dir = -1;
1193b4be Poul Sander 2025-02-03 21:28 9837
    window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
1193b4be Poul Sander 2025-02-03 21:28 9838
    if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
1193b4be Poul Sander 2025-02-03 21:28 9839
        select_dir = +1;
1193b4be Poul Sander 2025-02-03 21:28 9840
    PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 9841
    PopStyleColor(2);
1193b4be Poul Sander 2025-02-03 21:28 9842
    g.IO.KeyRepeatRate = backup_repeat_rate;
1193b4be Poul Sander 2025-02-03 21:28 9843
    g.IO.KeyRepeatDelay = backup_repeat_delay;
1193b4be Poul Sander 2025-02-03 21:28 9844
1193b4be Poul Sander 2025-02-03 21:28 9845
    ImGuiTabItem* tab_to_scroll_to = NULL;
1193b4be Poul Sander 2025-02-03 21:28 9846
    if (select_dir != 0)
1193b4be Poul Sander 2025-02-03 21:28 9847
        if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
1193b4be Poul Sander 2025-02-03 21:28 9848
        {
1193b4be Poul Sander 2025-02-03 21:28 9849
            int selected_order = TabBarGetTabOrder(tab_bar, tab_item);
1193b4be Poul Sander 2025-02-03 21:28 9850
            int target_order = selected_order + select_dir;
1193b4be Poul Sander 2025-02-03 21:28 9851
1193b4be Poul Sander 2025-02-03 21:28 9852
            // Skip tab item buttons until another tab item is found or end is reached
1193b4be Poul Sander 2025-02-03 21:28 9853
            while (tab_to_scroll_to == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9854
            {
1193b4be Poul Sander 2025-02-03 21:28 9855
                // If we are at the end of the list, still scroll to make our tab visible
1193b4be Poul Sander 2025-02-03 21:28 9856
                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 9857
1193b4be Poul Sander 2025-02-03 21:28 9858
                // Cross through buttons
1193b4be Poul Sander 2025-02-03 21:28 9859
                // (even if first/last item is a button, return it so we can update the scroll)
1193b4be Poul Sander 2025-02-03 21:28 9860
                if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
1193b4be Poul Sander 2025-02-03 21:28 9861
                {
1193b4be Poul Sander 2025-02-03 21:28 9862
                    target_order += select_dir;
1193b4be Poul Sander 2025-02-03 21:28 9863
                    selected_order += select_dir;
1193b4be Poul Sander 2025-02-03 21:28 9864
                    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 9865
                }
1193b4be Poul Sander 2025-02-03 21:28 9866
            }
1193b4be Poul Sander 2025-02-03 21:28 9867
        }
1193b4be Poul Sander 2025-02-03 21:28 9868
    window->DC.CursorPos = backup_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 9869
    tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 9870
1193b4be Poul Sander 2025-02-03 21:28 9871
    return tab_to_scroll_to;
1193b4be Poul Sander 2025-02-03 21:28 9872
}
1193b4be Poul Sander 2025-02-03 21:28 9873
1193b4be Poul Sander 2025-02-03 21:28 9874
static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9875
{
1193b4be Poul Sander 2025-02-03 21:28 9876
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9877
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9878
1193b4be Poul Sander 2025-02-03 21:28 9879
    // We use g.Style.FramePadding.y to match the square ArrowButton size
1193b4be Poul Sander 2025-02-03 21:28 9880
    const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 9881
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9882
    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 9883
    tab_bar->BarRect.Min.x += tab_list_popup_button_width;
1193b4be Poul Sander 2025-02-03 21:28 9884
1193b4be Poul Sander 2025-02-03 21:28 9885
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
1193b4be Poul Sander 2025-02-03 21:28 9886
    arrow_col.w *= 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 9887
    PushStyleColor(ImGuiCol_Text, arrow_col);
1193b4be Poul Sander 2025-02-03 21:28 9888
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
1193b4be Poul Sander 2025-02-03 21:28 9889
    bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
1193b4be Poul Sander 2025-02-03 21:28 9890
    PopStyleColor(2);
1193b4be Poul Sander 2025-02-03 21:28 9891
1193b4be Poul Sander 2025-02-03 21:28 9892
    ImGuiTabItem* tab_to_select = NULL;
1193b4be Poul Sander 2025-02-03 21:28 9893
    if (open)
1193b4be Poul Sander 2025-02-03 21:28 9894
    {
1193b4be Poul Sander 2025-02-03 21:28 9895
        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9896
        {
1193b4be Poul Sander 2025-02-03 21:28 9897
            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
1193b4be Poul Sander 2025-02-03 21:28 9898
            if (tab->Flags & ImGuiTabItemFlags_Button)
1193b4be Poul Sander 2025-02-03 21:28 9899
                continue;
1193b4be Poul Sander 2025-02-03 21:28 9900
1193b4be Poul Sander 2025-02-03 21:28 9901
            const char* tab_name = TabBarGetTabName(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 9902
            if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
1193b4be Poul Sander 2025-02-03 21:28 9903
                tab_to_select = tab;
1193b4be Poul Sander 2025-02-03 21:28 9904
        }
1193b4be Poul Sander 2025-02-03 21:28 9905
        EndCombo();
1193b4be Poul Sander 2025-02-03 21:28 9906
    }
1193b4be Poul Sander 2025-02-03 21:28 9907
1193b4be Poul Sander 2025-02-03 21:28 9908
    window->DC.CursorPos = backup_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 9909
    return tab_to_select;
1193b4be Poul Sander 2025-02-03 21:28 9910
}
1193b4be Poul Sander 2025-02-03 21:28 9911
1193b4be Poul Sander 2025-02-03 21:28 9912
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9913
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
1193b4be Poul Sander 2025-02-03 21:28 9914
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9915
// - BeginTabItem()
1193b4be Poul Sander 2025-02-03 21:28 9916
// - EndTabItem()
1193b4be Poul Sander 2025-02-03 21:28 9917
// - TabItemButton()
1193b4be Poul Sander 2025-02-03 21:28 9918
// - TabItemEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9919
// - SetTabItemClosed()
1193b4be Poul Sander 2025-02-03 21:28 9920
// - TabItemCalcSize() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9921
// - TabItemBackground() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9922
// - TabItemLabelAndCloseButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9923
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9924
1193b4be Poul Sander 2025-02-03 21:28 9925
bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 9926
{
1193b4be Poul Sander 2025-02-03 21:28 9927
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9928
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9929
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9930
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9931
1193b4be Poul Sander 2025-02-03 21:28 9932
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 9933
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9934
    {
1193b4be Poul Sander 2025-02-03 21:28 9935
        IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 9936
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9937
    }
1193b4be Poul Sander 2025-02-03 21:28 9938
    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 9939
1193b4be Poul Sander 2025-02-03 21:28 9940
    bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
1193b4be Poul Sander 2025-02-03 21:28 9941
    if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
1193b4be Poul Sander 2025-02-03 21:28 9942
    {
1193b4be Poul Sander 2025-02-03 21:28 9943
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
1193b4be Poul Sander 2025-02-03 21:28 9944
        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 9945
    }
1193b4be Poul Sander 2025-02-03 21:28 9946
    return ret;
1193b4be Poul Sander 2025-02-03 21:28 9947
}
1193b4be Poul Sander 2025-02-03 21:28 9948
1193b4be Poul Sander 2025-02-03 21:28 9949
void    ImGui::EndTabItem()
1193b4be Poul Sander 2025-02-03 21:28 9950
{
1193b4be Poul Sander 2025-02-03 21:28 9951
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9952
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9953
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9954
        return;
1193b4be Poul Sander 2025-02-03 21:28 9955
1193b4be Poul Sander 2025-02-03 21:28 9956
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 9957
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9958
    {
1193b4be Poul Sander 2025-02-03 21:28 9959
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 9960
        return;
1193b4be Poul Sander 2025-02-03 21:28 9961
    }
1193b4be Poul Sander 2025-02-03 21:28 9962
    IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
1193b4be Poul Sander 2025-02-03 21:28 9963
    ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
1193b4be Poul Sander 2025-02-03 21:28 9964
    if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
1193b4be Poul Sander 2025-02-03 21:28 9965
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 9966
}
1193b4be Poul Sander 2025-02-03 21:28 9967
1193b4be Poul Sander 2025-02-03 21:28 9968
bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 9969
{
1193b4be Poul Sander 2025-02-03 21:28 9970
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9971
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9972
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9973
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9974
1193b4be Poul Sander 2025-02-03 21:28 9975
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 9976
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9977
    {
1193b4be Poul Sander 2025-02-03 21:28 9978
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 9979
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9980
    }
1193b4be Poul Sander 2025-02-03 21:28 9981
    return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
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
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 9985
{
1193b4be Poul Sander 2025-02-03 21:28 9986
    // Layout whole tab bar if not already done
1193b4be Poul Sander 2025-02-03 21:28 9987
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9988
    if (tab_bar->WantLayout)
1193b4be Poul Sander 2025-02-03 21:28 9989
    {
1193b4be Poul Sander 2025-02-03 21:28 9990
        ImGuiNextItemData backup_next_item_data = g.NextItemData;
1193b4be Poul Sander 2025-02-03 21:28 9991
        TabBarLayout(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9992
        g.NextItemData = backup_next_item_data;
1193b4be Poul Sander 2025-02-03 21:28 9993
    }
1193b4be Poul Sander 2025-02-03 21:28 9994
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9995
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9996
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9997
1193b4be Poul Sander 2025-02-03 21:28 9998
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 9999
    const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
1193b4be Poul Sander 2025-02-03 21:28 10000
1193b4be Poul Sander 2025-02-03 21:28 10001
    // If the user called us with *p_open == false, we early out and don't render.
1193b4be Poul Sander 2025-02-03 21:28 10002
    // 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 10003
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 10004
    if (p_open && !*p_open)
1193b4be Poul Sander 2025-02-03 21:28 10005
    {
1193b4be Poul Sander 2025-02-03 21:28 10006
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
1193b4be Poul Sander 2025-02-03 21:28 10007
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10008
    }
1193b4be Poul Sander 2025-02-03 21:28 10009
1193b4be Poul Sander 2025-02-03 21:28 10010
    IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
1193b4be Poul Sander 2025-02-03 21:28 10011
    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 10012
1193b4be Poul Sander 2025-02-03 21:28 10013
    // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
1193b4be Poul Sander 2025-02-03 21:28 10014
    if (flags & ImGuiTabItemFlags_NoCloseButton)
1193b4be Poul Sander 2025-02-03 21:28 10015
        p_open = NULL;
1193b4be Poul Sander 2025-02-03 21:28 10016
    else if (p_open == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10017
        flags |= ImGuiTabItemFlags_NoCloseButton;
1193b4be Poul Sander 2025-02-03 21:28 10018
1193b4be Poul Sander 2025-02-03 21:28 10019
    // Acquire tab data
1193b4be Poul Sander 2025-02-03 21:28 10020
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
1193b4be Poul Sander 2025-02-03 21:28 10021
    bool tab_is_new = false;
1193b4be Poul Sander 2025-02-03 21:28 10022
    if (tab == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10023
    {
1193b4be Poul Sander 2025-02-03 21:28 10024
        tab_bar->Tabs.push_back(ImGuiTabItem());
1193b4be Poul Sander 2025-02-03 21:28 10025
        tab = &tab_bar->Tabs.back();
1193b4be Poul Sander 2025-02-03 21:28 10026
        tab->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 10027
        tab_bar->TabsAddedNew = tab_is_new = true;
1193b4be Poul Sander 2025-02-03 21:28 10028
    }
1193b4be Poul Sander 2025-02-03 21:28 10029
    tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
1193b4be Poul Sander 2025-02-03 21:28 10030
1193b4be Poul Sander 2025-02-03 21:28 10031
    // Calculate tab contents size
1193b4be Poul Sander 2025-02-03 21:28 10032
    ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
1193b4be Poul Sander 2025-02-03 21:28 10033
    tab->RequestedWidth = -1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10034
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)
1193b4be Poul Sander 2025-02-03 21:28 10035
        size.x = tab->RequestedWidth = g.NextItemData.Width;
1193b4be Poul Sander 2025-02-03 21:28 10036
    if (tab_is_new)
1193b4be Poul Sander 2025-02-03 21:28 10037
        tab->Width = ImMax(1.0f, size.x);
1193b4be Poul Sander 2025-02-03 21:28 10038
    tab->ContentWidth = size.x;
1193b4be Poul Sander 2025-02-03 21:28 10039
    tab->BeginOrder = tab_bar->TabsActiveCount++;
1193b4be Poul Sander 2025-02-03 21:28 10040
1193b4be Poul Sander 2025-02-03 21:28 10041
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 10042
    const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
1193b4be Poul Sander 2025-02-03 21:28 10043
    const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 10044
    const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
1193b4be Poul Sander 2025-02-03 21:28 10045
    const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
1193b4be Poul Sander 2025-02-03 21:28 10046
    tab->LastFrameVisible = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 10047
    tab->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 10048
    tab->Window = docked_window;
1193b4be Poul Sander 2025-02-03 21:28 10049
1193b4be Poul Sander 2025-02-03 21:28 10050
    // Append name _WITH_ the zero-terminator
1193b4be Poul Sander 2025-02-03 21:28 10051
    // (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 10052
    if (docked_window != NULL)
1193b4be Poul Sander 2025-02-03 21:28 10053
    {
1193b4be Poul Sander 2025-02-03 21:28 10054
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
1193b4be Poul Sander 2025-02-03 21:28 10055
        tab->NameOffset = -1;
1193b4be Poul Sander 2025-02-03 21:28 10056
    }
1193b4be Poul Sander 2025-02-03 21:28 10057
    else
1193b4be Poul Sander 2025-02-03 21:28 10058
    {
1193b4be Poul Sander 2025-02-03 21:28 10059
        tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
1193b4be Poul Sander 2025-02-03 21:28 10060
        tab_bar->TabsNames.append(label, label + strlen(label) + 1);
1193b4be Poul Sander 2025-02-03 21:28 10061
    }
1193b4be Poul Sander 2025-02-03 21:28 10062
1193b4be Poul Sander 2025-02-03 21:28 10063
    // Update selected tab
1193b4be Poul Sander 2025-02-03 21:28 10064
    if (!is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10065
    {
1193b4be Poul Sander 2025-02-03 21:28 10066
        if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
1193b4be Poul Sander 2025-02-03 21:28 10067
            if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
1193b4be Poul Sander 2025-02-03 21:28 10068
                TabBarQueueFocus(tab_bar, tab); // New tabs gets activated
1193b4be Poul Sander 2025-02-03 21:28 10069
        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 10070
            TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10071
    }
1193b4be Poul Sander 2025-02-03 21:28 10072
1193b4be Poul Sander 2025-02-03 21:28 10073
    // Lock visibility
1193b4be Poul Sander 2025-02-03 21:28 10074
    // (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 10075
    bool tab_contents_visible = (tab_bar->VisibleTabId == id);
1193b4be Poul Sander 2025-02-03 21:28 10076
    if (tab_contents_visible)
1193b4be Poul Sander 2025-02-03 21:28 10077
        tab_bar->VisibleTabWasSubmitted = true;
1193b4be Poul Sander 2025-02-03 21:28 10078
1193b4be Poul Sander 2025-02-03 21:28 10079
    // 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 10080
    if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10081
        if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
1193b4be Poul Sander 2025-02-03 21:28 10082
            tab_contents_visible = true;
1193b4be Poul Sander 2025-02-03 21:28 10083
1193b4be Poul Sander 2025-02-03 21:28 10084
    // 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 10085
    // 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 10086
    if (tab_appearing && (!tab_bar_appearing || tab_is_new))
1193b4be Poul Sander 2025-02-03 21:28 10087
    {
1193b4be Poul Sander 2025-02-03 21:28 10088
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
1193b4be Poul Sander 2025-02-03 21:28 10089
        if (is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10090
            return false;
1193b4be Poul Sander 2025-02-03 21:28 10091
        return tab_contents_visible;
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
    if (tab_bar->SelectedTabId == id)
1193b4be Poul Sander 2025-02-03 21:28 10095
        tab->LastFrameSelected = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 10096
1193b4be Poul Sander 2025-02-03 21:28 10097
    // Backup current layout position
1193b4be Poul Sander 2025-02-03 21:28 10098
    const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 10099
1193b4be Poul Sander 2025-02-03 21:28 10100
    // Layout
1193b4be Poul Sander 2025-02-03 21:28 10101
    const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
1193b4be Poul Sander 2025-02-03 21:28 10102
    size.x = tab->Width;
1193b4be Poul Sander 2025-02-03 21:28 10103
    if (is_central_section)
1193b4be Poul Sander 2025-02-03 21:28 10104
        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 10105
    else
1193b4be Poul Sander 2025-02-03 21:28 10106
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10107
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 10108
    ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 10109
1193b4be Poul Sander 2025-02-03 21:28 10110
    // 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 10111
    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 10112
    if (want_clip_rect)
1193b4be Poul Sander 2025-02-03 21:28 10113
        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 10114
1193b4be Poul Sander 2025-02-03 21:28 10115
    ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 10116
    ItemSize(bb.GetSize(), style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 10117
    window->DC.CursorMaxPos = backup_cursor_max_pos;
1193b4be Poul Sander 2025-02-03 21:28 10118
1193b4be Poul Sander 2025-02-03 21:28 10119
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 10120
    {
1193b4be Poul Sander 2025-02-03 21:28 10121
        if (want_clip_rect)
1193b4be Poul Sander 2025-02-03 21:28 10122
            PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 10123
        window->DC.CursorPos = backup_main_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 10124
        return tab_contents_visible;
1193b4be Poul Sander 2025-02-03 21:28 10125
    }
1193b4be Poul Sander 2025-02-03 21:28 10126
1193b4be Poul Sander 2025-02-03 21:28 10127
    // Click to Select a tab
1193b4be Poul Sander 2025-02-03 21:28 10128
    ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
1193b4be Poul Sander 2025-02-03 21:28 10129
    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 10130
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
1193b4be Poul Sander 2025-02-03 21:28 10131
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 10132
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 10133
    if (pressed && !is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10134
        TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10135
1193b4be Poul Sander 2025-02-03 21:28 10136
    // 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 10137
    // 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 10138
    if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 10139
        g.ActiveIdWindow = docked_window;
1193b4be Poul Sander 2025-02-03 21:28 10140
1193b4be Poul Sander 2025-02-03 21:28 10141
    // Drag and drop a single floating window node moves it
1193b4be Poul Sander 2025-02-03 21:28 10142
    ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
1193b4be Poul Sander 2025-02-03 21:28 10143
    const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
1193b4be Poul Sander 2025-02-03 21:28 10144
    if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 10145
    {
1193b4be Poul Sander 2025-02-03 21:28 10146
        // Move
1193b4be Poul Sander 2025-02-03 21:28 10147
        StartMouseMovingWindow(docked_window);
1193b4be Poul Sander 2025-02-03 21:28 10148
    }
1193b4be Poul Sander 2025-02-03 21:28 10149
    else if (held && !tab_appearing && IsMouseDragging(0))
1193b4be Poul Sander 2025-02-03 21:28 10150
    {
1193b4be Poul Sander 2025-02-03 21:28 10151
        // Drag and drop: re-order tabs
1193b4be Poul Sander 2025-02-03 21:28 10152
        int drag_dir = 0;
1193b4be Poul Sander 2025-02-03 21:28 10153
        float drag_distance_from_edge_x = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 10154
        if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
1193b4be Poul Sander 2025-02-03 21:28 10155
        {
1193b4be Poul Sander 2025-02-03 21:28 10156
            // 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 10157
            if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
1193b4be Poul Sander 2025-02-03 21:28 10158
            {
1193b4be Poul Sander 2025-02-03 21:28 10159
                drag_dir = -1;
1193b4be Poul Sander 2025-02-03 21:28 10160
                drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
1193b4be Poul Sander 2025-02-03 21:28 10161
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 10162
            }
1193b4be Poul Sander 2025-02-03 21:28 10163
            else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
1193b4be Poul Sander 2025-02-03 21:28 10164
            {
1193b4be Poul Sander 2025-02-03 21:28 10165
                drag_dir = +1;
1193b4be Poul Sander 2025-02-03 21:28 10166
                drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 10167
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 10168
            }
1193b4be Poul Sander 2025-02-03 21:28 10169
        }
1193b4be Poul Sander 2025-02-03 21:28 10170
1193b4be Poul Sander 2025-02-03 21:28 10171
        // Extract a Dockable window out of it's tab bar
1193b4be Poul Sander 2025-02-03 21:28 10172
        const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking);
1193b4be Poul Sander 2025-02-03 21:28 10173
        if (can_undock)
1193b4be Poul Sander 2025-02-03 21:28 10174
        {
1193b4be Poul Sander 2025-02-03 21:28 10175
            // 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 10176
            bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
1193b4be Poul Sander 2025-02-03 21:28 10177
            if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 10178
            {
1193b4be Poul Sander 2025-02-03 21:28 10179
                float threshold_base = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 10180
                float threshold_x = (threshold_base * 2.2f);
1193b4be Poul Sander 2025-02-03 21:28 10181
                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 10182
                //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 10183
1193b4be Poul Sander 2025-02-03 21:28 10184
                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 10185
                if (distance_from_edge_y >= threshold_y)
1193b4be Poul Sander 2025-02-03 21:28 10186
                    undocking_tab = true;
1193b4be Poul Sander 2025-02-03 21:28 10187
                if (drag_distance_from_edge_x > threshold_x)
1193b4be Poul Sander 2025-02-03 21:28 10188
                    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 10189
                        undocking_tab = true;
1193b4be Poul Sander 2025-02-03 21:28 10190
            }
1193b4be Poul Sander 2025-02-03 21:28 10191
1193b4be Poul Sander 2025-02-03 21:28 10192
            if (undocking_tab)
1193b4be Poul Sander 2025-02-03 21:28 10193
            {
1193b4be Poul Sander 2025-02-03 21:28 10194
                // Undock
1193b4be Poul Sander 2025-02-03 21:28 10195
                // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
1193b4be Poul Sander 2025-02-03 21:28 10196
                DockContextQueueUndockWindow(&g, docked_window);
1193b4be Poul Sander 2025-02-03 21:28 10197
                g.MovingWindow = docked_window;
1193b4be Poul Sander 2025-02-03 21:28 10198
                SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
1193b4be Poul Sander 2025-02-03 21:28 10199
                g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
1193b4be Poul Sander 2025-02-03 21:28 10200
                g.ActiveIdNoClearOnFocusLoss = true;
1193b4be Poul Sander 2025-02-03 21:28 10201
                SetActiveIdUsingAllKeyboardKeys();
1193b4be Poul Sander 2025-02-03 21:28 10202
            }
1193b4be Poul Sander 2025-02-03 21:28 10203
        }
1193b4be Poul Sander 2025-02-03 21:28 10204
    }
1193b4be Poul Sander 2025-02-03 21:28 10205
1193b4be Poul Sander 2025-02-03 21:28 10206
#if 0
1193b4be Poul Sander 2025-02-03 21:28 10207
    if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
1193b4be Poul Sander 2025-02-03 21:28 10208
    {
1193b4be Poul Sander 2025-02-03 21:28 10209
        // Enlarge tab display when hovering
1193b4be Poul Sander 2025-02-03 21:28 10210
        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 10211
        display_draw_list = GetForegroundDrawList(window);
1193b4be Poul Sander 2025-02-03 21:28 10212
        TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
1193b4be Poul Sander 2025-02-03 21:28 10213
    }
1193b4be Poul Sander 2025-02-03 21:28 10214
#endif
1193b4be Poul Sander 2025-02-03 21:28 10215
1193b4be Poul Sander 2025-02-03 21:28 10216
    // Render tab shape
1193b4be Poul Sander 2025-02-03 21:28 10217
    ImDrawList* display_draw_list = window->DrawList;
1193b4be Poul Sander 2025-02-03 21:28 10218
    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));
1193b4be Poul Sander 2025-02-03 21:28 10219
    TabItemBackground(display_draw_list, bb, flags, tab_col);
1193b4be Poul Sander 2025-02-03 21:28 10220
    if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 10221
    {
1193b4be Poul Sander 2025-02-03 21:28 10222
        float x_offset = IM_TRUNC(0.4f * style.TabRounding);
1193b4be Poul Sander 2025-02-03 21:28 10223
        if (x_offset < 2.0f * g.CurrentDpiScale)
1193b4be Poul Sander 2025-02-03 21:28 10224
            x_offset = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 10225
        float y_offset = 1.0f * g.CurrentDpiScale;
1193b4be Poul Sander 2025-02-03 21:28 10226
        display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize);
1193b4be Poul Sander 2025-02-03 21:28 10227
    }
1193b4be Poul Sander 2025-02-03 21:28 10228
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 10229
1193b4be Poul Sander 2025-02-03 21:28 10230
    // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
1193b4be Poul Sander 2025-02-03 21:28 10231
    const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
1193b4be Poul Sander 2025-02-03 21:28 10232
    if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10233
        TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10234
1193b4be Poul Sander 2025-02-03 21:28 10235
    if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
1193b4be Poul Sander 2025-02-03 21:28 10236
        flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
1193b4be Poul Sander 2025-02-03 21:28 10237
1193b4be Poul Sander 2025-02-03 21:28 10238
    // Render tab label, process close button
1193b4be Poul Sander 2025-02-03 21:28 10239
    const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
1193b4be Poul Sander 2025-02-03 21:28 10240
    bool just_closed;
1193b4be Poul Sander 2025-02-03 21:28 10241
    bool text_clipped;
1193b4be Poul Sander 2025-02-03 21:28 10242
    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);
1193b4be Poul Sander 2025-02-03 21:28 10243
    if (just_closed && p_open != NULL)
1193b4be Poul Sander 2025-02-03 21:28 10244
    {
1193b4be Poul Sander 2025-02-03 21:28 10245
        *p_open = false;
1193b4be Poul Sander 2025-02-03 21:28 10246
        TabBarCloseTab(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10247
    }
1193b4be Poul Sander 2025-02-03 21:28 10248
1193b4be Poul Sander 2025-02-03 21:28 10249
    // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent)
1193b4be Poul Sander 2025-02-03 21:28 10250
    // That state is copied to window->DockTabItemStatusFlags by our caller.
1193b4be Poul Sander 2025-02-03 21:28 10251
    if (docked_window && (hovered || g.HoveredId == close_button_id))
1193b4be Poul Sander 2025-02-03 21:28 10252
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
1193b4be Poul Sander 2025-02-03 21:28 10253
1193b4be Poul Sander 2025-02-03 21:28 10254
    // Restore main window position so user can draw there
1193b4be Poul Sander 2025-02-03 21:28 10255
    if (want_clip_rect)
1193b4be Poul Sander 2025-02-03 21:28 10256
        PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 10257
    window->DC.CursorPos = backup_main_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 10258
1193b4be Poul Sander 2025-02-03 21:28 10259
    // Tooltip
1193b4be Poul Sander 2025-02-03 21:28 10260
    // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
1193b4be Poul Sander 2025-02-03 21:28 10261
    // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
1193b4be Poul Sander 2025-02-03 21:28 10262
    // FIXME: This is a mess.
1193b4be Poul Sander 2025-02-03 21:28 10263
    // FIXME: We may want disabled tab to still display the tooltip?
1193b4be Poul Sander 2025-02-03 21:28 10264
    if (text_clipped && g.HoveredId == id && !held)
1193b4be Poul Sander 2025-02-03 21:28 10265
        if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
1193b4be Poul Sander 2025-02-03 21:28 10266
            SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
1193b4be Poul Sander 2025-02-03 21:28 10267
1193b4be Poul Sander 2025-02-03 21:28 10268
    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 10269
    if (is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10270
        return pressed;
1193b4be Poul Sander 2025-02-03 21:28 10271
    return tab_contents_visible;
1193b4be Poul Sander 2025-02-03 21:28 10272
}
1193b4be Poul Sander 2025-02-03 21:28 10273
1193b4be Poul Sander 2025-02-03 21:28 10274
// [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 10275
// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
1193b4be Poul Sander 2025-02-03 21:28 10276
// Tabs closed by the close button will automatically be flagged to avoid this issue.
1193b4be Poul Sander 2025-02-03 21:28 10277
void    ImGui::SetTabItemClosed(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 10278
{
1193b4be Poul Sander 2025-02-03 21:28 10279
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10280
    bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
1193b4be Poul Sander 2025-02-03 21:28 10281
    if (is_within_manual_tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10282
    {
1193b4be Poul Sander 2025-02-03 21:28 10283
        ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 10284
        ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
1193b4be Poul Sander 2025-02-03 21:28 10285
        if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
1193b4be Poul Sander 2025-02-03 21:28 10286
            tab->WantClose = true; // Will be processed by next call to TabBarLayout()
1193b4be Poul Sander 2025-02-03 21:28 10287
    }
1193b4be Poul Sander 2025-02-03 21:28 10288
    else if (ImGuiWindow* window = FindWindowByName(label))
1193b4be Poul Sander 2025-02-03 21:28 10289
    {
1193b4be Poul Sander 2025-02-03 21:28 10290
        if (window->DockIsActive)
1193b4be Poul Sander 2025-02-03 21:28 10291
            if (ImGuiDockNode* node = window->DockNode)
1193b4be Poul Sander 2025-02-03 21:28 10292
            {
1193b4be Poul Sander 2025-02-03 21:28 10293
                ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
1193b4be Poul Sander 2025-02-03 21:28 10294
                TabBarRemoveTab(node->TabBar, tab_id);
1193b4be Poul Sander 2025-02-03 21:28 10295
                window->DockTabWantClose = true;
1193b4be Poul Sander 2025-02-03 21:28 10296
            }
1193b4be Poul Sander 2025-02-03 21:28 10297
    }
1193b4be Poul Sander 2025-02-03 21:28 10298
}
1193b4be Poul Sander 2025-02-03 21:28 10299
1193b4be Poul Sander 2025-02-03 21:28 10300
ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
1193b4be Poul Sander 2025-02-03 21:28 10301
{
1193b4be Poul Sander 2025-02-03 21:28 10302
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10303
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 10304
    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 10305
    if (has_close_button_or_unsaved_marker)
1193b4be Poul Sander 2025-02-03 21:28 10306
        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 10307
    else
1193b4be Poul Sander 2025-02-03 21:28 10308
        size.x += g.Style.FramePadding.x + 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10309
    return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
1193b4be Poul Sander 2025-02-03 21:28 10310
}
1193b4be Poul Sander 2025-02-03 21:28 10311
1193b4be Poul Sander 2025-02-03 21:28 10312
ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 10313
{
1193b4be Poul Sander 2025-02-03 21:28 10314
    return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument));
1193b4be Poul Sander 2025-02-03 21:28 10315
}
1193b4be Poul Sander 2025-02-03 21:28 10316
1193b4be Poul Sander 2025-02-03 21:28 10317
void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
1193b4be Poul Sander 2025-02-03 21:28 10318
{
1193b4be Poul Sander 2025-02-03 21:28 10319
    // 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 10320
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10321
    const float width = bb.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 10322
    IM_UNUSED(flags);
1193b4be Poul Sander 2025-02-03 21:28 10323
    IM_ASSERT(width > 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10324
    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 10325
    const float y1 = bb.Min.y + 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10326
    const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
1193b4be Poul Sander 2025-02-03 21:28 10327
    draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
1193b4be Poul Sander 2025-02-03 21:28 10328
    draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
1193b4be Poul Sander 2025-02-03 21:28 10329
    draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
1193b4be Poul Sander 2025-02-03 21:28 10330
    draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
1193b4be Poul Sander 2025-02-03 21:28 10331
    draw_list->PathFillConvex(col);
1193b4be Poul Sander 2025-02-03 21:28 10332
    if (g.Style.TabBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 10333
    {
1193b4be Poul Sander 2025-02-03 21:28 10334
        draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
1193b4be Poul Sander 2025-02-03 21:28 10335
        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 10336
        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 10337
        draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
1193b4be Poul Sander 2025-02-03 21:28 10338
        draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 10339
    }
1193b4be Poul Sander 2025-02-03 21:28 10340
}
1193b4be Poul Sander 2025-02-03 21:28 10341
1193b4be Poul Sander 2025-02-03 21:28 10342
// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
1193b4be Poul Sander 2025-02-03 21:28 10343
// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
1193b4be Poul Sander 2025-02-03 21:28 10344
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 10345
{
1193b4be Poul Sander 2025-02-03 21:28 10346
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10347
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 10348
1193b4be Poul Sander 2025-02-03 21:28 10349
    if (out_just_closed)
1193b4be Poul Sander 2025-02-03 21:28 10350
        *out_just_closed = false;
1193b4be Poul Sander 2025-02-03 21:28 10351
    if (out_text_clipped)
1193b4be Poul Sander 2025-02-03 21:28 10352
        *out_text_clipped = false;
1193b4be Poul Sander 2025-02-03 21:28 10353
1193b4be Poul Sander 2025-02-03 21:28 10354
    if (bb.GetWidth() <= 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 10355
        return;
1193b4be Poul Sander 2025-02-03 21:28 10356
1193b4be Poul Sander 2025-02-03 21:28 10357
    // 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 10358
    // 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 10359
#if 0
1193b4be Poul Sander 2025-02-03 21:28 10360
    const float backup_alpha = g.Style.Alpha;
1193b4be Poul Sander 2025-02-03 21:28 10361
    if (!is_contents_visible)
1193b4be Poul Sander 2025-02-03 21:28 10362
        g.Style.Alpha *= 0.7f;
1193b4be Poul Sander 2025-02-03 21:28 10363
#endif
1193b4be Poul Sander 2025-02-03 21:28 10364
1193b4be Poul Sander 2025-02-03 21:28 10365
    // Render text label (with clipping + alpha gradient) + unsaved marker
1193b4be Poul Sander 2025-02-03 21:28 10366
    ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
1193b4be Poul Sander 2025-02-03 21:28 10367
    ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
1193b4be Poul Sander 2025-02-03 21:28 10368
1193b4be Poul Sander 2025-02-03 21:28 10369
    // Return clipped state ignoring the close button
1193b4be Poul Sander 2025-02-03 21:28 10370
    if (out_text_clipped)
1193b4be Poul Sander 2025-02-03 21:28 10371
    {
1193b4be Poul Sander 2025-02-03 21:28 10372
        *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 10373
        //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 10374
    }
1193b4be Poul Sander 2025-02-03 21:28 10375
1193b4be Poul Sander 2025-02-03 21:28 10376
    const float button_sz = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 10377
    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 10378
1193b4be Poul Sander 2025-02-03 21:28 10379
    // Close Button & Unsaved Marker
1193b4be Poul Sander 2025-02-03 21:28 10380
    // 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 10381
    //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
1193b4be Poul Sander 2025-02-03 21:28 10382
    //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
1193b4be Poul Sander 2025-02-03 21:28 10383
    //  '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 10384
    bool close_button_pressed = false;
1193b4be Poul Sander 2025-02-03 21:28 10385
    bool close_button_visible = false;
1193b4be Poul Sander 2025-02-03 21:28 10386
    if (close_button_id != 0)
1193b4be Poul Sander 2025-02-03 21:28 10387
        if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
1193b4be Poul Sander 2025-02-03 21:28 10388
            if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
1193b4be Poul Sander 2025-02-03 21:28 10389
                close_button_visible = true;
1193b4be Poul Sander 2025-02-03 21:28 10390
    bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
1193b4be Poul Sander 2025-02-03 21:28 10391
1193b4be Poul Sander 2025-02-03 21:28 10392
    if (close_button_visible)
1193b4be Poul Sander 2025-02-03 21:28 10393
    {
1193b4be Poul Sander 2025-02-03 21:28 10394
        ImGuiLastItemData last_item_backup = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 10395
        if (CloseButton(close_button_id, button_pos))
1193b4be Poul Sander 2025-02-03 21:28 10396
            close_button_pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 10397
        g.LastItemData = last_item_backup;
1193b4be Poul Sander 2025-02-03 21:28 10398
1193b4be Poul Sander 2025-02-03 21:28 10399
        // Close with middle mouse button
1193b4be Poul Sander 2025-02-03 21:28 10400
        if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
1193b4be Poul Sander 2025-02-03 21:28 10401
            close_button_pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 10402
    }
1193b4be Poul Sander 2025-02-03 21:28 10403
    else if (unsaved_marker_visible)
1193b4be Poul Sander 2025-02-03 21:28 10404
    {
1193b4be Poul Sander 2025-02-03 21:28 10405
        const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
1193b4be Poul Sander 2025-02-03 21:28 10406
        RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
1193b4be Poul Sander 2025-02-03 21:28 10407
    }
1193b4be Poul Sander 2025-02-03 21:28 10408
1193b4be Poul Sander 2025-02-03 21:28 10409
    // This is all rather complicated
1193b4be Poul Sander 2025-02-03 21:28 10410
    // (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 10411
    // 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..
1193b4be Poul Sander 2025-02-03 21:28 10412
    float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10413
    if (close_button_visible || unsaved_marker_visible)
1193b4be Poul Sander 2025-02-03 21:28 10414
    {
1193b4be Poul Sander 2025-02-03 21:28 10415
        text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
1193b4be Poul Sander 2025-02-03 21:28 10416
        text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 10417
        ellipsis_max_x = text_pixel_clip_bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 10418
    }
1193b4be Poul Sander 2025-02-03 21:28 10419
    RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
1193b4be Poul Sander 2025-02-03 21:28 10420
1193b4be Poul Sander 2025-02-03 21:28 10421
#if 0
1193b4be Poul Sander 2025-02-03 21:28 10422
    if (!is_contents_visible)
1193b4be Poul Sander 2025-02-03 21:28 10423
        g.Style.Alpha = backup_alpha;
1193b4be Poul Sander 2025-02-03 21:28 10424
#endif
1193b4be Poul Sander 2025-02-03 21:28 10425
1193b4be Poul Sander 2025-02-03 21:28 10426
    if (out_just_closed)
1193b4be Poul Sander 2025-02-03 21:28 10427
        *out_just_closed = close_button_pressed;
1193b4be Poul Sander 2025-02-03 21:28 10428
}
1193b4be Poul Sander 2025-02-03 21:28 10429
1193b4be Poul Sander 2025-02-03 21:28 10430
1193b4be Poul Sander 2025-02-03 21:28 10431
#endif // #ifndef IMGUI_DISABLE
1970-01-01 00:00 10432