diff --git a/README.md b/README.md index d44ec576..29fcab16 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Frameworks: - Cinder: https://github.com/simongeilfus/Cinder-ImGui - cocos2d-x: https://github.com/c0i/imguix https://github.com/ocornut/imgui/issues/551 - Flexium/SFML (FlexGUI): https://github.com/DXsmiley/FlexGUI +- GML/GameMakerStudio2 (ImGuiGML): https://marketplace.yoyogames.com/assets/6221/imguigml - Irrlicht (IrrIMGUI): https://github.com/ZahlGraf/IrrIMGUI - Ogre: https://bitbucket.org/LMCrashy/ogreimgui/src - openFrameworks (ofxImGui): https://github.com/jvcleave/ofxImGui diff --git a/imgui.cpp b/imgui.cpp index c64a8339..db04e144 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -213,6 +213,7 @@ Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. Also read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. @@ -656,7 +657,6 @@ static ImRect GetVisibleRect(); static void CloseInactivePopups(ImGuiWindow* ref_window); static void ClosePopupToLevel(int remaining); static ImGuiWindow* GetFrontMostModalRootWindow(); -static ImVec2 FindBestPopupWindowPos(const ImVec2& base_pos, const ImVec2& size, int* last_dir, const ImRect& rect_to_avoid); static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data); static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); @@ -1846,7 +1846,7 @@ ImGuiWindow::ImGuiWindow(const char* name) AutoFitFramesX = AutoFitFramesY = -1; AutoFitOnlyGrows = false; AutoFitChildAxises = 0x00; - AutoPosLastDirection = -1; + AutoPosLastDirection = ImGuiDir_None; HiddenFrames = 0; SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); @@ -2037,26 +2037,35 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id) bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; + + // Test for bounding box overlap, as updated as ItemAdd() if (!window->DC.LastItemRectHoveredRect) return false; IM_ASSERT((flags & ImGuiHoveredFlags_FlattenChilds) == 0); // Flags not supported by this function + // Test if we are hovering the right window (our window could be behind another window) // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. //if (g.HoveredWindow != window) // return false; if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) return false; + + // Test if another item is active (e.g. being dragged) if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) return false; + + // Test if interactions on this window are blocked by an active popup or modal if (!IsWindowContentHoverable(window, flags)) return false; + + // Test if the item is disabled if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) return false; - // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten. + + // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) return false; return true; @@ -2244,6 +2253,11 @@ int ImGui::GetFrameCount() return GImGui->FrameCount; } +ImDrawList* ImGui::GetOverlayDrawList() +{ + return &GImGui->OverlayDrawList; +} + void ImGui::NewFrame() { ImGuiContext& g = *GImGui; @@ -2457,9 +2471,10 @@ void ImGui::NewFrame() } else if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) { - // Scroll - const int scroll_lines = (window->Flags & ImGuiWindowFlags_ComboBox) ? 3 : 5; - SetWindowScrollY(window, window->Scroll.y - g.IO.MouseWheel * window->CalcFontSize() * scroll_lines); + // Mouse wheel Scrolling + float scroll_amount = 5 * window->CalcFontSize(); + scroll_amount = (float)(int)ImMin(scroll_amount, (window->ContentsRegionRect.GetHeight() + window->WindowPadding.y * 2.0f) * 0.67f); + SetWindowScrollY(window, window->Scroll.y - g.IO.MouseWheel * scroll_amount); } } @@ -2776,8 +2791,6 @@ static int ChildWindowComparer(const void* lhs, const void* rhs) return d; if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) return d; - if (int d = (a->Flags & ImGuiWindowFlags_ComboBox) - (b->Flags & ImGuiWindowFlags_ComboBox)) - return d; return (a->OrderWithinParent - b->OrderWithinParent); } @@ -3648,17 +3661,17 @@ void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_ { ImGuiContext& g = *GImGui; char window_name[16]; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip%02d", g.TooltipOverrideCount); + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); if (override_previous_tooltip) - if (ImGuiWindow* window = ImGui::FindWindowByName(window_name)) + if (ImGuiWindow* window = FindWindowByName(window_name)) if (window->Active) { // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one. window->HiddenFrames = 1; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip%02d", ++g.TooltipOverrideCount); + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); } ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; - ImGui::Begin(window_name, NULL, flags | extra_flags); + Begin(window_name, NULL, flags | extra_flags); } void ImGui::SetTooltipV(const char* fmt, va_list args) @@ -3696,7 +3709,7 @@ void ImGui::OpenPopupEx(ImGuiID id, bool reopen_existing) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; int current_stack_size = g.CurrentPopupStack.Size; - ImGuiPopupRef popup_ref = ImGuiPopupRef(id, window, window->GetID("##menus"), g.IO.MousePos); // Tagged as new ref because constructor sets Window to NULL (we are passing the ParentWindow info here) + ImGuiPopupRef popup_ref = ImGuiPopupRef(id, window, window->GetID("##Menus"), g.IO.MousePos); // Tagged as new ref because constructor sets Window to NULL (we are passing the ParentWindow info here) if (g.OpenPopupStack.Size < current_stack_size + 1) g.OpenPopupStack.push_back(popup_ref); else if (reopen_existing || g.OpenPopupStack[current_stack_size].PopupId != id) @@ -3810,9 +3823,9 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) char name[20]; if (flags & ImGuiWindowFlags_ChildMenu) - ImFormatString(name, IM_ARRAYSIZE(name), "##menu_%d", g.CurrentPopupStack.Size); // Recycle windows based on depth + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth else - ImFormatString(name, IM_ARRAYSIZE(name), "##popup_%08x", id); // Not recycling, so we can close/open during the same frame + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame bool is_open = Begin(name, NULL, flags); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) @@ -3937,7 +3950,7 @@ static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, b const ImVec2 content_avail = ImGui::GetContentRegionAvail(); ImVec2 size = ImFloor(size_arg); - const int auto_fit_axises = ((size.x == 0.0f) ? 0x01 : 0x00) | ((size.y == 0.0f) ? 0x02 : 0x00); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); if (size.x <= 0.0f) size.x = ImMax(content_avail.x, 4.0f) - fabsf(size.x); // Arbitrary minimum zero-ish child size of 4.0f (0.0f causing too much issues) if (size.y <= 0.0f) @@ -3979,7 +3992,7 @@ void ImGui::EndChild() ImGuiWindow* window = GetCurrentWindow(); IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss - if ((window->Flags & ImGuiWindowFlags_ComboBox) || window->BeginCount > 1) + if (window->BeginCount > 1) { ImGui::End(); } @@ -3987,9 +4000,9 @@ void ImGui::EndChild() { // When using auto-filling child window, we don't provide full width/height to ItemSize so that it doesn't feed back into automatic size-fitting. ImVec2 sz = GetWindowSize(); - if (window->AutoFitChildAxises & 0x01) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f sz.x = ImMax(4.0f, sz.x); - if (window->AutoFitChildAxises & 0x02) + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) sz.y = ImMax(4.0f, sz.y); ImGui::End(); @@ -4034,29 +4047,67 @@ static void CheckStacksSize(ImGuiWindow* window, bool write) IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); } -static ImVec2 FindBestPopupWindowPos(const ImVec2& base_pos, const ImVec2& size, int* last_dir, const ImRect& r_inner) +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox +}; + +static ImVec2 FindBestWindowPosForPopup(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default) { const ImGuiStyle& style = GImGui->Style; - // Clamp into visible area while not overlapping the cursor. Safety padding is optional if our popup size won't fit without it. + // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) + // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. ImVec2 safe_padding = style.DisplaySafeAreaPadding; ImRect r_outer(GetVisibleRect()); r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x*2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y*2) ? -safe_padding.y : 0.0f)); - ImVec2 base_pos_clamped = ImClamp(base_pos, r_outer.Min, r_outer.Max - size); + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); - for (int n = (*last_dir != -1) ? -1 : 0; n < 4; n++) // Last, Right, down, up, left. (Favor last used direction). + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) { - const int dir = (n == -1) ? *last_dir : n; - ImRect rect(dir == 0 ? r_inner.Max.x : r_outer.Min.x, dir == 1 ? r_inner.Max.y : r_outer.Min.y, dir == 3 ? r_inner.Min.x : r_outer.Max.x, dir == 2 ? r_inner.Min.y : r_outer.Max.y); - if (rect.GetWidth() < size.x || rect.GetHeight() < size.y) + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Default popup policy + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + if (avail_w < size.x || avail_h < size.y) continue; + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; *last_dir = dir; - return ImVec2(dir == 0 ? r_inner.Max.x : dir == 3 ? r_inner.Min.x - size.x : base_pos_clamped.x, dir == 1 ? r_inner.Max.y : dir == 2 ? r_inner.Min.y - size.y : base_pos_clamped.y); + return pos; } // Fallback, try to keep within display - *last_dir = -1; - ImVec2 pos = base_pos; + *last_dir = ImGuiDir_None; + ImVec2 pos = ref_pos; pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); return pos; @@ -4086,12 +4137,8 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl window->Flags = flags; g.WindowsById.SetVoidPtr(window->ID, window); - if (flags & ImGuiWindowFlags_NoSavedSettings) - { - // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. - window->Size = window->SizeFull = size; - } - else + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) { // Retrieve settings from .ini file // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. @@ -4107,8 +4154,8 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl if (ImLengthSqr(settings->Size) > 0.00001f) size = settings->Size; } - window->Size = window->SizeFull = size; } + window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) { @@ -4131,7 +4178,7 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl return window; } -static ImVec2 CalcSizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) +static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) { ImGuiContext& g = *GImGui; if (g.SetNextWindowSizeConstraint) @@ -4151,6 +4198,8 @@ static ImVec2 CalcSizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) new_size = data.DesiredSize; } } + + // Minimum size if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) { new_size = ImMax(new_size, g.Style.WindowMinSize); @@ -4159,7 +4208,15 @@ static ImVec2 CalcSizeFullWithConstraint(ImGuiWindow* window, ImVec2 new_size) return new_size; } -static ImVec2 CalcSizeAutoFit(ImGuiWindow* window) +static ImVec2 CalcSizeContents(ImGuiWindow* window) +{ + ImVec2 sz; + sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); + sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); + return sz + window->WindowPadding; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; @@ -4168,16 +4225,16 @@ static ImVec2 CalcSizeAutoFit(ImGuiWindow* window) if ((flags & ImGuiWindowFlags_Tooltip) != 0) { // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. - size_auto_fit = window->SizeContents; + size_auto_fit = size_contents; } else { - // Handling case of auto fit window not fitting on the screen (on either axis): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. - size_auto_fit = ImClamp(window->SizeContents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); - ImVec2 size_auto_fit_after_constraint = CalcSizeFullWithConstraint(window, size_auto_fit); - if (size_auto_fit_after_constraint.x < window->SizeContents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) + // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); + ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) size_auto_fit.y += style.ScrollbarSize; - if (size_auto_fit_after_constraint.y < window->SizeContents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) + if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) size_auto_fit.x += style.ScrollbarSize; } return size_auto_fit; @@ -4220,6 +4277,47 @@ static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) return ImGuiCol_WindowBg; } +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +struct ImGuiResizeGripDef +{ + ImVec2 CornerPos; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; + +const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right + { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left + { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left + { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right +}; + +static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) rect.Max -= ImVec2(1,1); + if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness); + if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding); + if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y); + if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + IM_ASSERT(0); + return ImRect(); +} + // Push a new ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. @@ -4239,13 +4337,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; // Find or create - bool window_is_new = false; ImGuiWindow* window = FindWindowByName(name); if (!window) { ImVec2 size_on_first_use = (g.SetNextWindowSizeCond != 0) ? g.SetNextWindowSizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. window = CreateNewWindow(name, size_on_first_use, flags); - window_is_new = true; } const int current_frame = g.FrameCount; @@ -4255,6 +4351,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) else flags = window->Flags; + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); + window->CloseButton = (p_open != NULL); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack ImGuiWindow* parent_window = first_begin_of_the_frame ? (!g.CurrentWindowStack.empty() ? g.CurrentWindowStack.back() : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); @@ -4263,26 +4373,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) g.CurrentWindowStack.push_back(window); SetCurrentWindow(window); CheckStacksSize(window, true); - - bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; - window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed - window_just_activated_by_user |= (window != popup_ref.Window); popup_ref.Window = window; g.CurrentPopupStack.push_back(popup_ref); window->PopupId = popup_ref.PopupId; } - const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); - window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); - window->CloseButton = (p_open != NULL); - // Process SetNextWindow***() calls - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); - bool window_pos_set_by_api = false, window_size_set_by_api = false; + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; if (g.SetNextWindowPosCond) { window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) != 0; @@ -4302,13 +4403,16 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } if (g.SetNextWindowSizeCond) { - window_size_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0; + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0 && (g.SetNextWindowSizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0 && (g.SetNextWindowSizeVal.y > 0.0f); SetWindowSize(window, g.SetNextWindowSizeVal, g.SetNextWindowSizeCond); g.SetNextWindowSizeCond = 0; } if (g.SetNextWindowContentSizeCond) { + // Adjust passed "client size" to become a "window size" window->SizeContentsExplicit = g.SetNextWindowContentSizeVal; + window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); g.SetNextWindowContentSizeCond = 0; } else if (first_begin_of_the_frame) @@ -4350,7 +4454,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DrawList->Clear(); window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); ImRect fullscreen_rect(GetVisibleRect()); - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_ComboBox|ImGuiWindowFlags_Popup))) + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); else PushClipRect(fullscreen_rect.Min, fullscreen_rect.Max, true); @@ -4358,7 +4462,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window_just_activated_by_user) { // Popup first latch mouse position, will position itself when it appears next frame - window->AutoPosLastDirection = -1; + window->AutoPosLastDirection = ImGuiDir_None; if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) window->PosFloat = g.IO.MousePos; } @@ -4383,11 +4487,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // SIZE // Update contents size from last frame for auto-fitting (unless explicitly specified) - window->SizeContents.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : ((window_is_new ? 0.0f : window->DC.CursorMaxPos.x - window->Pos.x) + window->Scroll.x)); - window->SizeContents.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : ((window_is_new ? 0.0f : window->DC.CursorMaxPos.y - window->Pos.y) + window->Scroll.y)); - window->SizeContents += window->WindowPadding; + window->SizeContents = CalcSizeContents(window); - // Hide popup/tooltip window when first appearing while we measure size (because we recycle them) + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) if (window->HiddenFrames > 0) window->HiddenFrames--; if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user) @@ -4395,8 +4497,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->HiddenFrames = 1; if (flags & ImGuiWindowFlags_AlwaysAutoResize) { - if (!window_size_set_by_api) - window->Size = window->SizeFull = ImVec2(0.f, 0.f); + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; window->SizeContents = ImVec2(0.f, 0.f); } } @@ -4405,57 +4509,55 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; window->WindowPadding = style.WindowPadding; - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_ComboBox | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); const float window_rounding = window->WindowRounding; const float window_border_size = window->WindowBorderSize; // Calculate auto-fit size, handle automatic resize - const ImVec2 size_auto_fit = CalcSizeAutoFit(window); - if (window->Collapsed) - { - // We still process initial auto-fit on collapsed windows to get a window width, - // But otherwise we don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. - if (window->AutoFitFramesX > 0) - window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; - if (window->AutoFitFramesY > 0) - window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; - } - else if (!window_size_set_by_api) - { - if (flags & ImGuiWindowFlags_AlwaysAutoResize) - { - window->SizeFull = size_auto_fit; - } - else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) - { - // Auto-fit only grows during the first few frames - if (window->AutoFitFramesX > 0) - window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; - if (window->AutoFitFramesY > 0) - window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); + ImVec2 size_full_modified(FLT_MAX, FLT_MAX); + if (flags & ImGuiWindowFlags_AlwaysAutoResize && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + window->SizeFull.x = size_full_modified.x = size_auto_fit.x; + if (!window_size_y_set_by_api) + window->SizeFull.y = size_full_modified.y = size_auto_fit.y; + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit only grows during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (!window->Collapsed) MarkIniSettingsDirty(window); - } } // Apply minimum/maximum window size constraints and final size - window->SizeFull = CalcSizeFullWithConstraint(window, window->SizeFull); + window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull; if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) { - IM_ASSERT(window_size_set_by_api); // Submitted by BeginChild() + IM_ASSERT(window_size_x_set_by_api && window_size_y_set_by_api); // Submitted by BeginChild() window->Size = window->SizeFull; } // SCROLLBAR STATUS - // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). We need to do this before manual resize (below) is effective. + // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). if (!window->Collapsed) { - window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > window->SizeFull.y) && !(flags & ImGuiWindowFlags_NoScrollbar)); - window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > window->SizeFull.x - (window->ScrollbarY ? style.ScrollbarSize : 0.0f) - window->WindowPadding.x) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + // When reading the current size we need to read it after size constraints have been applied + float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; + float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f) - window->WindowPadding.x) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); if (window->ScrollbarX && !window->ScrollbarY) - window->ScrollbarY = (window->SizeContents.y > window->SizeFull.y + style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars + style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); } @@ -4487,21 +4589,21 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) rect_to_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight()); else rect_to_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); - window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); } else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) { ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); - window->PosFloat = FindBestPopupWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); } // Position tooltip (always follows mouse) if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api) { ImVec2 ref_pos = g.IO.MousePos; - ImRect rect_to_avoid(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24, ref_pos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead? - window->PosFloat = FindBestPopupWindowPos(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); - if (window->AutoPosLastDirection == -1) + ImRect rect_to_avoid(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24, ref_pos.y + 24); // FIXME: Completely hard-coded. Store boxes in mouse cursor data? Scale? Center on cursor hit-point? + window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + if (window->AutoPosLastDirection == ImGuiDir_None) window->PosFloat = ref_pos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. } @@ -4537,6 +4639,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) window->DrawList->AddRectFilled(fullscreen_rect.Min, fullscreen_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio)); + // Apply focus, new windows appears in front + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + want_focus = true; + // Draw window + handle manual resize ImRect title_bar_rect = window->TitleBarRect(); if (window->Collapsed) @@ -4549,39 +4657,87 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } else { - ImU32 resize_col = 0; - const float resize_corner_size = ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f); + // Handle resize for: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = { 0 }; + const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4 + const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0; + + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f); + const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f); if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && !(flags & ImGuiWindowFlags_NoResize)) { - // Manual resize - // Using the FlattenChilds button flag, we make the resize button accessible even if we are hovering over a child window - const ImVec2 br = window->Rect().GetBR(); - const ImRect resize_rect(br - ImFloor(ImVec2(resize_corner_size * 0.75f, resize_corner_size * 0.75f)), br); - const ImGuiID resize_id = window->GetID("#RESIZE"); - bool hovered, held; - ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds); - resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); - if (hovered || held) - g.MouseCursor = ImGuiMouseCursor_ResizeNWSE; + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); - ImVec2 size_target(FLT_MAX,FLT_MAX); - if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0]) + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) { - // Manual auto-fit when double-clicking - size_target = size_auto_fit; - ClearActiveID(); + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size); + resize_rect.FixInverted(); + bool hovered, held; + ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChilds); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcSizeAfterConstraint(window, size_auto_fit); + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip + CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); + } + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); } - else if (held) + for (int border_n = 0; border_n < resize_border_count; border_n++) { - // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position - size_target = (g.IO.MousePos - g.ActiveIdClickOffset - window->Pos) + resize_rect.GetSize(); + const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check. + const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise + bool hovered, held; + ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE); + ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n+4)), &hovered, &held, ImGuiButtonFlags_FlattenChilds); + if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held) + { + g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) border_held = border_n; + } + if (held) + { + ImVec2 border_target = window->Pos; + ImVec2 border_posn; + if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); } + if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); } + if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); } + if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); } + CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + } } + PopID(); - if (size_target.x != FLT_MAX && size_target.y != FLT_MAX) + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) { - window->SizeFull = CalcSizeFullWithConstraint(window, size_target); + window->Pos = window->PosFloat = ImVec2((float)(int)pos_target.x, (float)(int)pos_target.y); MarkIniSettingsDirty(window); } + window->Size = window->SizeFull; title_bar_rect = window->TitleBarRect(); } @@ -4591,7 +4747,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); // Title bar - const bool window_is_focused = g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow; + const bool window_is_focused = want_focus || (g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow); if (!(flags & ImGuiWindowFlags_NoTitleBar)) window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImDrawCornerFlags_Top); @@ -4611,24 +4767,35 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->ScrollbarY) Scrollbar(ImGuiLayoutType_Vertical); - // Render resize grip - // (after the input handling so we don't have a frame of latency) + // Render resize grips (after their input handling so we don't have a frame of latency) if (!(flags & ImGuiWindowFlags_NoResize)) { - const ImVec2 br = window->Rect().GetBR(); - window->DrawList->PathLineTo(br + ImVec2(-resize_corner_size, -window_border_size)); - window->DrawList->PathLineTo(br + ImVec2(-window_border_size, -resize_corner_size)); - window->DrawList->PathArcToFast(ImVec2(br.x - window_rounding - window_border_size, br.y - window_rounding - window_border_size), window_rounding, 0, 3); - window->DrawList->PathFillConvex(resize_col); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } } // Borders if (window_border_size > 0.0f) window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); + if (border_held != -1) + { + ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f); + window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); + } if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) - window->DrawList->AddLine(title_bar_rect.GetBL()+ImVec2(1,-1), title_bar_rect.GetBR()+ImVec2(-1,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } + // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. + window->SizeFullAtLastBegin = window->SizeFull; + // Update ContentsRegionMax. All the variable it depends on are set above in this function. window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); @@ -4678,10 +4845,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->AutoFitFramesY > 0) window->AutoFitFramesY--; - // New windows appears in front (we need to do that AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) - if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) - if (!(flags & (ImGuiWindowFlags_ChildWindow|ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) - FocusWindow(window); + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + FocusWindow(window); // Title bar if (!(flags & ImGuiWindowFlags_NoTitleBar)) @@ -4732,10 +4898,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Inner rectangle // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. - window->InnerRect.Min.x = title_bar_rect.Min.x; - window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight(); - window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x; - window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y; + window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; + window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.). @@ -4748,10 +4914,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. const float border_size = window->WindowBorderSize; ImRect clip_rect; - clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); - clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + border_size); - clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f))); - clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - border_size); + clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y); + clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y); PushClipRect(clip_rect.Min, clip_rect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) @@ -4852,10 +5018,8 @@ void ImGui::Scrollbar(ImGuiLayoutType direction) 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); + ? ImRect(window->InnerRect.Min.x, window_rect.Max.y - style.ScrollbarSize, window->InnerRect.Max.x, window_rect.Max.y - border_size) + : ImRect(window_rect.Max.x - style.ScrollbarSize, window->InnerRect.Min.y, window_rect.Max.x - border_size, window->InnerRect.Max.y); if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) return; @@ -5520,14 +5684,7 @@ void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& s void ImGui::SetNextWindowContentSize(const ImVec2& size) { ImGuiContext& g = *GImGui; - g.SetNextWindowContentSizeVal = size; - g.SetNextWindowContentSizeCond = ImGuiCond_Always; -} - -void ImGui::SetNextWindowContentWidth(float width) -{ - ImGuiContext& g = *GImGui; - g.SetNextWindowContentSizeVal = ImVec2(width, g.SetNextWindowContentSizeCond ? g.SetNextWindowContentSizeVal.y : 0.0f); + g.SetNextWindowContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. g.SetNextWindowContentSizeCond = ImGuiCond_Always; } @@ -5749,6 +5906,15 @@ void ImGui::SetScrollHere(float center_y_ratio) SetScrollFromPosY(target_y, center_y_ratio); } +// FIXME-NAV: This function is a placeholder for the upcoming Navigation branch + Focusing features. +// In the current branch this function will only set the scrolling, in the navigation branch it will also set your navigation cursor. +// Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable. +void ImGui::SetItemDefaultFocus() +{ + if (IsWindowAppearing()) + SetScrollHere(); +} + void ImGui::SetKeyboardFocusHere(int offset) { IM_ASSERT(offset >= -1); // -1 is allowed but not below @@ -8941,62 +9107,25 @@ bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_fla return InputIntN(label, v, 4, extra_flags); } -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; -} - -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; -} - -// 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. -bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +static float CalcMaxPopupHeightFromItemCount(int items_count) { - 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; + 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, ImVec2 popup_size) +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; + bool backup_has_next_window_size_constraint = g.SetNextWindowSizeConstraint; + g.SetNextWindowSizeConstraint = false; + 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(); @@ -9008,21 +9137,17 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImVec2 popu if (!ItemAdd(total_bb, id)) return false; - const float arrow_size = SmallSquareSize(); - bool hovered, held; bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); - bool popup_open = IsPopupOpen(id); + const float arrow_size = SmallSquareSize(); const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); - if (preview_value != NULL) 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); @@ -9035,62 +9160,81 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImVec2 popu if (!popup_open) return false; - if (popup_size.x == 0.0f) - popup_size.x = w; - - float popup_y1 = frame_bb.Max.y; - float popup_y2 = ImClamp(popup_y1 + popup_size.y, popup_y1, g.IO.DisplaySize.y - style.DisplaySafeAreaPadding.y); - if ((popup_y2 - popup_y1) < ImMin(popup_size.y, frame_bb.Min.y - style.DisplaySafeAreaPadding.y)) + if (backup_has_next_window_size_constraint) { - // Position our combo ABOVE because there's more space to fit! (FIXME: Handle in Begin() or use a shared helper. We have similar code in Begin() for popup placement) - popup_y1 = ImClamp(frame_bb.Min.y - popup_size.y, style.DisplaySafeAreaPadding.y, frame_bb.Min.y); - popup_y2 = frame_bb.Min.y; - SetNextWindowPos(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FrameBorderSize), ImGuiCond_Always, ImVec2(0.0f, 1.0f)); + g.SetNextWindowSizeConstraint = true; + g.SetNextWindowSizeConstraintRect.Min.x = ImMax(g.SetNextWindowSizeConstraintRect.Min.x, w); } else { - // Position our combo below - SetNextWindowPos(ImVec2(frame_bb.Min.x, frame_bb.Max.y - style.FrameBorderSize), ImGuiCond_Always, ImVec2(0.0f, 0.0f)); + 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))); } - SetNextWindowSize(ImVec2(popup_size.x, popup_y2 - popup_y1), ImGuiCond_Appearing); - PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); - if (!BeginPopupEx(id, ImGuiWindowFlags_ComboBox)) + 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)) { + ImVec2 size_contents = CalcSizeContents(popup_window); + ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents)); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; + ImVec2 pos = FindBestWindowPosForPopup(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + if (!Begin(name, NULL, window_flags)) + { + EndPopup(); IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above return false; } - Spacing(); + + // Horizontally align ourselves with the framed text + if (style.FramePadding.x != style.WindowPadding.x) + Indent(style.FramePadding.x - style.WindowPadding.x); return true; } void ImGui::EndCombo() { + const ImGuiStyle& style = GImGui->Style; + if (style.FramePadding.x != style.WindowPadding.x) + Unindent(style.FramePadding.x - style.WindowPadding.x); EndPopup(); - PopStyleVar(); } -// Combo box function. -bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +// 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; - const ImGuiStyle& style = g.Style; const char* preview_text = NULL; if (*current_item >= 0 && *current_item < items_count) items_getter(data, *current_item, &preview_text); - // Size default to hold ~7 items - if (height_in_items < 0) - height_in_items = 7; - float popup_height = (g.FontSize + style.ItemSpacing.y) * ImMin(items_count, height_in_items) + (style.FramePadding.y * 3); + // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here. + if (popup_max_height_in_items != -1 && !g.SetNextWindowSizeConstraint) + { + float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, popup_max_height)); + } - if (!BeginCombo(label, preview_text, ImVec2(0.0f, popup_height))) + if (!BeginCombo(label, preview_text, 0)) return false; // Display items - // FIXME-OPT: Use clipper + // 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++) { @@ -9104,8 +9248,8 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi value_changed = true; *current_item = i; } - if (item_selected && IsWindowAppearing()) - SetScrollHere(); + if (item_selected) + SetItemDefaultFocus(); PopID(); } @@ -9113,6 +9257,55 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi return value_changed; } +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; +} + +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; +} + +// 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. +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; +} + // Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) @@ -9431,7 +9624,7 @@ bool ImGui::BeginMenu(const char* label, bool enabled) 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].ParentMenuSet == window->GetID("##menus")); + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentMenuSet == window->GetID("##Menus")); 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) @@ -9747,7 +9940,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) EndPopup(); } -static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, float* ref_col) +static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col) { bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); @@ -9900,7 +10093,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag PopItemWidth(); } - bool picker_active = false; + ImGuiWindow* picker_active_window = NULL; if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) { if (!(flags & ImGuiColorEditFlags_NoInputs)) @@ -9922,7 +10115,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (BeginPopup("picker")) { - picker_active = true; + picker_active_window = g.CurrentWindow; if (label != label_display_end) { TextUnformatted(label, label_display_end); @@ -9944,7 +10137,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag } // Convert back - if (!picker_active) + if (picker_active_window == NULL) { if (!value_changed_as_float) for (int n = 0; n < 4; n++) @@ -9964,6 +10157,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag PopID(); EndGroup(); + // Drag and Drop Target if (window->DC.LastItemRectHoveredRect && BeginDragDropTarget()) // NB: The LastItemRectHoveredRect test is merely an optional micro-optimization { if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) @@ -9979,6 +10173,10 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag EndDragDropTarget(); } + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + return value_changed; } @@ -10042,6 +10240,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); ImVec2 picker_pos = window->DC.CursorPos; float square_sz = SmallSquareSize(); @@ -10051,6 +10250,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + float wheel_thickness = sv_picker_size * 0.08f; float wheel_r_outer = sv_picker_size * 0.50f; float wheel_r_inner = wheel_r_outer - wheel_thickness; @@ -10166,7 +10368,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) { - memcpy(col, ref_col, ((flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4) * sizeof(float)); + memcpy(col, ref_col, components * sizeof(float)); value_changed = true; } } @@ -10296,7 +10498,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl EndGroup(); PopID(); - return value_changed; + return value_changed && memcmp(backup_initial_col, col, components * sizeof(float)); } // Horizontal separating line. @@ -10839,7 +11041,7 @@ void ImGui::Indent(float indent_w) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); - window->DC.IndentX += (indent_w > 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; } @@ -10847,7 +11049,7 @@ void ImGui::Unindent(float indent_w) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); - window->DC.IndentX -= (indent_w > 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; } diff --git a/imgui.h b/imgui.h index 294f6695..7e309af8 100644 --- a/imgui.h +++ b/imgui.h @@ -73,19 +73,20 @@ typedef unsigned int ImGuiID; // unique ID used by widgets (typically hash typedef unsigned short ImWchar; // character for keyboard input/display typedef void* ImTextureID; // user data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_ -typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_ typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_ -typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ -typedef int ImDrawCornerFlags; // flags: corner flags for AddRect*() etc. // enum ImDrawCornerFlags_ -typedef int ImGuiColorEditFlags; // flags: color edit flags for Color*() // enum ImGuiColorEditFlags_ +typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImDrawCornerFlags; // flags: for ImDrawList::AddRect*() etc. // enum ImDrawCornerFlags_ +typedef int ImGuiColorEditFlags; // flags: for ColorEdit*(), ColorPicker*() // enum ImGuiColorEditFlags_ typedef int ImGuiDragDropFlags; // flags: for *DragDrop*() // enum ImGuiDragDropFlags_ -typedef int ImGuiWindowFlags; // flags: window flags for Begin*() // enum ImGuiWindowFlags_ typedef int ImGuiColumnsFlags; // flags: for *Columns*() // enum ImGuiColumnsFlags_ +typedef int ImGuiComboFlags; // flags: for BeginCombo() // enum ImGuiComboFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() // enum ImGuiHoveredFlags_ typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ -typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(), Collapsing*() // enum ImGuiTreeNodeFlags_ -typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() // enum ImGuiHoveredFlags_ +typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(),CollapsingHeader()// enum ImGuiTreeNodeFlags_ +typedef int ImGuiWindowFlags; // flags: for Begin*() // enum ImGuiWindowFlags_ typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); typedef void (*ImGuiSizeConstraintCallback)(ImGuiSizeConstraintCallbackData* data); #ifdef _MSC_VER @@ -161,8 +162,7 @@ namespace ImGui IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. - IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (enforce the range of scrollbars). set axis to 0.0f to leave it automatic. call before Begin() - IMGUI_API void SetNextWindowContentWidth(float width); // set next window content width (enforce the range of horizontal scrollbar). call before Begin() + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. @@ -180,9 +180,8 @@ namespace ImGui IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] - IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. + IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. - IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it) IMGUI_API ImGuiStorage* GetStateStorage(); @@ -220,8 +219,8 @@ namespace ImGui IMGUI_API void NewLine(); // undo a SameLine() IMGUI_API void Spacing(); // add vertical spacing IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size - IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if >0 - IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if >0 + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) IMGUI_API void EndGroup(); IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position @@ -280,22 +279,27 @@ namespace ImGui // Widgets: Main IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text - IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding IMGUI_API bool Checkbox(const char* label, bool* v); IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); IMGUI_API bool RadioButton(const char* label, bool active); IMGUI_API bool RadioButton(const char* label, int* v, int v_button); - IMGUI_API bool Combo(const char* label, int* current_item, const char* const* items, int items_count, int height_in_items = -1); - IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items = -1); // separate items with \0, end item-list with \0\0 - IMGUI_API bool Combo(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); IMGUI_API void PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL); + // Widgets: Combo Box + // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds) // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound @@ -406,7 +410,7 @@ namespace ImGui IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. - // Logging: all text output from interface is redirected to tty/file/clipboard. By default, tree nodes are automatically opened during logging. + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard @@ -431,6 +435,12 @@ namespace ImGui IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); + // Focus + // (FIXME: Those functions will be reworked after we merge the navigation branch + have a pass at focusing/tabbing features.) + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged) + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window (WIP navigation branch only). Pleaase use instead of SetScrollHere(). + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + // Utilities IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered by mouse (and usable)? IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) @@ -451,6 +461,7 @@ namespace ImGui IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. IMGUI_API float GetTime(); IMGUI_API int GetFrameCount(); + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text IMGUI_API const char* GetStyleColorName(ImGuiCol idx); IMGUI_API ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = +0.0f); // utility to find the closest point the last item bounding rectangle edge. useful to visually link items IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); @@ -523,13 +534,14 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // (WIP) Enable resize from any corners and borders. Your back-end needs to honor the different values of io.MouseCursor set by imgui. + // [Internal] - ImGuiWindowFlags_ChildWindow = 1 << 22, // Don't use! For internal use by BeginChild() - ImGuiWindowFlags_ComboBox = 1 << 23, // Don't use! For internal use by ComboBox() - ImGuiWindowFlags_Tooltip = 1 << 24, // Don't use! For internal use by BeginTooltip() - ImGuiWindowFlags_Popup = 1 << 25, // Don't use! For internal use by BeginPopup() - ImGuiWindowFlags_Modal = 1 << 26, // Don't use! For internal use by BeginPopupModal() - ImGuiWindowFlags_ChildMenu = 1 << 27 // Don't use! For internal use by BeginMenu() + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() }; // Flags for ImGui::InputText() @@ -584,6 +596,17 @@ enum ImGuiSelectableFlags_ ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too }; +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + // Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() enum ImGuiHoveredFlags_ { @@ -763,7 +786,7 @@ enum ImGuiMouseCursor_ ImGuiMouseCursor_Move, // Unused ImGuiMouseCursor_ResizeNS, // Unused ImGuiMouseCursor_ResizeEW, // When hovering over a column - ImGuiMouseCursor_ResizeNESW, // Unused + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window ImGuiMouseCursor_Count_ }; @@ -940,6 +963,7 @@ struct ImGuiIO #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + static inline void SetNextWindowContentWidth(float width) { ImGui::SetNextWindowContentSize(ImVec2(width, 0.0f)); } // OBSOLETE 1.53+ (nb: original version preserved last Y value set by SetNextWindowContentSize()) static inline bool IsRootWindowOrAnyChildHovered(ImGuiHoveredFlags flags = 0) { return IsItemHovered(flags | ImGuiHoveredFlags_FlattenChilds); } // OBSOLETE 1.53+ use flags directly bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // OBSOLETE 1.52+. use SetNextWindowSize() instead if you want to set a window size. static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } // OBSOLETE 1.52+ @@ -949,9 +973,6 @@ namespace ImGui static inline bool IsMouseHoveringAnyWindow() { return IsAnyWindowHovered(); } // OBSOLETE 1.51+ static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } // OBSOLETE 1.51+ static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } // OBSOLETE 1.49+ - static inline ImFont* GetWindowFont() { return GetFont(); } // OBSOLETE 1.48+ - static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETE 1.48+ - static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETE 1.42+ } #endif @@ -1599,6 +1620,7 @@ struct ImFont IMGUI_API void SetFallbackChar(ImWchar c); float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c74df600..589c65bd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -80,7 +80,7 @@ static void ShowExampleAppLongText(bool* p_open); static void ShowExampleAppAutoResize(bool* p_open); static void ShowExampleAppConstrainedResize(bool* p_open); static void ShowExampleAppFixedOverlay(bool* p_open); -static void ShowExampleAppManipulatingWindowTitle(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); static void ShowExampleAppMainMenuBar(); static void ShowExampleMenuFile(); @@ -133,34 +133,34 @@ void ImGui::ShowTestWindow(bool* p_open) static bool show_app_auto_resize = false; static bool show_app_constrained_resize = false; static bool show_app_fixed_overlay = false; - static bool show_app_manipulating_window_title = false; + static bool show_app_window_titles = false; static bool show_app_custom_rendering = false; static bool show_app_style_editor = false; static bool show_app_metrics = false; static bool show_app_about = false; - if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); - if (show_app_console) ShowExampleAppConsole(&show_app_console); - if (show_app_log) ShowExampleAppLog(&show_app_log); - if (show_app_layout) ShowExampleAppLayout(&show_app_layout); - if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); - if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); - if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); - if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); - if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); - if (show_app_manipulating_window_title) ShowExampleAppManipulatingWindowTitle(&show_app_manipulating_window_title); - if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); - - if (show_app_metrics) ImGui::ShowMetricsWindow(&show_app_metrics); - if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } if (show_app_about) { - ImGui::Begin("About ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); ImGui::Text("dear imgui, %s", ImGui::GetVersion()); ImGui::Separator(); - ImGui::Text("By Omar Cornut and all github contributors."); - ImGui::Text("ImGui is licensed under the MIT License, see LICENSE for more information."); + ImGui::Text("By Omar Cornut and all dear imgui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); ImGui::End(); } @@ -170,6 +170,7 @@ void ImGui::ShowTestWindow(bool* p_open) static bool no_move = false; static bool no_resize = false; static bool no_collapse = false; + static bool no_close = false; // Demonstrate the various window flags. Typically you would just use the default. ImGuiWindowFlags window_flags = 0; @@ -179,6 +180,8 @@ void ImGui::ShowTestWindow(bool* p_open) if (no_move) window_flags |= ImGuiWindowFlags_NoMove; if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver); if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) { @@ -211,7 +214,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); - ImGui::MenuItem("Manipulating window title", NULL, &show_app_manipulating_window_title); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); ImGui::EndMenu(); } @@ -219,7 +222,7 @@ void ImGui::ShowTestWindow(bool* p_open) { ImGui::MenuItem("Metrics", NULL, &show_app_metrics); ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); - ImGui::MenuItem("About ImGui", NULL, &show_app_about); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); ImGui::EndMenu(); } ImGui::EndMenuBar(); @@ -241,6 +244,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); ImGui::Checkbox("No collapse", &no_collapse); + ImGui::Checkbox("No close", &no_close); if (ImGui::TreeNode("Style")) { @@ -248,7 +252,7 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::TreePop(); } - if (ImGui::TreeNode("Logging")) + if (ImGui::TreeNode("Capture/Logging")) { ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. You can also call ImGui::LogText() to output directly to the log without a visual output."); ImGui::LogButtons(); @@ -318,7 +322,7 @@ void ImGui::ShowTestWindow(bool* p_open) static int item = 1; ImGui::Combo("combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); // Combo using values packed in a single constant string (for really quick combo) - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK" }; + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO", "PPPP", "QQQQQQQQQQ", "RRR", "SSSS" }; static int item2 = -1; ImGui::Combo("combo scroll", &item2, items, IM_ARRAYSIZE(items)); // Combo using proper array. You can also pass a callback to retrieve array value, no need to create/copy an array just for that. @@ -1664,7 +1668,7 @@ void ImGui::ShowTestWindow(bool* p_open) if (ImGui::TreeNode("Horizontal Scrolling")) { - ImGui::SetNextWindowContentWidth(1500); + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); ImGui::Columns(10); int ITEMS_COUNT = 2000; @@ -1827,6 +1831,8 @@ void ImGui::ShowTestWindow(bool* p_open) if (ImGui::TreeNode("Dragging")) { ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + ImGui::Text("IsMouseDragging(%d) = %d", button, ImGui::IsMouseDragging(button)); ImGui::Button("Drag Me"); if (ImGui::IsItemActive()) { @@ -1845,12 +1851,16 @@ void ImGui::ShowTestWindow(bool* p_open) if (ImGui::TreeNode("Mouse cursors")) { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_Count_); + + ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); ImGui::Text("Hover to see mouse cursors:"); ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); for (int i = 0; i < ImGuiMouseCursor_Count_; i++) { char label[32]; - sprintf(label, "Mouse cursor %d", i); + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); ImGui::Bullet(); ImGui::Selectable(label, false); if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(i); @@ -2276,8 +2286,8 @@ static void ShowExampleAppFixedOverlay(bool* p_open) } // Demonstrate using "##" and "###" in identifiers to manipulate ID generation. -// Read section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." about ID. -static void ShowExampleAppManipulatingWindowTitle(bool*) +// This apply to regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. +static void ShowExampleAppWindowTitles(bool*) { // By default, Windows are uniquely identified by their title. // You can use the "##" and "###" markers to manipulate the display/ID. @@ -2295,7 +2305,7 @@ static void ShowExampleAppManipulatingWindowTitle(bool*) // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" char buf[128]; - sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime()/0.25f)&3], rand()); + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime()/0.25f)&3], ImGui::GetFrameCount()); ImGui::SetNextWindowPos(ImVec2(100,300), ImGuiCond_FirstUseEver); ImGui::Begin(buf); ImGui::Text("This window has a changing title."); @@ -2781,7 +2791,7 @@ static void ShowExampleAppLog(bool* p_open) if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) { const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; - log.AddLog("[%s] Hello, time is %.1f, rand() %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, (int)rand()); + log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); last_time = time; } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2c937e59..7a5bb2de 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -22,6 +22,9 @@ #if !defined(alloca) #ifdef _WIN32 #include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif #elif defined(__GLIBC__) || defined(__sun) #include // alloca #else diff --git a/imgui_internal.h b/imgui_internal.h index 1bf61084..736f1acd 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -286,6 +286,7 @@ struct IMGUI_API ImRect void Translate(const ImVec2& v) { Min.x += v.x; Min.y += v.y; Max.x += v.x; Max.y += v.y; } void ClipWith(const ImRect& clip) { if (Min.x < clip.Min.x) Min.x = clip.Min.x; if (Min.y < clip.Min.y) Min.y = clip.Min.y; if (Max.x > clip.Max.x) Max.x = clip.Max.x; if (Max.y > clip.Max.y) Max.y = clip.Max.y; } void Floor() { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; } + void FixInverted() { if (Min.x > Max.x) ImSwap(Min.x, Max.x); if (Min.y > Max.y) ImSwap(Min.y, Max.y); } bool IsFinite() const { return Min.x != FLT_MAX; } ImVec2 GetClosestPoint(ImVec2 p, bool on_edge) const { @@ -642,7 +643,7 @@ struct IMGUI_API ImGuiDrawContext ImVec2 CursorPos; ImVec2 CursorPosPrevLine; ImVec2 CursorStartPos; - ImVec2 CursorMaxPos; // Implicitly calculate the size of our contents, always extending. Saved into window->SizeContents at the end of the frame + ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame float CurrentLineHeight; float CurrentLineTextBaseOffset; float PrevLineHeight; @@ -727,7 +728,8 @@ struct IMGUI_API ImGuiWindow ImVec2 Pos; // Position rounded-up to nearest pixel ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) ImVec2 SizeFull; // Size when non collapsed - ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame + ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars. + ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc. ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() ImRect ContentsRegionRect; // Maximum visible content position in window coordinates. ~~ (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis ImVec2 WindowPadding; // Window padding at the time of begin. @@ -751,7 +753,7 @@ struct IMGUI_API ImGuiWindow int AutoFitFramesX, AutoFitFramesY; bool AutoFitOnlyGrows; int AutoFitChildAxises; - int AutoPosLastDirection; + ImGuiDir AutoPosLastDirection; int HiddenFrames; ImGuiCond SetWindowPosAllowFlags; // store condition flags for next SetWindowPos() call. ImGuiCond SetWindowSizeAllowFlags; // store condition flags for next SetWindowSize() call. @@ -873,10 +875,6 @@ namespace ImGui IMGUI_API void EndColumns(); // close columns IMGUI_API void PushColumnClipRect(int column_index = -1); - // FIXME-WIP: New Combo API - IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImVec2 popup_size = ImVec2(0.0f,0.0f)); - IMGUI_API void EndCombo(); - // NB: All position are in absolute pixels coordinates (never using window coordinates internally) // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true);