| // dear imgui, v1.64 WIP |
| // (widgets code) |
| |
| #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |
| #define _CRT_SECURE_NO_WARNINGS |
| #endif |
| |
| #include "imgui.h" |
| #ifndef IMGUI_DEFINE_MATH_OPERATORS |
| #define IMGUI_DEFINE_MATH_OPERATORS |
| #endif |
| #include "imgui_internal.h" |
| |
| #include <ctype.h> // toupper, isprint |
| |
| // Visual Studio warnings |
| #ifdef _MSC_VER |
| #pragma warning (disable: 4127) // condition expression is constant |
| #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen |
| #endif |
| |
| // Clang/GCC warnings with -Weverything |
| #ifdef __clang__ |
| #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. |
| #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // |
| #elif defined(__GNUC__) |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked |
| #if __GNUC__ >= 8 |
| #pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead |
| #endif |
| #endif |
| |
| //------------------------------------------------------------------------- |
| // Forward Declarations |
| //------------------------------------------------------------------------- |
| |
| //------------------------------------------------------------------------- |
| // SHARED UTILITIES |
| //------------------------------------------------------------------------- |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Text |
| // - TextUnformatted() |
| // - Text() |
| // - TextV() |
| // - TextColored() |
| // - TextColoredV() |
| // - TextDisabled() |
| // - TextDisabledV() |
| // - TextWrapped() |
| // - TextWrappedV() |
| // - LabelText() |
| // - LabelTextV() |
| // - BulletText() |
| // - BulletTextV() |
| //------------------------------------------------------------------------- |
| |
| void ImGui::TextUnformatted(const char* text, const char* text_end) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| IM_ASSERT(text != NULL); |
| const char* text_begin = text; |
| if (text_end == NULL) |
| text_end = text + strlen(text); // FIXME-OPT |
| |
| const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); |
| const float wrap_pos_x = window->DC.TextWrapPos; |
| const bool wrap_enabled = wrap_pos_x >= 0.0f; |
| if (text_end - text > 2000 && !wrap_enabled) |
| { |
| // Long text! |
| // Perform manual coarse clipping to optimize for long multi-line text |
| // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. |
| // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. |
| const char* line = text; |
| const float line_height = GetTextLineHeight(); |
| const ImRect clip_rect = window->ClipRect; |
| ImVec2 text_size(0,0); |
| |
| if (text_pos.y <= clip_rect.Max.y) |
| { |
| ImVec2 pos = text_pos; |
| |
| // Lines to skip (can't skip when logging text) |
| if (!g.LogEnabled) |
| { |
| int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); |
| if (lines_skippable > 0) |
| { |
| int lines_skipped = 0; |
| while (line < text_end && lines_skipped < lines_skippable) |
| { |
| const char* line_end = strchr(line, '\n'); |
| if (!line_end) |
| line_end = text_end; |
| line = line_end + 1; |
| lines_skipped++; |
| } |
| pos.y += lines_skipped * line_height; |
| } |
| } |
| |
| // Lines to render |
| if (line < text_end) |
| { |
| ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); |
| while (line < text_end) |
| { |
| const char* line_end = strchr(line, '\n'); |
| if (IsClippedEx(line_rect, 0, false)) |
| break; |
| |
| const ImVec2 line_size = CalcTextSize(line, line_end, false); |
| text_size.x = ImMax(text_size.x, line_size.x); |
| RenderText(pos, line, line_end, false); |
| if (!line_end) |
| line_end = text_end; |
| line = line_end + 1; |
| line_rect.Min.y += line_height; |
| line_rect.Max.y += line_height; |
| pos.y += line_height; |
| } |
| |
| // Count remaining lines |
| int lines_skipped = 0; |
| while (line < text_end) |
| { |
| const char* line_end = strchr(line, '\n'); |
| if (!line_end) |
| line_end = text_end; |
| line = line_end + 1; |
| lines_skipped++; |
| } |
| pos.y += lines_skipped * line_height; |
| } |
| |
| text_size.y += (pos - text_pos).y; |
| } |
| |
| ImRect bb(text_pos, text_pos + text_size); |
| ItemSize(bb); |
| ItemAdd(bb, 0); |
| } |
| else |
| { |
| const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; |
| const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); |
| |
| // Account of baseline offset |
| ImRect bb(text_pos, text_pos + text_size); |
| ItemSize(text_size); |
| if (!ItemAdd(bb, 0)) |
| return; |
| |
| // Render (we don't hide text after ## in this end-user function) |
| RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); |
| } |
| } |
| |
| void ImGui::Text(const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| TextV(fmt, args); |
| va_end(args); |
| } |
| |
| void ImGui::TextV(const char* fmt, va_list args) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); |
| TextUnformatted(g.TempBuffer, text_end); |
| } |
| |
| void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| TextColoredV(col, fmt, args); |
| va_end(args); |
| } |
| |
| void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) |
| { |
| PushStyleColor(ImGuiCol_Text, col); |
| TextV(fmt, args); |
| PopStyleColor(); |
| } |
| |
| void ImGui::TextDisabled(const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| TextDisabledV(fmt, args); |
| va_end(args); |
| } |
| |
| void ImGui::TextDisabledV(const char* fmt, va_list args) |
| { |
| PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); |
| TextV(fmt, args); |
| PopStyleColor(); |
| } |
| |
| void ImGui::TextWrapped(const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| TextWrappedV(fmt, args); |
| va_end(args); |
| } |
| |
| void ImGui::TextWrappedV(const char* fmt, va_list args) |
| { |
| bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set |
| if (need_wrap) PushTextWrapPos(0.0f); |
| TextV(fmt, args); |
| if (need_wrap) PopTextWrapPos(); |
| } |
| |
| void ImGui::LabelText(const char* label, const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| LabelTextV(label, fmt, args); |
| va_end(args); |
| } |
| |
| // Add a label+text combo aligned to other label+value widgets |
| void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const float w = CalcItemWidth(); |
| |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); |
| const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); |
| ItemSize(total_bb, style.FramePadding.y); |
| if (!ItemAdd(total_bb, 0)) |
| return; |
| |
| // Render |
| const char* value_text_begin = &g.TempBuffer[0]; |
| const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); |
| RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); |
| if (label_size.x > 0.0f) |
| RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); |
| } |
| |
| void ImGui::BulletText(const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| BulletTextV(fmt, args); |
| va_end(args); |
| } |
| |
| // Text with a little bullet aligned to the typical tree node. |
| void ImGui::BulletTextV(const char* fmt, va_list args) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| |
| const char* text_begin = g.TempBuffer; |
| const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); |
| const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); |
| const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it |
| const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding |
| ItemSize(bb); |
| if (!ItemAdd(bb, 0)) |
| return; |
| |
| // Render |
| RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); |
| RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); |
| } |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Main |
| // - ButtonBehavior() [Internal] |
| // - Button() |
| // - SmallButton() |
| // - InvisibleButton() |
| // - ArrowButton() |
| // - CloseButton() [Internal] |
| // - CollapseButton() [Internal] |
| // - Image() |
| // - ImageButton() |
| // - Checkbox() |
| // - CheckboxFlags() |
| // - RadioButton() |
| // - ProgressBar() |
| // - Bullet() |
| //------------------------------------------------------------------------- |
| |
| bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = GetCurrentWindow(); |
| |
| if (flags & ImGuiButtonFlags_Disabled) |
| { |
| if (out_hovered) *out_hovered = false; |
| if (out_held) *out_held = false; |
| if (g.ActiveId == id) ClearActiveID(); |
| return false; |
| } |
| |
| // Default behavior requires click+release on same spot |
| if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) |
| flags |= ImGuiButtonFlags_PressedOnClickRelease; |
| |
| ImGuiWindow* backup_hovered_window = g.HoveredWindow; |
| if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) |
| g.HoveredWindow = window; |
| |
| bool pressed = false; |
| bool hovered = ItemHoverable(bb, id); |
| |
| // Drag source doesn't report as hovered |
| if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) |
| hovered = false; |
| |
| // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button |
| if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) |
| if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) |
| { |
| hovered = true; |
| SetHoveredID(id); |
| if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy |
| { |
| pressed = true; |
| FocusWindow(window); |
| } |
| } |
| |
| if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) |
| g.HoveredWindow = backup_hovered_window; |
| |
| // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. |
| if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) |
| hovered = false; |
| |
| // Mouse |
| if (hovered) |
| { |
| if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) |
| { |
| // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat |
| // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds |
| // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> .. |
| // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release) |
| // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> .. |
| // FIXME-NAV: We don't honor those different behaviors. |
| if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) |
| { |
| SetActiveID(id, window); |
| if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
| SetFocusID(id, window); |
| FocusWindow(window); |
| } |
| if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) |
| { |
| pressed = true; |
| if (flags & ImGuiButtonFlags_NoHoldingActiveID) |
| ClearActiveID(); |
| else |
| SetActiveID(id, window); // Hold on ID |
| FocusWindow(window); |
| } |
| if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) |
| { |
| if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release> |
| pressed = true; |
| ClearActiveID(); |
| } |
| |
| // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). |
| // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. |
| if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) |
| pressed = true; |
| } |
| |
| if (pressed) |
| g.NavDisableHighlight = true; |
| } |
| |
| // Gamepad/Keyboard navigation |
| // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. |
| if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) |
| hovered = true; |
| |
| if (g.NavActivateDownId == id) |
| { |
| bool nav_activated_by_code = (g.NavActivateId == id); |
| bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); |
| if (nav_activated_by_code || nav_activated_by_inputs) |
| pressed = true; |
| if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) |
| { |
| // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. |
| g.NavActivateId = id; // This is so SetActiveId assign a Nav source |
| SetActiveID(id, window); |
| if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
| SetFocusID(id, window); |
| g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); |
| } |
| } |
| |
| bool held = false; |
| if (g.ActiveId == id) |
| { |
| if (g.ActiveIdSource == ImGuiInputSource_Mouse) |
| { |
| if (g.ActiveIdIsJustActivated) |
| g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; |
| if (g.IO.MouseDown[0]) |
| { |
| held = true; |
| } |
| else |
| { |
| if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) |
| if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release> |
| if (!g.DragDropActive) |
| pressed = true; |
| ClearActiveID(); |
| } |
| if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
| g.NavDisableHighlight = true; |
| } |
| else if (g.ActiveIdSource == ImGuiInputSource_Nav) |
| { |
| if (g.NavActivateDownId != id) |
| ClearActiveID(); |
| } |
| } |
| |
| if (out_hovered) *out_hovered = hovered; |
| if (out_held) *out_held = held; |
| |
| return pressed; |
| } |
| |
| bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| ImVec2 pos = window->DC.CursorPos; |
| if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) |
| pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; |
| ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); |
| |
| const ImRect bb(pos, pos + size); |
| ItemSize(bb, style.FramePadding.y); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) |
| flags |= ImGuiButtonFlags_Repeat; |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); |
| if (pressed) |
| MarkItemEdited(id); |
| |
| // Render |
| const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| RenderNavHighlight(bb, id); |
| RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); |
| RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); |
| |
| // Automatically close popups |
| //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) |
| // CloseCurrentPopup(); |
| |
| return pressed; |
| } |
| |
| bool ImGui::Button(const char* label, const ImVec2& size_arg) |
| { |
| return ButtonEx(label, size_arg, 0); |
| } |
| |
| // Small buttons fits within text without additional vertical spacing. |
| bool ImGui::SmallButton(const char* label) |
| { |
| ImGuiContext& g = *GImGui; |
| float backup_padding_y = g.Style.FramePadding.y; |
| g.Style.FramePadding.y = 0.0f; |
| bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); |
| g.Style.FramePadding.y = backup_padding_y; |
| return pressed; |
| } |
| |
| // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. |
| // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) |
| bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. |
| IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); |
| |
| const ImGuiID id = window->GetID(str_id); |
| ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); |
| ItemSize(bb); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held); |
| |
| return pressed; |
| } |
| |
| bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiID id = window->GetID(str_id); |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); |
| const float default_size = GetFrameHeight(); |
| ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) |
| flags |= ImGuiButtonFlags_Repeat; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); |
| |
| // Render |
| const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| RenderNavHighlight(bb, id); |
| RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding); |
| RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir); |
| |
| return pressed; |
| } |
| |
| bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) |
| { |
| float sz = GetFrameHeight(); |
| return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0); |
| } |
| |
| // Button to close a window |
| bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| |
| // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. |
| // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). |
| const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); |
| bool is_clipped = !ItemAdd(bb, id); |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held); |
| if (is_clipped) |
| return pressed; |
| |
| // Render |
| ImVec2 center = bb.GetCenter(); |
| if (hovered) |
| window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9); |
| |
| float cross_extent = (radius * 0.7071f) - 1.0f; |
| ImU32 cross_col = GetColorU32(ImGuiCol_Text); |
| center -= ImVec2(0.5f, 0.5f); |
| window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f); |
| window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f); |
| |
| return pressed; |
| } |
| |
| bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) |
| { |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| |
| ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); |
| ItemAdd(bb, id); |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); |
| |
| ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| if (hovered || held) |
| window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9); |
| RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); |
| |
| // Switch to moving the window after mouse is moved beyond the initial drag threshold |
| if (IsItemActive() && IsMouseDragging()) |
| StartMouseMovingWindow(window); |
| |
| return pressed; |
| } |
| |
| void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); |
| if (border_col.w > 0.0f) |
| bb.Max += ImVec2(2, 2); |
| ItemSize(bb); |
| if (!ItemAdd(bb, 0)) |
| return; |
| |
| if (border_col.w > 0.0f) |
| { |
| window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); |
| window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col)); |
| } |
| else |
| { |
| window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); |
| } |
| } |
| |
| // frame_padding < 0: uses FramePadding from style (default) |
| // frame_padding = 0: no framing |
| // frame_padding > 0: set framing size |
| // The color used are the button colors. |
| bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| |
| // Default to using texture ID as ID. User can still push string/integer prefixes. |
| // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. |
| PushID((void*)user_texture_id); |
| const ImGuiID id = window->GetID("#image"); |
| PopID(); |
| |
| const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); |
| const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); |
| ItemSize(bb); |
| if (!ItemAdd(bb, id)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(bb, id, &hovered, &held); |
| |
| // Render |
| const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
| RenderNavHighlight(bb, id); |
| RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); |
| if (bg_col.w > 0.0f) |
| window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); |
| window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); |
| |
| return pressed; |
| } |
| |
| bool ImGui::Checkbox(const char* label, bool* v) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice |
| ItemSize(check_bb, style.FramePadding.y); |
| |
| ImRect total_bb = check_bb; |
| if (label_size.x > 0) |
| SameLine(0, style.ItemInnerSpacing.x); |
| const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); |
| if (label_size.x > 0) |
| { |
| ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); |
| total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); |
| } |
| |
| if (!ItemAdd(total_bb, id)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); |
| if (pressed) |
| { |
| *v = !(*v); |
| MarkItemEdited(id); |
| } |
| |
| RenderNavHighlight(total_bb, id); |
| RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); |
| if (*v) |
| { |
| const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); |
| const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); |
| RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); |
| } |
| |
| if (g.LogEnabled) |
| LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); |
| if (label_size.x > 0.0f) |
| RenderText(text_bb.Min, label); |
| |
| return pressed; |
| } |
| |
| bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) |
| { |
| bool v = ((*flags & flags_value) == flags_value); |
| bool pressed = Checkbox(label, &v); |
| if (pressed) |
| { |
| if (v) |
| *flags |= flags_value; |
| else |
| *flags &= ~flags_value; |
| } |
| |
| return pressed; |
| } |
| |
| bool ImGui::RadioButton(const char* label, bool active) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); |
| ItemSize(check_bb, style.FramePadding.y); |
| |
| ImRect total_bb = check_bb; |
| if (label_size.x > 0) |
| SameLine(0, style.ItemInnerSpacing.x); |
| const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); |
| if (label_size.x > 0) |
| { |
| ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); |
| total_bb.Add(text_bb); |
| } |
| |
| if (!ItemAdd(total_bb, id)) |
| return false; |
| |
| ImVec2 center = check_bb.GetCenter(); |
| center.x = (float)(int)center.x + 0.5f; |
| center.y = (float)(int)center.y + 0.5f; |
| const float radius = check_bb.GetHeight() * 0.5f; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); |
| if (pressed) |
| MarkItemEdited(id); |
| |
| RenderNavHighlight(total_bb, id); |
| window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); |
| if (active) |
| { |
| const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); |
| const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); |
| window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); |
| } |
| |
| if (style.FrameBorderSize > 0.0f) |
| { |
| window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); |
| window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); |
| } |
| |
| if (g.LogEnabled) |
| LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); |
| if (label_size.x > 0.0f) |
| RenderText(text_bb.Min, label); |
| |
| return pressed; |
| } |
| |
| bool ImGui::RadioButton(const char* label, int* v, int v_button) |
| { |
| const bool pressed = RadioButton(label, *v == v_button); |
| if (pressed) |
| *v = v_button; |
| return pressed; |
| } |
| |
| // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size |
| void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| |
| ImVec2 pos = window->DC.CursorPos; |
| ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); |
| ItemSize(bb, style.FramePadding.y); |
| if (!ItemAdd(bb, 0)) |
| return; |
| |
| // Render |
| fraction = ImSaturate(fraction); |
| RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); |
| bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); |
| const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); |
| RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); |
| |
| // Default displaying the fraction as percentage string, but user can override it |
| char overlay_buf[32]; |
| if (!overlay) |
| { |
| ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); |
| overlay = overlay_buf; |
| } |
| |
| ImVec2 overlay_size = CalcTextSize(overlay, NULL); |
| if (overlay_size.x > 0.0f) |
| RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); |
| } |
| |
| void ImGui::Bullet() |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); |
| const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); |
| ItemSize(bb); |
| if (!ItemAdd(bb, 0)) |
| { |
| SameLine(0, style.FramePadding.x*2); |
| return; |
| } |
| |
| // Render and stay on same line |
| RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); |
| SameLine(0, style.FramePadding.x*2); |
| } |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Combo Box |
| // - BeginCombo() |
| // - EndCombo() |
| // - Combo() |
| //------------------------------------------------------------------------- |
| |
| static float CalcMaxPopupHeightFromItemCount(int items_count) |
| { |
| ImGuiContext& g = *GImGui; |
| if (items_count <= 0) |
| return FLT_MAX; |
| return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); |
| } |
| |
| bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) |
| { |
| // Always consume the SetNextWindowSizeConstraint() call in our early return paths |
| ImGuiContext& g = *GImGui; |
| ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond; |
| g.NextWindowData.SizeConstraintCond = 0; |
| |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together |
| |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| |
| const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth(); |
| const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); |
| const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); |
| ItemSize(total_bb, style.FramePadding.y); |
| if (!ItemAdd(total_bb, id, &frame_bb)) |
| return false; |
| |
| bool hovered, held; |
| bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); |
| bool popup_open = IsPopupOpen(id); |
| |
| const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); |
| const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
| RenderNavHighlight(frame_bb, id); |
| if (!(flags & ImGuiComboFlags_NoPreview)) |
| window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left); |
| if (!(flags & ImGuiComboFlags_NoArrowButton)) |
| { |
| window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right); |
| RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); |
| } |
| RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding); |
| if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) |
| RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); |
| if (label_size.x > 0) |
| RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); |
| |
| if ((pressed || g.NavActivateId == id) && !popup_open) |
| { |
| if (window->DC.NavLayerCurrent == 0) |
| window->NavLastIds[0] = id; |
| OpenPopupEx(id); |
| popup_open = true; |
| } |
| |
| if (!popup_open) |
| return false; |
| |
| if (backup_next_window_size_constraint) |
| { |
| g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint; |
| g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); |
| } |
| else |
| { |
| if ((flags & ImGuiComboFlags_HeightMask_) == 0) |
| flags |= ImGuiComboFlags_HeightRegular; |
| IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one |
| int popup_max_height_in_items = -1; |
| if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; |
| else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; |
| else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; |
| SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); |
| } |
| |
| char name[16]; |
| ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth |
| |
| // Peak into expected window size so we can position it |
| if (ImGuiWindow* popup_window = FindWindowByName(name)) |
| if (popup_window->WasActive) |
| { |
| ImVec2 size_expected = CalcWindowExpectedSize(popup_window); |
| if (flags & ImGuiComboFlags_PopupAlignLeft) |
| popup_window->AutoPosLastDirection = ImGuiDir_Left; |
| ImRect r_outer = GetWindowAllowedExtentRect(popup_window); |
| ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox); |
| SetNextWindowPos(pos); |
| } |
| |
| // Horizontally align ourselves with the framed text |
| ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; |
| PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y)); |
| bool ret = Begin(name, NULL, window_flags); |
| PopStyleVar(); |
| if (!ret) |
| { |
| EndPopup(); |
| IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above |
| return false; |
| } |
| return true; |
| } |
| |
| void ImGui::EndCombo() |
| { |
| EndPopup(); |
| } |
| |
| // Getter for the old Combo() API: const char*[] |
| static bool Items_ArrayGetter(void* data, int idx, const char** out_text) |
| { |
| const char* const* items = (const char* const*)data; |
| if (out_text) |
| *out_text = items[idx]; |
| return true; |
| } |
| |
| // Getter for the old Combo() API: "item1\0item2\0item3\0" |
| static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) |
| { |
| // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. |
| const char* items_separated_by_zeros = (const char*)data; |
| int items_count = 0; |
| const char* p = items_separated_by_zeros; |
| while (*p) |
| { |
| if (idx == items_count) |
| break; |
| p += strlen(p) + 1; |
| items_count++; |
| } |
| if (!*p) |
| return false; |
| if (out_text) |
| *out_text = p; |
| return true; |
| } |
| |
| // Old API, prefer using BeginCombo() nowadays if you can. |
| bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) |
| { |
| ImGuiContext& g = *GImGui; |
| |
| // Call the getter to obtain the preview string which is a parameter to BeginCombo() |
| const char* preview_value = NULL; |
| if (*current_item >= 0 && *current_item < items_count) |
| items_getter(data, *current_item, &preview_value); |
| |
| // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. |
| if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond) |
| SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); |
| |
| if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) |
| return false; |
| |
| // Display items |
| // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) |
| bool value_changed = false; |
| for (int i = 0; i < items_count; i++) |
| { |
| PushID((void*)(intptr_t)i); |
| const bool item_selected = (i == *current_item); |
| const char* item_text; |
| if (!items_getter(data, i, &item_text)) |
| item_text = "*Unknown item*"; |
| if (Selectable(item_text, item_selected)) |
| { |
| value_changed = true; |
| *current_item = i; |
| } |
| if (item_selected) |
| SetItemDefaultFocus(); |
| PopID(); |
| } |
| |
| EndCombo(); |
| return value_changed; |
| } |
| |
| // Combo box helper allowing to pass an array of strings. |
| bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) |
| { |
| const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); |
| return value_changed; |
| } |
| |
| // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" |
| bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) |
| { |
| int items_count = 0; |
| const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open |
| while (*p) |
| { |
| p += strlen(p) + 1; |
| items_count++; |
| } |
| bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); |
| return value_changed; |
| } |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Data Type and Data Formatting Helpers [Internal] |
| // - PatchFormatStringFloatToInt() |
| // - DataTypeFormatString() |
| // - DataTypeApplyOp() |
| // - DataTypeApplyOpFromText() |
| // - GetMinimumStepAtDecimalPrecision |
| // - RoundScalarWithFormat<>() |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Drags |
| // - DragBehaviorT<>() [Internal] |
| // - DragBehavior() [Internal] |
| // - DragScalar() |
| // - DragScalarN() |
| // - DragFloat() |
| // - DragFloat2() |
| // - DragFloat3() |
| // - DragFloat4() |
| // - DragFloatRange2() |
| // - DragInt() |
| // - DragInt2() |
| // - DragInt3() |
| // - DragInt4() |
| // - DragIntRange2() |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Sliders |
| // - SliderBehaviorT<>() [Internal] |
| // - SliderBehavior() [Internal] |
| // - SliderScalar() |
| // - SliderScalarN() |
| // - SliderFloat() |
| // - SliderFloat2() |
| // - SliderFloat3() |
| // - SliderFloat4() |
| // - SliderAngle() |
| // - SliderInt() |
| // - SliderInt2() |
| // - SliderInt3() |
| // - SliderInt4() |
| // - VSliderScalar() |
| // - VSliderFloat() |
| // - VSliderInt() |
| //------------------------------------------------------------------------- |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Inputs (_excepted InputText_) |
| // - ImParseFormatFindStart() |
| // - ImParseFormatFindEnd() |
| // - ImParseFormatTrimDecorations() |
| // - ImParseFormatPrecision() |
| // - InputScalarAsWidgetReplacement() [Internal] |
| // - InputScalar() |
| // - InputScalarN() |
| // - InputFloat() |
| // - InputFloat2() |
| // - InputFloat3() |
| // - InputFloat4() |
| // - InputInt() |
| // - InputInt2() |
| // - InputInt3() |
| // - InputInt4() |
| // - InputDouble() |
| //------------------------------------------------------------------------- |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: InputText |
| // - InputText() |
| // - InputTextMultiline() |
| // - InputTextEx() [Internal] |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Color Editor / Picker |
| // - ColorEdit3() |
| // - ColorEdit4() |
| // - ColorPicker3() |
| // - RenderColorRectWithAlphaCheckerboard() [Internal] |
| // - ColorPicker4() |
| // - ColorButton() |
| // - SetColorEditOptions() |
| // - ColorTooltip() [Internal] |
| // - ColorEditOptionsPopup() [Internal] |
| // - ColorPickerOptionsPopup() [Internal] |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Trees |
| // - TreeNode() |
| // - TreeNodeV() |
| // - TreeNodeEx() |
| // - TreeNodeExV() |
| // - TreeNodeBehavior() [Internal] |
| // - TreePush() |
| // - TreePop() |
| // - TreeAdvanceToLabelPos() |
| // - GetTreeNodeToLabelSpacing() |
| // - SetNextTreeNodeOpen() |
| // - CollapsingHeader() |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Selectables |
| // - Selectable() |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: List Box |
| // - ListBox() |
| // - ListBoxHeader() |
| // - ListBoxFooter() |
| //------------------------------------------------------------------------- |
| |
| // FIXME: Rename to BeginListBox() |
| // Helper to calculate the size of a listbox and display a label on the right. |
| // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" |
| bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| const ImGuiStyle& style = GetStyle(); |
| const ImGuiID id = GetID(label); |
| const ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. |
| ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); |
| ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); |
| ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); |
| ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); |
| window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. |
| |
| BeginGroup(); |
| if (label_size.x > 0) |
| RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); |
| |
| BeginChildFrame(id, frame_bb.GetSize()); |
| return true; |
| } |
| |
| // FIXME: Rename to BeginListBox() |
| bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) |
| { |
| // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. |
| // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. |
| // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. |
| if (height_in_items < 0) |
| height_in_items = ImMin(items_count, 7); |
| float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); |
| |
| // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). |
| ImVec2 size; |
| size.x = 0.0f; |
| size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; |
| return ListBoxHeader(label, size); |
| } |
| |
| // FIXME: Rename to EndListBox() |
| void ImGui::ListBoxFooter() |
| { |
| ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; |
| const ImRect bb = parent_window->DC.LastItemRect; |
| const ImGuiStyle& style = GetStyle(); |
| |
| EndChildFrame(); |
| |
| // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) |
| // We call SameLine() to restore DC.CurrentLine* data |
| SameLine(); |
| parent_window->DC.CursorPos = bb.Min; |
| ItemSize(bb, style.FramePadding.y); |
| EndGroup(); |
| } |
| |
| bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) |
| { |
| const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); |
| return value_changed; |
| } |
| |
| bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) |
| { |
| if (!ListBoxHeader(label, items_count, height_in_items)) |
| return false; |
| |
| // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. |
| ImGuiContext& g = *GImGui; |
| bool value_changed = false; |
| ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. |
| while (clipper.Step()) |
| for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) |
| { |
| const bool item_selected = (i == *current_item); |
| const char* item_text; |
| if (!items_getter(data, i, &item_text)) |
| item_text = "*Unknown item*"; |
| |
| PushID(i); |
| if (Selectable(item_text, item_selected)) |
| { |
| *current_item = i; |
| value_changed = true; |
| } |
| if (item_selected) |
| SetItemDefaultFocus(); |
| PopID(); |
| } |
| ListBoxFooter(); |
| if (value_changed) |
| MarkItemEdited(g.CurrentWindow->DC.LastItemId); |
| |
| return value_changed; |
| } |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Data Plotting |
| // - PlotEx() [Internal] |
| // - PlotLines() |
| // - PlotHistogram() |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Value() helpers |
| // - Value() |
| //------------------------------------------------------------------------- |
| |
| |
| //------------------------------------------------------------------------- |
| // WIDGETS: Menus |
| // - ImGuiMenuColumns |
| // - BeginMainMenuBar() |
| // - EndMainMenuBar() |
| // - BeginMenuBar() |
| // - EndMenuBar() |
| // - BeginMenu() |
| // - EndMenu() |
| // - MenuItem() |
| //------------------------------------------------------------------------- |
| |
| // Helpers for internal use |
| ImGuiMenuColumns::ImGuiMenuColumns() |
| { |
| Count = 0; |
| Spacing = Width = NextWidth = 0.0f; |
| memset(Pos, 0, sizeof(Pos)); |
| memset(NextWidths, 0, sizeof(NextWidths)); |
| } |
| |
| void ImGuiMenuColumns::Update(int count, float spacing, bool clear) |
| { |
| IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); |
| Count = count; |
| Width = NextWidth = 0.0f; |
| Spacing = spacing; |
| if (clear) memset(NextWidths, 0, sizeof(NextWidths)); |
| for (int i = 0; i < Count; i++) |
| { |
| if (i > 0 && NextWidths[i] > 0.0f) |
| Width += Spacing; |
| Pos[i] = (float)(int)Width; |
| Width += NextWidths[i]; |
| NextWidths[i] = 0.0f; |
| } |
| } |
| |
| float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double |
| { |
| NextWidth = 0.0f; |
| NextWidths[0] = ImMax(NextWidths[0], w0); |
| NextWidths[1] = ImMax(NextWidths[1], w1); |
| NextWidths[2] = ImMax(NextWidths[2], w2); |
| for (int i = 0; i < 3; i++) |
| NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); |
| return ImMax(Width, NextWidth); |
| } |
| |
| float ImGuiMenuColumns::CalcExtraSpace(float avail_w) |
| { |
| return ImMax(0.0f, avail_w - Width); |
| } |
| |
| // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. |
| bool ImGui::BeginMainMenuBar() |
| { |
| ImGuiContext& g = *GImGui; |
| g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); |
| SetNextWindowPos(ImVec2(0.0f, 0.0f)); |
| SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y)); |
| PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); |
| PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); |
| ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; |
| bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar(); |
| PopStyleVar(2); |
| g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); |
| if (!is_open) |
| { |
| End(); |
| return false; |
| } |
| return true; |
| } |
| |
| void ImGui::EndMainMenuBar() |
| { |
| EndMenuBar(); |
| |
| // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window |
| ImGuiContext& g = *GImGui; |
| if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) |
| FocusFrontMostActiveWindowIgnoringOne(g.NavWindow); |
| |
| End(); |
| } |
| |
| bool ImGui::BeginMenuBar() |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| if (!(window->Flags & ImGuiWindowFlags_MenuBar)) |
| return false; |
| |
| IM_ASSERT(!window->DC.MenuBarAppending); |
| BeginGroup(); // Backup position on layer 0 |
| PushID("##menubar"); |
| |
| // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. |
| // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. |
| ImRect bar_rect = window->MenuBarRect(); |
| ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); |
| clip_rect.ClipWith(window->OuterRectClipped); |
| PushClipRect(clip_rect.Min, clip_rect.Max, false); |
| |
| window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); |
| window->DC.LayoutType = ImGuiLayoutType_Horizontal; |
| window->DC.NavLayerCurrent++; |
| window->DC.NavLayerCurrentMask <<= 1; |
| window->DC.MenuBarAppending = true; |
| AlignTextToFramePadding(); |
| return true; |
| } |
| |
| void ImGui::EndMenuBar() |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return; |
| ImGuiContext& g = *GImGui; |
| |
| // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. |
| if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) |
| { |
| ImGuiWindow* nav_earliest_child = g.NavWindow; |
| while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) |
| nav_earliest_child = nav_earliest_child->ParentWindow; |
| if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) |
| { |
| // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. |
| // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) |
| IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check |
| FocusWindow(window); |
| SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]); |
| g.NavLayer = 1; |
| g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. |
| g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; |
| NavMoveRequestCancel(); |
| } |
| } |
| |
| IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); |
| IM_ASSERT(window->DC.MenuBarAppending); |
| PopClipRect(); |
| PopID(); |
| window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. |
| window->DC.GroupStack.back().AdvanceCursor = false; |
| EndGroup(); // Restore position on layer 0 |
| window->DC.LayoutType = ImGuiLayoutType_Vertical; |
| window->DC.NavLayerCurrent--; |
| window->DC.NavLayerCurrentMask >>= 1; |
| window->DC.MenuBarAppending = false; |
| } |
| |
| bool ImGui::BeginMenu(const char* label, bool enabled) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| const ImGuiStyle& style = g.Style; |
| const ImGuiID id = window->GetID(label); |
| |
| ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| bool pressed; |
| bool menu_is_open = IsPopupOpen(id); |
| bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back()); |
| ImGuiWindow* backed_nav_window = g.NavWindow; |
| if (menuset_is_open) |
| g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) |
| |
| // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup). |
| ImVec2 popup_pos, pos = window->DC.CursorPos; |
| if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) |
| { |
| // Menu inside an horizontal menu bar |
| // Selectable extend their highlight by half ItemSpacing in each direction. |
| // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() |
| popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); |
| window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); |
| PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); |
| float w = label_size.x; |
| pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); |
| PopStyleVar(); |
| window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). |
| } |
| else |
| { |
| // Menu inside a menu |
| popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); |
| float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame |
| float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); |
| pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); |
| if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); |
| RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); |
| if (!enabled) PopStyleColor(); |
| } |
| |
| const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); |
| if (menuset_is_open) |
| g.NavWindow = backed_nav_window; |
| |
| bool want_open = false, want_close = false; |
| if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) |
| { |
| // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. |
| bool moving_within_opened_triangle = false; |
| if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar)) |
| { |
| if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) |
| { |
| ImRect next_window_rect = next_window->Rect(); |
| ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; |
| ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); |
| ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); |
| float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. |
| ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues |
| tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? |
| tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); |
| moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); |
| //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug |
| } |
| } |
| |
| want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); |
| want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); |
| |
| if (g.NavActivateId == id) |
| { |
| want_close = menu_is_open; |
| want_open = !menu_is_open; |
| } |
| if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open |
| { |
| want_open = true; |
| NavMoveRequestCancel(); |
| } |
| } |
| else |
| { |
| // Menu bar |
| if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it |
| { |
| want_close = true; |
| want_open = menu_is_open = false; |
| } |
| else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others |
| { |
| want_open = true; |
| } |
| else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open |
| { |
| want_open = true; |
| NavMoveRequestCancel(); |
| } |
| } |
| |
| if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' |
| want_close = true; |
| if (want_close && IsPopupOpen(id)) |
| ClosePopupToLevel(g.CurrentPopupStack.Size); |
| |
| if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) |
| { |
| // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. |
| OpenPopup(label); |
| return false; |
| } |
| |
| menu_is_open |= want_open; |
| if (want_open) |
| OpenPopup(label); |
| |
| if (menu_is_open) |
| { |
| // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) |
| SetNextWindowPos(popup_pos, ImGuiCond_Always); |
| ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; |
| if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) |
| flags |= ImGuiWindowFlags_ChildWindow; |
| menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) |
| } |
| |
| return menu_is_open; |
| } |
| |
| void ImGui::EndMenu() |
| { |
| // Nav: When a left move request _within our child menu_ failed, close the menu. |
| // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. |
| // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. |
| ImGuiContext& g = *GImGui; |
| ImGuiWindow* window = g.CurrentWindow; |
| if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) |
| { |
| ClosePopupToLevel(g.OpenPopupStack.Size - 1); |
| NavMoveRequestCancel(); |
| } |
| |
| EndPopup(); |
| } |
| |
| bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) |
| { |
| ImGuiWindow* window = GetCurrentWindow(); |
| if (window->SkipItems) |
| return false; |
| |
| ImGuiContext& g = *GImGui; |
| ImGuiStyle& style = g.Style; |
| ImVec2 pos = window->DC.CursorPos; |
| ImVec2 label_size = CalcTextSize(label, NULL, true); |
| |
| ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled); |
| bool pressed; |
| if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) |
| { |
| // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful |
| // Note that in this situation we render neither the shortcut neither the selected tick mark |
| float w = label_size.x; |
| window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); |
| PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); |
| pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); |
| PopStyleVar(); |
| window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). |
| } |
| else |
| { |
| ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); |
| float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame |
| float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); |
| pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); |
| if (shortcut_size.x > 0.0f) |
| { |
| PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); |
| RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); |
| PopStyleColor(); |
| } |
| if (selected) |
| RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); |
| } |
| return pressed; |
| } |
| |
| bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) |
| { |
| if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) |
| { |
| if (p_selected) |
| *p_selected = !*p_selected; |
| return true; |
| } |
| return false; |
| } |
| |