mirror of
https://github.com/ocornut/imgui.git
synced 2024-12-18 08:51:06 +08:00
1384 lines
52 KiB
C++
1384 lines
52 KiB
C++
// dear imgui, v1.64 WIP
|
|
// (widgets code)
|
|
|
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include "imgui.h"
|
|
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#endif
|
|
#include "imgui_internal.h"
|
|
|
|
#include <ctype.h> // toupper, isprint
|
|
|
|
// 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
|
|
#endif
|
|
|
|
// Clang/GCC warnings with -Weverything
|
|
#ifdef __clang__
|
|
#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 //
|
|
#elif defined(__GNUC__)
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
|
|
#if __GNUC__ >= 8
|
|
#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
|
|
#endif
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Forward Declarations
|
|
//-------------------------------------------------------------------------
|
|
|
|
//-------------------------------------------------------------------------
|
|
// SHARED UTILITIES
|
|
//-------------------------------------------------------------------------
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Text
|
|
// - TextUnformatted()
|
|
// - Text()
|
|
// - TextV()
|
|
// - TextColored()
|
|
// - TextColoredV()
|
|
// - TextDisabled()
|
|
// - TextDisabledV()
|
|
// - TextWrapped()
|
|
// - TextWrappedV()
|
|
// - LabelText()
|
|
// - LabelTextV()
|
|
// - BulletText()
|
|
// - BulletTextV()
|
|
//-------------------------------------------------------------------------
|
|
|
|
void ImGui::TextUnformatted(const char* text, const char* text_end)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(text != NULL);
|
|
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.CurrentLineTextBaseOffset);
|
|
const float wrap_pos_x = window->DC.TextWrapPos;
|
|
const bool wrap_enabled = wrap_pos_x >= 0.0f;
|
|
if (text_end - text > 2000 && !wrap_enabled)
|
|
{
|
|
// 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.
|
|
const char* line = text;
|
|
const float line_height = GetTextLineHeight();
|
|
const ImRect clip_rect = window->ClipRect;
|
|
ImVec2 text_size(0,0);
|
|
|
|
if (text_pos.y <= clip_rect.Max.y)
|
|
{
|
|
ImVec2 pos = text_pos;
|
|
|
|
// Lines to skip (can't skip when logging text)
|
|
if (!g.LogEnabled)
|
|
{
|
|
int lines_skippable = (int)((clip_rect.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 = strchr(line, '\n');
|
|
if (!line_end)
|
|
line_end = text_end;
|
|
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)
|
|
{
|
|
const char* line_end = strchr(line, '\n');
|
|
if (IsClippedEx(line_rect, 0, false))
|
|
break;
|
|
|
|
const ImVec2 line_size = CalcTextSize(line, line_end, false);
|
|
text_size.x = ImMax(text_size.x, line_size.x);
|
|
RenderText(pos, line, line_end, false);
|
|
if (!line_end)
|
|
line_end = text_end;
|
|
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 = strchr(line, '\n');
|
|
if (!line_end)
|
|
line_end = text_end;
|
|
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(bb);
|
|
ItemAdd(bb, 0);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
|
|
// Account of baseline offset
|
|
ImRect bb(text_pos, text_pos + text_size);
|
|
ItemSize(text_size);
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
TextUnformatted(g.TempBuffer, text_end);
|
|
}
|
|
|
|
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)
|
|
{
|
|
PushStyleColor(ImGuiCol_Text, GImGui->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)
|
|
{
|
|
bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
|
|
if (need_wrap) PushTextWrapPos(0.0f);
|
|
TextV(fmt, args);
|
|
if (need_wrap) 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 ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
|
|
const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
|
|
ItemSize(total_bb, style.FramePadding.y);
|
|
if (!ItemAdd(total_bb, 0))
|
|
return;
|
|
|
|
// Render
|
|
const char* value_text_begin = &g.TempBuffer[0];
|
|
const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
|
|
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 = g.TempBuffer;
|
|
const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
|
|
const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
|
|
const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
|
|
const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
|
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding
|
|
ItemSize(bb);
|
|
if (!ItemAdd(bb, 0))
|
|
return;
|
|
|
|
// Render
|
|
RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
|
|
RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Main
|
|
// - ButtonBehavior() [Internal]
|
|
// - Button()
|
|
// - SmallButton()
|
|
// - InvisibleButton()
|
|
// - ArrowButton()
|
|
// - CloseButton() [Internal]
|
|
// - CollapseButton() [Internal]
|
|
// - Image()
|
|
// - ImageButton()
|
|
// - Checkbox()
|
|
// - CheckboxFlags()
|
|
// - RadioButton()
|
|
// - ProgressBar()
|
|
// - Bullet()
|
|
//-------------------------------------------------------------------------
|
|
|
|
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
|
|
if (flags & ImGuiButtonFlags_Disabled)
|
|
{
|
|
if (out_hovered) *out_hovered = false;
|
|
if (out_held) *out_held = false;
|
|
if (g.ActiveId == id) ClearActiveID();
|
|
return false;
|
|
}
|
|
|
|
// Default behavior requires click+release on same spot
|
|
if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
|
|
flags |= ImGuiButtonFlags_PressedOnClickRelease;
|
|
|
|
ImGuiWindow* backup_hovered_window = g.HoveredWindow;
|
|
if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
|
|
g.HoveredWindow = window;
|
|
|
|
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 (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
|
|
{
|
|
pressed = true;
|
|
FocusWindow(window);
|
|
}
|
|
}
|
|
|
|
if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
|
|
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
|
|
if (hovered)
|
|
{
|
|
if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
|
|
{
|
|
// | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
|
|
// PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
|
|
// PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
|
|
// PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
|
|
// PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
|
|
// FIXME-NAV: We don't honor those different behaviors.
|
|
if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
|
|
{
|
|
SetActiveID(id, window);
|
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
|
SetFocusID(id, window);
|
|
FocusWindow(window);
|
|
}
|
|
if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
|
|
{
|
|
pressed = true;
|
|
if (flags & ImGuiButtonFlags_NoHoldingActiveID)
|
|
ClearActiveID();
|
|
else
|
|
SetActiveID(id, window); // Hold on ID
|
|
FocusWindow(window);
|
|
}
|
|
if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
|
|
{
|
|
if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
|
|
pressed = true;
|
|
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 ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
|
|
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))
|
|
hovered = true;
|
|
|
|
if (g.NavActivateDownId == id)
|
|
{
|
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
|
bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
|
|
if (nav_activated_by_code || nav_activated_by_inputs)
|
|
pressed = true;
|
|
if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
|
|
{
|
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
|
g.NavActivateId = id; // This is so SetActiveId assign a Nav source
|
|
SetActiveID(id, window);
|
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
|
SetFocusID(id, window);
|
|
g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
|
}
|
|
}
|
|
|
|
bool held = false;
|
|
if (g.ActiveId == id)
|
|
{
|
|
if (g.ActiveIdSource == ImGuiInputSource_Mouse)
|
|
{
|
|
if (g.ActiveIdIsJustActivated)
|
|
g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
|
|
if (g.IO.MouseDown[0])
|
|
{
|
|
held = true;
|
|
}
|
|
else
|
|
{
|
|
if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
|
|
if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
|
|
if (!g.DragDropActive)
|
|
pressed = true;
|
|
ClearActiveID();
|
|
}
|
|
if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
|
g.NavDisableHighlight = true;
|
|
}
|
|
else if (g.ActiveIdSource == ImGuiInputSource_Nav)
|
|
{
|
|
if (g.NavActivateDownId != id)
|
|
ClearActiveID();
|
|
}
|
|
}
|
|
|
|
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.CurrentLineTextBaseOffset) // 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.CurrentLineTextBaseOffset - 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(bb, style.FramePadding.y);
|
|
if (!ItemAdd(bb, id))
|
|
return false;
|
|
|
|
if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
|
|
flags |= ImGuiButtonFlags_Repeat;
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
|
|
if (pressed)
|
|
MarkItemEdited(id);
|
|
|
|
// 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);
|
|
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();
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::Button(const char* label, const ImVec2& size_arg)
|
|
{
|
|
return ButtonEx(label, size_arg, 0);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
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(bb);
|
|
if (!ItemAdd(bb, id))
|
|
return false;
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiID id = window->GetID(str_id);
|
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
|
const float default_size = GetFrameHeight();
|
|
ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
|
|
if (!ItemAdd(bb, id))
|
|
return false;
|
|
|
|
if (window->DC.ItemFlags & 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, g.Style.FrameRounding);
|
|
RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir);
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
|
|
{
|
|
float sz = GetFrameHeight();
|
|
return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
|
|
}
|
|
|
|
// Button to close a window
|
|
bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
// We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence 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).
|
|
const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
|
|
bool is_clipped = !ItemAdd(bb, id);
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
|
|
if (is_clipped)
|
|
return pressed;
|
|
|
|
// Render
|
|
ImVec2 center = bb.GetCenter();
|
|
if (hovered)
|
|
window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
|
|
|
|
float cross_extent = (radius * 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);
|
|
|
|
ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
|
if (hovered || held)
|
|
window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
|
|
RenderArrow(bb.Min + g.Style.FramePadding, 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())
|
|
StartMouseMovingWindow(window);
|
|
|
|
return pressed;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
// frame_padding < 0: uses FramePadding from style (default)
|
|
// frame_padding = 0: no framing
|
|
// frame_padding > 0: set framing size
|
|
// The color used are the button colors.
|
|
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)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
|
|
// Default to using texture ID as ID. User can still push string/integer prefixes.
|
|
// We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
|
|
PushID((void*)user_texture_id);
|
|
const ImGuiID id = window->GetID("#image");
|
|
PopID();
|
|
|
|
const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
|
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
|
|
const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
|
|
ItemSize(bb);
|
|
if (!ItemAdd(bb, id))
|
|
return false;
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
|
|
|
|
// 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, style.FrameRounding));
|
|
if (bg_col.w > 0.0f)
|
|
window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
|
|
window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::Checkbox(const char* label, bool* v)
|
|
{
|
|
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);
|
|
|
|
const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice
|
|
ItemSize(check_bb, style.FramePadding.y);
|
|
|
|
ImRect total_bb = check_bb;
|
|
if (label_size.x > 0)
|
|
SameLine(0, style.ItemInnerSpacing.x);
|
|
const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
|
|
if (label_size.x > 0)
|
|
{
|
|
ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
|
|
total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
|
|
}
|
|
|
|
if (!ItemAdd(total_bb, id))
|
|
return false;
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
|
|
if (pressed)
|
|
{
|
|
*v = !(*v);
|
|
MarkItemEdited(id);
|
|
}
|
|
|
|
RenderNavHighlight(total_bb, id);
|
|
RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
|
|
if (*v)
|
|
{
|
|
const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
|
|
const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
|
|
RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f);
|
|
}
|
|
|
|
if (g.LogEnabled)
|
|
LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
|
|
if (label_size.x > 0.0f)
|
|
RenderText(text_bb.Min, label);
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
|
|
{
|
|
bool v = ((*flags & flags_value) == flags_value);
|
|
bool pressed = Checkbox(label, &v);
|
|
if (pressed)
|
|
{
|
|
if (v)
|
|
*flags |= flags_value;
|
|
else
|
|
*flags &= ~flags_value;
|
|
}
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::RadioButton(const char* label, bool active)
|
|
{
|
|
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);
|
|
|
|
const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1));
|
|
ItemSize(check_bb, style.FramePadding.y);
|
|
|
|
ImRect total_bb = check_bb;
|
|
if (label_size.x > 0)
|
|
SameLine(0, style.ItemInnerSpacing.x);
|
|
const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
|
|
if (label_size.x > 0)
|
|
{
|
|
ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
|
|
total_bb.Add(text_bb);
|
|
}
|
|
|
|
if (!ItemAdd(total_bb, id))
|
|
return false;
|
|
|
|
ImVec2 center = check_bb.GetCenter();
|
|
center.x = (float)(int)center.x + 0.5f;
|
|
center.y = (float)(int)center.y + 0.5f;
|
|
const float radius = check_bb.GetHeight() * 0.5f;
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
|
|
if (pressed)
|
|
MarkItemEdited(id);
|
|
|
|
RenderNavHighlight(total_bb, id);
|
|
window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
|
|
if (active)
|
|
{
|
|
const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
|
|
const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
|
|
window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16);
|
|
}
|
|
|
|
if (style.FrameBorderSize > 0.0f)
|
|
{
|
|
window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
|
|
window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
|
|
}
|
|
|
|
if (g.LogEnabled)
|
|
LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
|
|
if (label_size.x > 0.0f)
|
|
RenderText(text_bb.Min, label);
|
|
|
|
return pressed;
|
|
}
|
|
|
|
bool ImGui::RadioButton(const char* label, int* v, int v_button)
|
|
{
|
|
const bool pressed = RadioButton(label, *v == v_button);
|
|
if (pressed)
|
|
*v = v_button;
|
|
return pressed;
|
|
}
|
|
|
|
// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
|
|
void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
|
|
ItemSize(bb, style.FramePadding.y);
|
|
if (!ItemAdd(bb, 0))
|
|
return;
|
|
|
|
// Render
|
|
fraction = ImSaturate(fraction);
|
|
RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
|
|
bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
|
|
const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
|
|
RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
|
|
|
|
// Default displaying the fraction as percentage string, but user can override it
|
|
char overlay_buf[32];
|
|
if (!overlay)
|
|
{
|
|
ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
|
|
overlay = overlay_buf;
|
|
}
|
|
|
|
ImVec2 overlay_size = CalcTextSize(overlay, NULL);
|
|
if (overlay_size.x > 0.0f)
|
|
RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.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);
|
|
}
|
|
|
|
void ImGui::Bullet()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
|
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
|
|
ItemSize(bb);
|
|
if (!ItemAdd(bb, 0))
|
|
{
|
|
SameLine(0, style.FramePadding.x*2);
|
|
return;
|
|
}
|
|
|
|
// Render and stay on same line
|
|
RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
|
|
SameLine(0, style.FramePadding.x*2);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Combo Box
|
|
// - BeginCombo()
|
|
// - EndCombo()
|
|
// - Combo()
|
|
//-------------------------------------------------------------------------
|
|
|
|
static float CalcMaxPopupHeightFromItemCount(int items_count)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (items_count <= 0)
|
|
return FLT_MAX;
|
|
return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
|
|
}
|
|
|
|
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
|
|
{
|
|
// Always consume the SetNextWindowSizeConstraint() call in our early return paths
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
|
|
g.NextWindowData.SizeConstraintCond = 0;
|
|
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
|
|
|
|
const ImGuiStyle& style = g.Style;
|
|
const ImGuiID id = window->GetID(label);
|
|
|
|
const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
|
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
|
|
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
|
|
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));
|
|
ItemSize(total_bb, style.FramePadding.y);
|
|
if (!ItemAdd(total_bb, id, &frame_bb))
|
|
return false;
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
|
|
bool popup_open = IsPopupOpen(id);
|
|
|
|
const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
|
|
const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
|
|
RenderNavHighlight(frame_bb, id);
|
|
if (!(flags & ImGuiComboFlags_NoPreview))
|
|
window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
|
|
if (!(flags & ImGuiComboFlags_NoArrowButton))
|
|
{
|
|
window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
|
|
RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
|
|
}
|
|
RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
|
|
if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
|
|
RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
|
|
if (label_size.x > 0)
|
|
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
|
|
|
if ((pressed || g.NavActivateId == id) && !popup_open)
|
|
{
|
|
if (window->DC.NavLayerCurrent == 0)
|
|
window->NavLastIds[0] = id;
|
|
OpenPopupEx(id);
|
|
popup_open = true;
|
|
}
|
|
|
|
if (!popup_open)
|
|
return false;
|
|
|
|
if (backup_next_window_size_constraint)
|
|
{
|
|
g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
|
|
g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
|
|
}
|
|
else
|
|
{
|
|
if ((flags & ImGuiComboFlags_HeightMask_) == 0)
|
|
flags |= ImGuiComboFlags_HeightRegular;
|
|
IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
|
|
int popup_max_height_in_items = -1;
|
|
if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
|
|
else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
|
|
else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
|
|
SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
|
}
|
|
|
|
char name[16];
|
|
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
|
|
|
|
// Peak into expected window size so we can position it
|
|
if (ImGuiWindow* popup_window = FindWindowByName(name))
|
|
if (popup_window->WasActive)
|
|
{
|
|
ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
|
|
if (flags & ImGuiComboFlags_PopupAlignLeft)
|
|
popup_window->AutoPosLastDirection = ImGuiDir_Left;
|
|
ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
|
|
ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
|
|
SetNextWindowPos(pos);
|
|
}
|
|
|
|
// Horizontally align ourselves with the framed text
|
|
ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
|
|
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
|
|
bool ret = Begin(name, NULL, window_flags);
|
|
PopStyleVar();
|
|
if (!ret)
|
|
{
|
|
EndPopup();
|
|
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ImGui::EndCombo()
|
|
{
|
|
EndPopup();
|
|
}
|
|
|
|
// Getter for the old Combo() API: const char*[]
|
|
static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
|
|
{
|
|
const char* const* items = (const char* const*)data;
|
|
if (out_text)
|
|
*out_text = items[idx];
|
|
return true;
|
|
}
|
|
|
|
// Getter for the old Combo() API: "item1\0item2\0item3\0"
|
|
static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
|
|
{
|
|
// FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
|
|
const char* items_separated_by_zeros = (const char*)data;
|
|
int items_count = 0;
|
|
const char* p = items_separated_by_zeros;
|
|
while (*p)
|
|
{
|
|
if (idx == items_count)
|
|
break;
|
|
p += strlen(p) + 1;
|
|
items_count++;
|
|
}
|
|
if (!*p)
|
|
return false;
|
|
if (out_text)
|
|
*out_text = p;
|
|
return true;
|
|
}
|
|
|
|
// Old API, prefer using BeginCombo() nowadays if you can.
|
|
bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Call the getter to obtain the preview string which is a parameter to BeginCombo()
|
|
const char* preview_value = NULL;
|
|
if (*current_item >= 0 && *current_item < items_count)
|
|
items_getter(data, *current_item, &preview_value);
|
|
|
|
// 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.
|
|
if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
|
|
SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
|
|
|
if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
|
|
return false;
|
|
|
|
// Display items
|
|
// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
|
|
bool value_changed = false;
|
|
for (int i = 0; i < items_count; i++)
|
|
{
|
|
PushID((void*)(intptr_t)i);
|
|
const bool item_selected = (i == *current_item);
|
|
const char* item_text;
|
|
if (!items_getter(data, i, &item_text))
|
|
item_text = "*Unknown item*";
|
|
if (Selectable(item_text, item_selected))
|
|
{
|
|
value_changed = true;
|
|
*current_item = i;
|
|
}
|
|
if (item_selected)
|
|
SetItemDefaultFocus();
|
|
PopID();
|
|
}
|
|
|
|
EndCombo();
|
|
return value_changed;
|
|
}
|
|
|
|
// Combo box helper allowing to pass an array of strings.
|
|
bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
|
|
{
|
|
const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
|
|
return value_changed;
|
|
}
|
|
|
|
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
|
|
bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
|
|
{
|
|
int items_count = 0;
|
|
const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
|
|
while (*p)
|
|
{
|
|
p += strlen(p) + 1;
|
|
items_count++;
|
|
}
|
|
bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
|
|
return value_changed;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Data Type and Data Formatting Helpers [Internal]
|
|
// - PatchFormatStringFloatToInt()
|
|
// - DataTypeFormatString()
|
|
// - DataTypeApplyOp()
|
|
// - DataTypeApplyOpFromText()
|
|
// - GetMinimumStepAtDecimalPrecision
|
|
// - RoundScalarWithFormat<>()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Drags
|
|
// - DragBehaviorT<>() [Internal]
|
|
// - DragBehavior() [Internal]
|
|
// - DragScalar()
|
|
// - DragScalarN()
|
|
// - DragFloat()
|
|
// - DragFloat2()
|
|
// - DragFloat3()
|
|
// - DragFloat4()
|
|
// - DragFloatRange2()
|
|
// - DragInt()
|
|
// - DragInt2()
|
|
// - DragInt3()
|
|
// - DragInt4()
|
|
// - DragIntRange2()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Sliders
|
|
// - SliderBehaviorT<>() [Internal]
|
|
// - SliderBehavior() [Internal]
|
|
// - SliderScalar()
|
|
// - SliderScalarN()
|
|
// - SliderFloat()
|
|
// - SliderFloat2()
|
|
// - SliderFloat3()
|
|
// - SliderFloat4()
|
|
// - SliderAngle()
|
|
// - SliderInt()
|
|
// - SliderInt2()
|
|
// - SliderInt3()
|
|
// - SliderInt4()
|
|
// - VSliderScalar()
|
|
// - VSliderFloat()
|
|
// - VSliderInt()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Inputs (_excepted InputText_)
|
|
// - ImParseFormatFindStart()
|
|
// - ImParseFormatFindEnd()
|
|
// - ImParseFormatTrimDecorations()
|
|
// - ImParseFormatPrecision()
|
|
// - InputScalarAsWidgetReplacement() [Internal]
|
|
// - InputScalar()
|
|
// - InputScalarN()
|
|
// - InputFloat()
|
|
// - InputFloat2()
|
|
// - InputFloat3()
|
|
// - InputFloat4()
|
|
// - InputInt()
|
|
// - InputInt2()
|
|
// - InputInt3()
|
|
// - InputInt4()
|
|
// - InputDouble()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: InputText
|
|
// - InputText()
|
|
// - InputTextMultiline()
|
|
// - InputTextEx() [Internal]
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Color Editor / Picker
|
|
// - ColorEdit3()
|
|
// - ColorEdit4()
|
|
// - ColorPicker3()
|
|
// - RenderColorRectWithAlphaCheckerboard() [Internal]
|
|
// - ColorPicker4()
|
|
// - ColorButton()
|
|
// - SetColorEditOptions()
|
|
// - ColorTooltip() [Internal]
|
|
// - ColorEditOptionsPopup() [Internal]
|
|
// - ColorPickerOptionsPopup() [Internal]
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Trees
|
|
// - TreeNode()
|
|
// - TreeNodeV()
|
|
// - TreeNodeEx()
|
|
// - TreeNodeExV()
|
|
// - TreeNodeBehavior() [Internal]
|
|
// - TreePush()
|
|
// - TreePop()
|
|
// - TreeAdvanceToLabelPos()
|
|
// - GetTreeNodeToLabelSpacing()
|
|
// - SetNextTreeNodeOpen()
|
|
// - CollapsingHeader()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Selectables
|
|
// - Selectable()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: List Box
|
|
// - ListBox()
|
|
// - ListBoxHeader()
|
|
// - ListBoxFooter()
|
|
//-------------------------------------------------------------------------
|
|
|
|
// FIXME: Rename to BeginListBox()
|
|
// Helper to calculate the size of a listbox and display a label on the right.
|
|
// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
|
|
bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
const ImGuiStyle& style = GetStyle();
|
|
const ImGuiID id = GetID(label);
|
|
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
|
|
|
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
|
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
|
|
ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
|
|
ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
|
|
ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
|
window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
|
|
|
|
BeginGroup();
|
|
if (label_size.x > 0)
|
|
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
|
|
|
BeginChildFrame(id, frame_bb.GetSize());
|
|
return true;
|
|
}
|
|
|
|
// FIXME: Rename to BeginListBox()
|
|
bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
|
|
{
|
|
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
|
// We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
|
|
// I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
|
|
if (height_in_items < 0)
|
|
height_in_items = ImMin(items_count, 7);
|
|
float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
|
|
|
|
// We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
|
|
ImVec2 size;
|
|
size.x = 0.0f;
|
|
size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
|
|
return ListBoxHeader(label, size);
|
|
}
|
|
|
|
// FIXME: Rename to EndListBox()
|
|
void ImGui::ListBoxFooter()
|
|
{
|
|
ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
|
|
const ImRect bb = parent_window->DC.LastItemRect;
|
|
const ImGuiStyle& style = GetStyle();
|
|
|
|
EndChildFrame();
|
|
|
|
// Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
|
|
// We call SameLine() to restore DC.CurrentLine* data
|
|
SameLine();
|
|
parent_window->DC.CursorPos = bb.Min;
|
|
ItemSize(bb, style.FramePadding.y);
|
|
EndGroup();
|
|
}
|
|
|
|
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
|
|
{
|
|
const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
|
|
return value_changed;
|
|
}
|
|
|
|
bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
|
|
{
|
|
if (!ListBoxHeader(label, items_count, height_in_items))
|
|
return false;
|
|
|
|
// Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
|
|
ImGuiContext& g = *GImGui;
|
|
bool value_changed = false;
|
|
ImGuiListClipper clipper(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.
|
|
while (clipper.Step())
|
|
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
|
{
|
|
const bool item_selected = (i == *current_item);
|
|
const char* item_text;
|
|
if (!items_getter(data, i, &item_text))
|
|
item_text = "*Unknown item*";
|
|
|
|
PushID(i);
|
|
if (Selectable(item_text, item_selected))
|
|
{
|
|
*current_item = i;
|
|
value_changed = true;
|
|
}
|
|
if (item_selected)
|
|
SetItemDefaultFocus();
|
|
PopID();
|
|
}
|
|
ListBoxFooter();
|
|
if (value_changed)
|
|
MarkItemEdited(g.CurrentWindow->DC.LastItemId);
|
|
|
|
return value_changed;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Data Plotting
|
|
// - PlotEx() [Internal]
|
|
// - PlotLines()
|
|
// - PlotHistogram()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Value() helpers
|
|
// - Value()
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// WIDGETS: Menus
|
|
// - BeginMainMenuBar()
|
|
// - EndMainMenuBar()
|
|
// - BeginMenuBar()
|
|
// - EndMenuBar()
|
|
// - BeginMenu()
|
|
// - EndMenu()
|
|
// - MenuItem()
|
|
//-------------------------------------------------------------------------
|
|
|