From 6e0ee6ff03324b58b20001f25d18d40dfb921a91 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Dec 2025 20:15:25 +0100 Subject: [PATCH 1/9] Fonts: removed misleading SizePixels >= 0.0f test and stbtt_ScaleForMappingEmToPixels() call in ImGui_ImplStbTrueType_FontSrcInit(). (#8857) Logic was picked in 9a9712807e while extracting code from stbtt_PackFontRangesGatherRects(), but ScaleForMappingEmToPixels() was actually never called: we assert against negative SizePixels since 2015. --- imgui_draw.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 88ad02966..b01e57358 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4596,10 +4596,7 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* if (src->MergeMode && src->SizePixels == 0.0f) src->SizePixels = ref_size; - if (src->SizePixels >= 0.0f) - bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); - else - bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); if (src->MergeMode && src->SizePixels != 0.0f && ref_size != 0.0f) bd_font_data->ScaleFactor *= src->SizePixels / ref_size; // FIXME-NEWATLAS: Should tidy up that a bit From d1e262ad555e0410a792cae973b35baf8933e88b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Dec 2025 15:57:31 +0100 Subject: [PATCH 2/9] Internals: refactor RenderRectFilledRangeH() into RenderRectFilledInRangeH() to take absolute coordinates instead of normalized ones. Amend 01d4bf299a (#1296) --- imgui.h | 2 +- imgui_demo.cpp | 13 +++++++------ imgui_draw.cpp | 14 +++++++------- imgui_internal.h | 2 +- imgui_widgets.cpp | 7 +++++-- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/imgui.h b/imgui.h index facb99975..788061cd7 100644 --- a/imgui.h +++ b/imgui.h @@ -1927,7 +1927,7 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, - ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. + ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from legacy API (obsoleted 2020-08) that has got miscast to this enum, and will trigger an assert if needed. }; // Identify a mouse button. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 1d914b371..a131680bd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2045,10 +2045,12 @@ static void DemoWindowWidgetsProgressBars() if (ImGui::TreeNode("Progress Bars")) { // Animate a simple progress bar - static float progress = 0.0f, progress_dir = 1.0f; - progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; - if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } - if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + static float progress_accum = 0.0f, progress_dir = 1.0f; + progress_accum += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress_accum >= +1.1f) { progress_accum = +1.1f; progress_dir *= -1.0f; } + if (progress_accum <= -0.1f) { progress_accum = -0.1f; progress_dir *= -1.0f; } + + const float progress = IM_CLAMP(progress_accum, 0.0f, 1.0f); // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. @@ -2056,9 +2058,8 @@ static void DemoWindowWidgetsProgressBars() ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::Text("Progress Bar"); - float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f); char buf[32]; - sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); + sprintf(buf, "%d/%d", (int)(progress * 1753), 1753); ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b01e57358..64e88fb55 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5830,7 +5830,7 @@ begin: // - RenderBullet() // - RenderCheckMark() // - RenderArrowPointingAt() -// - RenderRectFilledRangeH() +// - RenderRectFilledInRangeH() // - RenderRectFilledWithHole() //----------------------------------------------------------------------------- // Function in need of a redesign (legacy mess) @@ -5913,15 +5913,15 @@ static inline float ImAcos01(float x) } // FIXME: Cleanup and move code to ImDrawList. -void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +// - Before 2025-12-04: RenderRectFilledRangeH() with 'float x_start_norm, float x_end_norm` <- normalized +// - After 2025-12-04: RenderRectFilledInRangeH() with 'float x1, float x2' <- absolute coords!! +void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding) { - if (x_end_norm == x_start_norm) + if (x1 == x2) return; - if (x_start_norm > x_end_norm) - ImSwap(x_start_norm, x_end_norm); - ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); - ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + ImVec2 p0 = ImVec2(x1, rect.Min.y); + ImVec2 p1 = ImVec2(x2, rect.Max.y); if (rounding == 0.0f) { draw_list->AddRectFilled(p0, p1, col, 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index 318d013d4..2da881038 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3587,7 +3587,7 @@ namespace ImGui IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); - IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); // Widgets: Text diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2e05af1c6..85e77f041 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1440,7 +1440,10 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over // Render RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); - RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); + float fill_x0 = ImLerp(bb.Min.x, bb.Max.x, fill_n0); + float fill_x1 = ImLerp(bb.Min.x, bb.Max.x, fill_n1); + if (fill_x0 < fill_x1) + RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_x0, fill_x1, style.FrameRounding); // Default displaying the fraction as percentage string, but user can override it // Don't display text for indeterminate bars by default @@ -1456,7 +1459,7 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over ImVec2 overlay_size = CalcTextSize(overlay, NULL); if (overlay_size.x > 0.0f) { - float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x; + float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : fill_x1 + style.ItemSpacing.x; RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); } } From d71091a95761e71262162150a3c164848d65c7a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Dec 2025 16:27:54 +0100 Subject: [PATCH 3/9] Internals: amend RenderRectFilledRangeH() to avoid shadowed variables. --- imgui_draw.cpp | 8 ++++---- imgui_internal.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 64e88fb55..bee1503de 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5915,13 +5915,13 @@ static inline float ImAcos01(float x) // FIXME: Cleanup and move code to ImDrawList. // - Before 2025-12-04: RenderRectFilledRangeH() with 'float x_start_norm, float x_end_norm` <- normalized // - After 2025-12-04: RenderRectFilledInRangeH() with 'float x1, float x2' <- absolute coords!! -void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding) +void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float fill_x0, float fill_x1, float rounding) { - if (x1 == x2) + if (fill_x0 > fill_x1) return; - ImVec2 p0 = ImVec2(x1, rect.Min.y); - ImVec2 p1 = ImVec2(x2, rect.Max.y); + ImVec2 p0 = ImVec2(fill_x0, rect.Min.y); + ImVec2 p1 = ImVec2(fill_x1, rect.Max.y); if (rounding == 0.0f) { draw_list->AddRectFilled(p0, p1, col, 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index 2da881038..c1fcfbaf4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3587,7 +3587,7 @@ namespace ImGui IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); - IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding); + IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float fill_x0, float fill_x1, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); // Widgets: Text From fee06a92ea7a21f3e5ebf77a30a3564bcb85cb43 Mon Sep 17 00:00:00 2001 From: achabense <60953653+achabense@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:36:16 +0800 Subject: [PATCH 4/9] Text: fixed word-wrapping function reading from *text_end when passed a string range. (#9107) Likely caused by 4d4889bf1b (#5720) --- docs/CHANGELOG.txt | 3 +++ imgui_draw.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 14ecb0dd0..ecd57ffbb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,9 @@ Other Changes: between hard minimum window size and table minimum size). - Fixed an issue where submitting non-integer row heights would eventually advance table parent layout by +0/+1 depending on its visibility. +- Text: + - Fixed low-level word-wrapping function reading from *text_end when passed + a string range. (#9107) [@achabense] - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Debug Tools: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index bee1503de..1b83fea92 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5364,7 +5364,7 @@ const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_e if ((flags & ImDrawTextFlags_WrapKeepBlanks) == 0) while (text < text_end && ImCharIsBlankA(*text)) text++; - if (*text == '\n') + if (text < text_end && *text == '\n') text++; return text; } From e2c49530cb677fa68ec56d5292575b861adc3596 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 13:42:15 +0100 Subject: [PATCH 5/9] ImDrawList: harden/clarify static asserts about ImDrawCmd / ImDrawCmdHeader layout. --- imgui_draw.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1b83fea92..88e897ac3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -444,10 +444,13 @@ void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) // In the majority of cases, you would want to call PushClipRect() and PushTexture() after this. void ImDrawList::_ResetForNewFrame() { - // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. + // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory to match ImDrawCmdHeader. IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0); IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == sizeof(ImVec4)); IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureRef)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == offsetof(ImDrawCmdHeader, ClipRect)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == offsetof(ImDrawCmdHeader, TexRef)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == offsetof(ImDrawCmdHeader, VtxOffset)); if (_Splitter._Count > 1) _Splitter.Merge(this); From 9d4fafa6713fdbf8b6424a8b88c5eec1dba953a8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 14:16:14 +0100 Subject: [PATCH 6/9] Nav: removed an unnecessary FindWindowByName() call. Amend c7016c25e8. It's not clear to me why it was committed like that, maybe a leftover of wip code which used the pointer before Begin(). --- imgui.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e21353a62..3fe059b6e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -14555,13 +14555,12 @@ void ImGui::NavUpdateWindowingOverlay() if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) return; - if (g.NavWindowingListWindow == NULL) - g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay"); const ImGuiViewport* viewport = GetMainViewport(); SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); Begin("##NavWindowingOverlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + g.NavWindowingListWindow = g.CurrentWindow; if (g.ContextName[0] != 0) SeparatorText(g.ContextName); for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) From ded52c71d983114a4efa9fef8ef31393998689e1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 15:07:44 +0100 Subject: [PATCH 7/9] Debug Log: can output to debugger. Added ImGuiDebugLogFlags_OutputToDebugger. --- docs/CHANGELOG.txt | 1 + imgui.cpp | 14 ++++++++++++-- imgui_internal.h | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ecd57ffbb..595072275 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -72,6 +72,7 @@ Other Changes: - Debug Tools: - Debug Log: fixed incorrectly printing characters in IO log when submitting non-ASCII values to io.AddInputCharacter(). (#9099) + - Debug Log: can output to debugger on Windows. (#5855) - Backends: - SDL_GPU3: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires application calling diff --git a/imgui.cpp b/imgui.cpp index 3fe059b6e..3c87bd87c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -17560,14 +17560,23 @@ void ImGui::DebugLogV(const char* fmt, va_list args) g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); g.DebugLogBuf.appendfv(fmt, args); g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size()); + + const char* str = g.DebugLogBuf.begin() + old_size; if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY) - IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size); + IMGUI_DEBUG_PRINTF("%s", str); +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) + if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToDebugger) + { + ::OutputDebugStringA("[imgui] "); + ::OutputDebugStringA(str); + } +#endif #ifdef IMGUI_ENABLE_TEST_ENGINE // IMGUI_TEST_ENGINE_LOG() adds a trailing \n automatically const int new_size = g.DebugLogBuf.size(); const bool trailing_carriage_return = (g.DebugLogBuf[new_size - 1] == '\n'); if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTestEngine) - IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0), g.DebugLogBuf.begin() + old_size); + IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0), str); #endif } @@ -17647,6 +17656,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) if (BeginPopup("Outputs")) { CheckboxFlags("OutputToTTY", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTTY); + CheckboxFlags("OutputToDebugger", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToDebugger); #ifndef IMGUI_ENABLE_TEST_ENGINE BeginDisabled(); #endif diff --git a/imgui_internal.h b/imgui_internal.h index c1fcfbaf4..23680045d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2078,7 +2078,8 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY - ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine + ImGuiDebugLogFlags_OutputToDebugger = 1 << 21, // Also send output to Debugger Console [Windows only] + ImGuiDebugLogFlags_OutputToTestEngine = 1 << 22, // Also send output to Dear ImGui Test Engine }; struct ImGuiDebugAllocEntry From a7ecbcdeba912ba8e945878edf6f0c18164ce1ad Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Dec 2025 16:45:45 +0100 Subject: [PATCH 8/9] Shuffle ImGuiColorEditFlags flag values. ImGuiColorEditFlags_AlphaOpaque, ImGuiColorEditFlags_AlphaNoBg, ImGuiColorEditFlags_AlphaPreviewHalf, ImGuiColorEditFlags_AlphaBar. --- imgui.h | 8 ++++---- imgui_demo.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index 788061cd7..8f60c2fc8 100644 --- a/imgui.h +++ b/imgui.h @@ -1878,12 +1878,12 @@ enum ImGuiColorEditFlags_ // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. // - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. // - The new flags may be combined better and allow finer controls. - ImGuiColorEditFlags_AlphaOpaque = 1 << 11, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. - ImGuiColorEditFlags_AlphaNoBg = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. - ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. + ImGuiColorEditFlags_AlphaOpaque = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. + ImGuiColorEditFlags_AlphaNoBg = 1 << 13, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 14, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. // User Options (right-click on widget to change some of them). - ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaBar = 1 << 18, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). ImGuiColorEditFlags_DisplayRGB = 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex. ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // " diff --git a/imgui_demo.cpp b/imgui_demo.cpp index a131680bd..294efd748 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1726,7 +1726,7 @@ static void DemoWindowWidgetsDragsAndSliders() // Sliders static float slider_f = 0.5f; static int slider_i = 50; - const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround; + const ImGuiSliderFlags flags_for_sliders = (flags & ~ImGuiSliderFlags_WrapAround); ImGui::Text("Underlying float value: %f", slider_f); ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); From fa4b47c5e2b53dba5093384562440bd9a6bba2a0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 16:29:15 +0100 Subject: [PATCH 9/9] Added RGBA color markers to ColorEdit3/ColorEdit4 + opt-in ImGuiSliderFlags_ColorMarkers for Drags/Sliders. + Added ImGuiColorEditFlags_NoColorMarkers + Added style.ColorMarkerSize. --- docs/CHANGELOG.txt | 8 ++++++++ imgui.cpp | 14 ++++++++++++++ imgui.h | 9 ++++++++- imgui_demo.cpp | 10 +++++++++- imgui_internal.h | 1 + imgui_widgets.cpp | 33 ++++++++++++++++++++++++++------- 6 files changed, 66 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 595072275..894baad88 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,14 @@ Other Changes: between hard minimum window size and table minimum size). - Fixed an issue where submitting non-integer row heights would eventually advance table parent layout by +0/+1 depending on its visibility. +- ColorEdit: + - Added R/G/B/A color markers next to each component (enabled by default). + - Added ImGuiColorEditFlags_NoColorMarkers to disable them. + - Added style.ColorMarkerSize to configure width of color component markers. +- Sliders, Drags: + - Added ImGuiSliderFlags_ColorMarkers to opt-in adding R/G/B/A color markers + next to each components, in multi-components functions. + - Added a way to select a specific marker color. - Text: - Fixed low-level word-wrapping function reading from *text_end when passed a string range. (#9107) [@achabense] diff --git a/imgui.cpp b/imgui.cpp index 3c87bd87c..a321d357e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1453,6 +1453,7 @@ ImGuiStyle::ImGuiStyle() DragDropTargetRounding = 0.0f; // Radius of the drag and drop target frame. DragDropTargetBorderSize = 2.0f; // Thickness of the drag and drop target border. DragDropTargetPadding = 3.0f; // Size to expand the drag and drop target from actual target item size. + ColorMarkerSize = 3.0f; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. @@ -1520,6 +1521,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) DragDropTargetRounding = ImTrunc(DragDropTargetRounding * scale_factor); DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor); DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor); + ColorMarkerSize = ImTrunc(ColorMarkerSize * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor); @@ -3923,6 +3925,18 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) } } +// FIXME: Might move those to style if there is a real need. +static const ImU32 GColorMarkers[4] = { IM_COL32(240,20,20,255), IM_COL32(20,240,20,255), IM_COL32(20,20,240,255), IM_COL32(140,140,140,255) }; + +void ImGui::RenderColorComponentMarker(int component_idx, const ImRect& bb, float rounding) +{ + if (!(component_idx >= 0 && component_idx < 4) || (bb.Min.x + 1 >= bb.Max.x)) + return; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(GColorMarkers[component_idx]), bb.Min.x, ImMin(bb.Min.x + g.Style.ColorMarkerSize, bb.Max.x), rounding); +} + void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags) { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 8f60c2fc8..8b359d57f 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.92.6 WIP" -#define IMGUI_VERSION_NUM 19252 +#define IMGUI_VERSION_NUM 19253 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1873,6 +1873,7 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default) + ImGuiColorEditFlags_NoColorMarkers = 1 << 11, // // ColorEdit: disable rendering R/G/B/A color marker. May also be disabled globally by setting style.ColorMarkerSize = 0. // Alpha preview // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. @@ -1927,6 +1928,11 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, + + // Color Markers + ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. + ImGuiSliderFlags_ColorMarkersIndexShift_ = 13, // [Internal] DragScalar(), SliderScalar(): Pass ([0..3] << ImGuiSliderFlags_ColorMarkersIndexShift_) along with ImGuiSliderFlags_ColorMarkers to select an individual R/G/B/A color. + ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from legacy API (obsoleted 2020-08) that has got miscast to this enum, and will trigger an assert if needed. }; @@ -2314,6 +2320,7 @@ struct ImGuiStyle float DragDropTargetRounding; // Radius of the drag and drop target frame. float DragDropTargetBorderSize; // Thickness of the drag and drop target border. float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size. + float ColorMarkerSize; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 294efd748..9dd4c5591 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1096,8 +1096,9 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaOpaque", &base_flags, ImGuiColorEditFlags_AlphaOpaque); ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaNoBg", &base_flags, ImGuiColorEditFlags_AlphaNoBg); ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaPreviewHalf", &base_flags, ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::CheckboxFlags("ImGuiColorEditFlags_NoDragDrop", &base_flags, ImGuiColorEditFlags_NoDragDrop); ImGui::CheckboxFlags("ImGuiColorEditFlags_NoOptions", &base_flags, ImGuiColorEditFlags_NoOptions); ImGui::SameLine(); HelpMarker("Right-click on the individual color widget to show options."); + ImGui::CheckboxFlags("ImGuiColorEditFlags_NoDragDrop", &base_flags, ImGuiColorEditFlags_NoDragDrop); + ImGui::CheckboxFlags("ImGuiColorEditFlags_NoColorMarkers", &base_flags, ImGuiColorEditFlags_NoColorMarkers); ImGui::CheckboxFlags("ImGuiColorEditFlags_HDR", &base_flags, ImGuiColorEditFlags_HDR); ImGui::SameLine(); HelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit"); @@ -1710,9 +1711,12 @@ static void DemoWindowWidgetsDragsAndSliders() ImGui::SameLine(); HelpMarker("Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic."); ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround); ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)"); + ImGui::CheckboxFlags("ImGuiSliderFlags_ColorMarkers", &flags, ImGuiSliderFlags_ColorMarkers); + //ImGui::CheckboxFlags("ImGuiSliderFlags_ColorMarkersG", &flags, 1 << ImGuiSliderFlags_ColorMarkersIndexShift_); // Not explicitly documented but possible. // Drags static float drag_f = 0.5f; + static float drag_f4[4]; static int drag_i = 50; ImGui::Text("Underlying float value: %f", drag_f); ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags); @@ -1722,14 +1726,17 @@ static void DemoWindowWidgetsDragsAndSliders() //ImGui::DragFloat("DragFloat (0 -> 0)", &drag_f, 0.005f, 0.0f, 0.0f, "%.3f", flags); // To test ClampZeroRange //ImGui::DragFloat("DragFloat (100 -> 100)", &drag_f, 0.005f, 100.0f, 100.0f, "%.3f", flags); ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags); + ImGui::DragFloat4("DragFloat4 (0 -> 1)", drag_f4, 0.005f, 0.0f, 1.0f, "%.3f", flags); // Multi-component item, mostly here to document the effect of ImGuiSliderFlags_ColorMarkers. // Sliders static float slider_f = 0.5f; + static float slider_f4[4]; static int slider_i = 50; const ImGuiSliderFlags flags_for_sliders = (flags & ~ImGuiSliderFlags_WrapAround); ImGui::Text("Underlying float value: %f", slider_f); ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); + ImGui::SliderFloat4("SliderFloat4 (0 -> 1)", slider_f4, 0.0f, 1.0f, "%.3f", flags); // Multi-component item, mostly here to document the effect of ImGuiSliderFlags_ColorMarkers. ImGui::TreePop(); } @@ -8444,6 +8451,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1); SeparatorText("Widgets"); + SliderFloat("ColorMarkerSize", &style.ColorMarkerSize, 0.0f, 8.0f, "%.0f"); Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); diff --git a/imgui_internal.h b/imgui_internal.h index 23680045d..849e48897 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3575,6 +3575,7 @@ namespace ImGui IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorComponentMarker(int component_idx, const ImRect& bb, float rounding); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None); // Navigation highlight #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 85e77f041..086a64c8d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2756,7 +2756,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding); + if ((flags & ImGuiSliderFlags_ColorMarkers) && style.ColorMarkerSize > 0.0f) + RenderColorComponentMarker(flags >> ImGuiSliderFlags_ColorMarkersIndexShift_, frame_bb, style.FrameRounding); + RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding); // Drag behavior const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags); @@ -2794,7 +2797,12 @@ bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); - value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); + + ImGuiSliderFlags flags_for_component = flags; + if ((flags & ImGuiSliderFlags_ColorMarkers) && (flags & (0x03 << ImGuiSliderFlags_ColorMarkersIndexShift_)) == 0 && (i < 4)) + flags_for_component |= (i << ImGuiSliderFlags_ColorMarkersIndexShift_); + + value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags_for_component); PopID(); PopItemWidth(); p_data = (void*)((char*)p_data + type_size); @@ -3342,7 +3350,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding); + if ((flags & ImGuiSliderFlags_ColorMarkers) && style.ColorMarkerSize > 0.0f) + RenderColorComponentMarker(flags >> ImGuiSliderFlags_ColorMarkersIndexShift_, frame_bb, style.FrameRounding); + RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding); // Slider behavior ImRect grab_bb; @@ -3386,7 +3397,12 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); - value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); + + ImGuiSliderFlags flags_for_component = flags; + if ((flags & ImGuiSliderFlags_ColorMarkers) && (flags & (0x03 << ImGuiSliderFlags_ColorMarkersIndexShift_ )) == 0 && (i < 4)) + flags_for_component |= (i << ImGuiSliderFlags_ColorMarkersIndexShift_); + + value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags_for_component); PopID(); PopItemWidth(); v = (void*)((char*)v + type_size); @@ -5784,8 +5800,10 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag { // RGB/HSV 0..255 Sliders const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1); + const float w_per_component = IM_TRUNC(w_items / components); + const bool draw_color_marker = (flags & (ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_NoColorMarkers)) == 0; - const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const bool hide_prefix = draw_color_marker || (w_per_component <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); static const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; static const char* fmt_table_int[3][4] = { @@ -5811,14 +5829,15 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag prev_split = next_split; // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. + ImGuiSliderFlags drag_flags = draw_color_marker ? (ImGuiSliderFlags_ColorMarkers | (n << ImGuiSliderFlags_ColorMarkersIndexShift_)) : ImGuiSliderFlags_None; if (flags & ImGuiColorEditFlags_Float) { - value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n], drag_flags); value_changed_as_float |= value_changed; } else { - value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n], drag_flags); } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);