Merge branch 'master' into docking

# Conflicts:
#	backends/imgui_impl_dx9.cpp
#	backends/imgui_impl_metal.mm
#	backends/imgui_impl_opengl2.cpp
#	backends/imgui_impl_opengl3.cpp
#	backends/imgui_impl_sdlgpu3.cpp
#	imgui.cpp
#	imgui_internal.h
#	imgui_widgets.cpp
This commit is contained in:
ocornut
2026-03-20 16:37:35 +01:00
13 changed files with 265 additions and 195 deletions

View File

@@ -19,6 +19,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
@@ -448,14 +449,15 @@ void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID)
{
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
if (tex->TexID != ImTextureID_Invalid)
if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID)
{
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
tex->SetStatus(ImTextureStatus_Destroyed);
}
}

View File

@@ -18,6 +18,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Metal: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture().
// 2025-02-03: Metal: Crash fix. (#8367)
@@ -319,7 +320,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer>
[commandEncoder setScissorRect:scissorRect];
// Bind texture, Draw
if (ImTextureID tex_id = pcmd->GetTexID())
ImTextureID tex_id = pcmd->GetTexID();
if (tex_id != ImTextureID_Invalid)
[commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(void*)(intptr_t)(tex_id) atIndex:0];
[commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0];

View File

@@ -27,6 +27,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures. (#8802)
// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture().

View File

@@ -25,6 +25,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295)
// 2025-12-11: OpenGL: Fixed embedded loader multiple init/shutdown cycles broken on some platforms. (#8792, #9112)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792)

View File

@@ -24,6 +24,7 @@
// CHANGELOG
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplSDLGPU3_DestroyTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2026-02-25: Removed unnecessary call to SDL_WaitForGPUIdle when releasing vertex/index buffers. (#9262)
// 2025-11-26: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL.
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
@@ -312,8 +313,9 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe
static void ImGui_ImplSDLGPU3_DestroyTexture(ImTextureData* tex)
{
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
if (SDL_GPUTexture* raw_tex = (SDL_GPUTexture*)(intptr_t)tex->GetTexID())
SDL_ReleaseGPUTexture(bd->InitInfo.Device, raw_tex);
if (tex->GetTexID() != ImTextureID_Invalid)
if (SDL_GPUTexture* raw_tex = (SDL_GPUTexture*)(intptr_t)tex->GetTexID())
SDL_ReleaseGPUTexture(bd->InitInfo.Device, raw_tex);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);

View File

@@ -26,6 +26,7 @@
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer2_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer2_CreateFontsTexture() and ImGui_ImplSDLRenderer2_DestroyFontsTexture().
// 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color.
@@ -269,8 +270,9 @@ void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex)
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID)
SDL_DestroyTexture(sdl_texture);
if (tex->TexID != ImTextureID_Invalid)
if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID)
SDL_DestroyTexture(sdl_texture);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);

View File

@@ -26,6 +26,7 @@
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture().
// 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color.
@@ -285,8 +286,9 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex)
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID)
SDL_DestroyTexture(sdl_texture);
if (tex->TexID != ImTextureID_Invalid)
if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID)
SDL_DestroyTexture(sdl_texture);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);

View File

@@ -41,16 +41,6 @@ HOW TO UPDATE?
Breaking Changes:
- Changed default ImTextureID_Invalid value to -1 instead of 0 if not #define-d.
(#9293, #8745, #8465, #7090)
- It seems like a better default since it will work with backends storing
indices or memory offsets inside ImTextureID, where 0 might be a valid value.
- If this is causing problem with e.g your custom ImTextureID definition, you can
add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub.
- If you have hardcoded e.g. 'if (tex_id == 0)' checks they should be updated.
e.g. OpenGL2, OpenGL3 and SDLRenderer3 backends incorrectly had 'IM_ASSERT(tex->TexID == 0)'
lines which were replaced with 'IM_ASSERT(tex->TexID == ImTextureID_Invalid)'.
If you have copied or forked backends consider fixing locally. (#9295)
- Separator(): fixed a legacy quirk where Separator() was submitting a zero-height
item for layout purpose, even though it draws a 1-pixel separator.
The fix could affect code e.g. computing height from multiple widgets in order to
@@ -63,6 +53,8 @@ Breaking Changes:
BeginChild("ScrollingRegion", { 0, -footer_height });
When such idiom was used and assuming zero-height Separator, it is likely that
in 1.92.7 the resulting window will have unexpected 1 pixel scrolling range.
- Multi-Select: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto.
Kept inline redirection enum (will obsolete).
- Combo(), ListBox(): commented out legacy signatures which were obsoleted in 1.90
(Nov 2023), when the getter callback type was changed from:
getter type: bool (*getter)(void* user_data, int idx, const char** out_text)
@@ -87,6 +79,19 @@ Other Changes:
signals to be emitted the same way regardless of that setting. (#9001, #9115)
- Fixed a glitch when using ImGuiInputTextFlags_ElideLeft where the local x offset
would be incorrect during the deactivation frame. (#9298)
- Fixed a crash introduced in 1.92.6 when handling ImGuiInputTextFlags_CallbackResize
in certain situations. (#9174)
- Fixed selection highlight Y1 offset being very slightly off (since 1.92.3). (#9311) [@v-ein]
- InputTextMultiline: fixed an issue introduced in 1.92.3 where line count calculated
for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is
disabled and the text buffer ends with '\n'. Fixed a similar issue related to clipping
large amount of text.
- InputTextMultiline: avoid going through reactivation code and fixed losing revert value
when activating scrollbar.
- InputTextMultiline: fixed an issue where edit buffer wouldn't be reapplied to back
buffer on the IsItemDeactivatedAfterEdit() frame. This could create issues when
using the idiom of not applying edits before IsItemDeactivatedAfterEdit().
(#9308, #8915, #8273)
- Style:
- Border sizes are now scaled (and rounded) by ScaleAllSizes().
- When using large values with ScallAllSizes(), the following items thickness
@@ -107,6 +112,10 @@ Other Changes:
BeginPopupContextItem(), BeginPopupContextWindow() or OpenPopupOnItemClick().
(#8803, #9270) [@exelix11, @ocornut]
- Popups: pressing North button (PS4/PS5 triangle, SwitchX, Xbox Y) also open popups menus.
- Multi-Select:
- Added ImGuiMultiSelectFlags_SelectOnClickAlways mode (rarely used).
This prevents Drag and Drop of multiple items, but it allows to start a new Box-Selection
from inside an existing selection (Excel style). (#9307, #1861)
- Clipper:
- Clear `DisplayStart`/`DisplayEnd` fields when `Step()` returns false.
- Added `UserIndex` helper storage. This is solely a convenience for cases where
@@ -115,10 +124,22 @@ Other Changes:
- Implemented a custom tweak to extend hit-testing bounding box when window is sitting
at the edge of a viewport (e.g. fullscreen or docked window), so that e.g. mouse the
mouse at the extreme of the screen will reach the scrollbar. (#9276)
- Fixed an issue which could lead initial click to move the current scroll by a pixel.
- Button:
- Moved ImGuiButtonFlags_AllowOverlap from imgui_internal.h to imgui.h,
as a convenience for when using e.g. InvisibleButton().
- Focus: fixed fallback "Debug" window temporarily taking focus and setting io.WantCaptureKeyboard
for one frame on e.g. application boot if no other windows are submitted. (#9243)
- Memory:
- Discard/GC of ImDrawList buffers for unused windows favor restoring them to
~Size*1.05 instead of Capacity when awakening again. Facilitate releasing ImDrawList
buffers after unusual usage spike. (#9303)
- Fixed GetForegroundDrawList()/GetBackgroundDrawList() per-viewport buffers not being
collected when unused for io.ConfigMemoryCompactTimer amount of time. (#9303)
- Demo: fixed IMGUI_DEMO_MARKER locations for examples applets. (#9261, #3689) [@pthom]
- Backends:
- DirectX9, OpenGL2, OpenGL3, Metal, SDLGPU3, SDLRenderer2, SDLRenderer3: fixed easy-to-fix
issues in code assuming ImTextureID_Invalid is always defined to 0. (#9295, #9310)
- SDLGPU3: removed unnecessary call to SDL_WaitForGPUIdle when releasing
vertex/index buffers. (#9262) [@jaenis]
- WebGPU: fixed version check for Emscripten 5.0.0+.

View File

@@ -403,10 +403,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures:
- likewise io.MousePos and GetMousePos() will use OS coordinates.
If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos.
- 2026/03/12 (1.92.7) - Changed default ImTextureID_Invalid to -1 instead of 0 if not #define-d. (#9293, #8745, #8465, #7090)
It seems like a better default since it will work with backends storing indices or memory offsets inside ImTextureID, where 0 might be a valid value.
If this is causing problem with e.g. your custom ImTextureID definition, you can add '#define ImTextureID_Invalid 0' to your imconfig.h + PLEASE report this to GitHub.
If you have hard-coded e.g. 'if (tex_id == 0)' checks they should be updated. e.g. OpenGL2, OpenGL3 and SDLRenderer3 backends incorrectly had 'IM_ASSERT(tex->TexID == 0)' lines which were replaced with 'IM_ASSERT(tex->TexID == ImTextureID_Invalid)'. (#9295)
- 2026/03/19 (1.92.7) - MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto.
- 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator.
The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263)
The "Console" example had such a bug:
@@ -4742,11 +4739,12 @@ void ImGui::GcCompactTransientMiscBuffers()
// Not freed:
// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data)
// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost.
// FIXME: Consider exposing of elaborating GC policy, e.g. being able to trim excessive ImDrawList gaps. (#9303)
void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window)
{
window->MemoryCompacted = true;
window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity;
window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity;
window->MemoryDrawListIdxCapacity = ImMin((int)(window->DrawList->IdxBuffer.Size * 1.05f), window->DrawList->IdxBuffer.Capacity);
window->MemoryDrawListVtxCapacity = ImMin((int)(window->DrawList->VtxBuffer.Size * 1.05f), window->DrawList->VtxBuffer.Capacity);
window->IDStack.clear();
window->DrawList->_ClearFreeMemory();
window->DC.ChildWindows.clear();
@@ -5318,12 +5316,12 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw
}
// Our ImDrawList system requires that there is always a command
if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount)
if (viewport->BgFgDrawListsLastTimeActive[drawlist_no] != (float)g.Time)
{
draw_list->_ResetForNewFrame();
draw_list->PushTexture(g.IO.Fonts->TexRef);
draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false);
viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount;
viewport->BgFgDrawListsLastTimeActive[drawlist_no] = (float)g.Time;
}
return draw_list;
}
@@ -5762,7 +5760,7 @@ void ImGui::NewFrame()
// As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves.
if (g.ActiveId != 0 && g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId)
{
IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() because it isn't marked alive anymore!\n");
IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() 0x%08X because it isn't marked alive anymore!\n", g.ActiveId);
ClearActiveID();
}
@@ -5864,7 +5862,8 @@ void ImGui::NewFrame()
// Mark all windows as not visible and compact unused memory.
IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);
const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
const bool gc_all = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f);
const float memory_compact_start_time = gc_all ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
for (ImGuiWindow* window : g.Windows)
{
window->WasActive = window->Active;
@@ -5874,7 +5873,7 @@ void ImGui::NewFrame()
window->BeginCount = 0;
// Garbage collect transient buffers of recently unused windows
if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
if ((!window->WasActive || gc_all) && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
GcCompactTransientWindowBuffers(window);
}
@@ -6351,7 +6350,7 @@ void ImGui::Render()
for (ImGuiViewportP* viewport : g.Viewports)
{
InitViewportDrawData(viewport);
if (viewport->BgFgDrawLists[0] != NULL)
if (viewport->BgFgDrawLists[0] != NULL && viewport->BgFgDrawListsLastTimeActive[0] == (float)g.Time)
AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport));
}
@@ -6383,7 +6382,7 @@ void ImGui::Render()
FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder);
// Add foreground ImDrawList (for each active viewport)
if (viewport->BgFgDrawLists[1] != NULL)
if (viewport->BgFgDrawLists[1] != NULL && viewport->BgFgDrawListsLastTimeActive[1] == (float)g.Time)
AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport));
// We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there (especially in docking branch).
@@ -15663,14 +15662,14 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s
// Store in heap
g.DragDropPayloadBufHeap.resize((int)data_size);
payload.Data = g.DragDropPayloadBufHeap.Data;
memcpy(payload.Data, data, data_size);
memcpy(payload.Data, data, (size_t)(int)data_size);
}
else if (data_size > 0)
{
// Store locally
memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
payload.Data = g.DragDropPayloadBufLocal;
memcpy(payload.Data, data, data_size);
memcpy(payload.Data, data, (size_t)(int)data_size);
}
else
{
@@ -16855,6 +16854,8 @@ static void ImGui::UpdateViewportsNewFrame()
}
AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size, ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows);
const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
g.CurrentDpiScale = 0.0f;
g.CurrentViewport = NULL;
g.MouseViewport = NULL;
@@ -16905,6 +16906,14 @@ static void ImGui::UpdateViewportsNewFrame()
}
viewport->UpdateWorkRect();
// Garbage collect transient buffers of recently BG/FG drawlists
for (int dl_n = 0; dl_n < IM_COUNTOF(viewport->BgFgDrawLists); dl_n++)
if (viewport->BgFgDrawListsLastTimeActive[dl_n] < memory_compact_start_time && viewport->BgFgDrawLists[dl_n] != NULL)
{
IM_DELETE(viewport->BgFgDrawLists[dl_n]);
viewport->BgFgDrawLists[dl_n] = NULL;
}
// Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
viewport->Alpha = 1.0f;
@@ -22708,6 +22717,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
{
ImGuiDebugAllocInfo* info = &g.DebugAllocInfo;
Text("%d current allocations", info->TotalAllocCount - info->TotalFreeCount);
Text("Releasing selected unused buffers after: %.2f secs", g.IO.ConfigMemoryCompactTimer);
if (SmallButton("GC now")) { g.GcCompactAll = true; }
Text("Recent frames with allocations:");
int buf_size = IM_COUNTOF(info->LastEntriesBuf);

34
imgui.h
View File

@@ -20,7 +20,7 @@
// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui
// - Issues & support ........... https://github.com/ocornut/imgui/issues
// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps)
// - Web version of the Demo .... https://pthom.github.io/imgui_manual (w/ source code browser)
// - Web version of the Demo .... https://pthom.github.io/imgui_explorer (w/ source code browser)
// For FIRST-TIME users having issues compiling/linking/running:
// please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above.
@@ -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.7 WIP"
#define IMGUI_VERSION_NUM 19265
#define IMGUI_VERSION_NUM 19266
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
#define IMGUI_HAS_VIEWPORT // In 'docking' WIP branch.
@@ -346,10 +346,10 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or
#endif
// Define this if you need to change the invalid value for your backend.
// - in v1.92.7 (2025/03/12): we changed default value from 0 to -1 as it is a better default, which supports storing offsets/indices.
// - If this is causing problem with your custom ImTextureID definition, you can add '#define ImTextureID_Invalid' to your imconfig + please report this to GitHub.
// - If your backend is using ImTextureID to store an index/offset and you need 0 to be valid, You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig.h file.
// - From 2026/03/12 to 2026/03/19 we experimented with changing to default to -1, but I worried it would cause too many issues in third-party code so it was reverted.
#ifndef ImTextureID_Invalid
#define ImTextureID_Invalid ((ImTextureID)-1)
#define ImTextureID_Invalid ((ImTextureID)0)
#endif
// ImTextureRef = higher-level identifier for a texture. Store a ImTextureID _or_ a ImTextureData*.
@@ -1037,7 +1037,7 @@ namespace ImGui
IMGUI_API void SetNavCursorVisible(bool visible); // alter visibility of keyboard/gamepad cursor. by default: show when using an arrow key, hide when clicking with mouse.
// Overlapping mode
IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Useful with invisible buttons, selectable, treenode covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this.
IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Typically useful with InvisibleButton(), Selectable(), TreeNode() covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this.
// Item/Widgets Utilities and Query Functions
// - Most of the functions are referring to the previous Item that has been submitted.
@@ -1350,7 +1350,7 @@ enum ImGuiTreeNodeFlags_
ImGuiTreeNodeFlags_None = 0,
ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected
ImGuiTreeNodeFlags_Framed = 1 << 1, // Draw frame with background (e.g. for CollapsingHeader)
ImGuiTreeNodeFlags_AllowOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one
ImGuiTreeNodeFlags_AllowOverlap = 1 << 2, // Hit testing will allow subsequent widgets to overlap this one. Require previous frame HoveredId to match before being usable. Shortcut to calling SetNextItemAllowOverlap().
ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack
ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes)
ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open
@@ -1410,7 +1410,7 @@ enum ImGuiSelectableFlags_
ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Frame will span all columns of its container table (text will still fit in current column)
ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too
ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text
ImGuiSelectableFlags_AllowOverlap = 1 << 4, // (WIP) Hit testing to allow subsequent widgets to overlap this one
ImGuiSelectableFlags_AllowOverlap = 1 << 4, // Hit testing will allow subsequent widgets to overlap this one. Require previous frame HoveredId to match before being usable. Shortcut to calling SetNextItemAllowOverlap().
ImGuiSelectableFlags_Highlight = 1 << 5, // Make the item be displayed as if it is hovered
ImGuiSelectableFlags_SelectOnNav = 1 << 6, // Auto-select when moved into, unless Ctrl is held. Automatic when in a BeginMultiSelect() block.
@@ -1956,6 +1956,7 @@ enum ImGuiButtonFlags_
ImGuiButtonFlags_MouseButtonMiddle = 1 << 2, // React on center mouse button
ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal]
ImGuiButtonFlags_EnableNav = 1 << 3, // InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default.
ImGuiButtonFlags_AllowOverlap = 1 << 12, // Hit testing will allow subsequent widgets to overlap this one. Require previous frame HoveredId to match before being usable. Shortcut to calling SetNextItemAllowOverlap().
};
// Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton()
@@ -3156,16 +3157,23 @@ enum ImGuiMultiSelectFlags_
ImGuiMultiSelectFlags_NoAutoClearOnReselect = 1 << 5, // Disable clearing selection when clicking/selecting an already selected item.
ImGuiMultiSelectFlags_BoxSelect1d = 1 << 6, // Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space.
ImGuiMultiSelectFlags_BoxSelect2d = 1 << 7, // Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items.
ImGuiMultiSelectFlags_BoxSelectNoScroll = 1 << 8, // Disable scrolling when box-selecting near edges of scope.
ImGuiMultiSelectFlags_BoxSelectNoScroll = 1 << 8, // Disable scrolling when box-selecting and moving mouse near edges of scope.
ImGuiMultiSelectFlags_ClearOnEscape = 1 << 9, // Clear selection when pressing Escape while scope is focused.
ImGuiMultiSelectFlags_ClearOnClickVoid = 1 << 10, // Clear selection when clicking on empty location within scope.
ImGuiMultiSelectFlags_ScopeWindow = 1 << 11, // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window.
ImGuiMultiSelectFlags_ScopeRect = 1 << 12, // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window.
ImGuiMultiSelectFlags_SelectOnClick = 1 << 13, // Apply selection on mouse down when clicking on unselected item. (Default)
ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 14, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection.
ImGuiMultiSelectFlags_SelectOnAuto = 1 << 13, // Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default)
ImGuiMultiSelectFlags_SelectOnClickAlways = 1 << 14, // Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior)
ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 15, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection.
//ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does.
ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one.
ImGuiMultiSelectFlags_NoSelectOnRightClick = 1 << 17, // Disable default right-click processing, which selects item on mouse down, and is designed for context-menus.
ImGuiMultiSelectFlags_SelectOnMask_ = ImGuiMultiSelectFlags_SelectOnAuto | ImGuiMultiSelectFlags_SelectOnClickAlways | ImGuiMultiSelectFlags_SelectOnClickRelease,
// Obsolete names
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
ImGuiMultiSelectFlags_SelectOnClick = ImGuiMultiSelectFlags_SelectOnAuto, // RENAMED in 1.92.6
#endif
};
// Main IO structure returned by BeginMultiSelect()/EndMultiSelect().
@@ -4040,8 +4048,8 @@ inline ImTextureID ImTextureRef::GetTexID() const
// Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too)
inline ImTextureID ImDrawCmd::GetTexID() const
{
// If you are getting this assert with ImTextureID_Invalid == 0 and your ImTextureID is used to store an index:
// - You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig file.
// If you are getting this assert with ImTextureID_Invalid == 0 and your ImTextureID is used to store an index or an offset:
// - You can add '#define ImTextureID_Invalid ((ImTextureID)-1)' in your imconfig.h file.
// If you are getting this assert with a renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92+):
// - You must correctly iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. See docs/BACKENDS.md.
ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above.

View File

@@ -2718,7 +2718,7 @@ struct ExampleDualListBox
}
if (child_visible)
{
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None;
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_BoxSelect1d;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);
ApplySelectionRequests(ms_io, side);
@@ -3327,10 +3327,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d
flags &= ~ImGuiMultiSelectFlags_ScopeRect;
if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect))
flags &= ~ImGuiMultiSelectFlags_ScopeWindow;
if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClick", &flags, ImGuiMultiSelectFlags_SelectOnClick) && (flags & ImGuiMultiSelectFlags_SelectOnClick))
flags &= ~ImGuiMultiSelectFlags_SelectOnClickRelease;
if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease) && (flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
flags &= ~ImGuiMultiSelectFlags_SelectOnClick;
if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnAuto", &flags, ImGuiMultiSelectFlags_SelectOnAuto))
flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnAuto);
ImGui::SameLine(); HelpMarker("Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default)");
if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickAlways", &flags, ImGuiMultiSelectFlags_SelectOnClickAlways))
flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnClickAlways);
ImGui::SameLine(); HelpMarker("Prevents Drag and Drop from being used on multi-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior)");
if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease))
flags &= ~(ImGuiMultiSelectFlags_SelectOnMask_ ^ ImGuiMultiSelectFlags_SelectOnClickRelease);
ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection.");
ImGui::TreePop();
}
@@ -11059,8 +11063,9 @@ struct ExampleAssetsBrowser
// Options
bool ShowTypeOverlay = true;
bool AllowSorting = true;
bool AllowDragUnselected = false;
bool AllowBoxSelect = true;
bool AllowBoxSelect = true; // Will set ImGuiMultiSelectFlags_BoxSelect2d
bool AllowBoxSelectInsideSelection = false; // Will set ImGuiMultiSelectFlags_SelectOnClickAlways
bool AllowDragUnselected = false; // Will set ImGuiMultiSelectFlags_SelectOnClickRelease
float IconSize = 32.0f;
int IconSpacing = 10;
int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.
@@ -11165,8 +11170,11 @@ struct ExampleAssetsBrowser
ImGui::Checkbox("Allow Sorting", &AllowSorting);
ImGui::SeparatorText("Selection Behavior");
ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected);
ImGui::Checkbox("Allow box-selection", &AllowBoxSelect);
if (ImGui::Checkbox("Allow box-selection from selected items", &AllowBoxSelectInsideSelection) && AllowBoxSelectInsideSelection)
AllowDragUnselected = false;
if (ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected) && AllowDragUnselected)
AllowBoxSelectInsideSelection = false;
ImGui::SeparatorText("Layout");
ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f");
@@ -11222,9 +11230,11 @@ struct ExampleAssetsBrowser
if (AllowBoxSelect)
ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d;
// - This feature allows dragging an unselected item without selecting it (rarely used)
// - Selection mode
if (AllowDragUnselected)
ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease;
ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; // Rarely used: Allows dragging an unselected item without selecting it(rarely used)
else if (AllowBoxSelectInsideSelection)
ms_flags |= ImGuiMultiSelectFlags_SelectOnClickAlways; // Rarely used: Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection.
// - Enable keyboard wrapping on X axis
// (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing:

View File

@@ -1049,7 +1049,6 @@ enum ImGuiButtonFlagsPrivate_
ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers)
//ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat -> use ImGuiItemFlags_ButtonRepeat instead.
ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping
ImGuiButtonFlags_AllowOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable.
//ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press
//ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions -> use BeginDisabled() or ImGuiItemFlags_Disabled
ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine
@@ -1270,6 +1269,7 @@ struct IMGUI_API ImGuiInputTextState
void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation
void OnCharPressed(unsigned int c);
float GetPreferredOffsetX() const;
const char* GetText() { return TextA.Data ? TextA.Data : ""; }
// Cursor & Selection
void CursorAnimReset();
@@ -2139,7 +2139,7 @@ struct ImGuiViewportP : public ImGuiViewport
float LastAlpha;
bool LastFocusedHadNavWindow;// Instead of maintaining a LastFocusedWindow (which may harder to correctly maintain), we merely store weither NavWindow != NULL last time the viewport was focused.
short PlatformMonitor;
int BgFgDrawListsLastFrame[2]; // Last frame number the background (0) and foreground (1) draw lists were used
float BgFgDrawListsLastTimeActive[2]; // Last frame number the background (0) and foreground (1) draw lists were used
ImDrawList* BgFgDrawLists[2]; // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays.
ImDrawData DrawDataP;
ImDrawDataBuilder DrawDataBuilder; // Temporary data while building final ImDrawData
@@ -2156,7 +2156,7 @@ struct ImGuiViewportP : public ImGuiViewport
ImVec2 BuildWorkInsetMin; // Work Area inset accumulator for current frame, to become next frame's WorkInset
ImVec2 BuildWorkInsetMax; // "
ImGuiViewportP() { Window = NULL; Idx = -1; LastFrameActive = BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = LastFocusedStampCount = -1; LastNameHash = 0; Alpha = LastAlpha = 1.0f; LastFocusedHadNavWindow = false; PlatformMonitor = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); }
ImGuiViewportP() { Window = NULL; Idx = -1; LastFrameActive = LastFocusedStampCount = -1; BgFgDrawListsLastTimeActive[0] = BgFgDrawListsLastTimeActive[1] = -1.0f; LastNameHash = 0; Alpha = LastAlpha = 1.0f; LastFocusedHadNavWindow = false; PlatformMonitor = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); }
~ImGuiViewportP() { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); }
void ClearRequestFlags() { PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; }

View File

@@ -1049,7 +1049,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize);
const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);
const float grab_h_pixels = (float)(int)ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);
const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
// As a special thing, we allow scrollbar near the edge of a screen/viewport to be reachable with mouse at the extreme edge (#9276)
@@ -3796,24 +3796,30 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;
flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
bool value_changed = false;
if (p_step == NULL)
const bool has_step_buttons = (p_step != NULL);
const float button_size = has_step_buttons ? GetFrameHeight() : 0.0f;
bool ret;
if (has_step_buttons)
{
if (InputText(label, buf, IM_COUNTOF(buf), flags))
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
}
else
{
const float button_size = GetFrameHeight();
// With Step Buttons
BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
PushID(label);
SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
if (InputText("", buf, IM_COUNTOF(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
ret = InputText("", buf, IM_COUNTOF(buf), flags); // PushID(label) + "" gives us the expected ID from outside point of view
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
}
else
{
// Without Step Buttons
ret = InputText(label, buf, IM_COUNTOF(buf), flags);
}
// Step buttons
// Apply
bool value_changed = ret ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false;
// Step buttons
if (has_step_buttons)
{
const ImVec2 backup_frame_padding = style.FramePadding;
style.FramePadding.x = style.FramePadding.y;
if (flags & ImGuiInputTextFlags_ReadOnly)
@@ -4403,6 +4409,7 @@ void ImGui::PopPasswordFont()
// Return false to discard a character.
static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
{
IM_ASSERT(state != NULL);
unsigned int c = *p_char;
ImGuiInputTextFlags flags = state->Flags;
@@ -4588,6 +4595,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li
ImGuiContext& g = *GImGui;
int size = 0;
const char* s;
bool trailing_line_already_counted = false;
if (flags & ImGuiInputTextFlags_WordWrap)
{
for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s)
@@ -4608,6 +4616,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li
}
else
{
// Inactive path: we don't know buf_end ahead of time.
const char* s_eol;
for (s = buf; ; s = s_eol + 1)
{
@@ -4616,6 +4625,7 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li
if ((s_eol = strchr(s, '\n')) != NULL)
continue;
s += strlen(s);
trailing_line_already_counted = true;
break;
}
}
@@ -4626,11 +4636,8 @@ static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* li
line_index->Offsets.push_back(0);
size++;
}
if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size)
{
if (buf_end > buf && buf_end[-1] == '\n' && !trailing_line_already_counted && size++ <= max_output_buffer_size)
line_index->Offsets.push_back((int)(buf_end - buf));
size++;
}
return size;
}
@@ -4731,6 +4738,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
draw_window->DC.CursorPos += style.FramePadding;
inner_size.x -= draw_window->ScrollbarSizes.x;
// FIXME: Could this be a ImGuiChildFlags to affect the SetLastItemDataForWindow() call?
g.LastItemData.ID = id;
g.LastItemData.ItemFlags = item_data_backup.ItemFlags;
g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
}
else
{
@@ -4770,8 +4782,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id);
const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive
const bool user_clicked = hovered && io.MouseClicked[0];
const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
const ImGuiID scrollbar_id = (is_multiline && state != NULL) ? GetWindowScrollbarID(draw_window, ImGuiAxis_Y) : 0;
const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == scrollbar_id;
const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == scrollbar_id;
bool clear_active_id = false;
bool select_all = false;
@@ -4780,7 +4793,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);
const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav || input_requested_by_reactivate);
const bool init_state = (init_make_active || user_scroll_active);
if (init_reload_from_user_buf)
{
int new_len = (int)ImStrlen(buf);
@@ -4793,7 +4805,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
state->Stb->select_start = state->ReloadSelectionStart;
state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below
}
else if ((init_state && g.ActiveId != id) || init_changed_specs)
else if ((init_make_active && g.ActiveId != id) || init_changed_specs)
{
// Access state even if we don't own it yet.
state = &g.InputTextState;
@@ -4806,8 +4818,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
const int buf_len = (int)ImStrlen(buf);
IM_ASSERT(((buf_len + 1 <= buf_size) || (buf_len == 0 && buf_size == 0)) && "Is your input buffer properly zero-terminated?");
state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
if (!user_scroll_finish)
{
state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
}
// Preserve cursor position and undo/redo stack if we come back to same widget
// FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
@@ -4903,7 +4918,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
ClearActiveID();
// Release focus when we click outside
if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
if (g.ActiveId == id && io.MouseClicked[0] && !init_make_active) //-V560
clear_active_id = true;
// Lock the decision of whether we are going to take the path displaying the cursor or selection
@@ -5218,7 +5233,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
}
// Process callbacks and apply result back to user's buffer.
// Process revert and user callbacks
const char* apply_new_text = NULL;
int apply_new_text_length = 0;
if (g.ActiveId == id)
@@ -5248,110 +5263,99 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
}
// FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId,
// even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks.
// If we do that, need to ensure that as special case, 'validated == true' also writes back.
// This also allows the user to use InputText() without maintaining any user-side storage.
// (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object
// unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
if (apply_edit_back_to_user_buffer)
// User callback
if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
{
// Apply current edited text immediately.
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
IM_ASSERT(callback != NULL);
// User callback
if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
// The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
ImGuiInputTextFlags event_flag = 0;
ImGuiKey event_key = ImGuiKey_None;
if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
{
IM_ASSERT(callback != NULL);
// The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
ImGuiInputTextFlags event_flag = 0;
ImGuiKey event_key = ImGuiKey_None;
if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
{
event_flag = ImGuiInputTextFlags_CallbackCompletion;
event_key = ImGuiKey_Tab;
}
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
{
event_flag = ImGuiInputTextFlags_CallbackHistory;
event_key = ImGuiKey_UpArrow;
}
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
{
event_flag = ImGuiInputTextFlags_CallbackHistory;
event_key = ImGuiKey_DownArrow;
}
else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
{
event_flag = ImGuiInputTextFlags_CallbackEdit;
}
else if (flags & ImGuiInputTextFlags_CallbackAlways)
{
event_flag = ImGuiInputTextFlags_CallbackAlways;
}
if (event_flag)
{
ImGuiInputTextCallbackData callback_data;
callback_data.Ctx = &g;
callback_data.ID = id;
callback_data.Flags = flags;
callback_data.EventFlag = event_flag;
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.UserData = callback_user_data;
// FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
char* callback_buf = is_readonly ? buf : state->TextA.Data;
IM_ASSERT(callback_buf == state->TextSrc);
state->CallbackTextBackup.resize(state->TextLen + 1);
memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);
callback_data.EventKey = event_key;
callback_data.Buf = callback_buf;
callback_data.BufTextLen = state->TextLen;
callback_data.BufSize = state->BufCapacity;
callback_data.BufDirty = false;
callback_data.CursorPos = state->Stb->cursor;
callback_data.SelectionStart = state->Stb->select_start;
callback_data.SelectionEnd = state->Stb->select_end;
// Call user code
callback(&callback_data);
// Read back what user may have modified
callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields
IM_ASSERT(callback_data.BufSize == state->BufCapacity);
IM_ASSERT(callback_data.Flags == flags);
if (callback_data.BufDirty || callback_data.CursorPos != state->Stb->cursor)
state->CursorFollow = true;
state->Stb->cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen);
state->Stb->select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen);
state->Stb->select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen);
if (callback_data.BufDirty)
{
// Callback may update buffer and thus set buf_dirty even in read-only mode.
IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
state->CursorAnimReset();
}
}
event_flag = ImGuiInputTextFlags_CallbackCompletion;
event_key = ImGuiKey_Tab;
}
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
{
event_flag = ImGuiInputTextFlags_CallbackHistory;
event_key = ImGuiKey_UpArrow;
}
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
{
event_flag = ImGuiInputTextFlags_CallbackHistory;
event_key = ImGuiKey_DownArrow;
}
else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
{
event_flag = ImGuiInputTextFlags_CallbackEdit;
}
else if (flags & ImGuiInputTextFlags_CallbackAlways)
{
event_flag = ImGuiInputTextFlags_CallbackAlways;
}
// Will copy result string if modified
if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
if (event_flag)
{
apply_new_text = state->TextSrc;
apply_new_text_length = state->TextLen;
value_changed = true;
ImGuiInputTextCallbackData callback_data;
callback_data.Ctx = &g;
callback_data.ID = id;
callback_data.Flags = flags;
callback_data.EventFlag = event_flag;
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.UserData = callback_user_data;
// FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
char* callback_buf = is_readonly ? buf : state->TextA.Data;
IM_ASSERT(callback_buf == state->TextSrc);
state->CallbackTextBackup.resize(state->TextLen + 1);
memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);
callback_data.EventKey = event_key;
callback_data.Buf = callback_buf;
callback_data.BufTextLen = state->TextLen;
callback_data.BufSize = state->BufCapacity;
callback_data.BufDirty = false;
callback_data.CursorPos = state->Stb->cursor;
callback_data.SelectionStart = state->Stb->select_start;
callback_data.SelectionEnd = state->Stb->select_end;
// Call user code
callback(&callback_data);
// Read back what user may have modified
callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields
IM_ASSERT(callback_data.BufSize == state->BufCapacity);
IM_ASSERT(callback_data.Flags == flags);
if (callback_data.BufDirty || callback_data.CursorPos != state->Stb->cursor)
state->CursorFollow = true;
state->Stb->cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen);
state->Stb->select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen);
state->Stb->select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen);
if (callback_data.BufDirty)
{
// Callback may update buffer and thus set buf_dirty even in read-only mode.
IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
state->CursorAnimReset();
}
}
}
// Will copy result string if modified.
// FIXME-OPT: Could mark dirty state from the stb_textedit callbacks
if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
{
apply_new_text = state->TextSrc;
apply_new_text_length = state->TextLen;
value_changed = true;
}
}
// Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
// This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer.
if (g.InputTextDeactivatedState.ID == id)
{
if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
@@ -5364,12 +5368,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
g.InputTextDeactivatedState.ID = 0;
}
// Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
// Write back result to user buffer. This can currently only happen when (g.ActiveId == id) or when just deactivated.
// - As soon as the InputText() is active, our stored in-widget value gets priority over any underlying modification of the user buffer.
// - Make sure we always reapply the live buffer back to the input/user buffer before clearing ActiveId, even thought strictly speaking
// it was not modified on this frame. This allows the user to use InputText() without maintaining any user-side storage.
// (PS: if you use this property together with ImGuiInputTextFlags_CallbackResize, you are at the risk of recreating a temporary
// allocated/string object every frame. Which in the grand scheme of scheme is nothing, but isn't dear imgui vibe).
if (apply_new_text != NULL)
{
//// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
//// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
//// without any storage on user's side.
IM_ASSERT(apply_new_text_length >= 0);
if (is_resizable)
{
@@ -5378,7 +5384,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
callback_data.ID = id;
callback_data.Flags = flags;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.EventActivated = (state != NULL && g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.Buf = buf;
callback_data.BufTextLen = apply_new_text_length;
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
@@ -5538,7 +5544,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (render_selection)
{
const ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
const float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
const float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME-DPI: those offsets should be part of the style? they don't play so well with multi-line selection.
const float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
@@ -5567,7 +5573,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize;
rect.Max.x = rect.Min.x + rect_width;
rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize;
rect.Min.y -= bg_offy_up;
rect.Min.y += bg_offy_up;
rect.ClipWith(clip_rect);
draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
}
@@ -8176,10 +8182,13 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags
{
ImGuiButtonFlags button_flags = *p_button_flags;
button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
else
button_flags &= ~(ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease);
if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickAlways)
button_flags |= ImGuiButtonFlags_PressedOnClick;
else if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)
button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
else // ImGuiMultiSelectFlags_SelectOnAuto
button_flags |= (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnClickRelease;
*p_button_flags = button_flags;
}
}
@@ -8299,7 +8308,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
// Box-select
ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
BoxSelectPreStartDrag(ms->BoxSelectId, item_data);
//----------------------------------------------------------------------------------------
@@ -10607,7 +10616,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
}
// Click to Select a tab
ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
ImGuiButtonFlags button_flags = ((ImGuiButtonFlags)(is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
bool hovered, held, pressed;