| // dear imgui, v1.64 |
| // (widgets code) |
| |
| /* |
| |
| Index of this file: |
| - Widgets: Text, etc. |
| - Widgets: Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc. |
| - Widgets: ComboBox |
| - Data Type and Data Formatting Helpers |
| - Widgets: DragScalar, DragFloat, DragInt, etc. |
| - Widgets: SliderScalar, SliderFloat, SliderInt, etc. |
| - Widgets: InputScalar, InputFloat, InputInt, etc. |
| - Widgets: InputText, InputTextMultiline |
| - Widgets: ColorEdit, ColorPicker, ColorButton, etc. |
| - Widgets: TreeNode, TreePush, TreePop, etc. |
| - Widgets: Selectable |
| - Widgets: ListBox |
| - Widgets: PlotLines, PlotHistogram |
| - Widgets: Value |
| - Widgets: MenuItem, BeginMenu, EndMenu, etc. |
| |
| */ |
| |
| #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 |
| #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier |
| #include <stddef.h> // intptr_t |
| #else |
| #include <stdint.h> // intptr_t |
| #endif |
| |
| // 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 |
| |
| //------------------------------------------------------------------------- |
| // Data |
| //------------------------------------------------------------------------- |
| |
| // Those MIN/MAX values are not define because we need to point to them |
| 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 |
| |
| //------------------------------------------------------------------------- |
| // Forward Declarations |
| //------------------------------------------------------------------------- |
| |
| // Data Type helpers |
| static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format); |
| static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2); |
| static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format); |
| |
| // For InputTextEx() |
| static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data); |
| static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); |
| static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); |
| |
| namespace ImGui |
| { |
| |
| // Template widget behaviors |
| template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
| static bool DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power); |
| |
| template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
| static bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb); |
| |
| } |
| |
| //------------------------------------------------------------------------- |
| // 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] |
| // - Scrollbar() [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; |
| } |
| |
| // 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. |
| void ImGui::Scrollbar(ImGuiLayoutType direction) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| |
| const bool horizontal = (direction == ImGuiLayoutType_Horizontal); |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); |
| |
| // Render background |
| bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); |
| float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; |
| const ImRect window_rect = window->Rect(); |
| const float border_size = window->WindowBorderSize; |
| ImRect bb = horizontal |
| ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) |
| : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); |
| if (!horizontal) |
| bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); |
| if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) |
| return; |
| |
| int window_rounding_corners; |
| if (horizontal) |
| window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); |
| else |
| window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); |
| window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); |
| bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); |
| |
| // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) |
| float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); |
| float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; |
| float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; |
| float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; |
| |
| // 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(win_size_contents_v, win_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 float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); |
| const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / 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; |
| const bool previously_held = (g.ActiveId == id); |
| ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); |
| |
| float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); |
| float scroll_ratio = ImSaturate(scroll_v / scroll_max); |
| float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; |
| if (held && grab_h_norm < 1.0f) |
| { |
| float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; |
| float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; |
| float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; |
| |
| // 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 (!previously_held) |
| { |
| // On initial click calculate the distance between mouse and the center of the grab |
| if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) |
| { |
| *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; |
| } |
| else |
| { |
| seek_absolute = true; |
| *click_delta_to_grab_center_v = 0.0f; |
| } |
| } |
| |
| // Apply scroll |
| // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position |
| const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); |
| scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); |
| if (horizontal) |
| window->Scroll.x = scroll_v; |
| else |
| window->Scroll.y = scroll_v; |
| |
| // Update values for rendering |
| scroll_ratio = ImSaturate(scroll_v / 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) |
| *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; |
| } |
| |
| // Render |
| const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); |
| ImRect grab_rect; |
| if (horizontal) |
| grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); |
| else |
| grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); |
| window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); |
| } |
| |
| 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<>() |
| //------------------------------------------------------------------------- |
| |
| struct ImGuiDataTypeInfo |
| { |
| size_t Size; |
| const char* PrintFmt; // Unused |
| const char* ScanFmt; |
| }; |
| |
| static const ImGuiDataTypeInfo GDataTypeInfo[] = |
| { |
| { sizeof(int), "%d", "%d" }, |
| { sizeof(unsigned int), "%u", "%u" }, |
| #ifdef _MSC_VER |
| { sizeof(ImS64), "%I64d","%I64d" }, |
| { sizeof(ImU64), "%I64u","%I64u" }, |
| #else |
| { sizeof(ImS64), "%lld", "%lld" }, |
| { sizeof(ImU64), "%llu", "%llu" }, |
| #endif |
| { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg |
| { sizeof(double), "%f", "%lf" }, |
| }; |
| IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); |
| |
| // FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f". |
| // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls. |
| // To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?! |
| static const char* PatchFormatStringFloatToInt(const char* fmt) |
| { |
| if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case. |
| return "%d"; |
| const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%) |
| const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user). |
| if (fmt_end > fmt_start && fmt_end[-1] == 'f') |
| { |
| #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
| if (fmt_start == fmt && fmt_end[0] == 0) |
| return "%d"; |
| ImGuiContext& g = *GImGui; |
| ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision. |
| return g.TempBuffer; |
| #else |
| IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d" |
| #endif |
| } |
| return fmt; |
| } |
| |
| static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format) |
| { |
| if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument |
| return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr); |
| if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument |
| return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr); |
| if (data_type == ImGuiDataType_Float) |
| return ImFormatString(buf, buf_size, format, *(const float*)data_ptr); |
| if (data_type == ImGuiDataType_Double) |
| return ImFormatString(buf, buf_size, format, *(const double*)data_ptr); |
| IM_ASSERT(0); |
| return 0; |
| } |
| |
| // FIXME: Adding support for clamping on boundaries of the data type would be nice. |
| static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2) |
| { |
| IM_ASSERT(op == '+' || op == '-'); |
| switch (data_type) |
| { |
| case ImGuiDataType_S32: |
| if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2; |
| else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2; |
| return; |
| case ImGuiDataType_U32: |
| if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2; |
| else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2; |
| return; |
| case ImGuiDataType_S64: |
| if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2; |
| else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2; |
| return; |
| case ImGuiDataType_U64: |
| if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2; |
| else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2; |
| return; |
| case ImGuiDataType_Float: |
| if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2; |
| else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2; |
| return; |
| case ImGuiDataType_Double: |
| if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2; |
| else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2; |
| return; |
| case ImGuiDataType_COUNT: break; |
| } |
| IM_ASSERT(0); |
| } |
| |
| // User can input math operators (e.g. +100) to edit a numerical values. |
| // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. |
| static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format) |
| { |
| while (ImCharIsBlankA(*buf)) |
| buf++; |
| |
| // We don't support '-' op because it would conflict with inputing negative value. |
| // Instead you can use +-100 to subtract from an existing value |
| char op = buf[0]; |
| if (op == '+' || op == '*' || op == '/') |
| { |
| buf++; |
| while (ImCharIsBlankA(*buf)) |
| buf++; |
| } |
| else |
| { |
| op = 0; |
| } |
| if (!buf[0]) |
| return false; |
| |
| // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. |
| IM_ASSERT(data_type < ImGuiDataType_COUNT); |
| int data_backup[2]; |
| IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup)); |
| memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size); |
| |
| if (format == NULL) |
| format = GDataTypeInfo[data_type].ScanFmt; |
| |
| int arg1i = 0; |
| if (data_type == ImGuiDataType_S32) |
| { |
| int* v = (int*)data_ptr; |
| int arg0i = *v; |
| float arg1f = 0.0f; |
| if (op && sscanf(initial_value_buf, format, &arg0i) < 1) |
| return false; |
| // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision |
| if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract) |
| else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply |
| else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide |
| else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant |
| } |
| else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) |
| { |
| // Assign constant |
| // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future. |
| sscanf(buf, format, data_ptr); |
| } |
| else if (data_type == ImGuiDataType_Float) |
| { |
| // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in |
| format = "%f"; |
| float* v = (float*)data_ptr; |
| float arg0f = *v, arg1f = 0.0f; |
| if (op && sscanf(initial_value_buf, format, &arg0f) < 1) |
| return false; |
| if (sscanf(buf, format, &arg1f) < 1) |
| return false; |
| if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) |
| else if (op == '*') { *v = arg0f * arg1f; } // Multiply |
| else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide |
| else { *v = arg1f; } // Assign constant |
| } |
| else if (data_type == ImGuiDataType_Double) |
| { |
| format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis |
| double* v = (double*)data_ptr; |
| double arg0f = *v, arg1f = 0.0; |
| if (op && sscanf(initial_value_buf, format, &arg0f) < 1) |
| return false; |
| if (sscanf(buf, format, &arg1f) < 1) |
| return false; |
| if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) |
| else if (op == '*') { *v = arg0f * arg1f; } // Multiply |
| else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide |
| else { *v = arg1f; } // Assign constant |
| } |
| return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0; |
| } |
| |
| static float GetMinimumStepAtDecimalPrecision(int decimal_precision) |
| { |
| static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; |
| if (decimal_precision < 0) |
| return FLT_MIN; |
| return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); |
| } |
| |
| template<typename TYPE> |
| static const char* ImAtoi(const char* src, TYPE* output) |
| { |
| int negative = 0; |
| if (*src == '-') { negative = 1; src++; } |
| if (*src == '+') { src++; } |
| TYPE v = 0; |
| while (*src >= '0' && *src <= '9') |
| v = (v * 10) + (*src++ - '0'); |
| *output = negative ? -v : v; |
| return src; |
| } |
| |
| template<typename TYPE, typename SIGNEDTYPE> |
| static inline TYPE RoundScalarWithFormat(const char* format, ImGuiDataType data_type, TYPE v) |
| { |
| const char* fmt_start = ImParseFormatFindStart(format); |
| if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string |
| return v; |
| char v_str[64]; |
| ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); |
| const char* p = v_str; |
| while (*p == ' ') |
| p++; |
| if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) |
| v = (TYPE)ImAtof(p); |
| else |
| ImAtoi(p, (SIGNEDTYPE*)&v); |
| return v; |
| } |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Drags |
| // - DragBehaviorT<>() [Internal] |
| // - DragBehavior() [Internal] |
| // - DragScalar() |
| // - DragScalarN() |
| // - DragFloat() |
| // - DragFloat2() |
| // - DragFloat3() |
| // - DragFloat4() |
| // - DragFloatRange2() |
| // - DragInt() |
| // - DragInt2() |
| // - DragInt3() |
| // - DragInt4() |
| // - DragIntRange2() |
| //------------------------------------------------------------------------- |
| |
| // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) |
| template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
| static bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power) |
| { |
| ImGuiContext& g = *GImGui; |
| |
| // Default tweak speed |
| bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX); |
| if (v_speed == 0.0f && has_min_max) |
| v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); |
| |
| // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings |
| float adjust_delta = 0.0f; |
| if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f) |
| { |
| adjust_delta = g.IO.MouseDelta.x; |
| if (g.IO.KeyAlt) |
| adjust_delta *= 1.0f/100.0f; |
| if (g.IO.KeyShift) |
| adjust_delta *= 10.0f; |
| } |
| else if (g.ActiveIdSource == ImGuiInputSource_Nav) |
| { |
| int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0; |
| adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; |
| v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); |
| } |
| adjust_delta *= v_speed; |
| |
| // Clear current value on activation |
| // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. |
| bool is_just_activated = g.ActiveIdIsJustActivated; |
| bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); |
| if (is_just_activated || is_already_past_limits_and_pushing_outward) |
| { |
| g.DragCurrentAccum = 0.0f; |
| g.DragCurrentAccumDirty = false; |
| } |
| else if (adjust_delta != 0.0f) |
| { |
| g.DragCurrentAccum += adjust_delta; |
| g.DragCurrentAccumDirty = true; |
| } |
| |
| if (!g.DragCurrentAccumDirty) |
| return false; |
| |
| TYPE v_cur = *v; |
| FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; |
| |
| const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max); |
| if (is_power) |
| { |
| // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range |
| FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power); |
| FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min)); |
| v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min); |
| v_old_ref_for_accum_remainder = v_old_norm_curved; |
| } |
| else |
| { |
| v_cur += (TYPE)g.DragCurrentAccum; |
| } |
| |
| // Round to user desired precision based on format string |
| v_cur = RoundScalarWithFormat<TYPE, SIGNEDTYPE>(format, data_type, v_cur); |
| |
| // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. |
| g.DragCurrentAccumDirty = false; |
| if (is_power) |
| { |
| FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power); |
| g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder); |
| } |
| else |
| { |
| g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); |
| } |
| |
| // Lose zero sign for float/double |
| if (v_cur == (TYPE)-0) |
| v_cur = (TYPE)0; |
| |
| // Clamp values (handle overflow/wrap-around) |
| if (*v != v_cur && has_min_max) |
| { |
| if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f)) |
| v_cur = v_min; |
| if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f)) |
| v_cur = v_max; |
| } |
| |
| // Apply result |
| if (*v == v_cur) |
| return false; |
| *v = v_cur; |
| return true; |
| } |
| |
| bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power) |
| { |
| ImGuiContext& g = *GImGui; |
| if (g.ActiveId == id) |
| { |
| if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) |
| ClearActiveID(); |
| else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) |
| ClearActiveID(); |
| } |
| if (g.ActiveId != id) |
| return false; |
| |
| switch (data_type) |
| { |
| case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v, v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power); |
| case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v, v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power); |
| case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v, v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power); |
| case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v, v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power); |
| case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, (float*)v, v_speed, v_min ? *(const float* )v_min : -FLT_MAX, v_max ? *(const float* )v_max : FLT_MAX, format, power); |
| case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX, v_max ? *(const double*)v_max : DBL_MAX, format, power); |
| case ImGuiDataType_COUNT: break; |
| } |
| IM_ASSERT(0); |
| return false; |
| } |
| |
| bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| if (power != 1.0f) |
| IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| const float w = CalcItemWidth(); |
| |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); |
| const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); |
| 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)); |
| |
| // NB- we don't call ItemSize() yet because we may turn into a text edit box below |
| if (!ItemAdd(total_bb, id, &frame_bb)) |
| { |
| ItemSize(total_bb, style.FramePadding.y); |
| return false; |
| } |
| const bool hovered = ItemHoverable(frame_bb, id); |
| |
| // Default format string when passing NULL |
| // Patch old "%.0f" format string to use "%d", read function comments for more details. |
| IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); |
| if (format == NULL) |
| format = GDataTypeInfo[data_type].PrintFmt; |
| else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) |
| format = PatchFormatStringFloatToInt(format); |
| |
| // Tabbing or CTRL-clicking on Drag turns it into an input box |
| bool start_text_input = false; |
| const bool tab_focus_requested = FocusableItemRegister(window, id); |
| if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) |
| { |
| SetActiveID(id, window); |
| SetFocusID(id, window); |
| FocusWindow(window); |
| g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); |
| if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id) |
| { |
| start_text_input = true; |
| g.ScalarAsInputTextId = 0; |
| } |
| } |
| if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) |
| return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format); |
| |
| // Actual drag behavior |
| ItemSize(total_bb, style.FramePadding.y); |
| const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power); |
| if (value_changed) |
| MarkItemEdited(id); |
| |
| // Draw frame |
| const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
| RenderNavHighlight(frame_bb, id); |
| RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); |
| |
| // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. |
| char value_buf[64]; |
| const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); |
| RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); |
| |
| if (label_size.x > 0.0f) |
| RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); |
| |
| return value_changed; |
| } |
| |
| bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| bool value_changed = false; |
| BeginGroup(); |
| PushID(label); |
| PushMultiItemsWidths(components); |
| size_t type_size = GDataTypeInfo[data_type].Size; |
| for (int i = 0; i < components; i++) |
| { |
| PushID(i); |
| value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power); |
| SameLine(0, g.Style.ItemInnerSpacing.x); |
| PopID(); |
| PopItemWidth(); |
| v = (void*)((char*)v + type_size); |
| } |
| PopID(); |
| |
| TextUnformatted(label, FindRenderedTextEnd(label)); |
| EndGroup(); |
| return value_changed; |
| } |
| |
| bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) |
| { |
| return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) |
| { |
| return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) |
| { |
| return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) |
| { |
| return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| PushID(label); |
| BeginGroup(); |
| PushMultiItemsWidths(2); |
| |
| bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power); |
| PopItemWidth(); |
| SameLine(0, g.Style.ItemInnerSpacing.x); |
| value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power); |
| PopItemWidth(); |
| SameLine(0, g.Style.ItemInnerSpacing.x); |
| |
| TextUnformatted(label, FindRenderedTextEnd(label)); |
| EndGroup(); |
| PopID(); |
| return value_changed; |
| } |
| |
| // NB: v_speed is float to allow adjusting the drag speed with more precision |
| bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format) |
| { |
| return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format) |
| { |
| return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format) |
| { |
| return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format) |
| { |
| return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| PushID(label); |
| BeginGroup(); |
| PushMultiItemsWidths(2); |
| |
| bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format); |
| PopItemWidth(); |
| SameLine(0, g.Style.ItemInnerSpacing.x); |
| value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format); |
| PopItemWidth(); |
| SameLine(0, g.Style.ItemInnerSpacing.x); |
| |
| TextUnformatted(label, FindRenderedTextEnd(label)); |
| EndGroup(); |
| PopID(); |
| |
| return value_changed; |
| } |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Sliders |
| // - SliderBehaviorT<>() [Internal] |
| // - SliderBehavior() [Internal] |
| // - SliderScalar() |
| // - SliderScalarN() |
| // - SliderFloat() |
| // - SliderFloat2() |
| // - SliderFloat3() |
| // - SliderFloat4() |
| // - SliderAngle() |
| // - SliderInt() |
| // - SliderInt2() |
| // - SliderInt3() |
| // - SliderInt4() |
| // - VSliderScalar() |
| // - VSliderFloat() |
| // - VSliderInt() |
| //------------------------------------------------------------------------- |
| |
| template<typename TYPE, typename FLOATTYPE> |
| static inline float SliderBehaviorCalcRatioFromValue(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos) |
| { |
| if (v_min == v_max) |
| return 0.0f; |
| |
| const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double); |
| const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); |
| if (is_power) |
| { |
| if (v_clamped < 0.0f) |
| { |
| const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min)); |
| return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos; |
| } |
| else |
| { |
| const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min))); |
| return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos); |
| } |
| } |
| |
| // Linear slider |
| return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min)); |
| } |
| |
| // FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc. |
| template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
| static bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb) |
| { |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| |
| const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; |
| const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); |
| const bool is_power = (power != 1.0f) && is_decimal; |
| |
| const float grab_padding = 2.0f; |
| const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f); |
| float grab_sz = style.GrabMinSize; |
| SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); |
| if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows |
| grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit |
| grab_sz = ImMin(grab_sz, slider_sz); |
| const float slider_usable_sz = slider_sz - grab_sz; |
| const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f; |
| const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f; |
| |
| // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f |
| float linear_zero_pos; // 0.0->1.0f |
| if (is_power && v_min * v_max < 0.0f) |
| { |
| // Different sign |
| const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power); |
| const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power); |
| linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0)); |
| } |
| else |
| { |
| // Same sign |
| linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; |
| } |
| |
| // Process interacting with the slider |
| bool value_changed = false; |
| if (g.ActiveId == id) |
| { |
| bool set_new_value = false; |
| float clicked_t = 0.0f; |
| if (g.ActiveIdSource == ImGuiInputSource_Mouse) |
| { |
| if (!g.IO.MouseDown[0]) |
| { |
| ClearActiveID(); |
| } |
| else |
| { |
| const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; |
| clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; |
| if (!is_horizontal) |
| clicked_t = 1.0f - clicked_t; |
| set_new_value = true; |
| } |
| } |
| else if (g.ActiveIdSource == ImGuiInputSource_Nav) |
| { |
| const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); |
| float delta = is_horizontal ? delta2.x : -delta2.y; |
| if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) |
| { |
| ClearActiveID(); |
| } |
| else if (delta != 0.0f) |
| { |
| clicked_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos); |
| const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; |
| if ((decimal_precision > 0) || is_power) |
| { |
| delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds |
| if (IsNavInputDown(ImGuiNavInput_TweakSlow)) |
| delta /= 10.0f; |
| } |
| else |
| { |
| if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow)) |
| delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps |
| else |
| delta /= 100.0f; |
| } |
| if (IsNavInputDown(ImGuiNavInput_TweakFast)) |
| delta *= 10.0f; |
| set_new_value = true; |
| if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits |
| set_new_value = false; |
| else |
| clicked_t = ImSaturate(clicked_t + delta); |
| } |
| } |
| |
| if (set_new_value) |
| { |
| TYPE v_new; |
| if (is_power) |
| { |
| // Account for power curve scale on both sides of the zero |
| if (clicked_t < linear_zero_pos) |
| { |
| // Negative: rescale to the negative range before powering |
| float a = 1.0f - (clicked_t / linear_zero_pos); |
| a = ImPow(a, power); |
| v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a); |
| } |
| else |
| { |
| // Positive: rescale to the positive range before powering |
| float a; |
| if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f) |
| a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); |
| else |
| a = clicked_t; |
| a = ImPow(a, power); |
| v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a); |
| } |
| } |
| else |
| { |
| // Linear slider |
| if (is_decimal) |
| { |
| v_new = ImLerp(v_min, v_max, clicked_t); |
| } |
| else |
| { |
| // For integer values we want the clicking position to match the grab box so we round above |
| // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. |
| FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t; |
| TYPE v_new_off_floor = (TYPE)(v_new_off_f); |
| TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5); |
| if (!is_decimal && v_new_off_floor < v_new_off_round) |
| v_new = v_min + v_new_off_round; |
| else |
| v_new = v_min + v_new_off_floor; |
| } |
| } |
| |
| // Round to user desired precision based on format string |
| v_new = RoundScalarWithFormat<TYPE,SIGNEDTYPE>(format, data_type, v_new); |
| |
| // Apply result |
| if (*v != v_new) |
| { |
| *v = v_new; |
| value_changed = true; |
| } |
| } |
| } |
| |
| // Output grab position so it can be displayed by the caller |
| float grab_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos); |
| if (!is_horizontal) |
| grab_t = 1.0f - grab_t; |
| const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); |
| if (is_horizontal) |
| *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding); |
| else |
| *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f); |
| |
| return value_changed; |
| } |
| |
| // For 32-bits and larger types, slider bounds are limited to half the natural type range. |
| // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. |
| // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. |
| bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb) |
| { |
| switch (data_type) |
| { |
| case ImGuiDataType_S32: |
| IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2); |
| return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags, out_grab_bb); |
| case ImGuiDataType_U32: |
| IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2); |
| return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags, out_grab_bb); |
| case ImGuiDataType_S64: |
| IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2); |
| return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags, out_grab_bb); |
| case ImGuiDataType_U64: |
| IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2); |
| return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags, out_grab_bb); |
| case ImGuiDataType_Float: |
| IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f); |
| return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags, out_grab_bb); |
| case ImGuiDataType_Double: |
| IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f); |
| return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb); |
| case ImGuiDataType_COUNT: break; |
| } |
| IM_ASSERT(0); |
| return false; |
| } |
| |
| bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| const float w = CalcItemWidth(); |
| |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| 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)); |
| |
| // NB- we don't call ItemSize() yet because we may turn into a text edit box below |
| if (!ItemAdd(total_bb, id, &frame_bb)) |
| { |
| ItemSize(total_bb, style.FramePadding.y); |
| return false; |
| } |
| |
| // Default format string when passing NULL |
| // Patch old "%.0f" format string to use "%d", read function comments for more details. |
| IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); |
| if (format == NULL) |
| format = GDataTypeInfo[data_type].PrintFmt; |
| else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) |
| format = PatchFormatStringFloatToInt(format); |
| |
| // Tabbing or CTRL-clicking on Slider turns it into an input box |
| bool start_text_input = false; |
| const bool tab_focus_requested = FocusableItemRegister(window, id); |
| const bool hovered = ItemHoverable(frame_bb, id); |
| if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) |
| { |
| SetActiveID(id, window); |
| SetFocusID(id, window); |
| FocusWindow(window); |
| g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); |
| if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id) |
| { |
| start_text_input = true; |
| g.ScalarAsInputTextId = 0; |
| } |
| } |
| if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) |
| return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format); |
| |
| ItemSize(total_bb, style.FramePadding.y); |
| |
| // Draw frame |
| const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
| RenderNavHighlight(frame_bb, id); |
| RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); |
| |
| // Slider behavior |
| ImRect grab_bb; |
| const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb); |
| if (value_changed) |
| MarkItemEdited(id); |
| |
| // Render grab |
| window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); |
| |
| // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. |
| char value_buf[64]; |
| const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); |
| RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); |
| |
| if (label_size.x > 0.0f) |
| RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); |
| |
| return value_changed; |
| } |
| |
| // Add multiple sliders on 1 line for compact edition of multiple components |
| bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| bool value_changed = false; |
| BeginGroup(); |
| PushID(label); |
| PushMultiItemsWidths(components); |
| size_t type_size = GDataTypeInfo[data_type].Size; |
| for (int i = 0; i < components; i++) |
| { |
| PushID(i); |
| value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power); |
| SameLine(0, g.Style.ItemInnerSpacing.x); |
| PopID(); |
| PopItemWidth(); |
| v = (void*)((char*)v + type_size); |
| } |
| PopID(); |
| |
| TextUnformatted(label, FindRenderedTextEnd(label)); |
| EndGroup(); |
| return value_changed; |
| } |
| |
| bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) |
| { |
| return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) |
| { |
| return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) |
| { |
| return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) |
| { |
| return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); |
| } |
| |
| bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) |
| { |
| float v_deg = (*v_rad) * 360.0f / (2*IM_PI); |
| bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); |
| *v_rad = v_deg * (2*IM_PI) / 360.0f; |
| return value_changed; |
| } |
| |
| bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format) |
| { |
| return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format) |
| { |
| return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format) |
| { |
| return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format) |
| { |
| return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format); |
| } |
| |
| bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) |
| { |
| 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 frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); |
| const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); |
| |
| ItemSize(bb, style.FramePadding.y); |
| if (!ItemAdd(frame_bb, id)) |
| return false; |
| |
| // Default format string when passing NULL |
| // Patch old "%.0f" format string to use "%d", read function comments for more details. |
| IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); |
| if (format == NULL) |
| format = GDataTypeInfo[data_type].PrintFmt; |
| else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) |
| format = PatchFormatStringFloatToInt(format); |
| |
| const bool hovered = ItemHoverable(frame_bb, id); |
| if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) |
| { |
| SetActiveID(id, window); |
| SetFocusID(id, window); |
| FocusWindow(window); |
| g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); |
| } |
| |
| // Draw frame |
| const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
| RenderNavHighlight(frame_bb, id); |
| RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); |
| |
| // Slider behavior |
| ImRect grab_bb; |
| const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb); |
| if (value_changed) |
| MarkItemEdited(id); |
| |
| // Render grab |
| window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); |
| |
| // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. |
| // For the vertical slider we allow centered text to overlap the frame padding |
| char value_buf[64]; |
| const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); |
| RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); |
| if (label_size.x > 0.0f) |
| RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); |
| |
| return value_changed; |
| } |
| |
| |