8637 lines
417 KiB
C++
8637 lines
417 KiB
C++
|
// dear imgui, v1.89.6
|
||
|
// (widgets code)
|
||
|
|
||
|
/*
|
||
|
|
||
|
Index of this file:
|
||
|
|
||
|
// [SECTION] Forward Declarations
|
||
|
// [SECTION] Widgets: Text, etc.
|
||
|
// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
|
||
|
// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
|
||
|
// [SECTION] Widgets: ComboBox
|
||
|
// [SECTION] Data Type and Data Formatting Helpers
|
||
|
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
|
||
|
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
|
||
|
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
|
||
|
// [SECTION] Widgets: InputText, InputTextMultiline
|
||
|
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
|
||
|
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
|
||
|
// [SECTION] Widgets: Selectable
|
||
|
// [SECTION] Widgets: ListBox
|
||
|
// [SECTION] Widgets: PlotLines, PlotHistogram
|
||
|
// [SECTION] Widgets: Value helpers
|
||
|
// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
|
||
|
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
|
||
|
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
|
||
|
// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
|
||
|
|
||
|
*/
|
||
|
|
||
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||
|
#define _CRT_SECURE_NO_WARNINGS
|
||
|
#endif
|
||
|
|
||
|
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||
|
#endif
|
||
|
|
||
|
#include "imgui.h"
|
||
|
#ifndef IMGUI_DISABLE
|
||
|
#include "imgui_internal.h"
|
||
|
|
||
|
// System includes
|
||
|
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
|
||
|
#include <stddef.h> // intptr_t
|
||
|
#else
|
||
|
#include <stdint.h> // intptr_t
|
||
|
#endif
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Warnings
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
// Visual Studio warnings
|
||
|
#ifdef _MSC_VER
|
||
|
#pragma warning (disable: 4127) // condition expression is constant
|
||
|
#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
|
||
|
#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
|
||
|
#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
|
||
|
#endif
|
||
|
#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).
|
||
|
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
|
||
|
#endif
|
||
|
|
||
|
// Clang/GCC warnings with -Weverything
|
||
|
#if defined(__clang__)
|
||
|
#if __has_warning("-Wunknown-warning-option")
|
||
|
#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!
|
||
|
#endif
|
||
|
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
|
||
|
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
|
||
|
#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.
|
||
|
#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.
|
||
|
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||
|
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0
|
||
|
#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.
|
||
|
#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
|
||
|
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
|
||
|
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
|
||
|
#elif defined(__GNUC__)
|
||
|
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
||
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
|
||
|
#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
|
||
|
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
|
||
|
#endif
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Data
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
// Widgets
|
||
|
static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
|
||
|
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.
|
||
|
|
||
|
// Those MIN/MAX values are not define because we need to point to them
|
||
|
static const signed char IM_S8_MIN = -128;
|
||
|
static const signed char IM_S8_MAX = 127;
|
||
|
static const unsigned char IM_U8_MIN = 0;
|
||
|
static const unsigned char IM_U8_MAX = 0xFF;
|
||
|
static const signed short IM_S16_MIN = -32768;
|
||
|
static const signed short IM_S16_MAX = 32767;
|
||
|
static const unsigned short IM_U16_MIN = 0;
|
||
|
static const unsigned short IM_U16_MAX = 0xFFFF;
|
||
|
static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
|
||
|
static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
|
||
|
static const ImU32 IM_U32_MIN = 0;
|
||
|
static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
|
||
|
#ifdef LLONG_MIN
|
||
|
static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
|
||
|
static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
|
||
|
#else
|
||
|
static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
|
||
|
static const ImS64 IM_S64_MAX = 9223372036854775807LL;
|
||
|
#endif
|
||
|
static const ImU64 IM_U64_MIN = 0;
|
||
|
#ifdef ULLONG_MAX
|
||
|
static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
|
||
|
#else
|
||
|
static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
|
||
|
#endif
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// [SECTION] Forward Declarations
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
// For InputTextEx()
|
||
|
static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
|
||
|
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
|
||
|
static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// [SECTION] Widgets: Text, etc.
|
||
|
//-------------------------------------------------------------------------
|
||
|
// - TextEx() [Internal]
|
||
|
// - TextUnformatted()
|
||
|
// - Text()
|
||
|
// - TextV()
|
||
|
// - TextColored()
|
||
|
// - TextColoredV()
|
||
|
// - TextDisabled()
|
||
|
// - TextDisabledV()
|
||
|
// - TextWrapped()
|
||
|
// - TextWrappedV()
|
||
|
// - LabelText()
|
||
|
// - LabelTextV()
|
||
|
// - BulletText()
|
||
|
// - BulletTextV()
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
|
||
|
// Accept null ranges
|
||
|
if (text == text_end)
|
||
|
text = text_end = "";
|
||
|
|
||
|
// Calculate length
|
||
|
const char* text_begin = text;
|
||
|
if (text_end == NULL)
|
||
|
text_end = text + strlen(text); // FIXME-OPT
|
||
|
|
||
|
const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
|
||
|
const float wrap_pos_x = window->DC.TextWrapPos;
|
||
|
const bool wrap_enabled = (wrap_pos_x >= 0.0f);
|
||
|
if (text_end - text <= 2000 || wrap_enabled)
|
||
|
{
|
||
|
// Common case
|
||
|
const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
|
||
|
const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
|
||
|
|
||
|
ImRect bb(text_pos, text_pos + text_size);
|
||
|
ItemSize(text_size, 0.0f);
|
||
|
if (!ItemAdd(bb, 0))
|
||
|
return;
|
||
|
|
||
|
// Render (we don't hide text after ## in this end-user function)
|
||
|
RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Long text!
|
||
|
// Perform manual coarse clipping to optimize for long multi-line text
|
||
|
// - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
|
||
|
// - 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.
|
||
|
// - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
|
||
|
const char* line = text;
|
||
|
const float line_height = GetTextLineHeight();
|
||
|
ImVec2 text_size(0, 0);
|
||
|
|
||
|
// Lines to skip (can't skip when logging text)
|
||
|
ImVec2 pos = text_pos;
|
||
|
if (!g.LogEnabled)
|
||
|
{
|
||
|
int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
|
||
|
if (lines_skippable > 0)
|
||
|
{
|
||
|
int lines_skipped = 0;
|
||
|
while (line < text_end && lines_skipped < lines_skippable)
|
||
|
{
|
||
|
const char* line_end = (const char*)memchr(line, '\n', text_end - line);
|
||
|
if (!line_end)
|
||
|
line_end = text_end;
|
||
|
if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
|
||
|
text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
|
||
|
line = line_end + 1;
|
||
|
lines_skipped++;
|
||
|
}
|
||
|
pos.y += lines_skipped * line_height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Lines to render
|
||
|
if (line < text_end)
|
||
|
{
|
||
|
ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
|
||
|
while (line < text_end)
|
||
|
{
|
||
|
if (IsClippedEx(line_rect, 0))
|
||
|
break;
|
||
|
|
||
|
const char* line_end = (const char*)memchr(line, '\n', text_end - line);
|
||
|
if (!line_end)
|
||
|
line_end = text_end;
|
||
|
text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
|
||
|
RenderText(pos, line, line_end, false);
|
||
|
line = line_end + 1;
|
||
|
line_rect.Min.y += line_height;
|
||
|
line_rect.Max.y += line_height;
|
||
|
pos.y += line_height;
|
||
|
}
|
||
|
|
||
|
// Count remaining lines
|
||
|
int lines_skipped = 0;
|
||
|
while (line < text_end)
|
||
|
{
|
||
|
const char* line_end = (const char*)memchr(line, '\n', text_end - line);
|
||
|
if (!line_end)
|
||
|
line_end = text_end;
|
||
|
if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
|
||
|
text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
|
||
|
line = line_end + 1;
|
||
|
lines_skipped++;
|
||
|
}
|
||
|
pos.y += lines_skipped * line_height;
|
||
|
}
|
||
|
text_size.y = (pos - text_pos).y;
|
||
|
|
||
|
ImRect bb(text_pos, text_pos + text_size);
|
||
|
ItemSize(text_size, 0.0f);
|
||
|
ItemAdd(bb, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ImGui::TextUnformatted(const char* text, const char* text_end)
|
||
|
{
|
||
|
TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
|
||
|
}
|
||
|
|
||
|
void ImGui::Text(const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
TextV(fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
void ImGui::TextV(const char* fmt, va_list args)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
|
||
|
const char* text, *text_end;
|
||
|
ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
|
||
|
TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
|
||
|
}
|
||
|
|
||
|
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
TextColoredV(col, fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
|
||
|
{
|
||
|
PushStyleColor(ImGuiCol_Text, col);
|
||
|
TextV(fmt, args);
|
||
|
PopStyleColor();
|
||
|
}
|
||
|
|
||
|
void ImGui::TextDisabled(const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
TextDisabledV(fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
void ImGui::TextDisabledV(const char* fmt, va_list args)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
|
||
|
TextV(fmt, args);
|
||
|
PopStyleColor();
|
||
|
}
|
||
|
|
||
|
void ImGui::TextWrapped(const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
TextWrappedV(fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
void ImGui::TextWrappedV(const char* fmt, va_list args)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set
|
||
|
if (need_backup)
|
||
|
PushTextWrapPos(0.0f);
|
||
|
TextV(fmt, args);
|
||
|
if (need_backup)
|
||
|
PopTextWrapPos();
|
||
|
}
|
||
|
|
||
|
void ImGui::LabelText(const char* label, const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
LabelTextV(label, fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
// Add a label+text combo aligned to other label+value widgets
|
||
|
void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
const ImGuiStyle& style = g.Style;
|
||
|
const float w = CalcItemWidth();
|
||
|
|
||
|
const char* value_text_begin, *value_text_end;
|
||
|
ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
|
||
|
const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
|
||
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||
|
|
||
|
const ImVec2 pos = window->DC.CursorPos;
|
||
|
const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
|
||
|
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));
|
||
|
ItemSize(total_bb, style.FramePadding.y);
|
||
|
if (!ItemAdd(total_bb, 0))
|
||
|
return;
|
||
|
|
||
|
// Render
|
||
|
RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
|
||
|
if (label_size.x > 0.0f)
|
||
|
RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
|
||
|
}
|
||
|
|
||
|
void ImGui::BulletText(const char* fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
BulletTextV(fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
// Text with a little bullet aligned to the typical tree node.
|
||
|
void ImGui::BulletTextV(const char* fmt, va_list args)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
const ImGuiStyle& style = g.Style;
|
||
|
|
||
|
const char* text_begin, *text_end;
|
||
|
ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
|
||
|
const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
|
||
|
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
|
||
|
ImVec2 pos = window->DC.CursorPos;
|
||
|
pos.y += window->DC.CurrLineTextBaseOffset;
|
||
|
ItemSize(total_size, 0.0f);
|
||
|
const ImRect bb(pos, pos + total_size);
|
||
|
if (!ItemAdd(bb, 0))
|
||
|
return;
|
||
|
|
||
|
// Render
|
||
|
ImU32 text_col = GetColorU32(ImGuiCol_Text);
|
||
|
RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
|
||
|
RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// [SECTION] Widgets: Main
|
||
|
//-------------------------------------------------------------------------
|
||
|
// - ButtonBehavior() [Internal]
|
||
|
// - Button()
|
||
|
// - SmallButton()
|
||
|
// - InvisibleButton()
|
||
|
// - ArrowButton()
|
||
|
// - CloseButton() [Internal]
|
||
|
// - CollapseButton() [Internal]
|
||
|
// - GetWindowScrollbarID() [Internal]
|
||
|
// - GetWindowScrollbarRect() [Internal]
|
||
|
// - Scrollbar() [Internal]
|
||
|
// - ScrollbarEx() [Internal]
|
||
|
// - Image()
|
||
|
// - ImageButton()
|
||
|
// - Checkbox()
|
||
|
// - CheckboxFlagsT() [Internal]
|
||
|
// - CheckboxFlags()
|
||
|
// - RadioButton()
|
||
|
// - ProgressBar()
|
||
|
// - Bullet()
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
// The ButtonBehavior() function is key to many interactions and used by many/most widgets.
|
||
|
// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
|
||
|
// this code is a little complex.
|
||
|
// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
|
||
|
// See the series of events below and the corresponding state reported by dear imgui:
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
|
||
|
// Frame N+0 (mouse is outside bb) - - - - - -
|
||
|
// Frame N+1 (mouse moves inside bb) - true - - - -
|
||
|
// Frame N+2 (mouse button is down) - true true true - true
|
||
|
// Frame N+3 (mouse button is down) - true true - - -
|
||
|
// Frame N+4 (mouse moves outside bb) - - true - - -
|
||
|
// Frame N+5 (mouse moves inside bb) - true true - - -
|
||
|
// Frame N+6 (mouse button is released) true true - - true -
|
||
|
// Frame N+7 (mouse button is released) - true - - - -
|
||
|
// Frame N+8 (mouse moves outside bb) - - - - - -
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
|
||
|
// Frame N+2 (mouse button is down) true true true true - true
|
||
|
// Frame N+3 (mouse button is down) - true true - - -
|
||
|
// Frame N+6 (mouse button is released) - true - - true -
|
||
|
// Frame N+7 (mouse button is released) - true - - - -
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
|
||
|
// Frame N+2 (mouse button is down) - true - - - true
|
||
|
// Frame N+3 (mouse button is down) - true - - - -
|
||
|
// Frame N+6 (mouse button is released) true true - - - -
|
||
|
// Frame N+7 (mouse button is released) - true - - - -
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
|
||
|
// Frame N+0 (mouse button is down) - true - - - true
|
||
|
// Frame N+1 (mouse button is down) - true - - - -
|
||
|
// Frame N+2 (mouse button is released) - true - - - -
|
||
|
// Frame N+3 (mouse button is released) - true - - - -
|
||
|
// Frame N+4 (mouse button is down) true true true true - true
|
||
|
// Frame N+5 (mouse button is down) - true true - - -
|
||
|
// Frame N+6 (mouse button is released) - true - - true -
|
||
|
// Frame N+7 (mouse button is released) - true - - - -
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// Note that some combinations are supported,
|
||
|
// - PressedOnDragDropHold can generally be associated with any flag.
|
||
|
// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
|
||
|
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
|
||
|
// Repeat+ Repeat+ Repeat+ Repeat+
|
||
|
// PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick
|
||
|
//-------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
// Frame N+0 (mouse button is down) - true - true
|
||
|
// ... - - - -
|
||
|
// Frame N + RepeatDelay true true - true
|
||
|
// ... - - - -
|
||
|
// Frame N + RepeatDelay + RepeatRate*N true true - true
|
||
|
//-------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
|
||
|
// Default only reacts to left mouse button
|
||
|
if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
|
||
|
flags |= ImGuiButtonFlags_MouseButtonDefault_;
|
||
|
|
||
|
// Default behavior requires click + release inside bounding box
|
||
|
if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
|
||
|
flags |= ImGuiButtonFlags_PressedOnDefault_;
|
||
|
|
||
|
ImGuiWindow* backup_hovered_window = g.HoveredWindow;
|
||
|
const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;
|
||
|
if (flatten_hovered_children)
|
||
|
g.HoveredWindow = window;
|
||
|
|
||
|
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||
|
// Alternate registration spot, for when caller didn't use ItemAdd()
|
||
|
if (id != 0 && g.LastItemData.ID != id)
|
||
|
IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
|
||
|
#endif
|
||
|
|
||
|
bool pressed = false;
|
||
|
bool hovered = ItemHoverable(bb, id);
|
||
|
|
||
|
// Drag source doesn't report as hovered
|
||
|
if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
|
||
|
hovered = false;
|
||
|
|
||
|
// Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
|
||
|
if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
|
||
|
if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
||
|
{
|
||
|
hovered = true;
|
||
|
SetHoveredID(id);
|
||
|
if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
|
||
|
{
|
||
|
pressed = true;
|
||
|
g.DragDropHoldJustPressedId = id;
|
||
|
FocusWindow(window);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (flatten_hovered_children)
|
||
|
g.HoveredWindow = backup_hovered_window;
|
||
|
|
||
|
// AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
|
||
|
if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
|
||
|
hovered = false;
|
||
|
|
||
|
// Mouse handling
|
||
|
const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
|
||
|
if (hovered)
|
||
|
{
|
||
|
// Poll mouse buttons
|
||
|
// - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
|
||
|
// - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
|
||
|
int mouse_button_clicked = -1;
|
||
|
int mouse_button_released = -1;
|
||
|
for (int button = 0; button < 3; button++)
|
||
|
if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
|
||
|
{
|
||
|
if (IsMouseClicked(button, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
|
||
|
if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
|
||
|
}
|
||
|
|
||
|
// Process initial action
|
||
|
if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
|
||
|
{
|
||
|
if (mouse_button_clicked != -1 && g.ActiveId != id)
|
||
|
{
|
||
|
if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
|
||
|
SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
|
||
|
if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
|
||
|
{
|
||
|
SetActiveID(id, window);
|
||
|
g.ActiveIdMouseButton = mouse_button_clicked;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
SetFocusID(id, window);
|
||
|
FocusWindow(window);
|
||
|
}
|
||
|
if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
|
||
|
{
|
||
|
pressed = true;
|
||
|
if (flags & ImGuiButtonFlags_NoHoldingActiveId)
|
||
|
ClearActiveID();
|
||
|
else
|
||
|
SetActiveID(id, window); // Hold on ID
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
SetFocusID(id, window);
|
||
|
g.ActiveIdMouseButton = mouse_button_clicked;
|
||
|
FocusWindow(window);
|
||
|
}
|
||
|
}
|
||
|
if (flags & ImGuiButtonFlags_PressedOnRelease)
|
||
|
{
|
||
|
if (mouse_button_released != -1)
|
||
|
{
|
||
|
const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior
|
||
|
if (!has_repeated_at_least_once)
|
||
|
pressed = true;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
SetFocusID(id, window);
|
||
|
ClearActiveID();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
|
||
|
// Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
|
||
|
if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat))
|
||
|
if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, test_owner_id, ImGuiInputFlags_Repeat))
|
||
|
pressed = true;
|
||
|
}
|
||
|
|
||
|
if (pressed)
|
||
|
g.NavDisableHighlight = true;
|
||
|
}
|
||
|
|
||
|
// Gamepad/Keyboard navigation
|
||
|
// We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
|
||
|
if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
|
||
|
if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
|
||
|
hovered = true;
|
||
|
if (g.NavActivateDownId == id)
|
||
|
{
|
||
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
||
|
bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
|
||
|
if (!nav_activated_by_inputs && (flags & ImGuiButtonFlags_Repeat))
|
||
|
{
|
||
|
// Avoid pressing multiple keys from triggering excessive amount of repeat events
|
||
|
const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
|
||
|
const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
|
||
|
const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
|
||
|
const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
|
||
|
nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
|
||
|
}
|
||
|
if (nav_activated_by_code || nav_activated_by_inputs)
|
||
|
{
|
||
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
||
|
pressed = true;
|
||
|
SetActiveID(id, window);
|
||
|
g.ActiveIdSource = g.NavInputSource;
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
SetFocusID(id, window);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Process while held
|
||
|
bool held = false;
|
||
|
if (g.ActiveId == id)
|
||
|
{
|
||
|
if (g.ActiveIdSource == ImGuiInputSource_Mouse)
|
||
|
{
|
||
|
if (g.ActiveIdIsJustActivated)
|
||
|
g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
|
||
|
|
||
|
const int mouse_button = g.ActiveIdMouseButton;
|
||
|
if (mouse_button == -1)
|
||
|
{
|
||
|
// Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
|
||
|
ClearActiveID();
|
||
|
}
|
||
|
else if (IsMouseDown(mouse_button, test_owner_id))
|
||
|
{
|
||
|
held = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
|
||
|
bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
|
||
|
if ((release_in || release_anywhere) && !g.DragDropActive)
|
||
|
{
|
||
|
// Report as pressed when releasing the mouse (this is the most common path)
|
||
|
bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
|
||
|
bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
|
||
|
bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
|
||
|
if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
|
||
|
pressed = true;
|
||
|
}
|
||
|
ClearActiveID();
|
||
|
}
|
||
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
||
|
g.NavDisableHighlight = true;
|
||
|
}
|
||
|
else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
|
||
|
{
|
||
|
// When activated using Nav, we hold on the ActiveID until activation button is released
|
||
|
if (g.NavActivateDownId != id)
|
||
|
ClearActiveID();
|
||
|
}
|
||
|
if (pressed)
|
||
|
g.ActiveIdHasBeenPressedBefore = true;
|
||
|
}
|
||
|
|
||
|
if (out_hovered) *out_hovered = hovered;
|
||
|
if (out_held) *out_held = held;
|
||
|
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
const ImGuiStyle& style = g.Style;
|
||
|
const ImGuiID id = window->GetID(label);
|
||
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||
|
|
||
|
ImVec2 pos = window->DC.CursorPos;
|
||
|
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)
|
||
|
pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
|
||
|
ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
|
||
|
|
||
|
const ImRect bb(pos, pos + size);
|
||
|
ItemSize(size, style.FramePadding.y);
|
||
|
if (!ItemAdd(bb, id))
|
||
|
return false;
|
||
|
|
||
|
if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
|
||
|
flags |= ImGuiButtonFlags_Repeat;
|
||
|
|
||
|
bool hovered, held;
|
||
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
|
||
|
|
||
|
// Render
|
||
|
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
||
|
RenderNavHighlight(bb, id);
|
||
|
RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
|
||
|
|
||
|
if (g.LogEnabled)
|
||
|
LogSetNextTextDecoration("[", "]");
|
||
|
RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
|
||
|
|
||
|
// Automatically close popups
|
||
|
//if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
|
||
|
// CloseCurrentPopup();
|
||
|
|
||
|
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
bool ImGui::Button(const char* label, const ImVec2& size_arg)
|
||
|
{
|
||
|
return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
|
||
|
}
|
||
|
|
||
|
// Small buttons fits within text without additional vertical spacing.
|
||
|
bool ImGui::SmallButton(const char* label)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
float backup_padding_y = g.Style.FramePadding.y;
|
||
|
g.Style.FramePadding.y = 0.0f;
|
||
|
bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
|
||
|
g.Style.FramePadding.y = backup_padding_y;
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
|
||
|
// 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)
|
||
|
bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
// Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
|
||
|
IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
|
||
|
|
||
|
const ImGuiID id = window->GetID(str_id);
|
||
|
ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
|
||
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
||
|
ItemSize(size);
|
||
|
if (!ItemAdd(bb, id))
|
||
|
return false;
|
||
|
|
||
|
bool hovered, held;
|
||
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
|
||
|
|
||
|
IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
const ImGuiID id = window->GetID(str_id);
|
||
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
||
|
const float default_size = GetFrameHeight();
|
||
|
ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
|
||
|
if (!ItemAdd(bb, id))
|
||
|
return false;
|
||
|
|
||
|
if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat)
|
||
|
flags |= ImGuiButtonFlags_Repeat;
|
||
|
|
||
|
bool hovered, held;
|
||
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
|
||
|
|
||
|
// Render
|
||
|
const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
||
|
const ImU32 text_col = GetColorU32(ImGuiCol_Text);
|
||
|
RenderNavHighlight(bb, id);
|
||
|
RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
|
||
|
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);
|
||
|
|
||
|
IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
|
||
|
{
|
||
|
float sz = GetFrameHeight();
|
||
|
return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
|
||
|
}
|
||
|
|
||
|
// Button to close a window
|
||
|
bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
|
|
||
|
// 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)
|
||
|
// 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?
|
||
|
const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
|
||
|
ImRect bb_interact = bb;
|
||
|
const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
|
||
|
if (area_to_visible_ratio < 1.5f)
|
||
|
bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f));
|
||
|
|
||
|
// Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
|
||
|
// (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
|
||
|
bool is_clipped = !ItemAdd(bb_interact, id);
|
||
|
|
||
|
bool hovered, held;
|
||
|
bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
|
||
|
if (is_clipped)
|
||
|
return pressed;
|
||
|
|
||
|
// Render
|
||
|
// FIXME: Clarify this mess
|
||
|
ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
|
||
|
ImVec2 center = bb.GetCenter();
|
||
|
if (hovered)
|
||
|
window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col);
|
||
|
|
||
|
float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
|
||
|
ImU32 cross_col = GetColorU32(ImGuiCol_Text);
|
||
|
center -= ImVec2(0.5f, 0.5f);
|
||
|
window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
|
||
|
window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
|
||
|
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
|
|
||
|
ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
|
||
|
ItemAdd(bb, id);
|
||
|
bool hovered, held;
|
||
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
|
||
|
|
||
|
// Render
|
||
|
ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
||
|
ImU32 text_col = GetColorU32(ImGuiCol_Text);
|
||
|
if (hovered || held)
|
||
|
window->DrawList->AddCircleFilled(bb.GetCenter()/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col);
|
||
|
RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
|
||
|
|
||
|
// Switch to moving the window after mouse is moved beyond the initial drag threshold
|
||
|
if (IsItemActive() && IsMouseDragging(0))
|
||
|
StartMouseMovingWindow(window);
|
||
|
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
|
||
|
{
|
||
|
return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
|
||
|
}
|
||
|
|
||
|
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
|
||
|
ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
|
||
|
{
|
||
|
const ImRect outer_rect = window->Rect();
|
||
|
const ImRect inner_rect = window->InnerRect;
|
||
|
const float border_size = window->WindowBorderSize;
|
||
|
const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
|
||
|
IM_ASSERT(scrollbar_size > 0.0f);
|
||
|
if (axis == ImGuiAxis_X)
|
||
|
return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x, outer_rect.Max.y);
|
||
|
else
|
||
|
return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y);
|
||
|
}
|
||
|
|
||
|
void ImGui::Scrollbar(ImGuiAxis axis)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
|
const ImGuiID id = GetWindowScrollbarID(window, axis);
|
||
|
|
||
|
// Calculate scrollbar bounding box
|
||
|
ImRect bb = GetWindowScrollbarRect(window, axis);
|
||
|
ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
|
||
|
if (axis == ImGuiAxis_X)
|
||
|
{
|
||
|
rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
|
||
|
if (!window->ScrollbarY)
|
||
|
rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
|
||
|
rounding_corners |= ImDrawFlags_RoundCornersTopRight;
|
||
|
if (!window->ScrollbarX)
|
||
|
rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
|
||
|
}
|
||
|
float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
|
||
|
float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
|
||
|
ImS64 scroll = (ImS64)window->Scroll[axis];
|
||
|
ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_avail, (ImS64)size_contents, rounding_corners);
|
||
|
window->Scroll[axis] = (float)scroll;
|
||
|
}
|
||
|
|
||
|
// Vertical/Horizontal scrollbar
|
||
|
// The entire piece of code below is rather confusing because:
|
||
|
// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
|
||
|
// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
|
||
|
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
|
||
|
// Still, the code should probably be made simpler..
|
||
|
bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_avail_v, ImS64 size_contents_v, ImDrawFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
const float bb_frame_width = bb_frame.GetWidth();
|
||
|
const float bb_frame_height = bb_frame.GetHeight();
|
||
|
if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
|
||
|
return false;
|
||
|
|
||
|
// 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)
|
||
|
float alpha = 1.0f;
|
||
|
if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
|
||
|
alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
|
||
|
if (alpha <= 0.0f)
|
||
|
return false;
|
||
|
|
||
|
const ImGuiStyle& style = g.Style;
|
||
|
const bool allow_interaction = (alpha >= 1.0f);
|
||
|
|
||
|
ImRect bb = bb_frame;
|
||
|
bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
|
||
|
|
||
|
// V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
|
||
|
const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
|
||
|
|
||
|
// Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
|
||
|
// But we maintain a minimum size in pixel to allow for the user to still aim inside.
|
||
|
IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
|
||
|
const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), (ImS64)1);
|
||
|
const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_avail_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
|
||
|
const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
|
||
|
|
||
|
// Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
|
||
|
bool held = false;
|
||
|
bool hovered = false;
|
||
|
ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
|
||
|
ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
|
||
|
|
||
|
const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_avail_v);
|
||
|
float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
|
||
|
float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
|
||
|
if (held && allow_interaction && grab_h_norm < 1.0f)
|
||
|
{
|
||
|
const float scrollbar_pos_v = bb.Min[axis];
|
||
|
const float mouse_pos_v = g.IO.MousePos[axis];
|
||
|
|
||
|
// Click position in scrollbar normalized space (0.0f->1.0f)
|
||
|
const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
|
||
|
SetHoveredID(id);
|
||
|
|
||
|
bool seek_absolute = false;
|
||
|
if (g.ActiveIdIsJustActivated)
|
||
|
{
|
||
|
// On initial click calculate the distance between mouse and the center of the grab
|
||
|
seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
|
||
|
if (seek_absolute)
|
||
|
g.ScrollbarClickDeltaToGrabCenter = 0.0f;
|
||
|
else
|
||
|
g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
|
||
|
}
|
||
|
|
||
|
// Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
|
||
|
// 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
|
||
|
const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
|
||
|
*p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
|
||
|
|
||
|
// Update values for rendering
|
||
|
scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
|
||
|
grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
|
||
|
|
||
|
// Update distance to grab now that we have seeked and saturated
|
||
|
if (seek_absolute)
|
||
|
g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
|
||
|
}
|
||
|
|
||
|
// Render
|
||
|
const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
|
||
|
const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
|
||
|
window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
|
||
|
ImRect grab_rect;
|
||
|
if (axis == ImGuiAxis_X)
|
||
|
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);
|
||
|
else
|
||
|
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);
|
||
|
window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
|
||
|
|
||
|
return held;
|
||
|
}
|
||
|
|
||
|
void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
|
||
|
{
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return;
|
||
|
|
||
|
ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
||
|
if (border_col.w > 0.0f)
|
||
|
bb.Max += ImVec2(2, 2);
|
||
|
ItemSize(bb);
|
||
|
if (!ItemAdd(bb, 0))
|
||
|
return;
|
||
|
|
||
|
if (border_col.w > 0.0f)
|
||
|
{
|
||
|
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
|
||
|
window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
|
||
|
// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
|
||
|
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = GetCurrentWindow();
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
const ImVec2 padding = g.Style.FramePadding;
|
||
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2.0f);
|
||
|
ItemSize(bb);
|
||
|
if (!ItemAdd(bb, id))
|
||
|
return false;
|
||
|
|
||
|
bool hovered, held;
|
||
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
|
||
|
|
||
|
// Render
|
||
|
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
||
|
RenderNavHighlight(bb, id);
|
||
|
RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
|
||
|
if (bg_col.w > 0.0f)
|
||
|
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
|
||
|
window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
|
||
|
|
||
|
return pressed;
|
||
|
}
|
||
|
|
||
|
bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
|
||
|
{
|
||
|
ImGuiContext& g = *GImGui;
|
||
|
ImGuiWindow* window = g.CurrentWindow;
|
||
|
if (window->SkipItems)
|
||
|
return false;
|
||
|
|
||
|
< |