diff --git a/src/Libs/imgui/LICENSE.txt b/src/Libs/imgui/LICENSE.txt index 00ae473..d5ba315 100644 --- a/src/Libs/imgui/LICENSE.txt +++ b/src/Libs/imgui/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2025 Omar Cornut +Copyright (c) 2014-2026 Omar Cornut Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Libs/imgui/backends/imgui_impl_sdl2.cpp b/src/Libs/imgui/backends/imgui_impl_sdl2.cpp index f0b8b2b..f768383 100644 --- a/src/Libs/imgui/backends/imgui_impl_sdl2.cpp +++ b/src/Libs/imgui/backends/imgui_impl_sdl2.cpp @@ -25,7 +25,11 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-04-16: Made ImGui_ImplSDL2_GetContentScaleForWindow(), ImGui_ImplSDL2_GetContentScaleForDisplay() helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) +// 2026-03-09: [Docking] Fixed an issue dated 2025/04/09 (1.92 WIP) where a refactor+merge caused ImGuiBackendFlags_HasMouseHoveredViewport to never be set, causing foreign windows to be ignored when deciding of hovered viewport. (#9284) +// 2026-02-13: Inputs: systems other than X11 are back to starting mouse capture on mouse down (reverts 2025-02-26 change). Only X11 requires waiting for a drag by default (not ideal, but a better default for X11 users). Added ImGui_ImplSDL2_SetMouseCaptureMode() for X11 debugger users. (#3650, #6410, #9235) +// 2026-01-15: Changed GetClipboardText() handler to return nullptr on error aka clipboard contents is not text. Consistent with other backends. (#9168) // 2025-09-24: Skip using the SDL_GetGlobalMouseState() state when one of our window is hovered, as the SDL_MOUSEMOTION data is reliable. Fix macOS notch mouse coordinates issue in fullscreen mode + better perf on X11. (#7919, #7786) // 2025-09-18: Call platform_io.ClearPlatformHandlers() on shutdown. // 2025-09-15: Content Scales are always reported as 1.0 on Wayland. (#8921) @@ -36,7 +40,7 @@ // 2025-04-09: Don't attempt to call SDL_CaptureMouse() on drivers where we don't call SDL_GetGlobalMouseState(). (#8561) // 2025-03-21: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad regardless of ImGuiConfigFlags_NavEnableGamepad being set. // 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468) -// 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650) +// 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g. Linux debuggers not claiming capture back. (#6410, #3650) // 2025-02-25: [Docking] Revert to use SDL_GetDisplayBounds() for WorkPos/WorkRect if SDL_GetDisplayUsableBounds() failed. // 2025-02-24: Avoid calling SDL_GetGlobalMouseState() when mouse is in relative mode. // 2025-02-21: [Docking] Update monitors and work areas information every frame, as the later may change regardless of monitor changes. (#8415) @@ -172,8 +176,8 @@ struct ImGui_ImplSDL2_Data SDL_Cursor* MouseLastCursor; int MouseLastLeaveFrame; bool MouseCanUseGlobalState; - bool MouseCanUseCapture; bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state. + ImGui_ImplSDL2_MouseCaptureMode MouseCaptureMode; // Gamepad handling ImVector Gamepads; @@ -203,7 +207,10 @@ static const char* ImGui_ImplSDL2_GetClipboardText(ImGuiContext*) ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); if (bd->ClipboardTextData) SDL_free(bd->ClipboardTextData); - bd->ClipboardTextData = SDL_GetClipboardText(); + if (SDL_HasClipboardText()) + bd->ClipboardTextData = SDL_GetClipboardText(); + else + bd->ClipboardTextData = nullptr; return bd->ClipboardTextData; } @@ -219,7 +226,7 @@ static void ImGui_ImplSDL2_PlatformSetImeData(ImGuiContext*, ImGuiViewport* view { SDL_Rect r; r.x = (int)(data->InputPos.x - viewport->Pos.x); - r.y = (int)(data->InputPos.y - viewport->Pos.y + data->InputLineHeight); + r.y = (int)(data->InputPos.y - viewport->Pos.y); r.w = 1; r.h = (int)data->InputLineHeight; SDL_SetTextInputRect(&r); @@ -559,28 +566,31 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void bd->WindowID = SDL_GetWindowID(window); bd->Renderer = renderer; - // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960) - // We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame. -#ifndef __APPLE__ - bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState; -#else - bd->MouseCanReportHoveredViewport = false; -#endif - // Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse() // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) bd->MouseCanUseGlobalState = false; - bd->MouseCanUseCapture = false; + bd->MouseCaptureMode = ImGui_ImplSDL2_MouseCaptureMode_Disabled; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE const char* sdl_backend = SDL_GetCurrentVideoDriver(); const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; for (const char* item : capture_and_global_state_whitelist) if (strncmp(sdl_backend, item, strlen(item)) == 0) - bd->MouseCanUseGlobalState = bd->MouseCanUseCapture = true; + { + bd->MouseCanUseGlobalState = true; + bd->MouseCaptureMode = (strcmp(item, "x11") == 0) ? ImGui_ImplSDL2_MouseCaptureMode_EnabledAfterDrag : ImGui_ImplSDL2_MouseCaptureMode_Enabled; + } #endif if (bd->MouseCanUseGlobalState) io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) + // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960) + // We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame. +#ifndef __APPLE__ + bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState; +#else + bd->MouseCanReportHoveredViewport = false; +#endif + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText; platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText; @@ -720,7 +730,15 @@ void ImGui_ImplSDL2_Shutdown() IM_DELETE(bd); } -// This code is incredibly messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4. +void ImGui_ImplSDL2_SetMouseCaptureMode(ImGui_ImplSDL2_MouseCaptureMode mode) +{ + ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); + if (mode == ImGui_ImplSDL2_MouseCaptureMode_Disabled && bd->MouseCaptureMode != ImGui_ImplSDL2_MouseCaptureMode_Disabled) + SDL_CaptureMouse(SDL_FALSE); + bd->MouseCaptureMode = mode; +} + +// This code is rather messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4. static void ImGui_ImplSDL2_UpdateMouseData() { ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); @@ -729,8 +747,12 @@ static void ImGui_ImplSDL2_UpdateMouseData() // We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below) #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE // - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside. - // - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue we wait until mouse has moved to begin capture. - if (bd->MouseCanUseCapture) + // - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue on X11 we we wait until mouse has moved to begin capture. + if (bd->MouseCaptureMode == ImGui_ImplSDL2_MouseCaptureMode_Enabled) + { + SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE); + } + else if (bd->MouseCaptureMode == ImGui_ImplSDL2_MouseCaptureMode_EnabledAfterDrag) { bool want_capture = false; for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++) @@ -763,7 +785,7 @@ static void ImGui_ImplSDL2_UpdateMouseData() // Note that SDL_GetGlobalMouseState() is in theory slow on X11, but this only runs on rather specific cases. If a problem we may provide a way to opt-out this feature. SDL_Window* hovered_window = SDL_GetMouseFocus(); const bool is_relative_mouse_mode = SDL_GetRelativeMouseMode() != 0; - if (hovered_window == NULL && bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0 && !is_relative_mouse_mode) + if (hovered_window == nullptr && bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0 && !is_relative_mouse_mode) { // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) @@ -830,6 +852,7 @@ float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window) return ImGui_ImplSDL2_GetContentScaleForDisplay(SDL_GetWindowDisplayIndex(window)); } +// SDL_GetDisplayDPI() seems rather unreliable on Linux. float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) { const char* sdl_driver = SDL_GetCurrentVideoDriver(); @@ -839,7 +862,11 @@ float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) float dpi = 0.0f; if (SDL_GetDisplayDPI(display_index, &dpi, nullptr, nullptr) == 0) + { + if (dpi < 96.0f) + dpi = 96.0f; return dpi / 96.0f; + } #endif #endif IM_UNUSED(display_index); @@ -998,7 +1025,7 @@ static void ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(SDL_Window* window, if (out_size != nullptr) *out_size = ImVec2((float)w, (float)h); if (out_framebuffer_scale != nullptr) - *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f); + *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / (float)w, (float)display_h / (float)h) : ImVec2(1.0f, 1.0f); } void ImGui_ImplSDL2_NewFrame() @@ -1023,7 +1050,7 @@ void ImGui_ImplSDL2_NewFrame() Uint64 current_time = SDL_GetPerformanceCounter(); if (current_time <= bd->Time) current_time = bd->Time + 1; - io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f); + io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / (double)frequency) : (float)(1.0f / 60.0f); bd->Time = current_time; if (bd->MouseLastLeaveFrame && bd->MouseLastLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0) diff --git a/src/Libs/imgui/backends/imgui_impl_sdl2.h b/src/Libs/imgui/backends/imgui_impl_sdl2.h index 1ad8365..e091ba3 100644 --- a/src/Libs/imgui/backends/imgui_impl_sdl2.h +++ b/src/Libs/imgui/backends/imgui_impl_sdl2.h @@ -51,4 +51,11 @@ IMGUI_IMPL_API float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_ind enum ImGui_ImplSDL2_GamepadMode { ImGui_ImplSDL2_GamepadMode_AutoFirst, ImGui_ImplSDL2_GamepadMode_AutoAll, ImGui_ImplSDL2_GamepadMode_Manual }; IMGUI_IMPL_API void ImGui_ImplSDL2_SetGamepadMode(ImGui_ImplSDL2_GamepadMode mode, struct _SDL_GameController** manual_gamepads_array = nullptr, int manual_gamepads_count = -1); +// (Advanced, for X11 users) Override Mouse Capture mode. Mouse capture allows receiving updated mouse position after clicking inside our window and dragging outside it. +// Having this 'Enabled' is in theory always better. But, on X11 if you crash/break to debugger while capture is active you may temporarily lose access to your mouse. +// The best solution is to setup your debugger to automatically release capture, e.g. 'setxkbmap -option grab:break_actions && xdotool key XF86Ungrab' or via a GDB script. See #3650. +// But you may independently decide on X11, when a debugger is attached, to set this value to ImGui_ImplSDL2_MouseCaptureMode_Disabled. +enum ImGui_ImplSDL2_MouseCaptureMode { ImGui_ImplSDL2_MouseCaptureMode_Enabled, ImGui_ImplSDL2_MouseCaptureMode_EnabledAfterDrag, ImGui_ImplSDL2_MouseCaptureMode_Disabled }; +IMGUI_IMPL_API void ImGui_ImplSDL2_SetMouseCaptureMode(ImGui_ImplSDL2_MouseCaptureMode mode); + #endif // #ifndef IMGUI_DISABLE diff --git a/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.cpp b/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.cpp index c923058..a16ec31 100644 --- a/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.cpp +++ b/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.cpp @@ -14,10 +14,11 @@ // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. -// Missing features: +// Missing features or Issues: // [ ] Renderer: Multi-viewport support (multiple windows). +// [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks: SDLRenderer2 does not support changing SDL_SCALE_MODE while rendering. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Learn about Dear ImGui: // - FAQ https://dearimgui.com/faq @@ -26,6 +27,9 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others cannot be supported). (#9378) +// 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. @@ -73,41 +77,6 @@ static ImGui_ImplSDLRenderer2_Data* ImGui_ImplSDLRenderer2_GetBackendData() } // Functions -bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer* renderer) -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!"); - - // Setup backend capabilities flags - ImGui_ImplSDLRenderer2_Data* bd = IM_NEW(ImGui_ImplSDLRenderer2_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_sdlrenderer2"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - bd->Renderer = renderer; - - return true; -} - -void ImGui_ImplSDLRenderer2_Shutdown() -{ - ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); - IM_DELETE(bd); -} - static void ImGui_ImplSDLRenderer2_SetupRenderState(SDL_Renderer* renderer) { // Clear out any viewports and cliprect set by the user @@ -123,6 +92,9 @@ void ImGui_ImplSDLRenderer2_NewFrame() IM_UNUSED(bd); } +// Draw callbacks +static void ImGui_ImplSDLRenderer2_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. + void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) { // If there's a scale factor set by the user, use that instead @@ -185,8 +157,7 @@ void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplSDLRenderer2_DrawCallback_ResetRenderState) ImGui_ImplSDLRenderer2_SetupRenderState(renderer); else pcmd->UserCallback(draw_list, pcmd); @@ -269,8 +240,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); @@ -293,6 +265,44 @@ void ImGui_ImplSDLRenderer2_DestroyDeviceObjects() } } +bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer* renderer) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!"); + + // Setup backend capabilities flags + ImGui_ImplSDLRenderer2_Data* bd = IM_NEW(ImGui_ImplSDLRenderer2_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_sdlrenderer2"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplSDLRenderer2_DrawCallback_ResetRenderState; + + bd->Renderer = renderer; + + return true; +} + +void ImGui_ImplSDLRenderer2_Shutdown() +{ + ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); +} + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.h b/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.h index 3d8de5f..9bb6853 100644 --- a/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.h +++ b/src/Libs/imgui/backends/imgui_impl_sdlrenderer2.h @@ -14,8 +14,9 @@ // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. -// Missing features: +// Missing features or Issues: // [ ] Renderer: Multi-viewport support (multiple windows). +// [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks: SDLRenderer2 does not support changing SDL_SCALE_MODE while rendering. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -41,7 +42,7 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_d IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); -// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex); // [BETA] Selected render state data shared with callbacks. diff --git a/src/Libs/imgui/imconfig.h b/src/Libs/imgui/imconfig.h index c6d9b8d..0d843be 100644 --- a/src/Libs/imgui/imconfig.h +++ b/src/Libs/imgui/imconfig.h @@ -49,7 +49,7 @@ //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). -//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Enable Test Engine / Automation features. diff --git a/src/Libs/imgui/imgui.cpp b/src/Libs/imgui/imgui.cpp index 77c88c6..09971db 100644 --- a/src/Libs/imgui/imgui.cpp +++ b/src/Libs/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (main code and documentation) // Help: @@ -20,14 +20,14 @@ // - 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_online/manual/imgui_manual.html (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. // Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. // Since 1.92, we encourage font loading questions to also be posted in 'Issues'. -// Copyright (c) 2014-2025 Omar Cornut +// Copyright (c) 2014-2026 Omar Cornut // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. @@ -168,6 +168,7 @@ CODE - Home, End Scroll to top, scroll to bottom. - Alt Toggle between scrolling layer and menu layer. - Ctrl+Tab then Ctrl+Arrows Move window. Hold Shift to resize instead of moving. + - Menu or Shift+F10 Open context menu. - Output when ImGuiConfigFlags_NavEnableKeyboard set, - io.WantCaptureKeyboard flag is set when keyboard is claimed. - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. @@ -208,7 +209,7 @@ CODE The UI can be highly dynamic, there are no construction or destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, fewer bugs. - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. - Or browse https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html for interactive web version. + Or browse pthom's online imgui_explorer: https://pthom.github.io/imgui_explorer for a web version w/ source code browser. - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build. - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori). You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in Wiki. @@ -396,12 +397,84 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: You can read releases logs https://github.com/ocornut/imgui/releases for more details. (Docking/Viewport Branch) - - 2025/XX/XX (1.XXXX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that: + - 2026/XX/XX (1.XXXX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that: - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore. you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos) - 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/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). + - Before: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); + - After: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); + - Before: void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + - After: void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); + - Before: void ImDrawList::PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f); + - After: void ImDrawList::PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); + Added inline redirection functions when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is off. + Marked the old functions are =delete when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is on, to allow for better type-checking. + Effectively the typical call site is changing from: + - Before: window->DrawList->AddRect(p_min, p_max, color, rounding, ImDrawFlags_None, border_size); + - After: window->DrawList->AddRect(p_min, p_max, color, rounding, border_size); + Notes: + - Users of C++ and other languages with type-checking will be notified at compile-time of any mistakes. + - Users of high-level bindings or languages with no type-checking will be notified at runtime via an assert for invalid flags value. + If you are a binding maintainer consider doing something to facilitate transition or error detection. + - This is perhaps the worst breaking change in our history :( but it makes ImDrawList function signatures consistent. + As we are aiming to add flags and features to variety of ImDrawList functions, that consistency becomes more important. + The new order is also more convenient as `flags` are less frequently used than `thickness` in real code. + - As a general policy in Dear ImGui, all our flags default to 0 so ImDrawFlags_None was likely written 0 in some call sites. + - Consider adding `#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS` in your imconfig.h, even temporarily, to clean up legacy code. + - 2026/04/23 (1.92.8) - DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, which is part of our new standard draw callbacks. (#9378) + - 2026/04/22 (1.92.8) - Backends: Vulkan: redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. + - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. + - When creating your own descriptor pool (instead of letting backend creates its own): need at least IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLER. + - 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: + float footer_height = style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + BeginChild("ScrollingRegion", { 0, -footer_height }); + Should be: + float footer_height = style.ItemSpacing.y + style.SeparatorSize + ImGui::GetFrameHeightWithSpacing(); + 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. + - 2026/02/23 (1.92.7) - Commented out legacy signature for Combo(), ListBox(), signatures which were obsoleted in 1.90 (Nov 2023), when the getter callback type was changed. + - Old getter type: bool (*getter)(void* user_data, int idx, const char** out_text) // Set label + return bool. False replaced label with placeholder. + - New getter type: const char* (*getter)(void* user_data, int idx) // Return label or NULL/empty label if missing + - 2026/01/08 (1.92.6) - Commented out legacy names obsoleted in 1.90 (Sept 2023): 'BeginChildFrame()' --> 'BeginChild()' with 'ImGuiChildFlags_FrameStyle'. 'EndChildFrame()' --> 'EndChild()'. 'ShowStackToolWindow()' --> 'ShowIDStackToolWindow()'. 'IM_OFFSETOF()' --> 'offsetof()'. + - 2026/01/07 (1.92.6) - Popups: changed compile-time 'ImGuiPopupFlags popup_flags = 1' default value to be '= 0' for BeginPopupContextItem(), BeginPopupContextWindow(), BeginPopupContextVoid(), OpenPopupOnItemClick(). Default value has same meaning before and after. + - Refer to GitHub topic #9157 if you have any question. + - Before this version, those functions had a 'ImGuiPopupFlags popup_flags = 1' default value in their function signature. + Explicitly passing a literal 0 meant ImGuiPopupFlags_MouseButtonLeft. The default literal 1 meant ImGuiPopupFlags_MouseButtonRight. + This was introduced by a change on 2020/06/23 (1.77) while changing the signature from 'int mouse_button' to 'ImGuiPopupFlags popup_flags' and trying to preserve then-legacy behavior. + We have now changed this behavior to cleanup a very old API quirk, facilitate use by bindings, and to remove the last and error-prone non-zero default value. + Also because we deemed it extremely rare to use those helper functions with the Left mouse button! As using the LMB would generally be triggered via another widget, e.g. a Button() + a OpenPopup()/BeginPopup() call. + - Before: The default = 1 means ImGuiPopupFlags_MouseButtonRight. Explicitly passing a literal 0 means ImGuiPopupFlags_MouseButtonLeft. + - After: The default = 0 means ImGuiPopupFlags_MouseButtonRight. Explicitly passing a literal 1 also means ImGuiPopupFlags_MouseButtonRight (if legacy behavior are enabled) or will assert (if legacy behavior are disabled). + - TL;DR: if you don't want to use right mouse button for popups, always specify it explicitly using a named ImGuiPopupFlags_MouseButtonXXXX value. + Recap: + - BeginPopupContextItem("foo"); // Behavior unchanged (use Right button) + - BeginPopupContextItem("foo", ImGuiPopupFlags_MouseButtonLeft); // Behavior unchanged (use Left button) + - BeginPopupContextItem("foo", ImGuiPopupFlags_MouseButtonLeft | xxx); // Behavior unchanged (use Left button + flags) + - BeginPopupContextItem("foo", ImGuiPopupFlags_MouseButtonRight | xxx); // Behavior unchanged (use Right button + flags) + - BeginPopupContextItem("foo", 1); // Behavior unchanged (as a courtesy we legacy interpret 1 as ImGuiPopupFlags_MouseButtonRight, will assert if disabling legacy behaviors. + - BeginPopupContextItem("foo", 0); // !! Behavior changed !! Was Left button. Now will defaults to Right button! --> Use ImGuiPopupFlags_MouseButtonLeft. + - BeginPopupContextItem("foo", ImGuiPopupFlags_NoReopen); // !! Behavior changed !! Was Left button + flags. Now will defaults to Right button! --> Use ImGuiPopupFlags_MouseButtonLeft | xxx. + - 2025/12/23 (1.92.6) - Fonts: AddFontDefault() now automatically selects an embedded font between the new scalable AddFontDefaultVector() and the classic pixel-clean AddFontDefaultBitmap(). + The default selection is based on (style.FontSizeBase * FontScaleMain * FontScaleDpi) reaching a small threshold, but old codebases may not set any of them properly. As as a result, it is likely that old codebase may still default to AddFontDefaultBitmap(). + Prefer calling either based on your own logic. You can call AddFontDefaultBitmap() to ensure legacy behavior. + - 2025/12/23 (1.92.6) - Fonts: removed ImFontConfig::PixelSnapV added in 1.92 which turns out is unnecessary (and misdocumented). Post-rescale GlyphOffset is always rounded. + - 2025/12/17 (1.92.6) - Renamed helper macro IM_ARRAYSIZE() -> IM_COUNTOF(). Kept redirection/legacy name for now. + - 2025/12/11 (1.92.6) - Hashing: handling of "###" operator to reset to seed within a string identifier doesn't include the "###" characters in the output hash anymore. + - Before: GetID("Hello###World") == GetID("###World") != GetID("World") + - After: GetID("Hello###World") == GetID("###World") == GetID("World") + - This has the property of facilitating concatenating and manipulating identifiers using "###", and will allow fixing other dangling issues. + - This will invalidate hashes (stored in .ini data) for Tables and Windows that are using the "###" operators. (#713, #1698) + - 2025/11/24 (1.92.6) - Fonts: Fixed handling of `ImFontConfig::FontDataOwnedByAtlas = false` which did erroneously make a copy of the font data, essentially defeating the purpose of this flag and wasting memory. + (trivia: undetected since July 2015, this is perhaps the oldest bug in Dear ImGui history, albeit for a rarely used feature, see #9086) + HOWEVER, fixing this bug is likely to surface bugs in user code using `FontDataOwnedByAtlas = false`. + - Prior to 1.92, font data only needed to be available during the atlas->AddFontXXX() call. + - Since 1.92, font data needs to available until atlas->RemoveFont(), or more typically until a shutdown of the owning context or font atlas. + - The fact that handling of `FontDataOwnedByAtlas = false` was broken bypassed the issue altogether. - 2025/11/06 (1.92.5) - BeginChild: commented out some legacy names which were obsoleted in 1.90.0 (Nov 2023), 1.90.9 (July 2024), 1.91.1 (August 2024): - ImGuiChildFlags_Border --> ImGuiChildFlags_Borders - ImGuiWindowFlags_NavFlattened --> ImGuiChildFlags_NavFlattened (moved to ImGuiChildFlags). BeginChild(name, size, 0, ImGuiWindowFlags_NavFlattened) --> BeginChild(name, size, ImGuiChildFlags_NavFlattened, 0) @@ -464,7 +537,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: io.Fonts->AddFontFromFileTTF("FontAwesome4.ttf", 0.0f, &cfg2); - You can use `Metrics/Debugger->Fonts->Font->Input Glyphs Overlap Detection Tool` to see list of glyphs available in multiple font sources. This can facilitate understanding which font input is providing which glyph. - Fonts: **IMPORTANT** on Thread Safety: - - A few functions such as font->CalcTextSizeA() were, by sheer luck (== accidentally) thread-safe even thou we had never provided that guarantee. They are definitively not thread-safe anymore as new glyphs may be loaded. + - A few functions such as font->CalcTextSizeA() were, by sheer luck (== accidentally) thread-safe even though we had never provided that guarantee. They are definitively not thread-safe anymore as new glyphs may be loaded. - Fonts: ImFont::FontSize was removed and does not make sense anymore. ImFont::LegacySize is the size passed to AddFont(). - Fonts: Removed support for PushFont(NULL) which was a shortcut for "default font". - Fonts: Renamed/moved 'io.FontGlobalScale' to 'style.FontScaleMain'. @@ -536,6 +609,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - 2024/11/06 (1.91.5) - commented/obsoleted out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or IMGUI_DISABLE_OBSOLETE_FUNCTIONS before) - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022). - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022). + - GetKeyIndex() is removed (obsoleted March 2022). The indirection is now unnecessary. - pre-1.87 backends are not supported: - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to io.KeysDown[], io.MouseDown[] fields. - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to io.NavInputs[] fields. @@ -1077,6 +1151,8 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - Run the examples/ applications and explore them. - Read Getting Started (https://github.com/ocornut/imgui/wiki/Getting-Started) guide. - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function. + - See pthom's online imgui_explorer (https://pthom.github.io/imgui_explorer) which is a web + version of the demo with a source code browser. - The demo covers most features of Dear ImGui, so you can read the code and see its output. - See documentation and comments at the top of imgui.cpp + effectively imgui.h. - 20+ standalone example applications using e.g. OpenGL/DirectX are provided in the @@ -1113,10 +1189,10 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: ---------- Q: About the ID Stack system.. - - Why is my widget not reacting when I click on it? - - How can I have widgets with an empty label? - - How can I have multiple widgets with the same label? - - How can I have multiple windows with the same label? + - How can I have multiple widgets with the same label? (using ## or PushID) + - How can I have widgets with an empty label? (using ##) + - How can I make a label dynamic? (using ###) + - General description of the label and ID Stack system. Q: How can I display an image? What is ImTextureID, how does it work? Q: How can I use my own math types instead of ImVec2? Q: How can I interact with standard C++ types (such as std::string and std::vector)? @@ -1259,6 +1335,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif // Debug options @@ -1266,12 +1343,13 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window // Default font size if unspecified in both style.FontSizeBase and AddFontXXX() calls. -static const float FONT_DEFAULT_SIZE = 20.0f; +static const float FONT_DEFAULT_SIZE_BASE = 20.0f; // When using Ctrl+Tab (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut. +static const float NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY = 0.60f; // Time to hold activation button (e.g. FaceDown) to turn the activation into a text input. static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. @@ -1321,6 +1399,7 @@ static void NavUpdateWindowing(); static void NavUpdateWindowingApplyFocus(ImGuiWindow* window); static void NavUpdateWindowingOverlay(); static void NavUpdateCancelRequest(); +static void NavUpdateContextMenuRequest(); static void NavUpdateCreateMoveRequest(); static void NavUpdateCreateTabbingRequest(); static float NavUpdatePageUpPageDown(); @@ -1331,8 +1410,8 @@ static bool NavScoreItem(ImGuiNavItemData* result, const ImRect& nav static void NavApplyItemToResult(ImGuiNavItemData* result); static void NavProcessItem(); static void NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags); -static ImGuiInputSource NavCalcPreferredRefPosSource(); -static ImVec2 NavCalcPreferredRefPos(); +static ImGuiInputSource NavCalcPreferredRefPosSource(ImGuiWindowFlags window_type); +static ImVec2 NavCalcPreferredRefPos(ImGuiWindowFlags window_type); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void NavRestoreLayer(ImGuiNavLayer layer); @@ -1460,15 +1539,16 @@ ImGuiStyle::ImGuiStyle() ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar - ScrollbarPadding = 2.0f; // Padding of scrollbar grab within its frame (same for both axises) + ScrollbarPadding = 2.0f; // Padding of scrollbar grab within its frame (same for both axes) GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + ImageRounding = 0.0f; // Rounding of Image() calls. ImageBorderSize = 0.0f; // Thickness of border around tabs. TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. - TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. + TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. FLT_MAX: never shrink, will behave like ImGuiTabBarFlags_FittingPolicyScroll. TabCloseButtonMinWidthSelected = -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. TabCloseButtonMinWidthUnselected = 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. @@ -1481,10 +1561,12 @@ 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. - SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText() + SeparatorSize = 1.0f; // Thickness of border in Separator(). + SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText(). SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. @@ -1515,17 +1597,22 @@ ImGuiStyle::ImGuiStyle() // Scale all spacing/padding/thickness values. Do not scale fonts. +// Consider not calling this if your initial scale factor if <1.0. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { _MainScale *= scale_factor; WindowPadding = ImTrunc(WindowPadding * scale_factor); WindowRounding = ImTrunc(WindowRounding * scale_factor); + WindowBorderSize = ImTrunc(WindowBorderSize * scale_factor); WindowMinSize = ImTrunc(WindowMinSize * scale_factor); WindowBorderHoverPadding = ImTrunc(WindowBorderHoverPadding * scale_factor); ChildRounding = ImTrunc(ChildRounding * scale_factor); + ChildBorderSize = ImTrunc(ChildBorderSize * scale_factor); PopupRounding = ImTrunc(PopupRounding * scale_factor); + PopupBorderSize = ImTrunc(PopupBorderSize * scale_factor); FramePadding = ImTrunc(FramePadding * scale_factor); + FrameBorderSize = ImTrunc(FrameBorderSize * scale_factor); FrameRounding = ImTrunc(FrameRounding * scale_factor); ItemSpacing = ImTrunc(ItemSpacing * scale_factor); ItemInnerSpacing = ImTrunc(ItemInnerSpacing * scale_factor); @@ -1539,17 +1626,24 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) GrabMinSize = ImTrunc(GrabMinSize * scale_factor); GrabRounding = ImTrunc(GrabRounding * scale_factor); LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor); + ImageRounding = ImTrunc(ImageRounding * scale_factor); ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor); TabRounding = ImTrunc(TabRounding * scale_factor); + TabBorderSize = ImTrunc(TabBorderSize * scale_factor); TabMinWidthBase = ImTrunc(TabMinWidthBase * scale_factor); TabMinWidthShrink = ImTrunc(TabMinWidthShrink * scale_factor); TabCloseButtonMinWidthSelected = (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor) : TabCloseButtonMinWidthSelected; TabCloseButtonMinWidthUnselected = (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor) : TabCloseButtonMinWidthUnselected; + TabBarBorderSize = ImTrunc(TabBarBorderSize * scale_factor); TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor); + TreeLinesSize = ImTrunc(TreeLinesSize * scale_factor); TreeLinesRounding = ImTrunc(TreeLinesRounding * scale_factor); DragDropTargetRounding = ImTrunc(DragDropTargetRounding * scale_factor); DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor); DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor); + ColorMarkerSize = ImTrunc(ColorMarkerSize * scale_factor); + SeparatorSize = ImTrunc(SeparatorSize * scale_factor); + SeparatorTextBorderSize = ImTrunc(SeparatorTextBorderSize * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); DockingSeparatorSize = ImTrunc(DockingSeparatorSize * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); @@ -1560,8 +1654,8 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) ImGuiIO::ImGuiIO() { // Most fields are initialized with zero - memset(this, 0, sizeof(*this)); - IM_STATIC_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT); + memset((void*)this, 0, sizeof(*this)); + IM_STATIC_ASSERT(IM_COUNTOF(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_COUNTOF(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT); // Settings ConfigFlags = ImGuiConfigFlags_None; @@ -1647,8 +1741,8 @@ ImGuiIO::ImGuiIO() MousePos = ImVec2(-FLT_MAX, -FLT_MAX); MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); MouseSource = ImGuiMouseSource_Mouse; - for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; - for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } + for (int i = 0; i < IM_COUNTOF(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_COUNTOF(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } AppAcceptingEvents = true; } @@ -1756,7 +1850,7 @@ void ImGuiIO::ClearInputMouse() key_data->DownDurationPrev = -1.0f; } MousePos = ImVec2(-FLT_MAX, -FLT_MAX); - for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++) + for (int n = 0; n < IM_COUNTOF(MouseDown); n++) { MouseDown[n] = false; MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f; @@ -2001,7 +2095,7 @@ void ImGuiIO::AddFocusEvent(bool focused) ImGuiPlatformIO::ImGuiPlatformIO() { // Most fields are initialized with zero - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); Platform_LocaleDecimalPoint = '.'; } @@ -2095,7 +2189,7 @@ bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; - return ((b1 == b2) && (b2 == b3)); + return (b1 == b2) && (b2 == b3); } void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) @@ -2426,11 +2520,8 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) #endif } -// Zero-terminated string hash, with support for ### to reset back to seed value -// We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. -// Because this syntax is rarely used we are optimizing for the common case. -// - If we reach ### in the string we discard the hash so far and reset to the seed. -// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build) +// Zero-terminated string hash, with support for ### to reset back to seed value. +// e.g. "label###id" outputs the same hash as "id" (and "label" is generally displayed by the UI functions) // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) { @@ -2442,11 +2533,16 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) #endif if (data_size != 0) { - while (data_size-- != 0) + while (data_size-- > 0) { unsigned char c = *data++; if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#') + { crc = seed; + data += 2; + data_size -= 2; + continue; + } #ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; #else @@ -2459,7 +2555,11 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) while (unsigned char c = *data++) { if (c == '#' && data[0] == '#' && data[1] == '#') + { crc = seed; + data += 2; + continue; + } #ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; #else @@ -2477,7 +2577,7 @@ const char* ImHashSkipUncontributingPrefix(const char* label) const char* result = label; while (unsigned char c = *label++) if (c == '#' && label[0] == '#' && label[1] == '#') - result = label - 1; + result = label + 2; return result; } @@ -2500,7 +2600,7 @@ ImFileHandle ImFileOpen(const char* filename, const char* mode) // We don't rely on current ImGuiContext as this is implied to be a helper function which doesn't depend on it (see #7314). wchar_t local_temp_stack[FILENAME_MAX]; ImVector local_temp_heap; - if (filename_wsize + mode_wsize > IM_ARRAYSIZE(local_temp_stack)) + if (filename_wsize + mode_wsize > IM_COUNTOF(local_temp_stack)) local_temp_heap.resize(filename_wsize + mode_wsize); wchar_t* filename_wbuf = local_temp_heap.Data ? local_temp_heap.Data : local_temp_stack; wchar_t* mode_wbuf = filename_wbuf + filename_wsize; @@ -3015,7 +3115,7 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 CountGrep = 0; if (default_filter) { - ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + ImStrncpy(InputBuf, default_filter, IM_COUNTOF(InputBuf)); Build(); } } @@ -3024,7 +3124,7 @@ bool ImGuiTextFilter::Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); - bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + bool value_changed = ImGui::InputText(label, InputBuf, IM_COUNTOF(InputBuf)); if (value_changed) Build(); return value_changed; @@ -3195,7 +3295,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE static bool GetSkipItemForListClipping() { ImGuiContext& g = *GImGui; - return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); + return g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems; } static void ImGuiListClipper_SortAndFuseRanges(ImVector& ranges, int offset = 0) @@ -3252,7 +3352,7 @@ static void ImGuiListClipper_SeekCursorAndSetupPrevLine(ImGuiListClipper* clippe ImGuiListClipper::ImGuiListClipper() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); } ImGuiListClipper::~ImGuiListClipper() @@ -3262,8 +3362,7 @@ ImGuiListClipper::~ImGuiListClipper() void ImGuiListClipper::Begin(int items_count, float items_height) { - if (Ctx == NULL) - Ctx = ImGui::GetCurrentContext(); + Ctx = ImGui::GetCurrentContext(); ImGuiContext& g = *Ctx; ImGuiWindow* window = g.CurrentWindow; @@ -3309,6 +3408,7 @@ void ImGuiListClipper::End() } TempData = NULL; } + DisplayStart = DisplayEnd = ItemsCount; // Clear this so code which may be reused past last Step() won't trip on a non-empty range. ItemsCount = -1; } @@ -3379,9 +3479,6 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) if (clipper->ItemsHeight <= 0.0f) { IM_ASSERT(data->StepNo == 1); - if (table) - IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); - bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision((float)clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) { @@ -3395,7 +3492,14 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) } if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. return false; - IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); + if (clipper->ItemsHeight <= 0.0f) + { + IM_ASSERT_USER_ERROR(clipper->ItemsHeight > 0.0f, "ImGuiListClipper: Failed to calculate item height! First item hasn't been submitted by user code, or has not moved the cursor vertically!"); + return false; + } + if (table) + IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); + calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } @@ -3420,7 +3524,8 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) if (is_nav_request) { data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringRect.Min.y, g.NavScoringRect.Max.y, nav_off_min, nav_off_max)); - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, nav_off_min, nav_off_max)); + if (!g.NavScoringNoClipRect.IsInverted()) + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, nav_off_min, nav_off_max)); } if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && g.NavTabbingDir == -1) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount)); @@ -3440,12 +3545,14 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. // RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. // As a workaround we currently half ItemSpacing worth on each side. - min_y -= g.Style.ItemSpacing.y; - max_y += g.Style.ItemSpacing.y; + float pad_y = g.Style.ItemSpacing.y; + min_y -= pad_y; + max_y += pad_y; // Box-select on 2D area requires different clipping. + // (best adding pad_y here than in BeginBoxSelect() as we are closer to current state) if (bs->UnclipMode) - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0)); + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y - pad_y, bs->UnclipRect.Max.y + pad_y, 0, 0)); } // Add main visible range @@ -3512,7 +3619,7 @@ bool ImGuiListClipper::Step() return ret; } -// Generic helper, equivalent to old ImGui::CalcListClipping() but statelesss +// Generic helper, equivalent to old ImGui::CalcListClipping() but stateless void ImGui::CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end) { *out_visible_start = ImMax((int)((clip_rect.Min.y - pos.y) / items_height), 0); @@ -3632,6 +3739,7 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarPadding) }, // ImGuiStyleVar_ScrollbarPadding { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageRounding) }, // ImGuiStyleVar_ImageRounding { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize) }, // ImGuiStyleVar_ImageBorderSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize @@ -3643,8 +3751,10 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesSize)}, // ImGuiStyleVar_TreeLinesSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesRounding)}, // ImGuiStyleVar_TreeLinesRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DragDropTargetRounding)}, // ImGuiStyleVar_DragDropTargetRounding { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorSize)}, // ImGuiStyleVar_SeparatorSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding @@ -3654,7 +3764,7 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = const ImGuiStyleVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) { IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); - IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarsInfo) == ImGuiStyleVar_COUNT); + IM_STATIC_ASSERT(IM_COUNTOF(GStyleVarsInfo) == ImGuiStyleVar_COUNT); return &GStyleVarsInfo[idx]; } @@ -3662,11 +3772,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { ImGuiContext& g = *GImGui; const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 1) - { - IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); - return; - } + IM_ASSERT_USER_ERROR_RET(var_info->DataType == ImGuiDataType_Float && var_info->Count == 1, "Calling PushStyleVar() variant with wrong type!"); float* pvar = (float*)var_info->GetVarPtr(&g.Style); g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; @@ -3676,11 +3782,7 @@ void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x) { ImGuiContext& g = *GImGui; const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2) - { - IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); - return; - } + IM_ASSERT_USER_ERROR_RET(var_info->DataType == ImGuiDataType_Float && var_info->Count == 2, "Calling PushStyleVar() variant with wrong type!"); ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); pvar->x = val_x; @@ -3690,11 +3792,7 @@ void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y) { ImGuiContext& g = *GImGui; const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2) - { - IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); - return; - } + IM_ASSERT_USER_ERROR_RET(var_info->DataType == ImGuiDataType_Float && var_info->Count == 2, "Calling PushStyleVar() variant with wrong type!"); ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); pvar->y = val_y; @@ -3704,11 +3802,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { ImGuiContext& g = *GImGui; const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2) - { - IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); - return; - } + IM_ASSERT_USER_ERROR_RET(var_info->DataType == ImGuiDataType_Float && var_info->Count == 2, "Calling PushStyleVar() variant with wrong type!"); ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; @@ -3759,6 +3853,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_CheckboxSelectedBg: return "CheckboxSelectedBg"; case ImGuiCol_SliderGrab: return "SliderGrab"; case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; case ImGuiCol_Button: return "Button"; @@ -3841,7 +3936,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool else { if (!text_end) - text_end = text + ImStrlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls) text_display_end = text_end; } @@ -3859,7 +3954,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end ImGuiWindow* window = g.CurrentWindow; if (!text_end) - text_end = text + ImStrlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls) if (text != text_end) { @@ -3928,8 +4023,8 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con text_end_full = FindRenderedTextEnd(text); const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f); - //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255)); - //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255)); + //draw_list->AddLineV(pos_max.x, pos_min.y - 4, pos_max.y + 6, IM_COL32(0, 0, 255, 255)); + //draw_list->AddLineV(ellipsis_max_x, pos_min.y - 2, pos_max.y + 3, IM_COL32(0, 255, 0, 255)); // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels. if (text_size.x > pos_max.x - pos_min.x) @@ -3948,13 +4043,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con // We can now claim the space between pos_max.x and ellipsis_max.x const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f); - float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x; - while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1])) - { - // Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text) - text_end_ellipsis--; - text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte - } + const float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x; // Render text, render ellipsis RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); @@ -3980,8 +4069,8 @@ void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders const float border_size = g.Style.FrameBorderSize; if (borders && border_size > 0.0f) { - window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, border_size); } } @@ -3992,11 +4081,20 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) const float border_size = g.Style.FrameBorderSize; if (border_size > 0.0f) { - window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, border_size); } } +void ImGui::RenderColorComponentMarker(const ImRect& bb, ImU32 col, float rounding) +{ + if (bb.Min.x + 1 >= bb.Max.x) + return; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + RenderRectFilledInRangeH(window->DrawList, bb, col, 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; @@ -4006,6 +4104,9 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl return; if (id == g.LastItemData.ID && (g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav)) return; + + // We don't early out on 'window->Flags & ImGuiWindowFlags_NoNavInputs' because it would be inconsistent with + // other code directly checking NavCursorVisible. Instead we aim for NavCursorVisible to always be false. ImGuiWindow* window = g.CurrentWindow; if (window->DC.NavHideHighlightOneFrame) return; @@ -4016,7 +4117,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl const float thickness = 2.0f; if (flags & ImGuiNavRenderCursorFlags_Compact) { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness); } else { @@ -4025,7 +4126,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness); if (!fully_visible) window->DrawList->PopClipRect(); } @@ -4059,7 +4160,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI); float a_max = a_min + IM_PI * 1.65f; draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max); - draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale); + draw_list->PathStroke(col_fill, 3.0f * scale); } draw_list->PopTexture(); } @@ -4161,7 +4262,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); if (shared_font_atlas == NULL) IO.Fonts->OwnerContext = this; - WithinEndChildID = 0; + WithinEndChildID = WithinEndPopupID = 0; TestEngine = NULL; InputEventsNextMouseSource = ImGuiMouseSource_Mouse; @@ -4231,6 +4332,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavWindow = NULL; NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; NavLayer = ImGuiNavLayer_Main; + NavIdItemFlags = ImGuiItemFlags_None; + NavOpenContextMenuItemId = NavOpenContextMenuWindowId = 0; NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; NavHighlightActivatedId = 0; @@ -4260,6 +4363,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this.. + ConfigNavEnableTabbing = true; ConfigNavWindowingWithGamepad = true; ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab); ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab); @@ -4299,6 +4403,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseStationaryTimer = 0.0f; InputTextPasswordFontBackupFlags = ImFontFlags_None; + InputTextReactivateId = 0; TempInputId = 0; memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; @@ -4328,6 +4433,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) SettingsLoaded = false; SettingsDirtyTimer = 0.0f; HookIdNext = 0; + DemoMarkerCallback = NULL; memset(LocalizationTable, 0, sizeof(LocalizationTable)); @@ -4401,7 +4507,7 @@ void ImGui::Initialize() TableSettingsAddSettingsHandler(); // Setup default localization table - LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS)); + LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_COUNTOF(GLocalizationEntriesEnUS)); // Setup default ImGuiPlatformIO clipboard/IME handlers. g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations @@ -4420,7 +4526,7 @@ void ImGui::Initialize() g.ViewportCreatedCount++; g.PlatformIO.Viewports.push_back(g.Viewports[0]); - // Build KeysMayBeCharInput[] lookup table (1 bool per named key) + // Build KeysMayBeCharInput[] lookup table (1 bit per named key) for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9) || key == ImGuiKey_Tab || key == ImGuiKey_Space || key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period @@ -4546,6 +4652,13 @@ void ImGui::Shutdown() g.Initialized = false; } +// When using multiple context it can be helpful to give name a name. +// (A) Will be visible in debugger, (B) Will be included in all IMGUI_DEBUG_LOG() calls, (C) Should be <= 15 characters long. +void ImGui::SetContextName(ImGuiContext* ctx, const char* name) +{ + ImStrncpy(ctx->ContextName, name, IM_COUNTOF(ctx->ContextName)); +} + // No specific ordering/dependency support, will see as needed ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) { @@ -4583,7 +4696,7 @@ void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) // ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NULL) { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); Ctx = ctx; Name = ImStrdup(name); NameBufLen = (int)ImStrlen(name) + 1; @@ -4628,13 +4741,13 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; if (window) { - bool backup_skip_items = window->SkipItems; - window->SkipItems = false; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) { ImGuiViewport* viewport = window->Viewport; g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() } + const bool backup_skip_items = window->SkipItems; + window->SkipItems = false; ImGui::UpdateCurrentFontSize(0.0f); window->SkipItems = backup_skip_items; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); @@ -4658,11 +4771,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(); @@ -4714,7 +4828,8 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdIsJustActivated = (g.ActiveId != id); if (g.ActiveIdIsJustActivated) { - IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"%s\") -> new:0x%08X (window \"%s\")\n", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : "", id, window ? window->Name : ""); + IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() 0x%08X in \"%s\"%*s(previously 0x%08X in \"%s\")\n", id, window ? window->Name : "", + ImMax(0, 20 - (int)(window ? strlen(window->Name) : 0)), "", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : ""); g.ActiveIdTimer = 0.0f; g.ActiveIdHasBeenPressedBefore = false; g.ActiveIdHasBeenEditedBefore = false; @@ -4770,8 +4885,12 @@ void ImGui::MarkItemEdited(ImGuiID id) // This marking is to be able to provide info for IsItemDeactivatedAfterEdit(). // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. ImGuiContext& g = *GImGui; + + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_EditedInternal; if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited) return; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; + if (g.ActiveId == id || g.ActiveId == 0) { // FIXME: Can't we fully rely on LastItemData yet? @@ -4785,9 +4904,6 @@ void ImGui::MarkItemEdited(ImGuiID id) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) // FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction. IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); - - //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; } bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) @@ -4881,7 +4997,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) // Test if another item is active (e.g. being dragged) const ImGuiID id = g.LastItemData.ID; if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) - if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap && !g.ActiveIdFromShortcut) { // When ActiveId == MoveId it means that either: // - (1) user clicked on void _or_ an item with no id, which triggers moving window (ActiveId is set even when window has _NoMove flag) @@ -4961,7 +5077,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag { g.HoveredIdPreviousFrameItemCount++; if (g.DebugDrawIdConflictsId == id) - window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, 2.0f); } #endif @@ -5124,6 +5240,13 @@ void ImGui::MemFree(void* ptr) return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData); } +void ImGui::DemoMarker(const char* file, int line, const char* section) +{ + ImGuiContext& g = *GImGui; + if (g.DemoMarkerCallback != NULL) + g.DemoMarkerCallback(file, line, section); +} + // We record the number of allocation in recent frames, as a way to audit/sanitize our guiding principles of "no allocations on idle/repeating frames" void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size) { @@ -5131,7 +5254,7 @@ void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr IM_UNUSED(ptr); if (entry->FrameCount != frame_count) { - info->LastEntriesIdx = (info->LastEntriesIdx + 1) % IM_ARRAYSIZE(info->LastEntriesBuf); + info->LastEntriesIdx = (info->LastEntriesIdx + 1) % IM_COUNTOF(info->LastEntriesBuf); entry = &info->LastEntriesBuf[info->LastEntriesIdx]; entry->FrameCount = frame_count; entry->AllocCount = entry->FreeCount = 0; @@ -5150,10 +5273,11 @@ void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr } } +// A conformant backend should return NULL on failure (e.g. clipboard data is not text). const char* ImGui::GetClipboardText() { ImGuiContext& g = *GImGui; - return g.PlatformIO.Platform_GetClipboardTextFn ? g.PlatformIO.Platform_GetClipboardTextFn(&g) : ""; + return g.PlatformIO.Platform_GetClipboardTextFn ? g.PlatformIO.Platform_GetClipboardTextFn(&g) : NULL; } void ImGui::SetClipboardText(const char* text) @@ -5216,7 +5340,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw { // Create the draw list on demand, because they are not frequently used for all viewports ImGuiContext& g = *GImGui; - IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->BgFgDrawLists)); + IM_ASSERT(drawlist_no < IM_COUNTOF(viewport->BgFgDrawLists)); ImDrawList* draw_list = viewport->BgFgDrawLists[drawlist_no]; if (draw_list == NULL) { @@ -5226,12 +5350,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; } @@ -5302,7 +5426,7 @@ void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* nod StartMouseMovingWindow(window); } -// This is not 100% symetric with StartMouseMovingWindow(). +// This is not 100% symmetric with StartMouseMovingWindow(). // We do NOT clear ActiveID, because: // - It would lead to rather confusing recursive code paths. Caller can call ClearActiveID() if desired. // - Some code intentionally cancel moving but keep the ActiveID to lock inputs (e.g. code path taken when clicking a disabled item). @@ -5317,7 +5441,7 @@ void ImGui::StopMouseMovingWindow() // Try to merge the window back into the main viewport. // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports) if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) - UpdateTryMergeWindowIntoHostViewport(window, g.MouseViewport); + UpdateTryMergeWindowIntoHostViewport(window->RootWindowDockTree, g.MouseViewport); // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button. if (!IsDragDropPayloadBeingAccepted()) @@ -5410,7 +5534,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() StartMouseMovingWindow(hovered_window); //-V595 // FIXME: In principle we might be able to call StopMouseMovingWindow() below. - // Please note how StartMouseMovingWindow() and StopMouseMovingWindow() and not entirely symetrical, at the later doesn't clear ActiveId. + // Please note how StartMouseMovingWindow() and StopMouseMovingWindow() and not entirely symmetrical, at the later doesn't clear ActiveId. // Cancel moving if clicked outside of title bar if ((hovered_window->BgClickFlags & ImGuiWindowBgClickFlags_Move) == 0) // set by io.ConfigWindowsMoveFromTitleBarOnly @@ -5471,7 +5595,7 @@ static void ScaleWindow(ImGuiWindow* window, float scale) static bool IsWindowActiveAndVisible(ImGuiWindow* window) { - return (window->Active) && (!window->Hidden); + return window->Active && !window->Hidden; } // The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) @@ -5508,7 +5632,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) const bool has_open_modal = (modal_window != NULL); int mouse_earliest_down = -1; bool mouse_any_down = false; - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) + for (int i = 0; i < IM_COUNTOF(io.MouseDown); i++) { if (io.MouseClicked[i]) { @@ -5613,8 +5737,8 @@ void ImGui::NewFrame() // Calculate frame-rate for the user, as a purely luxurious feature g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; - g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); - g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame)); + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_COUNTOF(g.FramerateSecPerFrame); + g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_COUNTOF(g.FramerateSecPerFrame)); g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX; // Process input queue (trickle as many events as possible), turn events into writes to IO structure @@ -5670,7 +5794,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(); } @@ -5684,6 +5808,8 @@ void ImGui::NewFrame() g.ActiveIdIsJustActivated = false; if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) g.TempInputId = 0; + if (g.InputTextReactivateId != 0 && g.InputTextReactivateId != g.DeactivatedItemData.ID) + g.InputTextReactivateId = 0; if (g.ActiveId == 0) { g.ActiveIdUsingNavDirMask = 0x00; @@ -5770,7 +5896,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; @@ -5780,7 +5907,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); } @@ -5828,7 +5955,7 @@ void ImGui::NewFrame() g.CurrentWindowStack.resize(0); g.BeginPopupStack.resize(0); g.ItemFlagsStack.resize(0); - g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags + g.ItemFlagsStack.push_back(ImGuiItemFlags_Default_); // Default flags g.CurrentItemFlags = g.ItemFlagsStack.back(); g.GroupStack.resize(0); @@ -5886,7 +6013,7 @@ static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) return d; if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) return d; - return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); + return a->BeginOrderWithinParent - b->BeginOrderWithinParent; } static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) @@ -5934,10 +6061,10 @@ static void FlattenDrawDataIntoSingleLayer(ImDrawDataBuilder* builder) { int n = builder->Layers[0]->Size; int full_size = n; - for (int i = 1; i < IM_ARRAYSIZE(builder->Layers); i++) + for (int i = 1; i < IM_COUNTOF(builder->Layers); i++) full_size += builder->Layers[i]->Size; builder->Layers[0]->resize(full_size); - for (int layer_n = 1; layer_n < IM_ARRAYSIZE(builder->Layers); layer_n++) + for (int layer_n = 1; layer_n < IM_COUNTOF(builder->Layers); layer_n++) { ImVector* layer = builder->Layers[layer_n]; if (layer->empty()) @@ -5998,14 +6125,6 @@ void ImGui::PopClipRect() window->ClipRect = window->DrawList->_ClipRectStack.back(); } -static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window) -{ - for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) - if (IsWindowActiveAndVisible(window->DC.ChildWindows[n])) - return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]); - return window; -} - static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) @@ -6017,15 +6136,16 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 // Draw behind window by moving the draw command at the FRONT of the draw list { // Draw list have been trimmed already, hence the explicit recreation of a draw command if missing. - // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate ImDrawCmd order. + // FIXME: This is a little bit complicated, solely to avoid creating/injecting an extra drawlist in drawdata. ImDrawList* draw_list = window->RootWindowDockTree->DrawList; draw_list->ChannelsMerge(); if (draw_list->CmdBuffer.Size == 0) draw_list->AddDrawCmd(); - draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that) - draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col); + draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to strictly ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that) ImDrawCmd cmd = draw_list->CmdBuffer.back(); - IM_ASSERT(cmd.ElemCount == 6); + IM_ASSERT(cmd.ElemCount == 0); + draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col); + cmd = draw_list->CmdBuffer.back(); draw_list->CmdBuffer.pop_back(); draw_list->CmdBuffer.push_front(cmd); draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command. @@ -6087,10 +6207,12 @@ static void ImGui::RenderDimmedBackgrounds() { // Draw dimming behind Ctrl+Tab target window and behind Ctrl+Tab UI window RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); - if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport) - RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport; - viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL; + if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Active && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport) + { + RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); + viewports_already_dimmed[1] = g.NavWindowingListWindow->Viewport; + } // Draw border around Ctrl+Tab target window ImGuiWindow* window = g.NavWindowingTargetAnim; @@ -6104,7 +6226,7 @@ static void ImGui::RenderDimmedBackgrounds() if (window->DrawList->CmdBuffer.Size == 0) window->DrawList->AddDrawCmd(); window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size); - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); // FIXME-DPI + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 3.0f); // FIXME-DPI window->DrawList->PopClipRect(); } @@ -6130,11 +6252,7 @@ void ImGui::EndFrame() // Don't process EndFrame() multiple times. if (g.FrameCountEnded == g.FrameCount) return; - if (!g.WithinFrameScope) - { - IM_ASSERT_USER_ERROR(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?"); - return; - } + IM_ASSERT_USER_ERROR_RET(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?"); CallContextHooks(&g, ImGuiContextHookType_EndFramePre); @@ -6156,10 +6274,14 @@ void ImGui::EndFrame() } g.WantTextInputNextFrame = ime_data->WantTextInput ? 1 : 0; - // Hide implicit/fallback "Debug" window if it hasn't been used + // Hide and unfocus implicit/fallback "Debug" window if it hasn't been used g.WithinFrameScopeWithImplicitWindow = false; - if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + if (g.CurrentWindow && g.CurrentWindow->IsFallbackWindow && g.CurrentWindow->WriteAccessed == false) + { g.CurrentWindow->Active = false; + if (g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow) + FocusWindow(NULL); + } End(); // Update navigation: Ctrl+Tab, wrap-around requests @@ -6254,7 +6376,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)); } @@ -6271,7 +6393,7 @@ void ImGui::Render() if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1]) AddRootWindowToDrawData(window); } - for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++) + for (int n = 0; n < IM_COUNTOF(windows_to_render_top_most); n++) if (windows_to_render_top_most[n] && IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily displayed as the top-most window AddRootWindowToDrawData(windows_to_render_top_most[n]); @@ -6286,7 +6408,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). @@ -6433,7 +6555,7 @@ bool ImGui::IsItemDeactivated() ImGuiContext& g = *GImGui; if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount); + return g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount; } bool ImGui::IsItemDeactivatedAfterEdit() @@ -6574,6 +6696,12 @@ ImVec2 ImGui::GetItemRectSize() return g.LastItemData.Rect.GetSize(); } +ImGuiItemFlags ImGui::GetItemFlags() +{ + ImGuiContext& g = *GImGui; + return g.LastItemData.ItemFlags; +} + // Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of 'ImGuiChildFlags child_flags = 0'. // ImGuiChildFlags_Borders is defined as always == 1 in order to allow old code passing 'true'. Read comments in imgui.h for details! bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) @@ -6604,10 +6732,8 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I IM_ASSERT((child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY)) != 0 && "Must use ImGuiChildFlags_AutoResizeX or ImGuiChildFlags_AutoResizeY with ImGuiChildFlags_AlwaysAutoResize!"); } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - //if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) - // child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; - //if (window_flags & ImGuiWindowFlags_NavFlattened) - // child_flags |= ImGuiChildFlags_NavFlattened; + //if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) { child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; } + //if (window_flags & ImGuiWindowFlags_NavFlattened) { child_flags |= ImGuiChildFlags_NavFlattened; } #endif if (child_flags & ImGuiChildFlags_AutoResizeX) child_flags &= ~ImGuiChildFlags_ResizeX; @@ -6618,7 +6744,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking; window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize)) - window_flags |= ImGuiWindowFlags_AlwaysAutoResize; + window_flags |= ImGuiWindowFlags_AlwaysAutoResize; // FIXME: Would be sane to not make single-axis flag set this. (#9355) if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; @@ -6767,6 +6893,14 @@ void ImGui::EndChild() g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } +ImGuiWindow* ImGui::FindFrontMostVisibleChildWindow(ImGuiWindow* window) +{ + for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) + if (IsWindowActiveAndVisible(window->DC.ChildWindows[n])) + return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]); + return window; +} + static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) { window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); @@ -6879,13 +7013,13 @@ static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) ImVec2 size_min; if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) { - size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : 4.0f; - size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : 4.0f; + size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : IMGUI_WINDOW_HARD_MIN_SIZE; + size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : IMGUI_WINDOW_HARD_MIN_SIZE; } else { - size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : 4.0f; - size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : 4.0f; + size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : IMGUI_WINDOW_HARD_MIN_SIZE; + size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : IMGUI_WINDOW_HARD_MIN_SIZE; } // Reduce artifacts with very small windows @@ -6951,11 +7085,11 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y; ImVec2 size_pad = window->WindowPadding * 2.0f; ImVec2 size_desired; - size_desired[ImGuiAxis_X] = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x; - size_desired[ImGuiAxis_Y] = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y; + size_desired.x = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x; + size_desired.y = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y; // Determine maximum window size - // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction + // Child windows are laid within their parent (unless they are also popups/menus) and thus have no restriction ImVec2 size_max = ImVec2(FLT_MAX, FLT_MAX); if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || (window->Flags & ImGuiWindowFlags_Popup) != 0) { @@ -6979,8 +7113,10 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont // When the window cannot fit all contents (either because of constraints, either because screen is too small), // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit); - bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); - bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); + float size_contents_for_scrollbar_x = (axis_mask & 1) ? size_contents.x : window->ContentSize.x; // See #9352. In theory this should use same logic as `window->ScrollbarY = ...` codepath in Begin(). Needs some plumbling. + float size_contents_for_scrollbar_y = (axis_mask & 2) ? size_contents.y : window->ContentSize.y; + bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x < size_contents_for_scrollbar_x + size_pad.x + decoration_w_without_scrollbars && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); + bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y < size_contents_for_scrollbar_y + size_pad.y + decoration_h_without_scrollbars && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); if (will_have_scrollbar_x) size_auto_fit.y += style.ScrollbarSize; if (will_have_scrollbar_y) @@ -7279,7 +7415,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, int* border_hove if (nav_resize_dir.x != 0.0f || nav_resize_dir.y != 0.0f) { const float NAV_RESIZE_SPEED = 600.0f; - const float resize_step = NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y); + const float resize_step = NAV_RESIZE_SPEED * g.IO.DeltaTime * GetScale(); g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step; g.NavWindowingAccumDeltaSize = ImMax(g.NavWindowingAccumDeltaSize, clamp_rect.Min - window->Pos - window->Size); // We need Pos+Size >= clmap_rect.Min, so Size >= clmap_rect.Min - Pos, so size_delta >= clmap_rect.Min - window->Pos - window->Size g.NavWindowingToggleLayer = false; @@ -7334,7 +7470,7 @@ static void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU const ImRect border_r = GetResizeBorderRect(window, border_n, rounding, 0.0f); window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); - window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size); + window->DrawList->PathStroke(border_col, border_size); } static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) @@ -7343,7 +7479,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) const float border_size = window->WindowBorderSize; const ImU32 border_col = GetColorU32(ImGuiCol_Border); if (border_size > 0.0f && (window->Flags & ImGuiWindowFlags_NoBackground) == 0) - window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, 0, window->WindowBorderSize); + window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, window->WindowBorderSize); else if (border_size > 0.0f) { if (window->ChildFlags & ImGuiChildFlags_ResizeX) // Similar code as 'resize_border_mask' computation in UpdateWindowManualResize() but we specifically only always draw explicit child resize border. @@ -7360,7 +7496,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { float y = window->Pos.y + window->TitleBarHeight - 1; - window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize); + window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); } } @@ -7428,15 +7564,26 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); } - // Render, for docked windows and host windows we ensure bg goes before decorations + // Render, for docked windows and host windows we ensure BG goes before decorations if (window->DockIsActive) window->DockNode->LastBgColor = bg_col; - ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList; - if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost)) - bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); - bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); - if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost)) - bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); + if (flags & ImGuiWindowFlags_DockNodeHost) + bg_col = 0; + if (bg_col & IM_COL32_A_MASK) + { + ImRect bg_rect(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size); + ImDrawFlags bg_rounding_flags; + if (window->DockIsActive) + bg_rounding_flags = CalcRoundingFlagsForRectInRect(bg_rect, window->DockNode->HostWindow->Rect(), 0.0f); + else + bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersBottom; + ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList; + if (window->DockIsActive) + bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); + bg_draw_list->AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags); + if (window->DockIsActive) + bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); + } } if (window->DockIsActive) window->DockNode->IsBgDrawnThisFrame = true; @@ -7459,7 +7606,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock @@ -7737,10 +7884,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond); if (first_begin_of_the_frame) { - bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL); - bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window); - bool dock_node_was_visible = window->DockNodeIsVisible; - bool dock_tab_was_visible = window->DockTabIsVisible; + const bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL); + const bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window); + const bool dock_node_was_visible = window->DockNodeIsVisible; + const bool dock_tab_was_visible = window->DockTabIsVisible; + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + if (has_dock_node || new_auto_dock_node) { BeginDocked(window, p_open); @@ -7758,10 +7907,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); } } - else - { - window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; - } } // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack @@ -7918,11 +8063,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) bool window_title_visible_elsewhere = false; if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive)) window_title_visible_elsewhere = true; - else if (g.NavWindowingListWindow != NULL && (flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using Ctrl+Tab + else if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->WasActive && (flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using Ctrl+Tab window_title_visible_elsewhere = true; else if (flags & ImGuiWindowFlags_ChildMenu) window_title_visible_elsewhere = true; - if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0) + if ((window_title_visible_elsewhere || window_just_activated_by_user) && !window_just_created && strcmp(name, window->Name) != 0) { size_t buf_len = (size_t)window->NameBufLen; window->Name = ImStrdupcpy(window->Name, &buf_len, name); @@ -8159,10 +8304,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) // Large values tend to lead to variety of artifacts and are not recommended. - if (window->ViewportOwned || window->DockIsActive) + if ((flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive) + window->WindowRounding = style.ChildRounding; + else if (window->RootWindowDockTree->ViewportOwned) window->WindowRounding = 0.0f; else - window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + window->WindowRounding = ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts. //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar)) @@ -8247,12 +8394,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)); ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + scrollbar_sizes_from_last_frame; ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f; - float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; - float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; + float size_for_scrollbars_x = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; + float size_for_scrollbars_y = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; bool scrollbar_x_prev = window->ScrollbarX; //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? - window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); - window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_for_scrollbars_y) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_for_scrollbars_x - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488) // (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause. @@ -8267,7 +8414,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize; if (window->ScrollbarX && !window->ScrollbarY) - window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarY = (needed_size_from_last_frame.y > size_for_scrollbars_y - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); // Amend the partially filled window->DecorationXXX values. @@ -8436,14 +8583,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; - // Default item width. Make it proportional to window size if window manually resizes - if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) - window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); + // Default item width. Make it proportional to window size if window can be manually resized. + // (we cannot use AutoFitFramesX/AutoFitFramesY which is a temporary state) + bool is_resizable_width; + if (flags & ImGuiWindowFlags_ChildWindow) + is_resizable_width = (window->Size.x > 0.0f) && !(window->ChildFlags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AlwaysAutoResize)); else - window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); - window->DC.ItemWidth = window->ItemWidthDefault; - window->DC.TextWrapPos = -1.0f; // disabled + is_resizable_width = (window->Size.x > 0.0f) && !(flags & ImGuiWindowFlags_AlwaysAutoResize); + if (is_resizable_width) + window->DC.ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); + else + window->DC.ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); + window->DC.ItemWidth = window->DC.ItemWidthDefault; window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPos = -1.0f; // Disabled window->DC.TextWrapPosStack.resize(0); if (flags & ImGuiWindowFlags_Modal) window->DC.ModalDimBgColor = ColorConvertFloat4ToU32(GetStyleColorVec4(ImGuiCol_ModalWindowDimBg)); @@ -8509,7 +8662,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // Set default BgClickFlags - // This is set at the end of this function, so UpdateWindowManualResize()/ClampWindowPos() may use last-frame value if overriden by user code. + // This is set at the end of this function, so UpdateWindowManualResize()/ClampWindowPos() may use last-frame value if overridden by user code. // FIXME: The general intent is that we will later expose config options to default to enable scrolling + select scrolling mouse button. window->BgClickFlags = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->BgClickFlags : (g.IO.ConfigWindowsMoveFromTitleBarOnly ? ImGuiWindowBgClickFlags_None : ImGuiWindowBgClickFlags_Move); @@ -8659,6 +8812,8 @@ void ImGui::End() ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back(); // Error checking: verify that user doesn't directly call End() on a child window. + if (window->Flags & ImGuiWindowFlags_Popup) + IM_ASSERT_USER_ERROR(g.WithinEndPopupID == window->ID, "Must call EndPopup() and not End()!"); if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive) IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!"); @@ -8722,11 +8877,7 @@ void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) void ImGui::PopItemFlag() { ImGuiContext& g = *GImGui; - if (g.ItemFlagsStack.Size <= 1) - { - IM_ASSERT_USER_ERROR(0, "Calling PopItemFlag() too many times!"); - return; - } + IM_ASSERT_USER_ERROR_RET(g.ItemFlagsStack.Size > 1, "Calling PopItemFlag() too many times!"); g.ItemFlagsStack.pop_back(); g.CurrentItemFlags = g.ItemFlagsStack.back(); } @@ -8756,11 +8907,7 @@ void ImGui::BeginDisabled(bool disabled) void ImGui::EndDisabled() { ImGuiContext& g = *GImGui; - if (g.DisabledStackSize <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling EndDisabled() too many times!"); - return; - } + IM_ASSERT_USER_ERROR_RET(g.DisabledStackSize > 0, "Calling EndDisabled() too many times!"); g.DisabledStackSize--; bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; //PopItemFlag(); @@ -8787,30 +8934,27 @@ void ImGui::BeginDisabledOverrideReenable() void ImGui::EndDisabledOverrideReenable() { ImGuiContext& g = *GImGui; - g.DisabledStackSize--; IM_ASSERT(g.DisabledStackSize > 0); + g.DisabledStackSize--; g.ItemFlagsStack.pop_back(); g.CurrentItemFlags = g.ItemFlagsStack.back(); g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup; } -void ImGui::PushTextWrapPos(float wrap_pos_x) +// ATTENTION THIS IS IN LEGACY LOCAL SPACE. +void ImGui::PushTextWrapPos(float wrap_local_pos_x) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); - window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPos = wrap_local_pos_x; } void ImGui::PopTextWrapPos() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (window->DC.TextWrapPosStack.Size <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling PopTextWrapPos() too many times!"); - return; - } + IM_ASSERT_USER_ERROR_RET(window->DC.TextWrapPosStack.Size > 0, "Calling PopTextWrapPos() too many times!"); window->DC.TextWrapPos = window->DC.TextWrapPosStack.back(); window->DC.TextWrapPosStack.pop_back(); } @@ -9262,15 +9406,22 @@ void ImGui::PushFocusScope(ImGuiID id) void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; - if (g.FocusScopeStack.Size <= g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack) - { - IM_ASSERT_USER_ERROR(0, "Calling PopFocusScope() too many times!"); - return; - } + IM_ASSERT_USER_ERROR_RET(g.FocusScopeStack.Size > g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack, "Calling PopFocusScope() too many times!"); g.FocusScopeStack.pop_back(); g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0; } +bool ImGui::IsInNavFocusRoute(ImGuiID focus_scope_id) +{ + ImGuiContext& g = *GImGui; + if (g.NavFocusScopeId == focus_scope_id) + return true; + for (const ImGuiFocusScopeData& focus_scope : g.NavFocusRoute) + if (focus_scope.ID == focus_scope_id) + return true; + return false; +} + void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) { ImGuiContext& g = *GImGui; @@ -9479,7 +9630,7 @@ void ImGui::UpdateFontsNewFrame() // Apply default font size the first time ImFont* font = ImGui::GetDefaultFont(); if (g.Style.FontSizeBase <= 0.0f) - g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE); + g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE_BASE); // Set initial font g.Font = font; @@ -9505,7 +9656,7 @@ ImFont* ImGui::GetDefaultFont() return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; } -// EXPERIMENTAL: DO NOT USE YET. +// EXPERIMENTAL. Use ImTextureDataQueueUpload() to queue updates. void ImGui::RegisterUserTexture(ImTextureData* tex) { ImGuiContext& g = *GImGui; @@ -9581,16 +9732,6 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) g.Style.FontSizeBase = g.FontSizeBase; - // Early out to avoid hidden window keeping bakes referenced and out of GC reach. - // However this would leave a pretty subtle and damning error surface area if g.FontBaked was mismatching. - // FIXME: perhaps g.FontSize should be updated? - if (window != NULL && window->SkipItems) - { - ImGuiTable* table = g.CurrentTable; - if (table == NULL || (table->CurrentColumn != -1 && table->Columns[table->CurrentColumn].IsSkipItems == false)) // See 8465#issuecomment-2951509561 and #8865. Ideally the SkipItems=true in tables would be amended with extra data. - return; - } - // Restoring is pretty much only used by PopFont() float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; if (final_size == 0.0f) @@ -9599,7 +9740,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) // Global scale factors final_size *= g.Style.FontScaleMain; // Main global scale factor - final_size *= g.Style.FontScaleDpi; // Per-monitor/viewport DPI scale factor, automatically updated when io.ConfigDpiScaleFonts is enabled. + final_size *= g.Style.FontScaleDpi; // Per-monitor/viewport DPI scale factor (in docking branch: automatically updated when io.ConfigDpiScaleFonts is enabled). // Window scale (mostly obsolete now) if (window != NULL) @@ -9620,10 +9761,24 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) final_size = ImClamp(final_size, 1.0f, IMGUI_FONT_SIZE_MAX); if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.FontSize = final_size; - g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; - g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontSize = g.FontSize; + + // Early out to avoid hidden window keeping bakes referenced and out of GC reach. + // - However this leave a pretty subtle and damning error surface area if g.FontBaked was mismatching. + // Probably needs to be reevaluated into e.g. setting g.FontBaked = nullptr to mark it as dirty. + // - Note that 'PushFont(); Begin(); End(); PopFont()' from within any collapsed window is not compromised, because Begin() calls SetCurrentWindow()->...->UpdateCurrentSize() + if (window != NULL && window->SkipItems) + { + ImGuiTable* table = g.CurrentTable; + const bool allow_early_out = table == NULL || (table->CurrentColumn != -1 && table->Columns[table->CurrentColumn].IsSkipItems == false); // See 8465#issuecomment-2951509561 and #8865. Ideally the SkipItems=true in tables would be amended with extra data. + if (allow_early_out) + return; + } + + g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; + g.FontBakedScale = (g.FontBaked != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontScale = g.FontBakedScale; } @@ -9657,11 +9812,7 @@ void ImGui::PushFont(ImFont* font, float font_size_base) void ImGui::PopFont() { ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); - return; - } + IM_ASSERT_USER_ERROR_RET(g.FontStack.Size > 0, "Calling PopFont() too many times!"); ImFontStackData* font_stack_data = &g.FontStack.back(); SetCurrentFont(font_stack_data->Font, font_stack_data->FontSizeBeforeScaling, font_stack_data->FontSizeAfterScaling); g.FontStack.pop_back(); @@ -9801,11 +9952,7 @@ ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) void ImGui::PopID() { ImGuiWindow* window = GImGui->CurrentWindow; - if (window->IDStack.Size <= 1) - { - IM_ASSERT_USER_ERROR(0, "Calling PopID() too many times!"); - return; - } + IM_ASSERT_USER_ERROR_RET(window->IDStack.Size > 1, "Calling PopID() too many times!"); window->IDStack.pop_back(); } @@ -9959,7 +10106,7 @@ static const char* const GKeyNames[] = "MouseLeft", "MouseRight", "MouseMiddle", "MouseX1", "MouseX2", "MouseWheelX", "MouseWheelY", "ModCtrl", "ModShift", "ModAlt", "ModSuper", // ReservedForModXXX are showing the ModXXX names. }; -IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames)); +IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_COUNTOF(GKeyNames)); const char* ImGui::GetKeyName(ImGuiKey key) { @@ -9983,7 +10130,7 @@ const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (IsLRModKey(key)) key_chord &= ~GetModForLRModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift" - ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s", + ImFormatString(g.TempKeychordName, IM_COUNTOF(g.TempKeychordName), "%s%s%s%s%s", (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", (key_chord & ImGuiMod_Shift) ? "Shift+" : "", (key_chord & ImGuiMod_Alt) ? "Alt+" : "", @@ -10007,7 +10154,7 @@ int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, flo if (t0 >= t1) return 0; if (repeat_rate <= 0.0f) - return (t0 < repeat_delay) && (t1 >= repeat_delay); + return t0 < repeat_delay && t1 >= repeat_delay; const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate); const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate); const int count = count_t1 - count_t0; @@ -10391,14 +10538,14 @@ bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id) bool ImGui::IsMouseDown(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array. } bool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyDown(MouseButtonToKey(button), owner_id), but this allows legacy code hijacking the io.Mousedown[] array. } @@ -10410,7 +10557,7 @@ bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership) return false; const float t = g.IO.MouseDownDuration[button]; @@ -10432,14 +10579,14 @@ bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGui bool ImGui::IsMouseReleased(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any) } bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } @@ -10449,7 +10596,7 @@ bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]); return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay); } @@ -10457,21 +10604,21 @@ bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay) bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); } bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), owner_id); } int ImGui::GetMouseClickedCount(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); return g.IO.MouseClickedCount[button]; } @@ -10500,7 +10647,7 @@ bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool c bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); if (lock_threshold < 0.0f) lock_threshold = g.IO.MouseDragThreshold; return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; @@ -10509,7 +10656,7 @@ bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_thresho bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); if (!g.IO.MouseDown[button]) return false; return IsMouseDragPastThreshold(button, lock_threshold); @@ -10556,7 +10703,7 @@ bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) bool ImGui::IsAnyMouseDown() { ImGuiContext& g = *GImGui; - for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + for (int n = 0; n < IM_COUNTOF(g.IO.MouseDown); n++) if (g.IO.MouseDown[n]) return true; return false; @@ -10568,7 +10715,7 @@ bool ImGui::IsAnyMouseDown() ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); if (lock_threshold < 0.0f) lock_threshold = g.IO.MouseDragThreshold; if (g.IO.MouseDown[button] || g.IO.MouseReleased[button]) @@ -10581,7 +10728,7 @@ ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold) void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr g.IO.MouseClickedPos[button] = g.IO.MousePos; } @@ -10616,11 +10763,12 @@ static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value) // [Internal] Do not use directly static ImGuiKeyChord GetMergedModsFromKeys() { + // Bypass IsKeyDown() for the unlikely case where user used a ImGuiInputFlags_LockXXXX on those. ImGuiKeyChord mods = 0; - if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) { mods |= ImGuiMod_Ctrl; } - if (ImGui::IsKeyDown(ImGuiMod_Shift)) { mods |= ImGuiMod_Shift; } - if (ImGui::IsKeyDown(ImGuiMod_Alt)) { mods |= ImGuiMod_Alt; } - if (ImGui::IsKeyDown(ImGuiMod_Super)) { mods |= ImGuiMod_Super; } + if (ImGui::GetKeyData(ImGuiMod_Ctrl)->Down) { mods |= ImGuiMod_Ctrl; } + if (ImGui::GetKeyData(ImGuiMod_Shift)->Down) { mods |= ImGuiMod_Shift; } + if (ImGui::GetKeyData(ImGuiMod_Alt)->Down) { mods |= ImGuiMod_Alt; } + if (ImGui::GetKeyData(ImGuiMod_Super)->Down) { mods |= ImGuiMod_Super; } return mods; } @@ -10724,7 +10872,7 @@ static void ImGui::UpdateMouseInputs() if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) g.NavHighlightItemUnderNav = false; - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) + for (int i = 0; i < IM_COUNTOF(io.MouseDown); i++) { io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; io.MouseClickedCount[i] = 0; // Will be filled below @@ -10843,15 +10991,21 @@ void ImGui::UpdateMouseWheel() LockWheelingWindow(NULL, 0.0f); } - ImVec2 wheel; - wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f; - wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f; - - //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y); ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; if (!mouse_window || mouse_window->Collapsed) return; + ImGuiID owner_id = mouse_window->ID; + ImVec2 wheel; + wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, owner_id) ? g.IO.MouseWheelH : 0.0f; + wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, owner_id) ? g.IO.MouseWheel : 0.0f; + //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y); + if (g.WheelingWindow != NULL) + { + SetKeyOwner(ImGuiKey_MouseWheelX, owner_id); + SetKeyOwner(ImGuiKey_MouseWheelY, owner_id); + } + // Zoom / Scale window // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned. if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling) @@ -10934,7 +11088,7 @@ void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse) static const char* GetInputSourceName(ImGuiInputSource source) { const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad" }; - IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); + IM_ASSERT(IM_COUNTOF(input_source_names) == ImGuiInputSource_COUNT); if (source < 0 || source >= ImGuiInputSource_COUNT) return "Unknown"; return input_source_names[source]; @@ -10942,7 +11096,7 @@ static const char* GetInputSourceName(ImGuiInputSource source) static const char* GetMouseSourceName(ImGuiMouseSource source) { const char* mouse_source_names[] = { "Mouse", "TouchScreen", "Pen" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT); + IM_ASSERT(IM_COUNTOF(mouse_source_names) == ImGuiMouseSource_COUNT); if (source < 0 || source >= ImGuiMouseSource_COUNT) return "Unknown"; return mouse_source_names[source]; @@ -10950,12 +11104,13 @@ static const char* GetMouseSourceName(ImGuiMouseSource source) static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) { ImGuiContext& g = *GImGui; + char buf[5]; if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (%.1f, %.1f) (%s)\n", prefix, e->MousePos.PosX, e->MousePos.PosY, GetMouseSourceName(e->MousePos.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseButton %d %s (%s)\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up", GetMouseSourceName(e->MouseButton.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseWheel (%.3f, %.3f) (%s)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY, GetMouseSourceName(e->MouseWheel.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseViewport){IMGUI_DEBUG_LOG_IO("[io] %s: MouseViewport (0x%08X)\n", prefix, e->MouseViewport.HoveredViewportID); return; } if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("[io] %s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; } - if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("[io] %s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; } + if (e->Type == ImGuiInputEventType_Text) { ImTextCharToUtf8(buf, e->Text.Char); IMGUI_DEBUG_LOG_IO("[io] %s: Text: '%s' (U+%08X)\n", prefix, buf, e->Text.Char); return; } if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("[io] %s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; } } #endif @@ -11088,6 +11243,8 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) #endif // Remaining events will be processed on the next frame + // FIXME-MULTITHREADING: io.AddKeyEvent() etc. calls are mostly thread-safe apart from the fact they push to this + // queue which may be resized here. Could potentially rework this to narrow down the section needing a mutex? (#5772) if (event_n == g.InputEventsQueue.Size) g.InputEventsQueue.resize(0); else @@ -11136,7 +11293,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); if (owner_id == ImGuiKeyOwner_Any) - return (owner_data->LockThisFrame == false); + return owner_data->LockThisFrame == false; // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of ActiveId/HoveredId // are acting as filter before this has a chance to filter), but sane as soon as user tries to look into things. @@ -11157,6 +11314,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) // - SetKeyOwner(..., None) : clears owner // - SetKeyOwner(..., Any, !Lock) : illegal (assert) // - SetKeyOwner(..., Any or None, Lock) : set lock +// Ownership is automatically released on the frame after a release, see code in UpdateKeyboardInputs(). void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; @@ -11183,30 +11341,34 @@ void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, I if (key_chord & ~ImGuiMod_Mask_) { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); } } -// This is more or less equivalent to: +// This is more or less equivalent to a fancier version of: // if (IsItemHovered() || IsItemActive()) // SetKeyOwner(key, GetItemID()); // Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times. // More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition. // Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and priority. -void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) +bool ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; ImGuiID id = g.LastItemData.ID; if (id == 0 || (g.HoveredId != id && g.ActiveId != id)) - return; + return false; if ((flags & ImGuiInputFlags_CondMask_) == 0) flags |= ImGuiInputFlags_CondDefault_; if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive))) { IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function! + if (!TestKeyOwner(key, id)) + return false; SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_); + return true; } + return false; } -void ImGui::SetItemKeyOwner(ImGuiKey key) +bool ImGui::SetItemKeyOwner(ImGuiKey key) { - SetItemKeyOwner(key, ImGuiInputFlags_None); + return SetItemKeyOwner(key, ImGuiInputFlags_None); } // This is the only public API until we expose owner_id versions of the API as replacements. @@ -11347,7 +11509,8 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si // to extend contents size of our parent container (e.g. window contents size, which is used for auto-resizing // windows, table column contents size used for auto-resizing columns, group size). // This was causing issues and ambiguities and we needed to retire that. -// From 1.89, extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. +// 2022/08/05 (1.89): extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. However we gated the new logic behind a '#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS' block. +// 2025/06/25 (1.92): removed the legacy path and turned into an assert. It was a mistake that there was a #ifndef before: our obsolescence schedule gets pushed back a bit more :( // // Previously this would make the window content size ~200x200: // Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK ANYMORE @@ -11700,6 +11863,8 @@ bool ImGui::ErrorLog(const char* msg) return g.IO.ConfigErrorRecoveryEnableAssert; } +// Display an error tooltip when same ID as HoveredId was submitted multiple times. +// See code in ItemHoverable() for an explanation of why we associate this error to HoveredId + code drawing of rectangles over individual items instances. void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() { #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -12103,7 +12268,7 @@ void ImGui::PushItemWidth(float item_width) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width - window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidth = (item_width == 0.0f ? window->DC.ItemWidthDefault : item_width); g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } @@ -12629,7 +12794,7 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext const char* window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d"; char window_name[32]; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount); + ImFormatString(window_name, IM_COUNTOF(window_name), window_name_template, g.TooltipOverrideCount); ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking; Begin(window_name, NULL, flags | extra_window_flags); // 2023-03-09: Added bool return value to the API, but currently always returning true. @@ -12817,7 +12982,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) popup_ref.RestoreNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type). popup_ref.OpenFrameCount = g.FrameCount; popup_ref.OpenParentId = parent_window->IDStack.back(); - popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); + popup_ref.OpenPopupPos = NavCalcPreferredRefPos(ImGuiWindowFlags_Popup); popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos; IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopupEx(0x%08X)\n", id); @@ -12883,16 +13048,16 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to // - Each popups may contain child windows, which is why we compare ->RootWindowDockTree! // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child // We step through every popup from bottom to top to validate their position relative to reference window. - bool ref_window_is_descendent_of_popup = false; + bool ref_window_is_descendant_of_popup = false; for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++) if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window) //if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree) // FIXME-MERGE if (IsWindowWithinBeginStackOf(ref_window, popup_window)) { - ref_window_is_descendent_of_popup = true; + ref_window_is_descendant_of_popup = true; break; } - if (!ref_window_is_descendent_of_popup) + if (!ref_window_is_descendant_of_popup) break; } } @@ -12986,7 +13151,7 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) char name[20]; IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx() - ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // No recycling, so we can close/open during the same frame + ImFormatString(name, IM_COUNTOF(name), "##Popup_%08x", id); // No recycling, so we can close/open during the same frame bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) @@ -13004,9 +13169,20 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext return false; } + // As we bypass BeginChild(), set ImGuiChildFlags_AlwaysAutoResize as it is checked independently from ImGuiWindowFlags_AlwaysAutoResize for now (see #9355) + // Ideally we should remove setting ImGuiWindowFlags_AlwaysAutoResize in BeginChild(). + if ((extra_window_flags & ImGuiWindowFlags_ChildWindow) && (extra_window_flags & ImGuiWindowFlags_AlwaysAutoResize)) + { + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) + g.NextWindowData.ChildFlags |= ImGuiChildFlags_AlwaysAutoResize; + else + g.NextWindowData.ChildFlags = ImGuiChildFlags_AlwaysAutoResize; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; + } + char name[128]; IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); - ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth + ImFormatString(name, IM_COUNTOF(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); @@ -13069,35 +13245,70 @@ void ImGui::EndPopup() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || g.BeginPopupStack.Size == 0) - { - IM_ASSERT_USER_ERROR(0, "Calling EndPopup() too many times or in wrong window!"); - return; - } + IM_ASSERT_USER_ERROR_RET((window->Flags & ImGuiWindowFlags_Popup) != 0 && g.BeginPopupStack.Size > 0, "Calling EndPopup() in wrong window!"); // Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests) if (g.NavWindow == window) NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY); // Child-popups don't need to be laid out + const ImGuiID backup_within_end_popup_id = g.WithinEndPopupID; const ImGuiID backup_within_end_child_id = g.WithinEndChildID; + g.WithinEndPopupID = window->ID; if (window->Flags & ImGuiWindowFlags_ChildWindow) g.WithinEndChildID = window->ID; End(); + g.WithinEndPopupID = backup_within_end_popup_id; g.WithinEndChildID = backup_within_end_child_id; } +ImGuiMouseButton ImGui::GetMouseButtonFromPopupFlags(ImGuiPopupFlags flags) +{ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if ((flags & ImGuiPopupFlags_InvalidMask_) != 0) // 1,2 --> ImGuiMouseButton_Right, ImGuiMouseButton_Middle + return (flags & ImGuiPopupFlags_InvalidMask_); +#else + IM_ASSERT((flags & ImGuiPopupFlags_InvalidMask_) == 0); +#endif + if (flags & ImGuiPopupFlags_MouseButtonMask_) + return ((flags & ImGuiPopupFlags_MouseButtonMask_) >> ImGuiPopupFlags_MouseButtonShift_) - 1; + return ImGuiMouseButton_Right; // Default == 1 +} + +bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return true; + if (g.NavOpenContextMenuItemId == id && (IsItemFocused() || id == g.CurrentWindow->MoveId)) + return true; + return false; +} + +bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) + return true; + if (g.NavOpenContextMenuWindowId && g.CurrentWindow->ID) + if (IsWindowChildOf(g.NavWindow, g.CurrentWindow, false, false)) // This enable ordering to be used to disambiguate item vs window (#8803) + return true; + return false; +} + // Helper to open a popup if mouse button is released over the item // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID)) { - ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) OpenPopupEx(id, popup_flags); } } @@ -13124,10 +13335,9 @@ bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flag ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) - int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItem ID. Using LastItem ID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID)) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } @@ -13139,10 +13349,8 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl if (!str_id) str_id = "window_context"; ImGuiID id = window->GetID(str_id); - int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); - if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) - OpenPopupEx(id, popup_flags); + if (IsPopupOpenRequestForWindow(popup_flags)) + OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } @@ -13153,7 +13361,7 @@ bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flag if (!str_id) str_id = "void_context"; ImGuiID id = window->GetID(str_id); - int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) if (GetTopMostPopupModal() == NULL) OpenPopupEx(id, popup_flags); @@ -13293,9 +13501,9 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) // as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin(). IM_ASSERT(g.CurrentWindow == window); const float scale = g.Style.MouseCursorScale; - const ImVec2 ref_pos = NavCalcPreferredRefPos(); + const ImVec2 ref_pos = NavCalcPreferredRefPos(ImGuiWindowFlags_Tooltip); - if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen && NavCalcPreferredRefPosSource() == ImGuiInputSource_Mouse) + if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen && NavCalcPreferredRefPosSource(ImGuiWindowFlags_Tooltip) == ImGuiInputSource_Mouse) { ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size); if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size))) @@ -13377,7 +13585,7 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) if (flags & ImGuiFocusedFlags_ChildWindows) return IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy); else - return (ref_window == cur_window); + return ref_window == cur_window; } static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) @@ -13435,6 +13643,7 @@ void ImGui::BringWindowToFocusFront(ImGuiWindow* window) } // Note technically focus related but rather adjacent and close to BringWindowToFocusFront() +// FIXME-FOCUS: Could opt-in/opt-out enable modal check like in FocusWindow(). void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -13610,7 +13819,9 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind void ImGui::SetNavCursorVisible(bool visible) { ImGuiContext& g = *GImGui; - if (g.IO.ConfigNavCursorVisibleAlways) + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + visible = false; + else if (g.IO.ConfigNavCursorVisibleAlways) visible = true; g.NavCursorVisible = visible; } @@ -13619,7 +13830,13 @@ void ImGui::SetNavCursorVisible(bool visible) void ImGui::SetNavCursorVisibleAfterMove() { ImGuiContext& g = *GImGui; - if (g.IO.ConfigNavCursorVisibleAuto) + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavCursorVisible = false; + else if (g.NavInputSource == ImGuiInputSource_Keyboard && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) == 0) + g.NavCursorVisible = false; + else if (g.NavInputSource == ImGuiInputSource_Gamepad && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + g.NavCursorVisible = false; + else if (g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = true; g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; } @@ -13683,6 +13900,9 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); + g.NavIdItemFlags = (g.LastItemData.ID == id) ? g.LastItemData.ItemFlags : ImGuiItemFlags_None; + if (id == g.ActiveIdIsAlive) + g.NavIdIsAlive = true; if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) g.NavHighlightItemUnderNav = true; @@ -13780,7 +14000,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, const ImRect& nav_bb) { if (quadrant == move_dir) { - ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + ImFormatString(buf, IM_COUNTOF(buf), "%.0f/%.0f", dist_box, dist_center); ImDrawList* draw_list = GetForegroundDrawList(window); draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 80)); draw_list->AddRectFilled(cand.Min, cand.Min + CalcTextSize(buf), IM_COL32(255, 0, 0, 200)); @@ -13791,7 +14011,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, const ImRect& nav_bb) const bool debug_tty = (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_Space)); if (debug_hovering || debug_tty) { - ImFormatString(buf, IM_ARRAYSIZE(buf), + ImFormatString(buf, IM_COUNTOF(buf), "d-box (%7.3f,%7.3f) -> %7.3f\nd-center (%7.3f,%7.3f) -> %7.3f\nd-axial (%7.3f,%7.3f) -> %7.3f\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "-WENS"[move_dir+1], "-WENS"[quadrant+1]); if (debug_hovering) @@ -13886,7 +14106,7 @@ static void ImGui::NavProcessItem() const ImGuiID id = g.LastItemData.ID; const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags; - // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816) + // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816, #7994) ImRect nav_bb = g.LastItemData.NavRect; if (window->DC.NavIsScrollPushableX == false) { @@ -13950,6 +14170,7 @@ static void ImGui::NavProcessItem() SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; + g.NavIdItemFlags = item_flags; if (g.LastItemData.ItemFlags & ImGuiItemFlags_HasSelectionUserData) { IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid); @@ -14107,7 +14328,7 @@ void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags wra // In theory we should test for NavMoveRequestButNoResultYet() but there's no point doing it: // as NavEndFrame() will do the same test. It will end up calling NavUpdateCreateWrappingRequest(). - if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == ImGuiNavLayer_Main) + if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == window->DC.NavLayerCurrent) g.NavMoveFlags = (g.NavMoveFlags & ~ImGuiNavMoveFlags_WrapMask_) | wrap_flags; } @@ -14198,31 +14419,29 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) } } -static ImGuiInputSource ImGui::NavCalcPreferredRefPosSource() +// Positioning logic altered slightly for remote activation: for Popup we want to use item rect, for Tooltip we leave things alone. (#9138) +// When calling for ImGuiWindowFlags_Popup we use LastItemData. +static ImGuiInputSource ImGui::NavCalcPreferredRefPosSource(ImGuiWindowFlags window_type) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + if ((window_type & ImGuiWindowFlags_Popup) && activated_shortcut) + return ImGuiInputSource_Keyboard; - // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. - if ((!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) && !activated_shortcut) + if (!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) return ImGuiInputSource_Mouse; else return ImGuiInputSource_Keyboard; // or Nav in general } -static ImVec2 ImGui::NavCalcPreferredRefPos() +static ImVec2 ImGui::NavCalcPreferredRefPos(ImGuiWindowFlags window_type) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; - ImGuiInputSource source = NavCalcPreferredRefPosSource(); - - const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; - - if (source != ImGuiInputSource_Mouse && !activated_shortcut && window == NULL) - source = ImGuiInputSource_Mouse; + ImGuiInputSource source = NavCalcPreferredRefPosSource(window_type); - // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. if (source == ImGuiInputSource_Mouse) { // Mouse (we need a fallback in case the mouse becomes invalid after being used) @@ -14234,8 +14453,9 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() else { // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; ImRect ref_rect; - if (activated_shortcut) + if (activated_shortcut && (window_type & ImGuiWindowFlags_Popup)) ref_rect = g.LastItemData.NavRect; else if (window != NULL) ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]); @@ -14341,6 +14561,7 @@ static void ImGui::NavUpdate() // Process NavCancel input (to close a popup, get back to parent, clear focus) NavUpdateCancelRequest(); + NavUpdateContextMenuRequest(); // Process manual activation request g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0; @@ -14349,21 +14570,25 @@ static void ImGui::NavUpdate() { const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner)); const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner))); - const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_NoOwner) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_NoOwner)); - const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, 0, ImGuiKeyOwner_NoOwner))); + const bool input_pressed_keyboard = nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner)); + bool input_pressed_gamepad = false; + if (activate_down && nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner) && (g.NavIdItemFlags & ImGuiItemFlags_Inputable)) // requires ImGuiItemFlags_Inputable to avoid retriggering regular buttons. + if (GetKeyData(ImGuiKey_NavGamepadActivate)->DownDurationPrev < NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY && GetKeyData(ImGuiKey_NavGamepadActivate)->DownDuration >= NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY) + input_pressed_gamepad = true; + if (g.ActiveId == 0 && activate_pressed) { g.NavActivateId = g.NavId; g.NavActivateFlags = ImGuiActivateFlags_PreferTweak; } - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed) + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (input_pressed_keyboard || input_pressed_gamepad)) { g.NavActivateId = g.NavId; g.NavActivateFlags = ImGuiActivateFlags_PreferInput; } - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down)) + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_pressed_keyboard || input_pressed_gamepad)) // FIXME-NAV: Unsure why input_pressed_xxx (migrated from input_down which was already dubious) g.NavActivateDownId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed)) + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed_keyboard || input_pressed_gamepad)) { g.NavActivatePressedId = g.NavId; NavHighlightActivated(g.NavId); @@ -14436,7 +14661,7 @@ static void ImGui::NavUpdate() // Update mouse position if requested // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied) if (set_mouse_pos && io.ConfigNavMoveSetMousePos && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) - TeleportMousePos(NavCalcPreferredRefPos()); + TeleportMousePos(NavCalcPreferredRefPos(ImGuiWindowFlags_Popup)); // [DEBUG] g.NavScoringDebugCount = 0; @@ -14568,7 +14793,7 @@ void ImGui::NavUpdateCreateMoveRequest() IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "", g.NavLayer); g.NavInitRequest = g.NavInitRequestFromMove = true; g.NavInitResult.ID = 0; - if (g.IO.ConfigNavCursorVisibleAuto) + if (g.IO.ConfigNavCursorVisibleAuto) // NO check for _NoNavInputs here as we assume MoveRequests cannot be created. g.NavCursorVisible = true; } @@ -14633,7 +14858,7 @@ void ImGui::NavUpdateCreateTabbingRequest() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; IM_ASSERT(g.NavMoveDir == ImGuiDir_None); - if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs) || !g.ConfigNavEnableTabbing) return; const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) && !g.IO.KeyCtrl && !g.IO.KeyAlt; @@ -14825,6 +15050,31 @@ static void ImGui::NavUpdateCancelRequest() } } +static void ImGui::NavUpdateContextMenuRequest() +{ + ImGuiContext& g = *GImGui; + g.NavOpenContextMenuItemId = g.NavOpenContextMenuWindowId = 0; + const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if ((!nav_keyboard_active && !nav_gamepad_active) || g.NavWindow == NULL) + return; + + bool request = false; + request |= nav_keyboard_active && (IsKeyReleased(ImGuiKey_Menu, ImGuiKeyOwner_NoOwner) || (IsKeyPressed(ImGuiKey_F10, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner) && g.IO.KeyMods == ImGuiMod_Shift)); + request |= nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadContextMenu, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner); + if (!request) + return; + g.NavOpenContextMenuItemId = g.NavId; + g.NavOpenContextMenuWindowId = g.NavWindow->ID; + + // Allow triggering for Begin()..BeginPopupContextItem(). A possible alternative would be to use g.NavLayer == ImGuiNavLayer_Menu. + if (g.NavId == g.NavWindow->GetID("#CLOSE") || g.NavId == g.NavWindow->GetID("#COLLAPSE")) + g.NavOpenContextMenuItemId = g.NavWindow->MoveId; + + g.NavInputSource = ImGuiInputSource_Keyboard; + SetNavCursorVisibleAfterMove(); +} + // Handle PageUp/PageDown/Home/End keys // Called from NavUpdateCreateMoveRequest() which will use our output to create a move request // FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference @@ -14929,9 +15179,13 @@ static void ImGui::NavUpdateCreateWrappingRequest() const ImGuiNavMoveFlags move_flags = g.NavMoveFlags; //const ImGuiAxis move_axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; + + // Menu layer does not maintain scrolling / content size (#9178) + ImVec2 wrap_size = (g.NavLayer == ImGuiNavLayer_Menu) ? window->Size : window->ContentSize + window->WindowPadding; + if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) { - bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x; + bb_rel.Min.x = bb_rel.Max.x = wrap_size.x; if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); // Previous row @@ -14951,7 +15205,7 @@ static void ImGui::NavUpdateCreateWrappingRequest() } if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) { - bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y; + bb_rel.Min.y = bb_rel.Max.y = wrap_size.y; if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column @@ -15198,7 +15452,7 @@ static void ImGui::NavUpdateWindowing() if (nav_move_dir.x != 0.0f || nav_move_dir.y != 0.0f) { const float NAV_MOVE_SPEED = 800.0f; - const float move_step = NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + const float move_step = NAV_MOVE_SPEED * io.DeltaTime * GetScale(); g.NavWindowingAccumDeltaPos += nav_move_dir * move_step; g.NavHighlightItemUnderNav = true; ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos); @@ -15269,13 +15523,12 @@ void ImGui::NavUpdateWindowingOverlay() if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) return; - if (g.NavWindowingListWindow == NULL) - g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay"); const ImGuiViewport* viewport = /*g.NavWindow ? g.NavWindow->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--) @@ -15378,7 +15631,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) // Magic fallback to handle items with no assigned ID, e.g. Text(), Image() // We build a throwaway ID based on current ID stack + relative AABB of items in window. - // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING/RESIZINGG OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING/RESIZING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. // Rely on keeping other window->LastItemXXX fields intact. source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect); @@ -15477,7 +15730,7 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s cond = ImGuiCond_Always; IM_ASSERT(type != NULL); - IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); + IM_ASSERT(ImStrlen(type) < IM_COUNTOF(payload.DataType) && "Payload type can be at most 32 characters long"); IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() @@ -15485,21 +15738,21 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) { // Copy payload - ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + ImStrncpy(payload.DataType, type, IM_COUNTOF(payload.DataType)); g.DragDropPayloadBufHeap.resize(0); if (data_size > sizeof(g.DragDropPayloadBufLocal)) { // 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 { @@ -15635,7 +15888,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop IM_ASSERT(viewport != NULL); ImRect bb = g.DragDropTargetRect; bb.Expand(-3.5f); - RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb); + RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb, g.Style.DragDropTargetRounding); } else if (draw_target_rect) { @@ -15666,16 +15919,16 @@ void ImGui::RenderDragDropTargetRectForItem(const ImRect& bb) bool push_clip_rect = !window->ClipRect.Contains(bb_display); if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); - RenderDragDropTargetRectEx(window->DrawList, bb_display); + RenderDragDropTargetRectEx(window->DrawList, bb_display, g.Style.DragDropTargetRounding); if (push_clip_rect) window->DrawList->PopClipRect(); } -void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb) +void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding) { ImGuiContext& g = *GImGui; - draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), g.Style.DragDropTargetRounding, 0); - draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), g.Style.DragDropTargetRounding, 0, g.Style.DragDropTargetBorderSize); + draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), rounding, 0); + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), rounding, g.Style.DragDropTargetBorderSize); } const ImGuiPayload* ImGui::GetDragDropPayload() @@ -15754,7 +16007,7 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* if (!text_end) text_end = FindRenderedTextEnd(text, text_end); - const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1); + const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + ImMax(g.Style.FramePadding.y, g.Style.ItemSpacing.y) + 1); if (ref_pos) g.LogLinePosY = ref_pos->y; if (log_new_line) @@ -15930,7 +16183,7 @@ void ImGui::LogButtons() const bool log_to_file = Button("Log To File"); SameLine(); const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); PushItemFlag(ImGuiItemFlags_NoTabStop, true); - SetNextItemWidth(80.0f); + SetNextItemWidth(CalcTextSize("999").x); SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); PopItemFlag(); PopID(); @@ -16369,6 +16622,12 @@ void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) // - DestroyPlatformWindows() //----------------------------------------------------------------------------- +const char* ImGuiViewport::GetDebugName() const +{ + const ImGuiViewportP* viewport = (const ImGuiViewportP*)this; + return viewport->Window ? viewport->Window->Name : "n/a"; +} + void ImGuiPlatformIO::ClearPlatformHandlers() { Platform_GetClipboardTextFn = NULL; @@ -16398,6 +16657,7 @@ void ImGuiPlatformIO::ClearRendererHandlers() Renderer_CreateWindow = Renderer_DestroyWindow = NULL; Renderer_SetWindowSize = NULL; Renderer_RenderWindow = Renderer_SwapBuffers = NULL; + DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = NULL; } ImGuiViewport* ImGui::GetMainViewport() @@ -16473,6 +16733,7 @@ static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window) // Heuristic, see #8948: depends on how backends handle OS-level parenting. +// Due to how parent viewport stack is layed out, note that IsViewportAbove(a,b) isn't always the same as !IsViewportAbove(b,a). static bool IsViewportAbove(ImGuiViewportP* potential_above, ImGuiViewportP* potential_below) { // If ImGuiBackendFlags_HasParentViewport if set, ->ParentViewport chain should be accurate. @@ -16494,37 +16755,39 @@ static bool IsViewportAbove(ImGuiViewportP* potential_above, ImGuiViewportP* pot return false; } -static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport) +static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport_dst) { ImGuiContext& g = *GImGui; - if (window->Viewport == viewport) + IM_ASSERT(window == window->RootWindowDockTree); + ImGuiViewportP* viewport_src = window->Viewport; // Current viewport + if (viewport_src == viewport_dst) return false; - if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0) + if ((viewport_dst->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0) return false; - if ((viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0) + if ((viewport_dst->Flags & ImGuiViewportFlags_IsMinimized) != 0) return false; - if (!viewport->GetMainRect().Contains(window->Rect())) + if (!viewport_dst->GetMainRect().Contains(window->Rect())) return false; if (GetWindowAlwaysWantOwnViewport(window)) return false; - for (ImGuiViewportP* viewport_2 : g.Viewports) + for (ImGuiViewportP* viewport_obstructing : g.Viewports) { - if (viewport_2 == viewport || viewport_2 == window->Viewport) + if (viewport_obstructing == viewport_src || viewport_obstructing == viewport_dst) continue; - if (viewport_2->GetMainRect().Overlaps(window->Rect())) - if (IsViewportAbove(viewport_2, viewport)) - if (window->Viewport == NULL || !IsViewportAbove(viewport_2, window->Viewport)) - return false; + if (viewport_obstructing->GetMainRect().Overlaps(window->Rect())) + if (IsViewportAbove(viewport_obstructing, viewport_dst)) + if (viewport_src == NULL || IsViewportAbove(viewport_src, viewport_obstructing)) + return false; // viewport_obstructing is between viewport_src and viewport_dst -> Cannot merge. } // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child) - ImGuiViewportP* old_viewport = window->Viewport; + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' merge into Viewport 0X%08X\n", window->Name, viewport_dst->ID); if (window->ViewportOwned) for (int n = 0; n < g.Windows.Size; n++) - if (g.Windows[n]->Viewport == old_viewport) - SetWindowViewport(g.Windows[n], viewport); - SetWindowViewport(window, viewport); + if (g.Windows[n]->Viewport == viewport_src) + SetWindowViewport(g.Windows[n], viewport_dst); + SetWindowViewport(window, viewport_dst); if ((window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) BringWindowToDisplayFront(window); @@ -16576,7 +16839,7 @@ void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) } } -// If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search ourselves. +// If the backend doesn't support ImGuiBackendFlags_HasMouseHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs for it, we do a search ourselves. // A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. // B) It requires Platform_GetWindowFocus to be implemented by backend. ImGuiViewportP* ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2& mouse_platform_pos) @@ -16586,7 +16849,8 @@ ImGuiViewportP* ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2& for (ImGuiViewportP* viewport : g.Viewports) if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_IsMinimized)) && viewport->GetMainRect().Contains(mouse_platform_pos)) if (best_candidate == NULL || best_candidate->LastFocusedStampCount < viewport->LastFocusedStampCount) - best_candidate = viewport; + if (viewport->PlatformWindowCreated) + best_candidate = viewport; return best_candidate; } @@ -16680,6 +16944,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; @@ -16730,6 +16996,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; @@ -16808,7 +17082,7 @@ static void ImGui::UpdateViewportsNewFrame() // If the backend doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that this search: // A) won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. // B) won't take account of how the backend apply parent<>child relationship to secondary viewports, which affects their Z order. - // C) uses LastFrameAsRefViewport as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO) + // C) uses LastFocusedStampCount as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO) viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos); } if (viewport_hovered != NULL) @@ -16907,6 +17181,10 @@ ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const g.ViewportCreatedCount++; IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'\n", id, window ? window->Name : ""); + // We assume the window becomes front-most (even when ImGuiViewportFlags_NoFocusOnAppearing is used). + // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available. + viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount; + // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport. // We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x); @@ -17055,7 +17333,7 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) bool use_mouse_ref = (!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !g.NavWindow); bool mouse_valid = IsMousePosValid(&mouse_ref); if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) && (!use_mouse_ref || mouse_valid)) - window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos()); + window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos(window->Flags)); else window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor; } @@ -17170,6 +17448,8 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win window->Viewport->Flags = viewport_flags; + window->Viewport->PlatformIconData = window->WindowClass.PlatformIconData; + // Update parent viewport ID // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) if (window->WindowClass.ParentViewportId != (ImGuiID)-1) @@ -17281,11 +17561,6 @@ void ImGui::UpdatePlatformWindows() // Show window g.PlatformIO.Platform_ShowWindow(viewport); - - // Even without focus, we assume the window becomes front-most. - // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available. - if (viewport->LastFocusedStampCount != g.ViewportFocusedStampCount) - viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount; } // Clear request flags @@ -17544,7 +17819,7 @@ struct ImGuiDockPreviewData float SplitRatio; ImRect DropRectsDraw[ImGuiDir_COUNT + 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects() - ImGuiDockPreviewData() : FutureNode(0) { IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false; SplitNode = NULL; SplitDir = ImGuiDir_None; SplitRatio = 0.f; for (int n = 0; n < IM_ARRAYSIZE(DropRectsDraw); n++) DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } + ImGuiDockPreviewData() : FutureNode(0) { IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false; SplitNode = NULL; SplitDir = ImGuiDir_None; SplitRatio = 0.f; for (int n = 0; n < IM_COUNTOF(DropRectsDraw); n++) DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } }; // Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes) @@ -17560,7 +17835,7 @@ struct ImGuiDockNodeSettings ImVec2ih Pos; ImVec2ih Size; ImVec2ih SizeRef; - ImGuiDockNodeSettings() { memset(this, 0, sizeof(*this)); SplitAxis = ImGuiAxis_None; } + ImGuiDockNodeSettings() { memset((void*)this, 0, sizeof(*this)); SplitAxis = ImGuiAxis_None; } }; //----------------------------------------------------------------------------- @@ -17572,6 +17847,7 @@ namespace ImGui // ImGuiDockContext static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id); static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node); + static void DockContextDeleteNode(ImGuiContext* ctx, ImGuiDockNode* node); static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node); static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req); static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx); @@ -17677,7 +17953,7 @@ void ImGui::DockContextShutdown(ImGuiContext* ctx) ImGuiDockContext* dc = &ctx->DockContext; for (int n = 0; n < dc->Nodes.Data.Size; n++) if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) - IM_DELETE(node); + DockContextDeleteNode(ctx, node); } void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs) @@ -17830,7 +18106,6 @@ static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id) static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node) { ImGuiContext& g = *ctx; - ImGuiDockContext* dc = &ctx->DockContext; IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRemoveNode 0x%08X\n", node->ID); IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node); @@ -17850,14 +18125,24 @@ static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, } else { - for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++) + for (int n = 0; parent_node && n < IM_COUNTOF(parent_node->ChildNodes); n++) if (parent_node->ChildNodes[n] == node) node->ParentNode->ChildNodes[n] = NULL; - dc->Nodes.SetVoidPtr(node->ID, NULL); - IM_DELETE(node); + DockContextDeleteNode(ctx, node); } } +// Raw-ish delete +static void ImGui::DockContextDeleteNode(ImGuiContext* ctx, ImGuiDockNode* node) +{ + ImGuiDockContext* dc = &ctx->DockContext; + if (node->TabBar) + IM_DELETE(node->TabBar); + node->TabBar = NULL; + dc->Nodes.SetVoidPtr(node->ID, NULL); + IM_DELETE(node); +} + static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs) { const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs; @@ -17982,7 +18267,7 @@ static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDoc // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set. char host_window_title[20]; ImGuiDockNode* root_node = DockNodeGetRootNode(node); - node->HostWindow = FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title))); + node->HostWindow = FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_COUNTOF(host_window_title))); } } @@ -18352,8 +18637,7 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id) ImGuiDockNode::~ImGuiDockNode() { - IM_DELETE(TabBar); - TabBar = NULL; + IM_ASSERT(TabBar == NULL); ChildNodes[0] = ChildNodes[1] = NULL; } @@ -18583,7 +18867,7 @@ struct ImGuiDockNodeTreeInfo int CountNodesWithWindows; //ImGuiWindowClass WindowClassForMerges; - ImGuiDockNodeTreeInfo() { memset(this, 0, sizeof(*this)); } + ImGuiDockNodeTreeInfo() { memset((void*)this, 0, sizeof(*this)); } }; static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeTreeInfo* info) @@ -18648,7 +18932,7 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount); bool remove = false; - remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount); + remove |= node_was_active && (window->WasActive == false); // Can't use 'window->LastFrameActive + 1 < g.FrameCount'. (see #9151) remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame remove |= (window->DockTabWantClose); if (remove) @@ -18683,10 +18967,13 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) node->WantHiddenTabBarToggle = false; // Apply toggles at a single point of the frame (here!) + const ImGuiDockNodeFlags prev_local_flags = node->LocalFlags; if (node->Windows.Size > 1) node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); else if (node->WantHiddenTabBarToggle) node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar); + if ((node->LocalFlags ^ prev_local_flags) & ImGuiDockNodeFlags_SavedFlagsMask_) + MarkIniSettingsDirty(); // Bit flaky to only do this here. Perhaps compare node flags every frame? #9380 node->WantHiddenTabBarToggle = false; DockNodeUpdateVisibleFlag(node); @@ -18929,7 +19216,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) // Begin into the host window char window_label[20]; - DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label)); + DockNodeGetHostWindowTitle(node, window_label, IM_COUNTOF(window_label)); ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost; window_flags |= ImGuiWindowFlags_NoFocusOnAppearing; window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse; @@ -19338,7 +19625,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline; if (!host_window->Collapsed && is_focused) tab_bar_flags |= ImGuiTabBarFlags_IsFocused; - tab_bar->ID = GetID("#TabBar"); + tab_bar->ID = node->ID;// GetID("#TabBar"); tab_bar->SeparatorMinX = node->Pos.x + host_window->WindowBorderSize; // Separator cover the whole node width tab_bar->SeparatorMaxX = node->Pos.x + node->Size.x - host_window->WindowBorderSize; BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags); @@ -19355,7 +19642,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w { ImGuiWindow* window = node->Windows[window_n]; if (window->LastFrameActive + 1 < g.FrameCount && node_was_active) - continue; // FIXME: Not sure if that's still taken/useful. + continue; // FIXME: Not sure if that's still taken/useful, as windows are normally removed in DockNodeUpdateFlagsAndCollapse(). ImGuiTabItemFlags tab_item_flags = 0; tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet; @@ -19962,15 +20249,9 @@ void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG parent_node->UpdateMergedFlags(); if (child_0) - { - ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL); - IM_DELETE(child_0); - } + DockContextDeleteNode(ctx, child_0); if (child_1) - { - ctx->DockContext.Nodes.SetVoidPtr(child_1->ID, NULL); - IM_DELETE(child_1); - } + DockContextDeleteNode(ctx, child_1); } // Update Pos/Size for a node hierarchy (don't affect child Windows yet) @@ -20356,7 +20637,7 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock window_flags |= ImGuiWindowFlags_NoBackground; char title[256]; - ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, dockspace_id); + ImFormatString(title, IM_COUNTOF(title), "%s/DockSpace_%08X", window->Name, dockspace_id); PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); Begin(title, NULL, window_flags); @@ -20418,7 +20699,7 @@ ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport* host_window_flags |= ImGuiWindowFlags_NoMouseInputs; char label[32]; - ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", viewport->ID); + ImFormatString(label, IM_COUNTOF(label), "WindowOverViewport_%08X", viewport->ID); PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); @@ -20717,7 +20998,7 @@ static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID ds out_node_remap_pairs->push_back(src_node->ID); out_node_remap_pairs->push_back(dst_node->ID); - for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++) + for (int child_n = 0; child_n < IM_COUNTOF(src_node->ChildNodes); child_n++) if (src_node->ChildNodes[child_n]) { dst_node->ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs); @@ -20941,8 +21222,12 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) { ImGuiContext& g = *GImGui; - // Clear fields ahead so most early-out paths don't have to do it - window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + // Specific extra processing for fallback window (#9151), could be in Begin() as well. + if (window->IsFallbackWindow && !window->WasActive) + { + DockNodeHideWindowDuringHostWindowCreation(window); + return; + } const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window); if (auto_dock_node) @@ -21780,7 +22065,7 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) if (!IsItemVisible()) return; draw_list->PushClipRect(board_min, board_max, true); - for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++) + for (int n = 0; n < IM_COUNTOF(keys_to_display); n++) { const KeyLayoutData* key_data = &keys_to_display[n]; ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset, start_pos.y + key_data->Row * key_step.y); @@ -21789,7 +22074,7 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding); ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y); ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y); - draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f); + draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, 2.0f); draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding); ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y); draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label); @@ -21867,23 +22152,21 @@ void ImGui::UpdateDebugToolFlashStyleColor() DebugFlashStyleColorStop(); } -static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) +ImU64 ImGui::DebugTextureIDToU64(ImTextureID tex_id) { - union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); - if (sizeof(tex_id) >= sizeof(void*)) - ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); - else - ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); - return buf; + ImU64 v = 0; + memcpy(&v, &tex_id, ImMin(sizeof(ImU64), sizeof(ImTextureID))); + return v; } static const char* FormatTextureRefForDebugDisplay(char* buf, int buf_size, ImTextureRef tex_ref) { + char* buf_p = buf; char* buf_end = buf + buf_size; if (tex_ref._TexData != NULL) - buf += ImFormatString(buf, buf_end - buf, "#%03d: ", tex_ref._TexData->UniqueID); - return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), tex_ref.GetTexID()); // Calling TexRef::GetTexID() to avoid assert of cmd->GetTexID() + buf_p += ImFormatString(buf_p, buf_end - buf_p, "#%03d: ", tex_ref._TexData->UniqueID); + ImFormatString(buf_p, buf_end - buf_p, "0x%X", ImGui::DebugTextureIDToU64(tex_ref.GetTexID())); + return buf; } #ifdef IMGUI_ENABLE_FREETYPE @@ -22069,9 +22352,9 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRe } PopStyleVar(); - char texid_desc[30]; + char texref_desc[30]; Text("Status = %s (%d), Format = %s (%d), UseColors = %d", ImTextureDataGetStatusName(tex->Status), tex->Status, ImTextureDataGetFormatName(tex->Format), tex->Format, tex->UseColors); - Text("TexID = %s, BackendUserData = %p", FormatTextureRefForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->GetTexRef()), tex->BackendUserData); + Text("TexRef = %s, BackendUserData = %p", FormatTextureRefForDebugDisplay(texref_desc, IM_COUNTOF(texref_desc), tex->GetTexRef()), tex->BackendUserData); TreePop(); } PopID(); @@ -22214,7 +22497,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); if (IsItemHovered()) - GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f); Indent(); char buf[128]; for (int rect_n = 0; rect_n < TRT_Count; rect_n++) @@ -22226,19 +22509,19 @@ void ImGui::ShowMetricsWindow(bool* p_open) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImRect r = Funcs::GetTableRect(table, rect_n, column_n); - ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); + ImFormatString(buf, IM_COUNTOF(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f); } } else { ImRect r = Funcs::GetTableRect(table, rect_n, -1); - ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); + ImFormatString(buf, IM_COUNTOF(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f); } } Unindent(); @@ -22259,7 +22542,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) { static char buf[64] = ""; SetNextItemWidth(-FLT_MIN); - InputText("##DebugTextEncodingBuf", buf, IM_ARRAYSIZE(buf)); + InputText("##DebugTextEncodingBuf", buf, IM_COUNTOF(buf)); if (buf[0] != 0) DebugTextEncoding(buf); } @@ -22529,9 +22812,10 @@ 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_ARRAYSIZE(info->LastEntriesBuf); + int buf_size = IM_COUNTOF(info->LastEntriesBuf); for (int n = buf_size - 1; n >= 0; n--) { ImGuiDebugAllocEntry* entry = &info->LastEntriesBuf[(info->LastEntriesIdx - n + buf_size) % buf_size]; @@ -22568,7 +22852,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) else Text("Mouse pos: "); Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); - int count = IM_ARRAYSIZE(io.MouseDown); + int count = IM_COUNTOF(io.MouseDown); Text("Mouse down:"); for (int i = 0; i < count; i++) if (IsMouseDown(i)) { SameLine(); Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } Text("Mouse clicked:"); for (int i = 0; i < count; i++) if (IsMouseClicked(i)) { SameLine(); Text("b%d (%d)", i, io.MouseClickedCount[i]); } Text("Mouse released:"); for (int i = 0; i < count; i++) if (IsMouseReleased(i)) { SameLine(); Text("b%d", i); } @@ -22701,7 +22985,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow)) { char buf[32]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext); + ImFormatString(buf, IM_COUNTOF(buf), "%d", window->BeginOrderWithinContext); float font_size = GetFontSize(); draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)); draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf); @@ -22725,7 +23009,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; - draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness); + draw_list->AddRect(r.Min, r.Max, col, 0.0f, thickness); } } else @@ -22744,10 +23028,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) char* p = buf; ImGuiDockNode* node = g.DebugHoveredDockNode; ImDrawList* overlay_draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport()); - p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID, node->IsCentralNode() ? " *CentralNode*" : ""); - p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId); - p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y); - p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y); + p += ImFormatString(p, buf + IM_COUNTOF(buf) - p, "DockId: %X%s\n", node->ID, node->IsCentralNode() ? " *CentralNode*" : ""); + p += ImFormatString(p, buf + IM_COUNTOF(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId); + p += ImFormatString(p, buf + IM_COUNTOF(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y); + p += ImFormatString(p, buf + IM_COUNTOF(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y); int depth = DockNodeGetDepth(node); overlay_draw_list->AddRect(node->Pos + ImVec2(3, 3) * (float)depth, node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255)); ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth; @@ -22952,9 +23236,9 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con } char texid_desc[30]; - FormatTextureRefForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TexRef); + FormatTextureRefForDebugDisplay(texid_desc, IM_COUNTOF(texid_desc), pcmd->TexRef); char buf[300]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", + ImFormatString(buf, IM_COUNTOF(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); bool pcmd_node_open = TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "%s", buf); if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list) @@ -22976,7 +23260,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con } // Display vertex information summary. Hover to get all triangles drawn in wire-frame - ImFormatString(buf, IM_ARRAYSIZE(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area); + ImFormatString(buf, IM_COUNTOF(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area); Selectable(buf); if (IsItemHovered() && fg_draw_list) DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, true, false); @@ -22987,7 +23271,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con while (clipper.Step()) for (int prim = clipper.DisplayStart, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++) { - char* buf_p = buf, * buf_end = buf + IM_ARRAYSIZE(buf); + char* buf_p = buf, * buf_end = buf + IM_COUNTOF(buf); ImVec2 triangle[3]; for (int n = 0; n < 3; n++, idx_i++) { @@ -23002,7 +23286,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con { ImDrawListFlags backup_flags = fg_draw_list->Flags; fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. - fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); + fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); fg_draw_list->Flags = backup_flags; } } @@ -23030,7 +23314,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co for (int n = 0; n < 3; n++, idx_n++) vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos)); if (show_mesh) - out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles + out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); // In yellow: mesh triangles } // Draw bounding boxes if (show_aabb) @@ -23120,6 +23404,9 @@ void ImGui::DebugNodeFont(ImFont* font) { const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); + + //if (DragFloat("ExtraSizeScale", &src->ExtraSizeScale, 0.01f, 0.10f, 2.0f)) + // ImFontAtlasFontRebuildOutput(atlas, font); #ifdef IMGUI_ENABLE_FREETYPE if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) { @@ -23178,7 +23465,7 @@ void ImGui::DebugNodeFont(ImFont* font) ImFontBaked* baked = &atlas->Builder->BakedPool[baked_n]; if (baked->OwnerFont != font) continue; - PushID(baked_n); + PushID(baked->BakedId); if (TreeNode("Glyphs", "Baked at { %.2fpx, d.%.2f }: %d glyphs%s", baked->Size, baked->RasterizerDensity, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) { if (SmallButton("Load all")) @@ -23197,7 +23484,7 @@ void ImGui::DebugNodeFont(ImFont* font) src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); } - DebugNodeFontGlyphesForSrcMask(font, baked, ~0); + DebugNodeFontGlyphsForSrcMask(font, baked, ~0); TreePop(); } PopID(); @@ -23206,7 +23493,7 @@ void ImGui::DebugNodeFont(ImFont* font) Unindent(); } -void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask) +void ImGui::DebugNodeFontGlyphsForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask) { ImDrawList* draw_list = GetWindowDrawList(); const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); @@ -23292,7 +23579,7 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings. char buf[256]; char* p = buf; - const char* buf_end = buf + IM_ARRAYSIZE(buf); + const char* buf_end = buf + IM_COUNTOF(buf); const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2); p += ImFormatString(p, buf_end - p, "%s 0x%08X (%d tabs)%s {", label, tab_bar->ID, tab_bar->Tabs.Size, is_active ? "" : " *Inactive*"); for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++) @@ -23308,8 +23595,8 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) { ImDrawList* draw_list = GetForegroundDrawList(tab_bar->Window); draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); - draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); - draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + draw_list->AddLineV(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y, tab_bar->BarRect.Max.y, IM_COL32(0, 255, 0, 255)); + draw_list->AddLineV(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y, tab_bar->BarRect.Max.y, IM_COL32(0, 255, 0, 255)); } if (open) { @@ -23481,7 +23768,7 @@ void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int wi if (window->ParentWindowInBeginStack != parent_in_begin_stack) continue; char buf[20]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "[%04d] Window", window->BeginOrderWithinContext); + ImFormatString(buf, IM_COUNTOF(buf), "[%04d] Window", window->BeginOrderWithinContext); //BulletText("[%04d] Window '%s'", window->BeginOrderWithinContext, window->Name); DebugNodeWindow(window, buf); TreePush(buf); @@ -23512,14 +23799,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 } @@ -23601,6 +23897,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 @@ -23661,8 +23958,8 @@ void ImGui::DebugDrawCursorPos(ImU32 col) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 pos = window->DC.CursorPos; - window->DrawList->AddLine(ImVec2(pos.x, pos.y - 3.0f), ImVec2(pos.x, pos.y + 4.0f), col, 1.0f); - window->DrawList->AddLine(ImVec2(pos.x - 3.0f, pos.y), ImVec2(pos.x + 4.0f, pos.y), col, 1.0f); + window->DrawList->AddLineV(pos.x, pos.y - 3.0f, pos.y + 4.0f, col, 1.0f); + window->DrawList->AddLineH(pos.x - 3.0f, pos.x + 4.0f, pos.y, col, 1.0f); } // Draw a 10px wide rectangle around CurposPos.x using Line Y1/Y2 in current window's DrawList @@ -23673,9 +23970,9 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float curr_x = window->DC.CursorPos.x; float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); - window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y1), ImVec2(curr_x + 5.0f, line_y1), col, 1.0f); - window->DrawList->AddLine(ImVec2(curr_x - 0.5f, line_y1), ImVec2(curr_x - 0.5f, line_y2), col, 1.0f); - window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y2), ImVec2(curr_x + 5.0f, line_y2), col, 1.0f); + window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f); + window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); + window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f); } // Draw last item rect in ForegroundDrawList (so it is always visible) @@ -23907,7 +24204,7 @@ static const char* DebugItemPathQuery_GetResultAsPath(ImGuiDebugItemPathQuery* q for (int stack_n = 0; stack_n < query->Results.Size; stack_n++) { char level_desc[256]; - DebugItemPathQuery_FormatLevelInfo(query, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc)); + DebugItemPathQuery_FormatLevelInfo(query, stack_n, false, level_desc, IM_COUNTOF(level_desc)); buf->append(stack_n == 0 ? "//" : "/"); for (const char* p = level_desc; *p != 0; ) { @@ -24000,7 +24297,7 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeFont(ImFont*) {} -void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont*, ImFontBaked*, int) {} +void ImGui::DebugNodeFontGlyphsForSrcMask(ImFont*, ImFontBaked*, int) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} @@ -24045,7 +24342,7 @@ void ImGui::ShowFontSelector(const char* label) "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" "- Read FAQ and docs/FONTS.md for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); + "- Legacy backend: if you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); } #endif // #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) diff --git a/src/Libs/imgui/imgui.h b/src/Libs/imgui/imgui.h index e42d702..86cb07b 100644 --- a/src/Libs/imgui/imgui.h +++ b/src/Libs/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (headers) // Help: @@ -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_online/manual/imgui_manual.html (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. @@ -29,8 +29,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.5" -#define IMGUI_VERSION_NUM 19250 +#define IMGUI_VERSION "1.92.8" +#define IMGUI_VERSION_NUM 19280 #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. @@ -97,7 +97,7 @@ Index of this file: #include #define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h #endif -#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! +#define IM_COUNTOF(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! #define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds. #define IM_STRINGIFY_HELPER(_EXPR) #_EXPR #define IM_STRINGIFY(_EXPR) IM_STRINGIFY_HELPER(_EXPR) // Preprocessor idiom to stringify e.g. an integer or a macro. @@ -345,7 +345,9 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. #endif -// Define this if you need 0 to be a valid ImTextureID for your backend. +// Define this if you need to change the invalid value for your backend. +// - 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)0) #endif @@ -354,7 +356,7 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or // The identifier is valid even before the texture has been uploaded to the GPU/graphics system. // This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`. // This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering. -// - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID. +// - When a texture is created by user code (e.g. custom images), we directly store the low-level ImTextureID. // Because of this, when displaying your own texture you are likely to ever only manage ImTextureID values on your side. // - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection // to extract the ImTextureID value during rendering, after texture upload has happened. @@ -764,6 +766,7 @@ namespace ImGui IMGUI_API bool CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags = 0); // when 'p_visible != NULL': if '*p_visible==true' display an additional small close button on upper right of the header which will set the bool to false when clicked, if '*p_visible==false' don't display the header. IMGUI_API void SetNextItemOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. IMGUI_API void SetNextItemStorageID(ImGuiID storage_id); // set id to use for open/close storage (default to same as item id). + IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); // retrieve tree node open/close state. // Widgets: Selectables // - A selectable highlights when hovered, and can display another color when selected. @@ -861,20 +864,23 @@ namespace ImGui // - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). // - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup(). // - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened. - // - IMPORTANT: Notice that for OpenPopupOnItemClick() we exceptionally default flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). IMGUI_API void OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks - IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) + IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. - // Popups: open+begin combined functions helpers + // Popups: Open+Begin popup combined functions helpers to create context menus. // - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking. - // - They are convenient to easily create context menus, hence the name. // - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add ImGuiWindowFlags to the BeginPopupContextXXX functions in the future. - // - IMPORTANT: Notice that we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight. - IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! - IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window. - IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows). + // - IMPORTANT: If you ever used the left mouse button with BeginPopupContextXXX() helpers before 1.92.6: + // - Before this version, OpenPopupOnItemClick(), BeginPopupContextItem(), BeginPopupContextWindow(), BeginPopupContextVoid() had 'a ImGuiPopupFlags popup_flags = 1' default value in their function signature. + // - Before: Explicitly passing a literal 0 meant ImGuiPopupFlags_MouseButtonLeft. The default = 1 meant ImGuiPopupFlags_MouseButtonRight. + // - After: The default = 0 means ImGuiPopupFlags_MouseButtonRight. Explicitly passing a literal 1 also means ImGuiPopupFlags_MouseButtonRight (if legacy behavior are enabled) or will assert (if legacy behavior are disabled). + // - TL;DR: if you don't want to use right mouse button for popups, always specify it explicitly using a named ImGuiPopupFlags_MouseButtonXXXX value. + // - Read "API BREAKING CHANGES" 2026/01/07 (1.92.6) entry in imgui.cpp or GitHub topic #9157 for all details. + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0);// open+begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // open+begin popup when clicked in void (where there are no windows). // Popups: query functions // - IsPopupOpen(): return true if the popup is open at the current BeginPopup() level of the popup stack. @@ -905,7 +911,7 @@ namespace ImGui // - 5. Call EndTable() IMGUI_API bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! - IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. + IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. 'min_row_height' include the minimum top and bottom padding aka CellPadding.y * 2.0f. IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible. @@ -916,7 +922,7 @@ namespace ImGui // The context menu can also be made available in columns body using ImGuiTableFlags_ContextMenuInBody. // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in // some advanced use cases (e.g. adding custom widgets in header row). - // - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled. + // - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled. When freezing columns you would usually also use ImGuiTableColumnFlags_NoHide on them. IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_id = 0); IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) @@ -962,19 +968,21 @@ namespace ImGui // Docking // - Read https://github.com/ocornut/imgui/wiki/Docking for details. // - Enable with io.ConfigFlags |= ImGuiConfigFlags_DockingEnable. - // - You can use most Docking facilities without calling any API. You don't necessarily need to call a DockSpaceXXX function to use Docking! + // - You can use many Docking facilities without calling any API. // - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking. // - Drag from window menu button (upper-left button) to undock an entire node (all windows). // - When io.ConfigDockingWithShift == true, you instead need to hold SHIFT to enable docking. + // - DockSpaceOverViewport: + // - This is a helper to create an invisible window covering a viewport, then submit a DockSpace() into it. + // - Most applications can simply call DockSpaceOverViewport() once to allow docking windows into e.g. the edge of your screen. + // e.g. ImGui::NewFrame(); ImGui::DockSpaceOverViewport(); // Create a dockspace in main viewport. + // or: ImGui::NewFrame(); ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode); // Create a dockspace in main viewport, central node is transparent. // - Dockspaces: - // - If you want to dock windows into the edge of your screen, most application can simply call DockSpaceOverViewport(): - // e.g. ImGui::NewFrame(); then ImGui::DockSpaceOverViewport(); // Create a dockspace in main viewport. - // or: ImGui::NewFrame(); then ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode); // Create a dockspace in main viewport, where central node is transparent. // - A dockspace is an explicit dock node within an existing window. - // - DockSpaceOverViewport() basically creates an invisible window covering a viewport, and submit a DockSpace() into it. // - IMPORTANT: Dockspaces need to be submitted _before_ any window they can host. Submit them early in your frame! // - IMPORTANT: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked. // If you have e.g. multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly. + // - See 'Demo->Examples->Dockspace' or 'Demo->Examples->Documents' for more detailed demos. // - Programmatic docking: // - There is no public API yet other than the very limited SetNextWindowDockID() function. Sorry for that! // - Read https://github.com/ocornut/imgui/wiki/Docking for examples of how to use current internal API. @@ -982,7 +990,7 @@ namespace ImGui IMGUI_API ImGuiID DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); IMGUI_API void SetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (control docking compatibility + provide hints to platform backend via custom viewport flags and platform parent/child relationship) - IMGUI_API ImGuiID GetWindowDockID(); + IMGUI_API ImGuiID GetWindowDockID(); // get dock id of current window, or 0 if not associated to any docking node. IMGUI_API bool IsWindowDocked(); // is current window docked into another window? // Logging/Capture @@ -1011,7 +1019,7 @@ namespace ImGui // Disabling [BETA API] // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors) // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) - // - Tooltips windows are automatically opted out of disabling. Note that IsItemHovered() by default returns false on disabled items, unless using ImGuiHoveredFlags_AllowWhenDisabled. + // - Tooltips windows are automatically opted out of disabling. Note that IsItemHovered() by default returns false on disabled items, unless using ImGuiHoveredFlags_AllowWhenDisabled. // - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) IMGUI_API void BeginDisabled(bool disabled = true); IMGUI_API void EndDisabled(); @@ -1029,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. @@ -1051,6 +1059,7 @@ namespace ImGui IMGUI_API ImVec2 GetItemRectMin(); // get upper-left bounding rectangle of the last item (screen space) IMGUI_API ImVec2 GetItemRectMax(); // get lower-right bounding rectangle of the last item (screen space) IMGUI_API ImVec2 GetItemRectSize(); // get size of last item + IMGUI_API ImGuiItemFlags GetItemFlags(); // get generic flags of last item // Viewports // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. @@ -1081,19 +1090,22 @@ namespace ImGui IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); - // Inputs Utilities: Keyboard/Mouse/Gamepad + // Inputs Utilities: Raw Keyboard/Mouse/Gamepad Access + // - Consider using the Shortcut() function instead of IsKeyPressed()/IsKeyChordPressed()! Shortcut() is easier to use and better featured (can do focus routing check). // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...). - // - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921) - // - (legacy: any use of ImGuiKey will assert when key < 512 to detect passing legacy native/user indices) + // - (legacy: before v1.87 (2022-02), we used ImGuiKey < 512 values to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921) IMGUI_API bool IsKeyDown(ImGuiKey key); // is key being held. - IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? Repeat rate uses io.KeyRepeatDelay / KeyRepeatRate. IMGUI_API bool IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)? IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord); // was key chord (mods + key) pressed, e.g. you can pass 'ImGuiMod_Ctrl | ImGuiKey_S' as a key-chord. This doesn't do any routing or focus check, please consider using Shortcut() function instead. IMGUI_API int GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names are provided for debugging purpose and are not meant to be saved persistently nor compared. IMGUI_API void SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard); // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call. - // Inputs Utilities: Shortcut Testing & Routing [BETA] + // Inputs Utilities: Shortcut Testing & Routing + // - Typical use is e.g.: 'if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_S)) { ... }'. + // - Flags: Default route use ImGuiInputFlags_RouteFocused, but see ImGuiInputFlags_RouteGlobal and other options in ImGuiInputFlags_! + // - Flags: Use ImGuiInputFlags_Repeat to support repeat. // - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. // ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments // ImGuiMod_Ctrl | ImGuiKey_C // Accepted by functions taking ImGuiKeyChord arguments @@ -1105,8 +1117,10 @@ namespace ImGui // The whole system is order independent, so if Child1 makes its calls before Parent, results will be identical. // This is an important property as it facilitate working with foreign code or larger codebase. // - To understand the difference: - // - IsKeyChordPressed() compares mods and call IsKeyPressed() -> function has no side-effect. - // - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed() -> function has (desirable) side-effects as it can prevents another call from getting the route. + // - IsKeyChordPressed() compares mods and call IsKeyPressed() + // -> the function has no side-effect. + // - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed() + // -> the function has (desirable) side-effects as it can prevents another call from getting the route. // - Visualize registered routes in 'Metrics/Debugger->Inputs'. IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); @@ -1114,10 +1128,11 @@ namespace ImGui // Inputs Utilities: Key/Input Ownership [BETA] // - One common use case would be to allow your items to disable standard inputs behaviors such // as Tab or Alt key handling, Mouse Wheel scrolling, etc. - // e.g. Button(...); SetItemKeyOwner(ImGuiKey_MouseWheelY); to make hovering/activating a button disable wheel for scrolling. + // e.g. `Button(...); if (SetItemKeyOwner(ImGuiKey_MouseWheelY)) { ... }` to make hovering/activating a button disable wheel for scrolling. // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. + // - The return value of SetItemKeyOwner() says if ownership has been requested for the item, which is a shortcut to calling yet non-public TestKeyOwner() function. // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. - IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'. // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. @@ -1156,7 +1171,9 @@ namespace ImGui IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. // Debug Utilities - // - Your main debugging friend is the ShowMetricsWindow() function, which is also accessible from Demo->Tools->Metrics Debugger + // - Your main debugging friend is the ShowMetricsWindow() function. + // - Interactive tools are all accessible from the 'Dear ImGui Demo->Tools' menu. + // - Read https://github.com/ocornut/imgui/wiki/Debug-Tools for a description of all available debug tools. IMGUI_API void DebugTextEncoding(const char* text); IMGUI_API void DebugFlashStyleColor(ImGuiCol idx); IMGUI_API void DebugStartItemPicker(); @@ -1263,7 +1280,7 @@ enum ImGuiChildFlags_ }; // Flags for ImGui::PushItemFlag() -// (Those are shared by all items) +// (Those are shared by all submitted items) enum ImGuiItemFlags_ { ImGuiItemFlags_None = 0, // (Default) @@ -1273,6 +1290,7 @@ enum ImGuiItemFlags_ ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held. ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. + ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). }; // Flags for ImGui::InputText() @@ -1291,7 +1309,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) - ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode: validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). Note that Shift+Enter always enter a new line either way. // Other options ImGuiInputTextFlags_ReadOnly = 1 << 9, // Read-only mode @@ -1333,13 +1351,13 @@ 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 ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Open on double-click instead of simple click (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Open when clicking on the arrow part (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. - ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). Note: will always open a tree/id scope and return true. If you never use that scope, add ImGuiTreeNodeFlags_NoTreePushOnOpen. ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node. ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode. @@ -1365,21 +1383,14 @@ enum ImGuiTreeNodeFlags_ }; // Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions. -// - To be backward compatible with older API which took an 'int mouse_button = 1' argument instead of 'ImGuiPopupFlags flags', -// we need to treat small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags. -// It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags. -// - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0. -// IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter -// and want to use another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag explicitly. +// - IMPORTANT: If you ever used the left mouse button with BeginPopupContextXXX() helpers before 1.92.6: Read "API BREAKING CHANGES" 2026/01/07 (1.92.6) entry in imgui.cpp or GitHub topic #9157. // - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later). enum ImGuiPopupFlags_ { ImGuiPopupFlags_None = 0, - ImGuiPopupFlags_MouseButtonLeft = 0, // For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left) - ImGuiPopupFlags_MouseButtonRight = 1, // For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right) - ImGuiPopupFlags_MouseButtonMiddle = 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle) - ImGuiPopupFlags_MouseButtonMask_ = 0x1F, - ImGuiPopupFlags_MouseButtonDefault_ = 1, + ImGuiPopupFlags_MouseButtonLeft = 1 << 2, // For BeginPopupContext*(): open on Left Mouse release. Only one button allowed! + ImGuiPopupFlags_MouseButtonRight = 2 << 2, // For BeginPopupContext*(): open on Right Mouse release. Only one button allowed! (default) + ImGuiPopupFlags_MouseButtonMiddle = 3 << 2, // For BeginPopupContext*(): open on Middle Mouse release. Only one button allowed! ImGuiPopupFlags_NoReopen = 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't reopen same popup if already open (won't reposition, won't reinitialize navigation) //ImGuiPopupFlags_NoReopenAlwaysNavInit = 1 << 6, // For OpenPopup*(), BeginPopupContext*(): focus and initialize navigation even when not reopening. ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 7, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack @@ -1387,6 +1398,9 @@ enum ImGuiPopupFlags_ ImGuiPopupFlags_AnyPopupId = 1 << 10, // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. ImGuiPopupFlags_AnyPopupLevel = 1 << 11, // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) ImGuiPopupFlags_AnyPopup = ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel, + ImGuiPopupFlags_MouseButtonShift_ = 2, // [Internal] + ImGuiPopupFlags_MouseButtonMask_ = 0x0C, // [Internal] + ImGuiPopupFlags_InvalidMask_ = 0x03, // [Internal] Reserve legacy bits 0-1 to detect incorrectly passing 1 or 2 to the function. }; // Flags for ImGui::Selectable() @@ -1397,7 +1411,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. @@ -1435,7 +1449,7 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab // Fitting/Resize policy - ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons. + ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling. Setting TabMinWidthShrink to FLT_MAX makes this behave like ImGuiTabBarFlags_FittingPolicyScroll. ImGuiTabBarFlags_FittingPolicyShrink = 1 << 8, // Shrink down tabs when they don't fit ImGuiTabBarFlags_FittingPolicyScroll = 1 << 9, // Enable scrolling buttons when tabs don't fit ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyMixed | ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll, @@ -1604,7 +1618,7 @@ enum ImGuiSortDirection : ImU8 // All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87). // Support for legacy keys was completely removed in 1.91.5. // Read details about the 1.87+ transition : https://github.com/ocornut/imgui/issues/4921 -// Note that "Keys" related to physical keys and are not the same concept as input "Characters", the later are submitted via io.AddInputCharacter(). +// Note that "Keys" related to physical keys and are not the same concept as input "Characters", the latter are submitted via io.AddInputCharacter(). // The keyboard key enum values are named after the keys on a standard US keyboard, and on other keyboard types the keys reported may not match the keycaps. enum ImGuiKey : int { @@ -1673,10 +1687,10 @@ enum ImGuiKey : int // // XBOX | SWITCH | PLAYSTA. | -> ACTION ImGuiKey_GamepadStart, // Menu | + | Options | ImGuiKey_GamepadBack, // View | - | Share | - ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) + ImGuiKey_GamepadFaceLeft, // X | Y | Square | Toggle Menu. Hold for Windowing mode (Focus/Move/Resize windows) ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel / Close / Exit - ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Text Input / On-screen Keyboard - ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak + ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Open Context Menu + ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle. Hold for 0.60f to Activate in Text Input mode (e.g. wired to an on-screen keyboard). ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move / Tweak / Resize Window (in Windowing mode) ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move / Tweak / Resize Window (in Windowing mode) ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak / Resize Window (in Windowing mode) @@ -1758,10 +1772,11 @@ enum ImGuiInputFlags_ }; // Configuration flags stored in io.ConfigFlags. Set by user/application. +// Note that nowadays most of our configuration options are in other ImGuiIO fields, e.g. io.ConfigWindowsMoveFromTitleBarOnly. enum ImGuiConfigFlags_ { ImGuiConfigFlags_None = 0, - ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + space/enter to activate. + ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + Space/Enter to activate. Note: some features such as basic Tabbing and CtrL+Tab are enabled by regardless of this flag (and may be disabled via other means, see #4828, #9218). ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad. ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct dear imgui to disable mouse inputs and interactions. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. @@ -1774,10 +1789,9 @@ enum ImGuiConfigFlags_ // When using viewports it is recommended that your default value for ImGuiCol_WindowBg is opaque (Alpha=1.0) so transition to a viewport won't be noticeable. ImGuiConfigFlags_ViewportsEnable = 1 << 10, // Viewport enable flags (require both ImGuiBackendFlags_PlatformHasViewports + ImGuiBackendFlags_RendererHasViewports set by the respective backends) - // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui) + // [Unused] User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui) ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. ImGuiConfigFlags_IsTouchScreen = 1 << 21, // Application is using a touch screen instead of a mouse. - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavMoveSetMousePos ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavCaptureKeyboard @@ -1800,7 +1814,7 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_RendererHasViewports = 1 << 10, // Backend Renderer supports multiple viewports. ImGuiBackendFlags_PlatformHasViewports = 1 << 11, // Backend Platform supports multiple viewports. ImGuiBackendFlags_HasMouseHoveredViewport=1 << 12, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under. - ImGuiBackendFlags_HasParentViewport = 1 << 13, // Backend Platform supports honoring viewport->ParentViewport/ParentViewportId value, by applying the corresponding parent/child relation at the Platform level. + ImGuiBackendFlags_HasParentViewport = 1 << 13, // Backend Platform supports honoring viewport->ParentViewport/ParentViewportId value, by applying the corresponding parent/child relationship at the Platform level. Child windows always appear in front of their parent window. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -1825,6 +1839,7 @@ enum ImGuiCol_ ImGuiCol_ScrollbarGrabHovered, ImGuiCol_ScrollbarGrabActive, ImGuiCol_CheckMark, // Checkbox tick and RadioButton circle + ImGuiCol_CheckboxSelectedBg, // Checkbox background when Selected, otherwise use FrameBg ImGuiCol_SliderGrab, ImGuiCol_SliderGrabActive, ImGuiCol_Button, @@ -1912,6 +1927,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ScrollbarPadding, // float ScrollbarPadding ImGuiStyleVar_GrabMinSize, // float GrabMinSize ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_ImageRounding, // float ImageRounding ImGuiStyleVar_ImageBorderSize, // float ImageBorderSize ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_TabBorderSize, // float TabBorderSize @@ -1923,8 +1939,10 @@ enum ImGuiStyleVar_ ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign ImGuiStyleVar_TreeLinesSize, // float TreeLinesSize ImGuiStyleVar_TreeLinesRounding, // float TreeLinesRounding + ImGuiStyleVar_DragDropTargetRounding, // float DragDropTargetRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_SeparatorSize, // float SeparatorSize ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign ImGuiStyleVar_SeparatorTextPadding, // ImVec2 SeparatorTextPadding @@ -1941,6 +1959,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() @@ -1955,19 +1974,20 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). 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_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target/source. 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. // - 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] // " @@ -2010,8 +2030,9 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_ClampOnInput = 1 << 9, // Clamp value to min/max bounds when input manually with Ctrl+Click. By default Ctrl+Click allows going out of bounds. 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_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. 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. @@ -2098,11 +2119,11 @@ enum ImGuiTableFlags_ // Features ImGuiTableFlags_None = 0, ImGuiTableFlags_Resizable = 1 << 0, // Enable resizing columns. - ImGuiTableFlags_Reorderable = 1 << 1, // Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers) + ImGuiTableFlags_Reorderable = 1 << 1, // Enable reordering columns in header row. (Need calling TableSetupColumn() + TableHeadersRow() to display headers, or using ImGuiTableFlags_ContextMenuInBody to access context-menu without headers). ImGuiTableFlags_Hideable = 1 << 2, // Enable hiding/disabling columns in context menu. ImGuiTableFlags_Sortable = 1 << 3, // Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate. - ImGuiTableFlags_NoSavedSettings = 1 << 4, // Disable persisting columns order, width and sort settings in the .ini file. - ImGuiTableFlags_ContextMenuInBody = 1 << 5, // Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow(). + ImGuiTableFlags_NoSavedSettings = 1 << 4, // Disable persisting columns order, width, visibility and sort settings in the .ini file. + ImGuiTableFlags_ContextMenuInBody = 1 << 5, // Right-click on columns body/contents will also display table context menu. By default it is available in TableHeadersRow(). // Decorations ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. @@ -2217,7 +2238,7 @@ struct ImGuiTableSortSpecs int SpecsCount; // Sort spec count. Most often 1. May be > 1 when ImGuiTableFlags_SortMulti is enabled. May be == 0 when ImGuiTableFlags_SortTristate is enabled. bool SpecsDirty; // Set to true when specs have changed since last time! Use this to sort again, then clear the flag. - ImGuiTableSortSpecs() { memset(this, 0, sizeof(*this)); } + ImGuiTableSortSpecs() { memset((void*)this, 0, sizeof(*this)); } }; // Sorting specification for one column of a table (sizeof == 12 bytes) @@ -2228,7 +2249,7 @@ struct ImGuiTableColumnSortSpecs ImS16 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) ImGuiSortDirection SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending - ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); } + ImGuiTableColumnSortSpecs() { memset((void*)this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -2351,7 +2372,7 @@ struct ImGuiStyle // - recap: ImGui::GetFontSize() == FontSizeBase * (FontScaleMain * FontScaleDpi * other_scaling_factors) float FontSizeBase; // Current base font size before external global factors are applied. Use PushFont(NULL, size) to modify. Use ImGui::GetFontSize() to obtain scaled value. float FontScaleMain; // Main global scale factor. May be set by application once, or exposed to end-user. - float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. In docking branch: when io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. @@ -2381,12 +2402,13 @@ struct ImGuiStyle float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + float ImageRounding; // Rounding of Image() calls. float ImageBorderSize; // Thickness of border around Image() calls. float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. float TabBorderSize; // Thickness of border around tabs. float TabMinWidthBase; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. float TabMinWidthShrink; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. - float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. + float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never shrink, will behave like ImGuiTabBarFlags_FittingPolicyScroll. float TabCloseButtonMinWidthUnselected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar. @@ -2395,12 +2417,14 @@ struct ImGuiStyle ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. - float DragDropTargetRounding; // Radius of the drag and drop target frame. + float DragDropTargetRounding; // Radius of the drag and drop target frame. When <0.0f: use FrameRounding. 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. + float SeparatorSize; // Thickness of border in Separator(). Must be >= 1.0f. float SeparatorTextBorderSize; // Thickness of border in SeparatorText() ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. @@ -2427,12 +2451,12 @@ struct ImGuiStyle ImGuiHoveredFlags HoverFlagsForTooltipNav; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. // [Internal] - float _MainScale; // FIXME-WIP: Reference scale, as applied by ScaleAllSizes(). + float _MainScale; // FIXME-WIP: Reference scale, as applied by ScaleAllSizes(). PLEASE DO NOT USE THIS FOR NOW. float _NextFrameFontSizeBase; // FIXME: Temporary hack until we finish remaining work. // Functions IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. See comments in definition. Consider not calling this if your initial scale factor if <1.0. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -2500,10 +2524,11 @@ struct ImGuiIO bool ConfigDockingTransparentPayload;// = false // [BETA] Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge. // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set) + // (sorry for the amount of "NoXXXX" flags, which may be harder to reason about! may rework someday) bool ConfigViewportsNoAutoMerge; // = false; // Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it. May also set ImGuiViewportFlags_NoAutoMerge on individual viewport. bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it. bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size). - bool ConfigViewportsNoDefaultParent; // = true // When false: set secondary viewports' ParentViewportId to main viewport ID by default. Expects the platform backend to setup a parent/child relationship between the OS windows based on this value. Some backend may ignore this. Set to true if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. + bool ConfigViewportsNoDefaultParent; // = true // Disable setting OS window parent to main viewport by default. The platform backend is expected to honor `viewport->ParentViewportID` to setup a parent/child relationship between the OS windows (supported if ImGuiBackendFlags_HasParentViewport is set). When parented: child windows always appear in front of their parent. Set to false if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. Parent/child relationship may be set on a per-window basis using ImGuiWindowClass. bool ConfigViewportsPlatformFocusSetsImGuiFocus;//= true // When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change). // DPI/Scaling options @@ -2517,7 +2542,7 @@ struct ImGuiIO bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). - bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will keep item active and select contents (single-line only). + bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. @@ -2539,7 +2564,7 @@ struct ImGuiIO // Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL] // - Error recovery is provided as a way to facilitate: - // - Recovery after a programming error (native code or scripting language - the later tends to facilitate iterating on code while running). + // - Recovery after a programming error (native code or scripting language - the latter tends to facilitate iterating on code while running). // - Recovery after running an exception handler or any error processing which may skip code after an error has been detected. // - Error recovery is not perfect nor guaranteed! It is a feature to ease development. // You not are not supposed to rely on it in the course of a normal application run. @@ -2731,30 +2756,33 @@ struct ImGuiInputTextCallbackData ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only void* UserData; // What user passed to InputText() // Read-only + ImGuiID ID; // Widget ID // Read-only // Arguments for the different callback events // - During Resize callback, Buf will be same as your input buffer. // - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback. // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary. // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. - ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0; ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History] + ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0; + bool EventActivated; // Input field just got activated // Read-only // [Always] + bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always] char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land: == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 - bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always] - int CursorPos; // // Read-write // [Completion,History,Always] - int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection - int SelectionEnd; // // Read-write // [Completion,History,Always] + int CursorPos; // // Read-write // [Completion,History,Always,CharFilter] + int SelectionStart; // // Read-write // [Completion,History,Always,CharFilter] == to SelectionEnd when no selection + int SelectionEnd; // // Read-write // [Completion,History,Always,CharFilter] // Helper functions for text manipulation. // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. IMGUI_API ImGuiInputTextCallbackData(); IMGUI_API void DeleteChars(int pos, int bytes_count); IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); - void SelectAll() { SelectionStart = 0; SelectionEnd = BufTextLen; } - void ClearSelection() { SelectionStart = SelectionEnd = BufTextLen; } - bool HasSelection() const { return SelectionStart != SelectionEnd; } + void SelectAll() { SelectionStart = 0; CursorPos = SelectionEnd = BufTextLen; } + void SetSelection(int s, int e) { IM_ASSERT(s >= 0 && s <= BufTextLen); IM_ASSERT(e >= 0 && e <= BufTextLen); SelectionStart = s; CursorPos = SelectionEnd = e; } + void ClearSelection() { SelectionStart = SelectionEnd = BufTextLen; } + bool HasSelection() const { return SelectionStart != SelectionEnd; } }; // Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). @@ -2772,7 +2800,7 @@ struct ImGuiSizeCallbackData // before we stabilize Docking features. Please be mindful if using this. // Provide hints: // - To the platform backend via altered viewport flags (enable/disable OS decoration, OS task bar icons, etc.) -// - To the platform backend for OS level parent/child relationships of viewport. +// - To the platform backend for OS level parent/child relationships of viewport (otherwise: default is configured via io.ConfigViewportsNoDefaultParent) // - To the docking system for various options and filtering. struct ImGuiWindowClass { @@ -2785,8 +2813,9 @@ struct ImGuiWindowClass ImGuiDockNodeFlags DockNodeFlagsOverrideSet; // [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!) bool DockingAlwaysTabBar; // Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar) bool DockingAllowUnclassed; // Set to true to allow windows of this class to be docked/merged with an unclassed window. // FIXME-DOCK: Move to DockNodeFlags override? + void* PlatformIconData; // [EXPERIMENTAL] Pass opaque data for Platform backend to handle. - ImGuiWindowClass() { memset(this, 0, sizeof(*this)); ParentViewportId = (ImGuiID)-1; DockingAllowUnclassed = true; } + ImGuiWindowClass() { memset((void*)this, 0, sizeof(*this)); ParentViewportId = (ImGuiID)-1; DockingAllowUnclassed = true; } }; // Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload() @@ -2964,15 +2993,16 @@ enum ImGuiListClipperFlags_ // - The clipper also handles various subtleties related to keyboard/gamepad navigation, wrapping etc. struct ImGuiListClipper { - ImGuiContext* Ctx; // Parent UI context int DisplayStart; // First item to display, updated by each call to Step() int DisplayEnd; // End of items to display (exclusive) + int UserIndex; // Helper storage for user convenience/code. Optional, and otherwise unused if you don't use it. int ItemsCount; // [Internal] Number of items float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it + ImGuiListClipperFlags Flags; // [Internal] Flags, currently not yet well exposed. double StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed double StartSeekOffsetY; // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows. + ImGuiContext* Ctx; // [Internal] Parent UI context void* TempData; // [Internal] Internal data - ImGuiListClipperFlags Flags; // [Internal] Flags, currently not yet well exposed. // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step, and you can call SeekCursorForItem() manually if you need) // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). @@ -2995,7 +3025,7 @@ struct ImGuiListClipper #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9] //inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] - //inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] + //inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset((void*)this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] #endif }; @@ -3003,17 +3033,18 @@ struct ImGuiListClipper // - It is important that we are keeping those disabled by default so they don't leak in user space. // - This is in order to allow user enabling implicit cast operators between ImVec2/ImVec4 and their own types (using IM_VEC2_CLASS_EXTRA in imconfig.h) // - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. -// - We intentionally provide ImVec2*float but not float*ImVec2: this is rare enough and we want to reduce the surface for possible user mistake. #ifdef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF // ImVec2 operators inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +inline ImVec2 operator*(const float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); } inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +inline ImVec2 operator+(const ImVec2& lhs) { return lhs; } inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } @@ -3025,12 +3056,20 @@ inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x = inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } // ImVec4 operators inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } +inline ImVec4 operator*(const float lhs, const ImVec4& rhs) { return ImVec4(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } +inline ImVec4 operator+(const ImVec4& lhs) { return lhs; } inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } +inline ImVec4& operator*=(ImVec4& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs; lhs.w *= rhs; return lhs; } +inline ImVec4& operator/=(ImVec4& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs; lhs.w /= rhs; return lhs; } +inline ImVec4& operator+=(ImVec4& lhs, const ImVec4& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z; lhs.w += rhs.w; return lhs; } +inline ImVec4& operator-=(ImVec4& lhs, const ImVec4& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z; lhs.w -= rhs.w; return lhs; } +inline ImVec4& operator*=(ImVec4& lhs, const ImVec4& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; lhs.z *= rhs.z; lhs.w *= rhs.w; return lhs; } +inline ImVec4& operator/=(ImVec4& lhs, const ImVec4& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z; lhs.w /= rhs.w; return lhs; } inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -3132,16 +3171,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(). @@ -3257,12 +3303,6 @@ typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibilit typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); #endif -// Special Draw callback value to request renderer backend to reset the graphics/render state. -// The renderer backend needs to handle this special value, otherwise it will crash trying to call a function at this address. -// This is useful, for example, if you submitted callbacks which you know have altered the render state and you want it to be restored. -// Render state is not reset by default because they are many perfectly useful way of altering render state (e.g. changing shader/blending settings before an Image call). -#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) - // Typically, 1 command = 1 GPU draw call (unless command is a callback) // - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, // this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. @@ -3280,7 +3320,7 @@ struct ImDrawCmd int UserCallbackDataSize; // 4 // Size of callback user data when using storage, otherwise 0. int UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1. - ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed + ImDrawCmd() { memset((void*)this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! @@ -3326,7 +3366,7 @@ struct ImDrawListSplitter int _Count; // Number of active channels (1+) ImVector _Channels; // Draw channels (not resized down so _Count might be < Channels.Size) - inline ImDrawListSplitter() { memset(this, 0, sizeof(*this)); } + inline ImDrawListSplitter() { memset((void*)this, 0, sizeof(*this)); } inline ~ImDrawListSplitter() { ClearFreeMemory(); } inline void Clear() { _Current = 0; _Count = 1; } // Do not clear Channels[] so our allocations are reused next frame IMGUI_API void ClearFreeMemory(); @@ -3336,16 +3376,15 @@ struct ImDrawListSplitter }; // Flags for ImDrawList functions -// (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused) enum ImDrawFlags_ { ImDrawFlags_None = 0, - ImDrawFlags_Closed = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) ImDrawFlags_RoundCornersTopLeft = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01. ImDrawFlags_RoundCornersTopRight = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02. ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04. ImDrawFlags_RoundCornersBottomRight = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08. ImDrawFlags_RoundCornersNone = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag! + ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, @@ -3353,6 +3392,7 @@ enum ImDrawFlags_ ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified. ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, + ImDrawFlags_InvalidMask_ = (ImDrawFlags)0x8000000F, }; // Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. @@ -3418,7 +3458,9 @@ struct ImDrawList // In future versions we will use textures to provide cheaper and higher-quality circles. // Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides. IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f); @@ -3439,7 +3481,7 @@ struct ImDrawList // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); @@ -3459,7 +3501,7 @@ struct ImDrawList inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse @@ -3469,14 +3511,15 @@ struct ImDrawList // Advanced: Draw Callbacks // - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible). - // - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. + // - Use special GetPlatformIO().DrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. + // - See other standard callbacks in GetPlatformIO(), which may or not be supported by your backend. // - Your rendering loop must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. All standard backends are honoring this. // - For some backends, the callback may access selected render-states exposed by the backend in a ImGui_ImplXXXX_RenderState structure pointed to by platform_io.Renderer_RenderState. // - IMPORTANT: please be mindful of the different level of indirection between using size==0 (copying argument) and using size>0 (copying pointed data into a buffer). // - If userdata_size == 0: we copy/store the 'userdata' argument as-is. It will be available unmodified in ImDrawCmd::UserCallbackData during render. // - If userdata_size > 0, we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data. // - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*. - IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0); + IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata = NULL, size_t userdata_size = 0); // Advanced: Miscellaneous IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible @@ -3506,8 +3549,15 @@ struct ImDrawList // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) { AddRect(p_min, p_max, col, rounding, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED. + inline void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) { AddPolyline(points, num_points, col, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED. + inline void PathStroke(ImU32 col, ImDrawFlags flags, float thickness) { PathStroke(col, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED. inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.0 inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.0 +#else + IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding /*= 0.0f*/, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) = delete; + inline void PathStroke(ImU32 col, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; #endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) @@ -3619,7 +3669,8 @@ struct ImTextureData bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. // Functions - ImTextureData() { memset(this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } + // - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear() instead of ClearFonts()? + ImTextureData() { memset((void*)this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } ~ImTextureData() { DestroyPixels(); } IMGUI_API void Create(ImTextureFormat format, int w, int h); IMGUI_API void DestroyPixels(); @@ -3634,7 +3685,7 @@ struct ImTextureData // - Call SetTexID() and SetStatus() after honoring texture requests. Never modify TexID and Status directly! // - A backend may decide to destroy a texture that we did not request to destroy, which is fine (e.g. freeing resources), but we immediately set the texture back in _WantCreate mode. void SetTexID(ImTextureID tex_id) { TexID = tex_id; } - void SetStatus(ImTextureStatus status) { Status = status; if (status == ImTextureStatus_Destroyed && !WantDestroyNextFrame) Status = ImTextureStatus_WantCreate; } + void SetStatus(ImTextureStatus status) { Status = status; if (status == ImTextureStatus_Destroyed && !WantDestroyNextFrame && Pixels != nullptr) Status = ImTextureStatus_WantCreate; } }; //----------------------------------------------------------------------------- @@ -3648,16 +3699,15 @@ struct ImFontConfig char Name[40]; // // Name (strictly to ease debugging, hence limited size buffer) void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size - bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the owner ImFontAtlas (will delete memory itself). + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the owner ImFontAtlas (will delete memory itself). SINCE 1.92, THE DATA NEEDS TO PERSIST FOR WHOLE DURATION OF ATLAS. // Options bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. - bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries. + bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Prevents fractional font size from working correctly! Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, OversampleH/V will default to 1. ImS8 OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. ImS8 OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. - float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). + float SizePixels; // // Output size in pixels for rasterizer (more or less maps to the resulting font height). const ImWchar* GlyphRanges; // NULL // *LEGACY* THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a small user-provided list of Unicode ranges (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. @@ -3667,9 +3717,10 @@ struct ImFontConfig float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled ImU32 FontNo; // 0 // Index of font within TTF/OTF file unsigned int FontLoaderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. - //unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Ue FontLoaderFlags. + //unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Use FontLoaderFlags. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. + float ExtraSizeScale; // 1.0f // Extra rasterizer scale over SizePixels. // [Internal] ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates) @@ -3677,6 +3728,9 @@ struct ImFontConfig const ImFontLoader* FontLoader; // Custom font backend for this source (default source is the one stored in ImFontAtlas) void* FontLoaderData; // Font loader opaque storage (per font config) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + bool PixelSnapV; // true // [Obsoleted in 1.91.6] Align Scaled GlyphOffset.y to pixel boundaries. +#endif IMGUI_API ImFontConfig(); }; @@ -3693,7 +3747,7 @@ struct ImFontGlyph float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId. int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) - ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } + ImFontGlyph() { memset((void*)this, 0, sizeof(*this)); PackId = -1; } }; // Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). @@ -3726,7 +3780,7 @@ struct ImFontAtlasRect unsigned short w, h; // Size ImVec2 uv0, uv1; // UV coordinates (in current texture) - ImFontAtlasRect() { memset(this, 0, sizeof(*this)); } + ImFontAtlasRect() { memset((void*)this, 0, sizeof(*this)); } }; // Flags for ImFontAtlas build @@ -3751,7 +3805,7 @@ enum ImFontAtlasFlags_ // - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. // Common pitfalls: -// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the +// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persists up until the // atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. // - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction. // You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, @@ -3762,20 +3816,22 @@ struct ImFontAtlas IMGUI_API ImFontAtlas(); IMGUI_API ~ImFontAtlas(); IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); - IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); // Selects between AddFontDefaultVector() and AddFontDefaultBitmap(). + IMGUI_API ImFont* AddFontDefaultVector(const ImFontConfig* font_cfg = NULL); // Embedded scalable font. Recommended at any higher size. + IMGUI_API ImFont* AddFontDefaultBitmap(const ImFontConfig* font_cfg = NULL); // Embedded classic pixel-clean font. Recommended at Size 13px with no scaling. IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. IMGUI_API void RemoveFont(ImFont* font); - IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures). + IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! + IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. You can call this mid-frame if you load new fonts afterwards! IMGUI_API void CompactCache(); // Compact cached glyphs and texture. IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. // As we are transitioning toward a new font system, we expect to obsolete those soon: IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. - IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -3924,7 +3980,7 @@ struct ImFontBaked unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) unsigned int WantDestroy:1; // 0 // // Queued for destroy unsigned int LoadNoFallback:1; // 0 // // Disable loading fallback in lower-level calls. - unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantagous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw. + unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantageous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw. int LastUsedFrame; // 4 // // Record of that time this was bounds ImGuiID BakedId; // 4 // // Unique ID for this baked storage ImFont* OwnerFont; // 4-8 // in // Parent font @@ -3947,6 +4003,7 @@ enum ImFontFlags_ ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. + ImFontFlags_ImplicitRefSize = 1 << 4, // [Internal] Reference size was not set explicitly. }; // Font runtime data and rendering @@ -3967,10 +4024,10 @@ struct ImFont ImGuiID FontId; // Unique identifier for the font float LegacySize; // 4 // in // Font size passed to AddFont(). Use for old code calling PushFont() expecting to use that size. (use ImGui::GetFontBaked() to get font baked at current bound size). ImVector Sources; // 16 // in // List of sources. Pointers within OwnerAtlas->Sources[] - ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). + ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). If you ever want to temporarily swap this for an alternative/dummy char, make sure to clear EllipsisAutoBake. ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') - ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. - bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. + ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 17 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 8K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph (== EllipsisChar) needs to be generated by combining multiple '.'. ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() @@ -4011,8 +4068,10 @@ 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: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) - // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. + // 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. if (TexRef._TexData != NULL) IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); @@ -4073,6 +4132,7 @@ struct ImGuiViewport // The library never uses those fields, they are merely storage to facilitate backend implementation. void* RendererUserData; // void* to hold custom data structure for the renderer (e.g. swap chain, framebuffers etc.). generally set by your Renderer_CreateWindow function. void* PlatformUserData; // void* to hold custom data structure for the OS / platform (e.g. windowing info, render context). generally set by your Platform_CreateWindow function. + void* PlatformIconData; // void* to hold custom data structure for the OS / platform to specify an icon. Currently unused for exposed to allow experiments. void* PlatformHandle; // void* to hold higher-level, platform window handle (e.g. HWND for Win32 backend, Uint32 WindowID for SDL, GLFWWindow* for GLFW), for FindViewportByPlatformHandle(). void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (always HWND on Win32 platform, unused for other platforms). bool PlatformWindowCreated; // Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created. @@ -4080,12 +4140,13 @@ struct ImGuiViewport bool PlatformRequestResize; // Platform window requested resize (e.g. window was resized by the OS / host window manager, authoritative size will be OS window size) bool PlatformRequestClose; // Platform window requested closure (e.g. window was moved by the OS / host window manager, e.g. pressing ALT-F4) - ImGuiViewport() { memset(this, 0, sizeof(*this)); } + ImGuiViewport() { memset((void*)this, 0, sizeof(*this)); } ~ImGuiViewport() { IM_ASSERT(PlatformUserData == NULL && RendererUserData == NULL); } // Helpers ImVec2 GetCenter() const { return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); } ImVec2 GetWorkCenter() const { return ImVec2(WorkPos.x + WorkSize.x * 0.5f, WorkPos.y + WorkSize.y * 0.5f); } + IMGUI_API const char* GetDebugName() const; }; //----------------------------------------------------------------------------- @@ -4149,7 +4210,7 @@ struct ImGuiPlatformIO // Optional: Access OS clipboard // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) - const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx); + const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx); // Should return NULL on failure (e.g. clipboard data is not text). void (*Platform_SetClipboardTextFn)(ImGuiContext* ctx, const char* text); void* Platform_ClipboardUserData; @@ -4179,6 +4240,12 @@ struct ImGuiPlatformIO // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; + // Standard draw callbacks provided by renderer backend. + ImDrawCallback DrawCallback_ResetRenderState; // Request to reset the graphics/render state. + ImDrawCallback DrawCallback_SetSamplerLinear; // Request backend to set texture sampling to Linear. + ImDrawCallback DrawCallback_SetSamplerNearest; // Request backend to set texture sampling to Nearest/Point. + //ImDrawCallback DrawCallback_SetSamplerCustom; // Request backend to set texture sampling using Backend Specific data. + //------------------------------------------------------------------ // Input - Interface with Platform & Renderer backends for Multi-Viewport support //------------------------------------------------------------------ @@ -4269,7 +4336,7 @@ struct ImGuiPlatformImeData float InputLineHeight; // Line height (for IME). ImGuiID ViewportId; // ID of platform window/viewport. - ImGuiPlatformImeData() { memset(this, 0, sizeof(*this)); } + ImGuiPlatformImeData() { memset((void*)this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -4291,19 +4358,20 @@ namespace ImGui inline void PopButtonRepeat() { PopItemFlag(); } inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } inline void PopTabStop() { PopItemFlag(); } + // You do not need those functions! See #7838 on GitHub for more info. IMGUI_API ImVec2 GetContentRegionMax(); // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! IMGUI_API ImVec2 GetWindowContentRegionMin(); // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! IMGUI_API ImVec2 GetWindowContentRegionMax(); // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! - // OBSOLETED in 1.90.0 (from September 2023) - inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } - inline void EndChildFrame() { EndChild(); } - //inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders - //inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders - inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } - IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1); - IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + // OBSOLETED in 1.90.0 (from September 2023) + //IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1); // Getter signature changed. See 2023/09/15 and 2026/02/27 commits. + //IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); // Getter signature changed. See 2023/09/15 and 2026/02/27 commits. + //inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + //inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + //inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, flags); } + //inline void EndChildFrame() { EndChild(); } + //inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } // OBSOLETED in 1.89.7 (from June 2023) //IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() _before_ item. //-- OBSOLETED in 1.89.4 (from March 2023) @@ -4371,6 +4439,8 @@ namespace ImGui //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } +#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) // OBSOLETED in 1.92.8: Use ImGui::GetPlatformIO().DrawCallback_ResetRenderState + //-- OBSOLETED in 1.92.0: ImFontAtlasCustomRect becomes ImTextureRect // - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y // - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h @@ -4413,10 +4483,12 @@ typedef ImFontAtlasRect ImFontAtlasCustomRect; //typedef ImGuiKeyChord ImGuiKeyModFlags; // == int //enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = 0, ImGuiKeyModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiKeyModFlags_Shift = ImGuiMod_Shift, ImGuiKeyModFlags_Alt = ImGuiMod_Alt, ImGuiKeyModFlags_Super = ImGuiMod_Super }; -#define IM_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // OBSOLETED IN 1.90 (now using C++11 standard version) +//#define IM_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // OBSOLETED IN 1.90 (now using C++11 standard version) #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +#define IM_ARRAYSIZE IM_COUNTOF // RENAMED IN 1.92.6: IM_ARRAYSIZE -> IM_COUNTOF + // RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022) #ifdef IMGUI_DISABLE_METRICS_WINDOW #error IMGUI_DISABLE_METRICS_WINDOW was renamed to IMGUI_DISABLE_DEBUG_TOOLS, please use new name. diff --git a/src/Libs/imgui/imgui_demo.cpp b/src/Libs/imgui/imgui_demo.cpp index d70fc76..3941966 100644 --- a/src/Libs/imgui/imgui_demo.cpp +++ b/src/Libs/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (demo code) // Help: @@ -12,7 +12,7 @@ // How to easily locate code? // - Use Tools->Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools -// - Browse an online version the demo with code linked to hovered widgets: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html +// - Browse pthom's online imgui_explorer: web version the demo w/ source code browser: https://pthom.github.io/imgui_explorer // - Find a visible string and search for it in the code! //--------------------------------------------------- @@ -73,6 +73,7 @@ Index of this file: // [SECTION] Demo Window / ShowDemoWindow() // [SECTION] DemoWindowMenuBar() // [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) +// [SECTION] Helpers: ExampleImageViewer // [SECTION] DemoWindowWidgetsBasic() // [SECTION] DemoWindowWidgetsBullets() // [SECTION] DemoWindowWidgetsCollapsingHeaders() @@ -108,6 +109,7 @@ Index of this file: // [SECTION] User Guide / ShowUserGuide() // [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() // [SECTION] Example App: Debug Console / ShowExampleAppConsole() +// [SECTION] Example App: Image Viewer / ShowExampleAppImageViewer() // [SECTION] Example App: Debug Log / ShowExampleAppLog() // [SECTION] Example App: Simple Layout / ShowExampleAppLayout() // [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() @@ -142,7 +144,7 @@ Index of this file: #include // PRId64/PRIu64, not avail in some MinGW headers. #endif #ifdef __EMSCRIPTEN__ -#include // __EMSCRIPTEN_major__ etc. +#include // __EMSCRIPTEN_MAJOR__ etc. #endif // Visual Studio warnings @@ -240,6 +242,7 @@ static void ShowExampleAppConsole(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); static void ShowExampleAppDockSpace(bool* p_open); static void ShowExampleAppDocuments(bool* p_open); +static void ShowExampleAppImageViewer(bool* p_open); static void ShowExampleAppLog(bool* p_open); static void ShowExampleAppLayout(bool* p_open); static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data); @@ -294,13 +297,18 @@ static void ShowDockingDisabledMessage() io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; } -// Helper to wire demo markers located in code to an interactive browser -typedef void (*ImGuiDemoMarkerCallback)(const char* file, int line, const char* section, void* user_data); -extern ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback; -extern void* GImGuiDemoMarkerCallbackUserData; -ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback = NULL; -void* GImGuiDemoMarkerCallbackUserData = NULL; -#define IMGUI_DEMO_MARKER(section) do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback("imgui_demo.cpp", __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0) +// Helper to wire demo markers located in code to an interactive browser (e.g. https://pthom.github.io/imgui_explorer) +#if IMGUI_VERSION_NUM >= 19263 +namespace ImGui { extern IMGUI_API void DemoMarker(const char* file, int line, const char* section); } +#define IMGUI_DEMO_MARKER(section) do { ImGui::DemoMarker("imgui_demo.cpp", __LINE__, section); } while (0) +#endif + +// Sneakily forward declare functions which aren't worth putting in public API yet +namespace ImGui +{ + IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); + IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool is_open); +} //----------------------------------------------------------------------------- // [SECTION] Demo Window / ShowDemoWindow() @@ -316,6 +324,7 @@ struct ImGuiDemoWindowData bool ShowAppCustomRendering = false; bool ShowAppDocuments = false; bool ShowAppDockSpace = false; + bool ShowAppImageViewer = false; bool ShowAppLog = false; bool ShowAppLayout = false; bool ShowAppPropertyEditor = false; @@ -362,6 +371,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (demo_data.ShowAppAssetsBrowser) { ShowExampleAppAssetsBrowser(&demo_data.ShowAppAssetsBrowser); } if (demo_data.ShowAppConsole) { ShowExampleAppConsole(&demo_data.ShowAppConsole); } if (demo_data.ShowAppCustomRendering) { ShowExampleAppCustomRendering(&demo_data.ShowAppCustomRendering); } + if (demo_data.ShowAppImageViewer) { ShowExampleAppImageViewer(&demo_data.ShowAppImageViewer); } if (demo_data.ShowAppLog) { ShowExampleAppLog(&demo_data.ShowAppLog); } if (demo_data.ShowAppLayout) { ShowExampleAppLayout(&demo_data.ShowAppLayout); } if (demo_data.ShowAppPropertyEditor) { ShowExampleAppPropertyEditor(&demo_data.ShowAppPropertyEditor, &demo_data); } @@ -446,14 +456,17 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); - IMGUI_DEMO_MARKER("Help"); if (ImGui::CollapsingHeader("Help")) { + IMGUI_DEMO_MARKER("Help"); ImGui::SeparatorText("ABOUT THIS DEMO:"); ImGui::BulletText("Sections below are demonstrating many aspects of the library."); ImGui::BulletText("The \"Examples\" menu above leads to more demo contents."); ImGui::BulletText("The \"Tools\" menu above gives access to: About Box, Style Editor,\n" "and Metrics/Debugger (general purpose Dear ImGui debugging tool)."); + ImGui::BulletText("Web demo (w/ source code browser): "); + ImGui::SameLine(0, 0); + ImGui::TextLinkOpenURL("https://pthom.github.io/imgui_explorer"); ImGui::SeparatorText("PROGRAMMER GUIDE:"); ImGui::BulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); @@ -469,13 +482,13 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::ShowUserGuide(); } - IMGUI_DEMO_MARKER("Configuration"); if (ImGui::CollapsingHeader("Configuration")) { ImGuiIO& io = ImGui::GetIO(); if (ImGui::TreeNode("Configuration##2")) { + IMGUI_DEMO_MARKER("Configuration"); ImGui::SeparatorText("General"); ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); ImGui::SameLine(); HelpMarker("Enable keyboard controls."); @@ -582,7 +595,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); ImGui::SameLine(); HelpMarker("Enable blinking cursor (optional as some users consider it to be distracting)."); ImGui::Checkbox("io.ConfigInputTextEnterKeepActive", &io.ConfigInputTextEnterKeepActive); - ImGui::SameLine(); HelpMarker("Pressing Enter will keep item active and select contents (single-line only)."); + ImGui::SameLine(); HelpMarker("Pressing Enter will reactivate item and select all text (single-line only)."); ImGui::Checkbox("io.ConfigDragClickToInputText", &io.ConfigDragClickToInputText); ImGui::SameLine(); HelpMarker("Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving)."); ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors); @@ -627,9 +640,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Spacing(); } - IMGUI_DEMO_MARKER("Configuration/Backend Flags"); if (ImGui::TreeNode("Backend Flags")) { + IMGUI_DEMO_MARKER("Configuration/Backend Flags"); HelpMarker( "Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities.\n" "Here we expose them as read-only fields to avoid breaking interactions with your backend."); @@ -652,9 +665,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Spacing(); } - IMGUI_DEMO_MARKER("Configuration/Style, Fonts"); if (ImGui::TreeNode("Style, Fonts")) { + IMGUI_DEMO_MARKER("Configuration/Style, Fonts"); ImGui::Checkbox("Style Editor", &demo_data.ShowStyleEditor); ImGui::SameLine(); HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); @@ -662,9 +675,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Spacing(); } - IMGUI_DEMO_MARKER("Configuration/Capture, Logging"); if (ImGui::TreeNode("Capture/Logging")) { + IMGUI_DEMO_MARKER("Configuration/Capture, Logging"); HelpMarker( "The logging API redirects all text output so you can easily capture the content of " "a window or a block. Tree nodes can be automatically expanded.\n" @@ -682,9 +695,9 @@ void ImGui::ShowDemoWindow(bool* p_open) } } - IMGUI_DEMO_MARKER("Window options"); if (ImGui::CollapsingHeader("Window options")) { + IMGUI_DEMO_MARKER("Window options"); if (ImGui::BeginTable("split", 3)) { ImGui::TableNextColumn(); ImGui::Checkbox("No titlebar", &no_titlebar); @@ -721,7 +734,6 @@ void ImGui::ShowDemoWindow(bool* p_open) static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) { - IMGUI_DEMO_MARKER("Menu"); if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Menu")) @@ -741,6 +753,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) ImGui::MenuItem("Custom rendering", NULL, &demo_data->ShowAppCustomRendering); ImGui::MenuItem("Documents", NULL, &demo_data->ShowAppDocuments); ImGui::MenuItem("Dockspace", NULL, &demo_data->ShowAppDockSpace); + ImGui::MenuItem("Image Viewer", NULL, &demo_data->ShowAppImageViewer); ImGui::MenuItem("Log", NULL, &demo_data->ShowAppLog); ImGui::MenuItem("Property editor", NULL, &demo_data->ShowAppPropertyEditor); ImGui::MenuItem("Simple layout", NULL, &demo_data->ShowAppLayout); @@ -772,7 +785,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) ImGui::Checkbox("Highlight ID Conflicts", &io.ConfigDebugHighlightIdConflicts); ImGui::EndDisabled(); ImGui::Checkbox("Assert on error recovery", &io.ConfigErrorRecoveryEnableAssert); - ImGui::TextDisabled("(see Demo->Configuration for details & more)"); + ImGui::TextDisabled("(see Demo->Configuration for more)"); ImGui::EndMenu(); } ImGui::MenuItem("Debug Log", NULL, &demo_data->ShowDebugLog, has_debug_tools); @@ -802,9 +815,9 @@ struct ExampleTreeNode // Tree structure char Name[28] = ""; int UID = 0; - ExampleTreeNode* Parent = NULL; + ExampleTreeNode* Parent = NULL; ImVector Childs; - unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily + int IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily // Leaf Data bool HasData = false; // All leaves have data @@ -817,7 +830,7 @@ struct ExampleTreeNode // (this is a minimal version of what a typical advanced application may provide) struct ExampleMemberInfo { - const char* Name; // Member name + const char* Name; // Member name ImGuiDataType DataType; // Member type int DataCount; // Member count (1 when scalar) int Offset; // Offset inside parent structure @@ -835,10 +848,10 @@ static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent) { ExampleTreeNode* node = IM_NEW(ExampleTreeNode); - snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name); + snprintf(node->Name, IM_COUNTOF(node->Name), "%s", name); node->UID = uid; node->Parent = parent; - node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0; + node->IndexInParent = parent ? parent->Childs.Size : 0; if (parent) parent->Childs.push_back(node); return node; @@ -852,28 +865,35 @@ static void ExampleTree_DestroyNode(ExampleTreeNode* node) } // Create example tree data -// (this allocates _many_ more times than most other code in either Dear ImGui or others demo) +// (warning: this can allocates MANY MANY more times than other code in all of Dear ImGui + demo combined) +// (a real application managing one million nodes would likely store its tree data differently) static ExampleTreeNode* ExampleTree_CreateDemoTree() { - static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" }; + // 20 root nodes -> 211 total nodes, ~261 allocs. + // 1000 root nodes -> ~11K total nodes, ~14K allocs. + // 10000 root nodes -> ~123K total nodes, ~154K allocs. + // 100000 root nodes -> ~1338K total nodes, ~1666K allocs. + const int ROOT_ITEMS_COUNT = 20; + + static const char* category_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" }; + const int category_count = IM_COUNTOF(category_names); const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name); char name_buf[NAME_MAX_LEN]; int uid = 0; ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL); - const int root_items_multiplier = 2; - for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++) + for (int idx_L0 = 0; idx_L0 < ROOT_ITEMS_COUNT; idx_L0++) { - snprintf(name_buf, IM_ARRAYSIZE(name_buf), "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier); + snprintf(name_buf, IM_COUNTOF(name_buf), "%s %d", category_names[idx_L0 / (ROOT_ITEMS_COUNT / category_count)], idx_L0 % (ROOT_ITEMS_COUNT / category_count)); ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0); const int number_of_childs = (int)strlen(node_L1->Name); for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) { - snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Child %d", idx_L1); + snprintf(name_buf, IM_COUNTOF(name_buf), "Child %d", idx_L1); ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1); node_L2->HasData = true; if (idx_L1 == 0) { - snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Sub-child %d", 0); + snprintf(name_buf, IM_COUNTOF(name_buf), "Sub-child %d", 0); ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2); node_L3->HasData = true; } @@ -883,14 +903,95 @@ static ExampleTreeNode* ExampleTree_CreateDemoTree() } //----------------------------------------------------------------------------- +// [SECTION] Helpers: ExampleImageViewer +//----------------------------------------------------------------------------- + +struct ExampleImageViewerData +{ + ImU32 ImageBgColor = IM_COL32(100, 100, 100, 255); + ImU32 GridColor = IM_COL32(255, 255, 255, 100); + bool GridEnabled = true; + bool ViewReset = true; + ImVec2 ViewOffset; // in image space + float Zoom = 10.0f; + float ZoomMin = 1.0f; + float ZoomMax = 10000.0f; +}; + +static void ExampleImageViewer_DrawOptions(ExampleImageViewerData* data) +{ + ImGui::SetNextItemShortcut(ImGuiKey_G, ImGuiInputFlags_Tooltip); // | ImGuiInputFlags_RouteGlobal + ImGui::Checkbox("Grid", &data->GridEnabled); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f); + float zoom_100 = data->Zoom * 100.0f; + if (ImGui::DragFloat("##Zoom", &zoom_100, 5.0f, data->ZoomMin * 100.0f, data->ZoomMax * 100.0f, "%.0f%%", ImGuiSliderFlags_AlwaysClamp)) + data->Zoom = zoom_100 / 100.0f; +} + +static void ExampleImageViewer_DrawCanvas(ExampleImageViewerData* data, ImVec2 canvas_size, ImTextureRef image_tex_ref, int image_w, int image_h) +{ + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + IM_ASSERT(canvas_size.x >= 0.0f && canvas_size.y >= 0.0f); + + // Layout canvas + ImGui::InvisibleButton("##Canvas", canvas_size); + ImVec2 canvas_min = ImGui::GetItemRectMin(); + ImVec2 canvas_max = ImGui::GetItemRectMax(); + + if (data->ViewReset) + data->ViewOffset = ImVec2((canvas_size.x * 0.5f / data->Zoom) - 0.5f, (canvas_size.y * 0.5f / data->Zoom) - 0.5f); // Add half a pixel padding + data->ViewReset = false; + + // Handle inputs + if (ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY)) + if (io.MouseWheel != 0.0f) + data->Zoom = IM_CLAMP(data->Zoom * (1.0f + io.MouseWheel * 0.10f), data->ZoomMin, data->ZoomMax); + float zoom = data->Zoom; // (float)(int)ViewZoom; + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0)) + { + data->ViewOffset.x -= io.MouseDelta.x / zoom; + data->ViewOffset.y -= io.MouseDelta.y / zoom; + } + + // Display image + ImVec2 image_min, image_max; + image_min.x = (float)(int)((canvas_min.x - (data->ViewOffset.x * zoom)) + (canvas_size.x * 0.5f)); + image_min.y = (float)(int)((canvas_min.y - (data->ViewOffset.y * zoom)) + (canvas_size.y * 0.5f)); + image_max.x = (float)(int)(image_min.x + image_w * zoom); + image_max.y = (float)(int)(image_min.y + image_h * zoom); + draw_list->AddRect(ImVec2(canvas_min.x - 1.0f, canvas_min.y - 1.0f), ImVec2(canvas_max.x + 1.0f, canvas_max.y + 1.0f), IM_COL32(255, 255, 255, 255)); + draw_list->PushClipRect(canvas_min, canvas_max, true); + draw_list->AddRectFilled(image_min, image_max, data->ImageBgColor); + if (platform_io.DrawCallback_SetSamplerNearest != NULL) + draw_list->AddCallback(platform_io.DrawCallback_SetSamplerNearest); + draw_list->AddImage(image_tex_ref, image_min, image_max); + if (platform_io.DrawCallback_SetSamplerLinear != NULL) + draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear); + + // Display grid lines for visible pixels + if (data->GridEnabled && zoom > 6.0f) + { + const float step = (float)zoom; + for (int px = (int)((canvas_min.x - image_min.x) / step); px <= (int)((canvas_max.x - image_min.x) / step); px++) + draw_list->AddLineV(image_min.x + px * step, canvas_min.y, canvas_max.y, data->GridColor, 1.0f); + for (int py = (int)((canvas_min.y - image_min.y) / step); py <= (int)((canvas_max.y - image_min.y) / step); py++) + draw_list->AddLineH(canvas_min.x, canvas_max.x, image_min.y + py * step, data->GridColor, 1.0f); + } + draw_list->PopClipRect(); +} + +//----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsBasic() //----------------------------------------------------------------------------- static void DemoWindowWidgetsBasic() { - IMGUI_DEMO_MARKER("Widgets/Basic"); if (ImGui::TreeNode("Basic")) { + IMGUI_DEMO_MARKER("Widgets/Basic"); ImGui::SeparatorText("General"); IMGUI_DEMO_MARKER("Widgets/Basic/Button"); @@ -963,7 +1064,7 @@ static void DemoWindowWidgetsBasic() // - Otherwise, see the 'Dear ImGui Demo->Widgets->Text Input->Resize Callback' for using ImGuiInputTextFlags_CallbackResize. IMGUI_DEMO_MARKER("Widgets/Basic/InputText"); static char str0[128] = "Hello, world!"; - ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::InputText("input text", str0, IM_COUNTOF(str0)); ImGui::SameLine(); HelpMarker( "USER:\n" "Hold Shift or use mouse to select text.\n" @@ -978,7 +1079,7 @@ static void DemoWindowWidgetsBasic() "in imgui_demo.cpp)."); static char str1[128] = ""; - ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1)); + ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_COUNTOF(str1)); IMGUI_DEMO_MARKER("Widgets/Basic/InputInt, InputFloat"); static int i0 = 123; @@ -1069,7 +1170,7 @@ static void DemoWindowWidgetsBasic() IMGUI_DEMO_MARKER("Widgets/Basic/Combo"); const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIIIIII", "JJJJ", "KKKKKKK" }; static int item_current = 0; - ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items)); + ImGui::Combo("combo", &item_current, items, IM_COUNTOF(items)); ImGui::SameLine(); HelpMarker( "Using the simplified one-liner Combo API here.\n" "Refer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API."); @@ -1081,7 +1182,7 @@ static void DemoWindowWidgetsBasic() IMGUI_DEMO_MARKER("Widgets/Basic/ListBox"); const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; static int item_current = 1; - ImGui::ListBox("listbox", &item_current, items, IM_ARRAYSIZE(items), 4); + ImGui::ListBox("listbox", &item_current, items, IM_COUNTOF(items), 4); ImGui::SameLine(); HelpMarker( "Using the simplified one-liner ListBox API here.\n" "Refer to the \"List boxes\" section below for an explanation of how to use the more flexible and general BeginListBox/EndListBox API."); @@ -1103,9 +1204,9 @@ static void DemoWindowWidgetsBasic() static void DemoWindowWidgetsBullets() { - IMGUI_DEMO_MARKER("Widgets/Bullets"); if (ImGui::TreeNode("Bullets")) { + IMGUI_DEMO_MARKER("Widgets/Bullets"); ImGui::BulletText("Bullet point 1"); ImGui::BulletText("Bullet point 2\nOn multiple lines"); if (ImGui::TreeNode("Tree node")) @@ -1125,9 +1226,9 @@ static void DemoWindowWidgetsBullets() static void DemoWindowWidgetsCollapsingHeaders() { - IMGUI_DEMO_MARKER("Widgets/Collapsing Headers"); if (ImGui::TreeNode("Collapsing Headers")) { + IMGUI_DEMO_MARKER("Widgets/Collapsing Headers"); static bool closable_group = true; ImGui::Checkbox("Show 2nd header", &closable_group); if (ImGui::CollapsingHeader("Header", ImGuiTreeNodeFlags_None)) @@ -1156,9 +1257,9 @@ static void DemoWindowWidgetsCollapsingHeaders() static void DemoWindowWidgetsColorAndPickers() { - IMGUI_DEMO_MARKER("Widgets/Color"); if (ImGui::TreeNode("Color/Picker Widgets")) { + IMGUI_DEMO_MARKER("Widgets/Color"); static ImVec4 color = ImVec4(114.0f / 255.0f, 144.0f / 255.0f, 154.0f / 255.0f, 200.0f / 255.0f); static ImGuiColorEditFlags base_flags = ImGuiColorEditFlags_None; @@ -1167,8 +1268,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"); @@ -1203,7 +1305,7 @@ static void DemoWindowWidgetsColorAndPickers() static ImVec4 saved_palette[32] = {}; if (saved_palette_init) { - for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + for (int n = 0; n < IM_COUNTOF(saved_palette); n++) { ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); @@ -1236,7 +1338,7 @@ static void DemoWindowWidgetsColorAndPickers() color = backup_color; ImGui::Separator(); ImGui::Text("Palette"); - for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + for (int n = 0; n < IM_COUNTOF(saved_palette); n++) { ImGui::PushID(n); if ((n % 8) != 0) @@ -1356,9 +1458,9 @@ static void DemoWindowWidgetsColorAndPickers() static void DemoWindowWidgetsComboBoxes() { - IMGUI_DEMO_MARKER("Widgets/Combo"); if (ImGui::TreeNode("Combo")) { + IMGUI_DEMO_MARKER("Widgets/Combo"); // Combo Boxes are also called "Dropdown" in other systems // Expose flags as checkbox for the demo static ImGuiComboFlags flags = 0; @@ -1389,7 +1491,7 @@ static void DemoWindowWidgetsComboBoxes() const char* combo_preview_value = items[item_selected_idx]; if (ImGui::BeginCombo("combo 1", combo_preview_value, flags)) { - for (int n = 0; n < IM_ARRAYSIZE(items); n++) + for (int n = 0; n < IM_COUNTOF(items); n++) { const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) @@ -1415,7 +1517,7 @@ static void DemoWindowWidgetsComboBoxes() ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F); filter.Draw("##Filter", -FLT_MIN); - for (int n = 0; n < IM_ARRAYSIZE(items); n++) + for (int n = 0; n < IM_COUNTOF(items); n++) { const bool is_selected = (item_selected_idx == n); if (filter.PassFilter(items[n])) @@ -1437,11 +1539,11 @@ static void DemoWindowWidgetsComboBoxes() // Simplified one-liner Combo() using an array of const char* // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control. static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview - ImGui::Combo("combo 4 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); + ImGui::Combo("combo 4 (array)", &item_current_3, items, IM_COUNTOF(items)); // Simplified one-liner Combo() using an accessor function static int item_current_4 = 0; - ImGui::Combo("combo 5 (function)", &item_current_4, [](void* data, int n) { return ((const char**)data)[n]; }, items, IM_ARRAYSIZE(items)); + ImGui::Combo("combo 5 (function)", &item_current_4, [](void* data, int n) { return ((const char**)data)[n]; }, items, IM_COUNTOF(items)); ImGui::TreePop(); } @@ -1453,9 +1555,9 @@ static void DemoWindowWidgetsComboBoxes() static void DemoWindowWidgetsDataTypes() { - IMGUI_DEMO_MARKER("Widgets/Data Types"); if (ImGui::TreeNode("Data Types")) { + IMGUI_DEMO_MARKER("Widgets/Data Types"); // DragScalar/InputScalar/SliderScalar functions allow various data types // - signed/unsigned // - 8/16/32/64-bits @@ -1588,9 +1690,9 @@ static void DemoWindowWidgetsDataTypes() static void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data) { - IMGUI_DEMO_MARKER("Widgets/Disable Blocks"); if (ImGui::TreeNode("Disable Blocks")) { + IMGUI_DEMO_MARKER("Widgets/Disable Blocks"); ImGui::Checkbox("Disable entire section above", &demo_data->DisableSections); ImGui::SameLine(); HelpMarker("Demonstrate using BeginDisabled()/EndDisabled() across other sections."); ImGui::TreePop(); @@ -1603,12 +1705,12 @@ static void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data) static void DemoWindowWidgetsDragAndDrop() { - IMGUI_DEMO_MARKER("Widgets/Drag and drop"); if (ImGui::TreeNode("Drag and Drop")) { - IMGUI_DEMO_MARKER("Widgets/Drag and drop/Standard widgets"); + IMGUI_DEMO_MARKER("Widgets/Drag and drop"); if (ImGui::TreeNode("Drag and drop in standard widgets")) { + IMGUI_DEMO_MARKER("Widgets/Drag and drop/Standard widgets"); // ColorEdit widgets automatically act as drag source and drag target. // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F // to allow your own widgets to use colors in their drag and drop interaction. @@ -1621,9 +1723,9 @@ static void DemoWindowWidgetsDragAndDrop() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Drag and drop/Copy-swap items"); if (ImGui::TreeNode("Drag and drop to copy/swap items")) { + IMGUI_DEMO_MARKER("Widgets/Drag and drop/Copy-swap items"); enum Mode { Mode_Copy, @@ -1640,7 +1742,7 @@ static void DemoWindowWidgetsDragAndDrop() "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn" }; - for (int n = 0; n < IM_ARRAYSIZE(names); n++) + for (int n = 0; n < IM_COUNTOF(names); n++) { ImGui::PushID(n); if ((n % 3) != 0) @@ -1689,9 +1791,9 @@ static void DemoWindowWidgetsDragAndDrop() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Drag to reorder items (simple)"); if (ImGui::TreeNode("Drag to reorder items (simple)")) { + IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Drag to reorder items (simple)"); // FIXME: there is temporary (usually single-frame) ID Conflict during reordering as a same item may be submitting twice. // This code was always slightly faulty but in a way which was not easily noticeable. // Until we fix this, enable ImGuiItemFlags_AllowDuplicateId to disable detecting the issue. @@ -1702,7 +1804,7 @@ static void DemoWindowWidgetsDragAndDrop() "We don't use the drag and drop api at all here! " "Instead we query when the item is held but not hovered, and order items accordingly."); static const char* item_names[] = { "Item One", "Item Two", "Item Three", "Item Four", "Item Five" }; - for (int n = 0; n < IM_ARRAYSIZE(item_names); n++) + for (int n = 0; n < IM_COUNTOF(item_names); n++) { const char* item = item_names[n]; ImGui::Selectable(item); @@ -1710,7 +1812,7 @@ static void DemoWindowWidgetsDragAndDrop() if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) { int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); - if (n_next >= 0 && n_next < IM_ARRAYSIZE(item_names)) + if (n_next >= 0 && n_next < IM_COUNTOF(item_names)) { item_names[n] = item_names[n_next]; item_names[n_next] = item; @@ -1723,9 +1825,9 @@ static void DemoWindowWidgetsDragAndDrop() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Tooltip at target location"); if (ImGui::TreeNode("Tooltip at target location")) { + IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Tooltip at target location"); for (int n = 0; n < 2; n++) { // Drop targets @@ -1761,9 +1863,9 @@ static void DemoWindowWidgetsDragAndDrop() static void DemoWindowWidgetsDragsAndSliders() { - IMGUI_DEMO_MARKER("Widgets/Drag and Slider Flags"); if (ImGui::TreeNode("Drag/Slider Flags")) { + IMGUI_DEMO_MARKER("Widgets/Drag and Slider Flags"); // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same! static ImGuiSliderFlags flags = ImGuiSliderFlags_None; ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", &flags, ImGuiSliderFlags_AlwaysClamp); @@ -1781,9 +1883,11 @@ 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); // 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); @@ -1793,14 +1897,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; + 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(); } @@ -1810,14 +1917,11 @@ static void DemoWindowWidgetsDragsAndSliders() // [SECTION] DemoWindowWidgetsFonts() //----------------------------------------------------------------------------- -// Forward declare ShowFontAtlas() which isn't worth putting in public API yet -namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } - static void DemoWindowWidgetsFonts() { - IMGUI_DEMO_MARKER("Widgets/Fonts"); if (ImGui::TreeNode("Fonts")) { + IMGUI_DEMO_MARKER("Widgets/Fonts"); ImFontAtlas* atlas = ImGui::GetIO().Fonts; ImGui::ShowFontAtlas(atlas); // FIXME-NEWATLAS: Provide a demo to add/create a procedural font? @@ -1831,9 +1935,9 @@ static void DemoWindowWidgetsFonts() static void DemoWindowWidgetsImages() { - IMGUI_DEMO_MARKER("Widgets/Images"); if (ImGui::TreeNode("Images")) { + IMGUI_DEMO_MARKER("Widgets/Images"); ImGuiIO& io = ImGui::GetIO(); ImGui::TextWrapped( "Below we are displaying the font texture (which is the only texture we have access to in this demo). " @@ -1856,40 +1960,29 @@ static void DemoWindowWidgetsImages() // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // Grab the current texture identifier used by the font atlas. - ImTextureRef my_tex_id = io.Fonts->TexRef; + ImFontAtlas* atlas = io.Fonts; + ImTextureRef my_tex_id = atlas->TexRef; + float my_tex_w = (float)atlas->TexData->Width; // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. + float my_tex_h = (float)atlas->TexData->Height; + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + + // Basic drawing + ImGui::SeparatorText("Image()/ImageWithBg() function"); + ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left + ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right + ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); + ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::PopStyleVar(); - // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. - float my_tex_w = (float)io.Fonts->TexData->Width; - float my_tex_h = (float)io.Fonts->TexData->Height; - - { - ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left - ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right - ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); - ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - if (ImGui::BeginItemTooltip()) - { - float region_sz = 32.0f; - float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; - float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; - float zoom = 4.0f; - if (region_x < 0.0f) { region_x = 0.0f; } - else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; } - if (region_y < 0.0f) { region_y = 0.0f; } - else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; } - ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); - ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); - ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); - ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); - ImGui::ImageWithBg(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(); - } + // Fancy widget + ImGui::SeparatorText("Interactive Image Viewer"); + static ExampleImageViewerData image_viewer; + ImVec2 canvas_size(ImGui::GetContentRegionAvail().x, my_tex_h * 2.0f); + ExampleImageViewer_DrawOptions(&image_viewer); + ExampleImageViewer_DrawCanvas(&image_viewer, canvas_size, my_tex_id, (int)my_tex_w, (int)my_tex_h); IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons"); + ImGui::SeparatorText("Textured Buttons"); ImGui::TextWrapped("And now some textured buttons.."); static int pressed_count = 0; for (int i = 0; i < 8; i++) @@ -1924,9 +2017,9 @@ static void DemoWindowWidgetsImages() static void DemoWindowWidgetsListBoxes() { - IMGUI_DEMO_MARKER("Widgets/List Boxes"); if (ImGui::TreeNode("List Boxes")) { + IMGUI_DEMO_MARKER("Widgets/List Boxes"); // BeginListBox() is essentially a thin wrapper to using BeginChild()/EndChild() // using the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. // You may be tempted to simply use BeginChild() directly. However note that BeginChild() requires EndChild() @@ -1944,7 +2037,7 @@ static void DemoWindowWidgetsListBoxes() if (ImGui::BeginListBox("listbox 1")) { - for (int n = 0; n < IM_ARRAYSIZE(items); n++) + for (int n = 0; n < IM_COUNTOF(items); n++) { const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) @@ -1965,7 +2058,7 @@ static void DemoWindowWidgetsListBoxes() ImGui::Text("Full-width:"); if (ImGui::BeginListBox("##listbox 2", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing()))) { - for (int n = 0; n < IM_ARRAYSIZE(items); n++) + for (int n = 0; n < IM_COUNTOF(items); n++) { bool is_selected = (item_selected_idx == n); ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0; @@ -1989,35 +2082,38 @@ static void DemoWindowWidgetsListBoxes() static void DemoWindowWidgetsMultiComponents() { - IMGUI_DEMO_MARKER("Widgets/Multi-component Widgets"); if (ImGui::TreeNode("Multi-component Widgets")) { + IMGUI_DEMO_MARKER("Widgets/Multi-component Widgets"); static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; static int vec4i[4] = { 1, 5, 100, 255 }; + static ImGuiSliderFlags flags = 0; + ImGui::CheckboxFlags("ImGuiSliderFlags_ColorMarkers", &flags, ImGuiSliderFlags_ColorMarkers); // Only passing this to Drag/Sliders + ImGui::SeparatorText("2-wide"); ImGui::InputFloat2("input float2", vec4f); - ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); ImGui::InputInt2("input int2", vec4i); - ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); - ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f, NULL, flags); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255, NULL, flags); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f, NULL, flags); + ImGui::SliderInt2("slider int2", vec4i, 0, 255, NULL, flags); ImGui::SeparatorText("3-wide"); ImGui::InputFloat3("input float3", vec4f); - ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); ImGui::InputInt3("input int3", vec4i); - ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); - ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f, NULL, flags); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255, NULL, flags); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f, NULL, flags); + ImGui::SliderInt3("slider int3", vec4i, 0, 255, NULL, flags); ImGui::SeparatorText("4-wide"); ImGui::InputFloat4("input float4", vec4f); - ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); ImGui::InputInt4("input int4", vec4i); - ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); - ImGui::SliderInt4("slider int4", vec4i, 0, 255); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f, NULL, flags); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255, NULL, flags); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f, NULL, flags); + ImGui::SliderInt4("slider int4", vec4i, 0, 255, NULL, flags); ImGui::SeparatorText("Ranges"); static float begin = 10, end = 90; @@ -2039,9 +2135,9 @@ static void DemoWindowWidgetsPlotting() // Plot/Graph widgets are not very good. // Consider using a third-party library such as ImPlot: https://github.com/epezent/implot // (see others https://github.com/ocornut/imgui/wiki/Useful-Extensions) - IMGUI_DEMO_MARKER("Widgets/Plotting"); if (ImGui::TreeNode("Plotting")) { + IMGUI_DEMO_MARKER("Widgets/Plotting"); ImGui::Text("Need better plotting and graphing? Consider using ImPlot:"); ImGui::TextLinkOpenURL("https://github.com/epezent/implot"); ImGui::Separator(); @@ -2051,8 +2147,8 @@ static void DemoWindowWidgetsPlotting() // Plot as lines and plot as histogram static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); - ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); + ImGui::PlotLines("Frame Times", arr, IM_COUNTOF(arr)); + ImGui::PlotHistogram("Histogram", arr, IM_COUNTOF(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!"); // Fill an array of contiguous float values to plot @@ -2067,7 +2163,7 @@ static void DemoWindowWidgetsPlotting() { static float phase = 0.0f; values[values_offset] = cosf(phase); - values_offset = (values_offset + 1) % IM_ARRAYSIZE(values); + values_offset = (values_offset + 1) % IM_COUNTOF(values); phase += 0.10f * values_offset; refresh_time += 1.0f / 60.0f; } @@ -2076,12 +2172,12 @@ static void DemoWindowWidgetsPlotting() // (in this example, we will display an average value) { float average = 0.0f; - for (int n = 0; n < IM_ARRAYSIZE(values); n++) + for (int n = 0; n < IM_COUNTOF(values); n++) average += values[n]; - average /= (float)IM_ARRAYSIZE(values); + average /= (float)IM_COUNTOF(values); char overlay[32]; sprintf(overlay, "avg %f", average); - ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); + ImGui::PlotLines("Lines", values, IM_COUNTOF(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); } // Use functions to generate output @@ -2112,14 +2208,16 @@ static void DemoWindowWidgetsPlotting() static void DemoWindowWidgetsProgressBars() { - IMGUI_DEMO_MARKER("Widgets/Progress Bars"); if (ImGui::TreeNode("Progress Bars")) { + IMGUI_DEMO_MARKER("Widgets/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. @@ -2127,9 +2225,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. @@ -2148,9 +2245,9 @@ static void DemoWindowWidgetsProgressBars() static void DemoWindowWidgetsQueryingStatuses() { - IMGUI_DEMO_MARKER("Widgets/Querying Item Status (Edited,Active,Hovered etc.)"); if (ImGui::TreeNode("Querying Item Status (Edited/Active/Hovered etc.)")) { + IMGUI_DEMO_MARKER("Widgets/Querying Item Status (Edited,Active,Hovered etc.)"); // Select an item type const char* item_names[] = { @@ -2159,7 +2256,7 @@ static void DemoWindowWidgetsQueryingStatuses() }; static int item_type = 4; static bool item_disabled = false; - ImGui::Combo("Item Type", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names)); + ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); @@ -2176,8 +2273,8 @@ static void DemoWindowWidgetsQueryingStatuses() if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) if (item_type == 3) { ret = ImGui::Checkbox("ITEM: Checkbox", &b); } // Testing checkbox if (item_type == 4) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item - if (item_type == 5) { ret = ImGui::InputText("ITEM: InputText", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which handles tabbing) - if (item_type == 6) { ret = ImGui::InputTextMultiline("ITEM: InputTextMultiline", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which uses a child window) + if (item_type == 5) { ret = ImGui::InputText("ITEM: InputText", &str[0], IM_COUNTOF(str)); } // Testing input text (which handles tabbing) + if (item_type == 6) { ret = ImGui::InputTextMultiline("ITEM: InputTextMultiline", &str[0], IM_COUNTOF(str)); } // Testing input text (which uses a child window) if (item_type == 7) { ret = ImGui::InputFloat("ITEM: InputFloat", col4f, 1.0f); } // Testing +/- buttons on scalar input if (item_type == 8) { ret = ImGui::InputFloat3("ITEM: InputFloat3", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) if (item_type == 9) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) @@ -2185,8 +2282,8 @@ static void DemoWindowWidgetsQueryingStatuses() if (item_type == 11) { ret = ImGui::MenuItem("ITEM: MenuItem"); } // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) if (item_type == 12) { ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); } // Testing tree node if (item_type == 13) { ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy. - if (item_type == 14) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); } - if (item_type == 15) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } + if (item_type == 14) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_COUNTOF(items)); } + if (item_type == 15) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_COUNTOF(items), IM_COUNTOF(items)); } bool hovered_delay_none = ImGui::IsItemHovered(); bool hovered_delay_stationary = ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary); @@ -2253,16 +2350,16 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::EndDisabled(); char buf[1] = ""; - ImGui::InputText("unused", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_ReadOnly); + ImGui::InputText("unused", buf, IM_COUNTOF(buf), ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); HelpMarker("This widget is only here to be able to tab-out of the widgets above and see e.g. Deactivated() status."); ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Querying Window Status (Focused,Hovered etc.)"); if (ImGui::TreeNode("Querying Window Status (Focused/Hovered etc.)")) { + IMGUI_DEMO_MARKER("Widgets/Querying Window Status (Focused,Hovered etc.)"); static bool embed_all_inside_a_child_window = false; ImGui::Checkbox("Embed everything inside a child window for testing _RootWindow flag.", &embed_all_inside_a_child_window); if (embed_all_inside_a_child_window) @@ -2364,10 +2461,10 @@ static void DemoWindowWidgetsQueryingStatuses() static void DemoWindowWidgetsSelectables() { - IMGUI_DEMO_MARKER("Widgets/Selectables"); //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Selectables")) { + IMGUI_DEMO_MARKER("Widgets/Selectables"); // Selectable() has 2 overloads: // - The one taking "bool selected" as a read-only selection information. // When Selectable() has been clicked it returns true and you can alter selection state accordingly. @@ -2388,20 +2485,50 @@ static void DemoWindowWidgetsSelectables() } IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line"); - if (ImGui::TreeNode("Rendering more items on the same line")) - { - // (1) Using SetNextItemAllowOverlap() - // (2) Using the Selectable() override that takes "bool* p_selected" parameter, the bool value is toggled automatically. - static bool selected[3] = { false, false, false }; - ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(); ImGui::SmallButton("Link 1"); - ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(); ImGui::SmallButton("Link 2"); - ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(); ImGui::SmallButton("Link 3"); + if (ImGui::TreeNode("Multiple items on the same line")) + { + IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple items on the same line"); + // - Using SetNextItemAllowOverlap() + // - Using the Selectable() override that takes "bool* p_selected" parameter, the bool value is toggled automatically. + { + static bool selected[3] = {}; + ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(); ImGui::SmallButton("Link 1"); + ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("hello.cpp", &selected[1]); ImGui::SameLine(); ImGui::SmallButton("Link 2"); + ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("hello.h", &selected[2]); ImGui::SameLine(); ImGui::SmallButton("Link 3"); + } + + // (2) + // - Using ImGuiSelectableFlags_AllowOverlap is a shortcut for calling SetNextItemAllowOverlap() + // - No visible label, display contents inside the selectable bounds. + // - We don't maintain actual selection in this example to keep things simple. + ImGui::Spacing(); + { + static bool checked[5] = {}; + static int selected_n = 0; + const float color_marker_w = ImGui::CalcTextSize("x").x; + for (int n = 0; n < 5; n++) + { + ImGui::PushID(n); + ImGui::AlignTextToFramePadding(); + if (ImGui::Selectable("##selectable", selected_n == n, ImGuiSelectableFlags_AllowOverlap)) + selected_n = n; + ImGui::SameLine(0, 0); + ImGui::Checkbox("##check", &checked[n]); + ImGui::SameLine(); + ImVec4 color((n & 1) ? 1.0f : 0.2f, (n & 2) ? 1.0f : 0.2f, 0.2f, 1.0f); + ImGui::ColorButton("##color", color, ImGuiColorEditFlags_NoTooltip, ImVec2(color_marker_w, 0)); + ImGui::SameLine(); + ImGui::Text("Some label"); + ImGui::PopID(); + } + } + ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables"); if (ImGui::TreeNode("In Tables")) { + IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables"); static bool selected[10] = {}; if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) @@ -2435,9 +2562,9 @@ static void DemoWindowWidgetsSelectables() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/Grid"); if (ImGui::TreeNode("Grid")) { + IMGUI_DEMO_MARKER("Widgets/Selectables/Grid"); static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; // Add in a bit of silly fun... @@ -2446,13 +2573,14 @@ static void DemoWindowWidgetsSelectables() if (winning_state) ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f))); + const float size = ImGui::CalcTextSize("Sailor").x; for (int y = 0; y < 4; y++) for (int x = 0; x < 4; x++) { if (x > 0) ImGui::SameLine(); ImGui::PushID(y * 4 + x); - if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(50, 50))) + if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(size, size))) { // Toggle clicked cell + toggle neighbors selected[y][x] ^= 1; @@ -2468,14 +2596,16 @@ static void DemoWindowWidgetsSelectables() ImGui::PopStyleVar(); ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment"); if (ImGui::TreeNode("Alignment")) { + IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment"); HelpMarker( "By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item " "basis using PushStyleVar(). You'll probably want to always keep your default situation to " "left-align otherwise it becomes difficult to layout multiple items on a same line"); + static bool selected[3 * 3] = { true, false, true, false, true, false, true, false, true }; + const float size = ImGui::CalcTextSize("(1.0,1.0)").x; for (int y = 0; y < 3; y++) { for (int x = 0; x < 3; x++) @@ -2485,7 +2615,7 @@ static void DemoWindowWidgetsSelectables() sprintf(name, "(%.1f,%.1f)", alignment.x, alignment.y); if (x > 0) ImGui::SameLine(); ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment); - ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80)); + ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(size, size)); ImGui::PopStyleVar(); } } @@ -2616,7 +2746,7 @@ struct ExampleDualListBox { const int* a = (const int*)lhs; const int* b = (const int*)rhs; - return (*a - *b); + return *a - *b; } void SortItems(int n) { @@ -2664,7 +2794,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); @@ -2729,9 +2859,9 @@ struct ExampleDualListBox static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_data) { - IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select"); if (ImGui::TreeNode("Selection State & Multi-Select")) { + IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select"); HelpMarker("Selections can be built using Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data."); ImGui::BulletText("Wiki page:"); @@ -2739,9 +2869,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TextLinkOpenURL("imgui/wiki/Multi-Select", "https://github.com/ocornut/imgui/wiki/Multi-Select"); // Without any fancy API: manage single-selection yourself. - IMGUI_DEMO_MARKER("Widgets/Selection State/Single-Select"); if (ImGui::TreeNode("Single-Select")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Single-Select"); static int selected = -1; for (int n = 0; n < 5; n++) { @@ -2755,9 +2885,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // Demonstrate implementation a most-basic form of multi-selection manually // This doesn't support the Shift modifier which requires BeginMultiSelect()! - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)"); if (ImGui::TreeNode("Multi-Select (manual/simplified, without BeginMultiSelect)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)"); HelpMarker("Hold Ctrl and Click to select multiple items."); static bool selection[5] = { false, false, false, false, false }; for (int n = 0; n < 5; n++) @@ -2777,9 +2907,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // Demonstrate handling proper multi-selection using the BeginMultiSelect/EndMultiSelect API. // Shift+Click w/ Ctrl and other standard features are supported. // We use the ImGuiSelectionBasicStorage helper which you may freely reimplement. - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select"); if (ImGui::TreeNode("Multi-Select")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select"); ImGui::Text("Supported features:"); ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space)."); ImGui::BulletText("Ctrl modifier to preserve and toggle selection."); @@ -2804,7 +2934,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d for (int n = 0; n < ITEMS_COUNT; n++) { char label[64]; - sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_COUNTOF(ExampleNames)]); bool item_is_selected = selection.Contains((ImGuiID)n); ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); @@ -2818,9 +2948,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with clipper)"); if (ImGui::TreeNode("Multi-Select (with clipper)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with clipper)"); // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection static ImGuiSelectionBasicStorage selection; @@ -2844,7 +2974,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) { char label[64]; - sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_COUNTOF(ExampleNames)]); bool item_is_selected = selection.Contains((ImGuiID)n); ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); @@ -2865,9 +2995,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // - (3) BeginXXXX process // - (4) Focus process // - (5) EndXXXX process - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with deletion)"); if (ImGui::TreeNode("Multi-Select (with deletion)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with deletion)"); // Storing items data separately from selection data. // (you may decide to store selection data inside your item (aka intrusive storage) if you don't need multiple views over same items) // Use a custom selection.Adapter: store item identifier in Selection (instead of index) @@ -2906,7 +3036,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d { const ImGuiID item_id = items[n]; char label[64]; - sprintf(label, "Object %05u: %s", item_id, ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]); + sprintf(label, "Object %05u: %s", item_id, ExampleNames[item_id % IM_COUNTOF(ExampleNames)]); bool item_is_selected = selection.Contains(item_id); ImGui::SetNextItemSelectionUserData(n); @@ -2926,13 +3056,13 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } // Implement a Dual List Box (#6648) - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (dual list box)"); if (ImGui::TreeNode("Multi-Select (dual list box)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (dual list box)"); // Init default state static ExampleDualListBox dlb; if (dlb.Items[0].Size == 0 && dlb.Items[1].Size == 0) - for (int item_id = 0; item_id < IM_ARRAYSIZE(ExampleNames); item_id++) + for (int item_id = 0; item_id < IM_COUNTOF(ExampleNames); item_id++) dlb.Items[0].push_back((ImGuiID)item_id); // Show @@ -2942,14 +3072,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (in a table)"); if (ImGui::TreeNode("Multi-Select (in a table)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (in a table)"); static ImGuiSelectionBasicStorage selection; const int ITEMS_COUNT = 10000; ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); - if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter)) + if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter, ImVec2(0.0f, ImGui::GetFontSize() * 20))) { ImGui::TableSetupColumn("Object"); ImGui::TableSetupColumn("Action"); @@ -2970,13 +3100,15 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d { ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::PushID(n); char label[64]; - sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_COUNTOF(ExampleNames)]); bool item_is_selected = selection.Contains((ImGuiID)n); ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap); ImGui::TableNextColumn(); ImGui::SmallButton("hello"); + ImGui::PopID(); } } @@ -2987,9 +3119,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)"); if (ImGui::TreeNode("Multi-Select (checkboxes)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)"); ImGui::Text("In a list of checkboxes (not selectable):"); ImGui::BulletText("Using _NoAutoSelect + _NoAutoClear flags."); ImGui::BulletText("Shift+Click to check multiple boxes."); @@ -3004,7 +3136,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) { - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_ARRAYSIZE(items)); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_COUNTOF(items)); ImGuiSelectionExternalStorage storage_wrapper; storage_wrapper.UserData = (void*)items; storage_wrapper.AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int n, bool selected) { bool* array = (bool*)self->UserData; array[n] = selected; }; @@ -3025,9 +3157,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } // Demonstrate individual selection scopes in same window - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (multiple scopes)"); if (ImGui::TreeNode("Multi-Select (multiple scopes)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (multiple scopes)"); // Use default select: Pass index to SetNextItemSelectionUserData(), store index in Selection const int SCOPES_COUNT = 3; const int ITEMS_COUNT = 8; // Per scope @@ -3055,7 +3187,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d for (int n = 0; n < ITEMS_COUNT; n++) { char label[64]; - sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_COUNTOF(ExampleNames)]); bool item_is_selected = selection->Contains((ImGuiID)n); ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); @@ -3087,9 +3219,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // are more likely to build an array mapping sequential indices to visible tree nodes, since your // filtering/search + clipping process will benefit from it. Having this will make this interpolation much easier. // - Consider this a prototype: we are working toward simplifying some of it. - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (trees)"); if (ImGui::TreeNode("Multi-Select (trees)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (trees)"); HelpMarker( "This is rather advanced and experimental. If you are getting started with multi-select, " "please don't start by looking at how to use it for a tree!\n\n" @@ -3122,16 +3254,6 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } } - static bool TreeNodeGetOpen(ExampleTreeNode* node) - { - return ImGui::GetStateStorage()->GetBool((ImGuiID)node->UID); - } - - static void TreeNodeSetOpen(ExampleTreeNode* node, bool open) - { - ImGui::GetStateStorage()->SetBool((ImGuiID)node->UID, open); - } - // When closing a node: 1) close and unselect all child nodes, 2) select parent if any child was selected. // FIXME: This is currently handled by user logic but I'm hoping to eventually provide tree node // features to do this automatically, e.g. a ImGuiTreeNodeFlags_AutoCloseChildNodes etc. @@ -3139,11 +3261,11 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d { // Recursive close (the test for depth == 0 is because we call this on a node that was just closed!) int unselected_count = selection->Contains((ImGuiID)node->UID) ? 1 : 0; - if (depth == 0 || TreeNodeGetOpen(node)) + if (depth == 0 || ImGui::TreeNodeGetOpen((ImGuiID)node->UID)) { for (ExampleTreeNode* child : node->Childs) unselected_count += TreeCloseAndUnselectChildNodes(child, selection, depth + 1); - TreeNodeSetOpen(node, false); + ImGui::TreeNodeSetOpen((ImGuiID)node->UID, false); } // Select root node if any of its child was selected, otherwise unselect @@ -3177,7 +3299,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d { if (node->Parent != NULL) // Root node isn't visible nor selectable in our scheme selection->SetItemSelected((ImGuiID)node->UID, selected); - if (node->Parent == NULL || TreeNodeGetOpen(node)) + if (node->Parent == NULL || ImGui::TreeNodeGetOpen((ImGuiID)node->UID)) for (ExampleTreeNode* child : node->Childs) TreeSetAllInOpenNodes(child, selection, selected); } @@ -3197,7 +3319,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d return NULL; // Recurse into childs. Query storage to tell if the node is open. - if (curr_node->Childs.Size > 0 && TreeNodeGetOpen(curr_node)) + if (curr_node->Childs.Size > 0 && ImGui::TreeNodeGetOpen((ImGuiID)curr_node->UID)) return curr_node->Childs[0]; // Next sibling, then into our own parent @@ -3239,10 +3361,10 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // - Showcase basic drag and drop. // - Showcase TreeNode variant (note that tree node don't expand in the demo: supporting expanding tree nodes + clipping a separate thing). // - Showcase using inside a table. - IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (advanced)"); //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Multi-Select (advanced)")) { + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (advanced)"); // Options enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode }; static bool use_clipper = true; @@ -3281,10 +3403,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(); } @@ -3344,7 +3470,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TableNextColumn(); const int item_id = items[n]; - const char* item_category = ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]; + const char* item_category = ExampleNames[item_id % IM_COUNTOF(ExampleNames)]; char label[64]; sprintf(label, "Object %05d: %s", item_id, item_category); @@ -3406,7 +3532,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d const int* payload_items = (int*)payload->Data; const int payload_count = (int)payload->DataSize / (int)sizeof(int); if (payload_count == 1) - ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]); + ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_COUNTOF(ExampleNames)]); else ImGui::Text("Dragging %d objects", payload_count); @@ -3434,7 +3560,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TableNextColumn(); ImGui::SetNextItemWidth(-FLT_MIN); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::InputText("###NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly); + ImGui::InputText("##NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly); ImGui::PopStyleVar(); } @@ -3485,12 +3611,12 @@ static void EditTabBarFittingPolicyFlags(ImGuiTabBarFlags* p_flags) static void DemoWindowWidgetsTabs() { - IMGUI_DEMO_MARKER("Widgets/Tabs"); if (ImGui::TreeNode("Tabs")) { - IMGUI_DEMO_MARKER("Widgets/Tabs/Basic"); + IMGUI_DEMO_MARKER("Widgets/Tabs"); if (ImGui::TreeNode("Basic")) { + IMGUI_DEMO_MARKER("Widgets/Tabs/Basic"); ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { @@ -3515,9 +3641,9 @@ static void DemoWindowWidgetsTabs() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Tabs/Advanced & Close Button"); if (ImGui::TreeNode("Advanced & Close Button")) { + IMGUI_DEMO_MARKER("Widgets/Tabs/Advanced & Close Button"); // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0). static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable; ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", &tab_bar_flags, ImGuiTabBarFlags_Reorderable); @@ -3532,7 +3658,7 @@ static void DemoWindowWidgetsTabs() ImGui::Text("Opened:"); const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" }; static bool opened[4] = { true, true, true, true }; // Persistent user state - for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + for (int n = 0; n < IM_COUNTOF(opened); n++) { ImGui::SameLine(); ImGui::Checkbox(names[n], &opened[n]); @@ -3542,7 +3668,7 @@ static void DemoWindowWidgetsTabs() // the underlying bool will be set to false when the tab is closed. if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { - for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + for (int n = 0; n < IM_COUNTOF(opened); n++) if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None)) { ImGui::Text("This is the %s tab!", names[n]); @@ -3556,9 +3682,9 @@ static void DemoWindowWidgetsTabs() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Tabs/TabItemButton & Leading-Trailing flags"); if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags")) { + IMGUI_DEMO_MARKER("Widgets/Tabs/TabItemButton & Leading-Trailing flags"); static ImVector active_tabs; static int next_tab_id = 0; if (next_tab_id == 0) // Initialize with some default tabs @@ -3601,7 +3727,7 @@ static void DemoWindowWidgetsTabs() { bool open = true; char name[16]; - snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]); + snprintf(name, IM_COUNTOF(name), "%04d", active_tabs[n]); if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None)) { ImGui::Text("This is the %s tab!", name); @@ -3629,12 +3755,12 @@ static void DemoWindowWidgetsTabs() static void DemoWindowWidgetsText() { - IMGUI_DEMO_MARKER("Widgets/Text"); if (ImGui::TreeNode("Text")) { - IMGUI_DEMO_MARKER("Widgets/Text/Colored Text"); + IMGUI_DEMO_MARKER("Widgets/Text"); if (ImGui::TreeNode("Colorful Text")) { + IMGUI_DEMO_MARKER("Widgets/Text/Colored Text"); // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Pink"); ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Yellow"); @@ -3643,9 +3769,9 @@ static void DemoWindowWidgetsText() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text/Font Size"); if (ImGui::TreeNode("Font Size")) { + IMGUI_DEMO_MARKER("Widgets/Text/Font Size"); ImGuiStyle& style = ImGui::GetStyle(); const float global_scale = style.FontScaleMain * style.FontScaleDpi; ImGui::Text("style.FontScaleMain = %0.2f", style.FontScaleMain); @@ -3680,9 +3806,9 @@ static void DemoWindowWidgetsText() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping"); if (ImGui::TreeNode("Word Wrapping")) { + IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping"); // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. ImGui::TextWrapped( "This text should automatically wrap on the edge of the window. The current implementation " @@ -3714,9 +3840,9 @@ static void DemoWindowWidgetsText() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text/UTF-8 Text"); if (ImGui::TreeNode("UTF-8 Text")) { + IMGUI_DEMO_MARKER("Widgets/Text/UTF-8 Text"); // UTF-8 test with Japanese characters // (Needs a suitable font? Try "Google Noto" or "Arial Unicode". See docs/FONTS.md for details.) // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 @@ -3735,7 +3861,7 @@ static void DemoWindowWidgetsText() ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis - ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("UTF-8 input", buf, IM_COUNTOF(buf)); ImGui::TreePop(); } ImGui::TreePop(); @@ -3748,9 +3874,9 @@ static void DemoWindowWidgetsText() static void DemoWindowWidgetsTextFilter() { - IMGUI_DEMO_MARKER("Widgets/Text Filter"); if (ImGui::TreeNode("Text Filter")) { + IMGUI_DEMO_MARKER("Widgets/Text Filter"); // Helper class to easy setup a text filter. // You may want to implement a more feature-full filtering scheme in your own application. HelpMarker("Not a widget per-se, but ImGuiTextFilter is a helper to perform simple filtering on text strings."); @@ -3762,7 +3888,7 @@ static void DemoWindowWidgetsTextFilter() " \"-xxx\" hide lines containing \"xxx\""); filter.Draw(); const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + for (int i = 0; i < IM_COUNTOF(lines); i++) if (filter.PassFilter(lines[i])) ImGui::BulletText("%s", lines[i]); ImGui::TreePop(); @@ -3777,12 +3903,12 @@ static void DemoWindowWidgetsTextInput() { // To wire InputText() with std::string or any other custom string type, // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. - IMGUI_DEMO_MARKER("Widgets/Text Input"); if (ImGui::TreeNode("Text Input")) { - IMGUI_DEMO_MARKER("Widgets/Text Input/Multi-line Text Input"); + IMGUI_DEMO_MARKER("Widgets/Text Input"); if (ImGui::TreeNode("Multi-line Text Input")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Multi-line Text Input"); // WE ARE USING A FIXED-SIZE BUFFER FOR SIMPLICITY HERE. // If you want to use InputText() with std::string or any custom dynamic string type: // - For std::string: use the wrapper in misc/cpp/imgui_stdlib.h/.cpp @@ -3807,13 +3933,13 @@ static void DemoWindowWidgetsTextInput() ImGui::CheckboxFlags("ImGuiInputTextFlags_AllowTabInput", &flags, ImGuiInputTextFlags_AllowTabInput); ImGui::SameLine(); HelpMarker("When _AllowTabInput is set, passing through the widget with Tabbing doesn't automatically activate it, in order to also cycling through subsequent widgets."); ImGui::CheckboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", &flags, ImGuiInputTextFlags_CtrlEnterForNewLine); - ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags); + ImGui::InputTextMultiline("##source", text, IM_COUNTOF(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags); ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text Input/Filtered Text Input"); if (ImGui::TreeNode("Filtered Text Input")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Filtered Text Input"); struct TextFilters { // Modify character input by altering 'data->Eventchar' (ImGuiInputTextFlags_CallbackCharFilter callback) @@ -3833,30 +3959,30 @@ static void DemoWindowWidgetsTextInput() } }; - static char buf1[32] = ""; ImGui::InputText("default", buf1, IM_ARRAYSIZE(buf1)); - static char buf2[32] = ""; ImGui::InputText("decimal", buf2, IM_ARRAYSIZE(buf2), ImGuiInputTextFlags_CharsDecimal); - static char buf3[32] = ""; ImGui::InputText("hexadecimal", buf3, IM_ARRAYSIZE(buf3), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); - static char buf4[32] = ""; ImGui::InputText("uppercase", buf4, IM_ARRAYSIZE(buf4), ImGuiInputTextFlags_CharsUppercase); - static char buf5[32] = ""; ImGui::InputText("no blank", buf5, IM_ARRAYSIZE(buf5), ImGuiInputTextFlags_CharsNoBlank); - static char buf6[32] = ""; ImGui::InputText("casing swap", buf6, IM_ARRAYSIZE(buf6), ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters. - static char buf7[32] = ""; ImGui::InputText("\"imgui\"", buf7, IM_ARRAYSIZE(buf7), ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters. + static char buf1[32] = ""; ImGui::InputText("default", buf1, IM_COUNTOF(buf1)); + static char buf2[32] = ""; ImGui::InputText("decimal", buf2, IM_COUNTOF(buf2), ImGuiInputTextFlags_CharsDecimal); + static char buf3[32] = ""; ImGui::InputText("hexadecimal", buf3, IM_COUNTOF(buf3), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[32] = ""; ImGui::InputText("uppercase", buf4, IM_COUNTOF(buf4), ImGuiInputTextFlags_CharsUppercase); + static char buf5[32] = ""; ImGui::InputText("no blank", buf5, IM_COUNTOF(buf5), ImGuiInputTextFlags_CharsNoBlank); + static char buf6[32] = ""; ImGui::InputText("casing swap", buf6, IM_COUNTOF(buf6), ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters. + static char buf7[32] = ""; ImGui::InputText("\"imgui\"", buf7, IM_COUNTOF(buf7), ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters. ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text Input/Password input"); if (ImGui::TreeNode("Password Input")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Password input"); static char password[64] = "password123"; - ImGui::InputText("password", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); + ImGui::InputText("password", password, IM_COUNTOF(password), ImGuiInputTextFlags_Password); ImGui::SameLine(); HelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); - ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); - ImGui::InputText("password (clear)", password, IM_ARRAYSIZE(password)); + ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_COUNTOF(password), ImGuiInputTextFlags_Password); + ImGui::InputText("password (clear)", password, IM_COUNTOF(password)); ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text Input/Completion, History, Edit Callbacks"); if (ImGui::TreeNode("Completion, History, Edit Callbacks")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Completion, History, Edit Callbacks"); struct Funcs { static int MyCallback(ImGuiInputTextCallbackData* data) @@ -3895,20 +4021,20 @@ static void DemoWindowWidgetsTextInput() } }; static char buf1[64]; - ImGui::InputText("Completion", buf1, IM_ARRAYSIZE(buf1), ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback); + ImGui::InputText("Completion", buf1, IM_COUNTOF(buf1), ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback); ImGui::SameLine(); HelpMarker( "Here we append \"..\" each time Tab is pressed. " "See 'Examples>Console' for a more meaningful demonstration of using this callback."); static char buf2[64]; - ImGui::InputText("History", buf2, IM_ARRAYSIZE(buf2), ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback); + ImGui::InputText("History", buf2, IM_COUNTOF(buf2), ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback); ImGui::SameLine(); HelpMarker( "Here we replace and select text each time Up/Down are pressed. " "See 'Examples>Console' for a more meaningful demonstration of using this callback."); static char buf3[64]; static int edit_count = 0; - ImGui::InputText("Edit", buf3, IM_ARRAYSIZE(buf3), ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); + ImGui::InputText("Edit", buf3, IM_COUNTOF(buf3), ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); ImGui::SameLine(); HelpMarker( "Here we toggle the casing of the first character on every edit + count edits."); ImGui::SameLine(); ImGui::Text("(%d)", edit_count); @@ -3916,9 +4042,9 @@ static void DemoWindowWidgetsTextInput() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text Input/Resize Callback"); if (ImGui::TreeNode("Resize Callback")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Resize Callback"); // To wire InputText() with std::string or any other custom string type, // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string. @@ -3962,25 +4088,25 @@ static void DemoWindowWidgetsTextInput() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text Input/Eliding, Alignment"); if (ImGui::TreeNode("Eliding, Alignment")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Eliding, Alignment"); static char buf1[128] = "/path/to/some/folder/with/long/filename.cpp"; static ImGuiInputTextFlags flags = ImGuiInputTextFlags_ElideLeft; ImGui::CheckboxFlags("ImGuiInputTextFlags_ElideLeft", &flags, ImGuiInputTextFlags_ElideLeft); - ImGui::InputText("Path", buf1, IM_ARRAYSIZE(buf1), flags); + ImGui::InputText("Path", buf1, IM_COUNTOF(buf1), flags); ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Text Input/Miscellaneous"); if (ImGui::TreeNode("Miscellaneous")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Miscellaneous"); static char buf1[16]; static ImGuiInputTextFlags flags = ImGuiInputTextFlags_EscapeClearsAll; ImGui::CheckboxFlags("ImGuiInputTextFlags_EscapeClearsAll", &flags, ImGuiInputTextFlags_EscapeClearsAll); ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly); ImGui::CheckboxFlags("ImGuiInputTextFlags_NoUndoRedo", &flags, ImGuiInputTextFlags_NoUndoRedo); - ImGui::InputText("Hello", buf1, IM_ARRAYSIZE(buf1), flags); + ImGui::InputText("Hello", buf1, IM_COUNTOF(buf1), flags); ImGui::TreePop(); } @@ -3995,9 +4121,9 @@ static void DemoWindowWidgetsTextInput() static void DemoWindowWidgetsTooltips() { - IMGUI_DEMO_MARKER("Widgets/Tooltips"); if (ImGui::TreeNode("Tooltips")) { + IMGUI_DEMO_MARKER("Widgets/Tooltips"); // Tooltips are windows following the mouse. They do not take focus away. ImGui::SeparatorText("General"); @@ -4022,7 +4148,7 @@ static void DemoWindowWidgetsTooltips() { ImGui::Text("I am a fancy tooltip"); static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::PlotLines("Curve", arr, IM_COUNTOF(arr)); ImGui::Text("Sin(time) = %f", sinf((float)ImGui::GetTime())); ImGui::EndTooltip(); } @@ -4094,13 +4220,13 @@ static void DemoWindowWidgetsTooltips() static void DemoWindowWidgetsTreeNodes() { - IMGUI_DEMO_MARKER("Widgets/Tree Nodes"); if (ImGui::TreeNode("Tree Nodes")) { + IMGUI_DEMO_MARKER("Widgets/Tree Nodes"); // See see "Examples -> Property Editor" (ShowExampleAppPropertyEditor() function) for a fancier, data-driven tree. - IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees"); if (ImGui::TreeNode("Basic trees")) { + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees"); for (int i = 0; i < 5; i++) { // Use SetNextItemOpen() so set the default state of a node to be open. We could @@ -4124,9 +4250,9 @@ static void DemoWindowWidgetsTreeNodes() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy lines"); if (ImGui::TreeNode("Hierarchy lines")) { + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy lines"); static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DefaultOpen; HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags"); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesNone", &base_flags, ImGuiTreeNodeFlags_DrawLinesNone); @@ -4153,9 +4279,19 @@ static void DemoWindowWidgetsTreeNodes() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes"); + if (ImGui::TreeNode("Clipping Large Trees")) + { + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Clipping Large Trees"); + ImGui::TextWrapped( + "- Using ImGuiListClipper with trees is a less easy than on arrays or grids.\n" + "- Refer to 'Demo->Examples->Property Editor' for an example of how to do that.\n" + "- Discuss in #3823"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Advanced, with Selectable nodes")) { + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes"); HelpMarker( "This is a more typical looking tree with selectable nodes.\n" "Click to select, Ctrl+Click to toggle, click on arrows or double-click to open."); @@ -4170,6 +4306,7 @@ static void DemoWindowWidgetsTreeNodes() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_FramePadding", &base_flags, ImGuiTreeNodeFlags_FramePadding); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsToParent", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsToParent); HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags"); @@ -4263,9 +4400,9 @@ static void DemoWindowWidgetsTreeNodes() static void DemoWindowWidgetsVerticalSliders() { - IMGUI_DEMO_MARKER("Widgets/Vertical Sliders"); if (ImGui::TreeNode("Vertical Sliders")) { + IMGUI_DEMO_MARKER("Widgets/Vertical Sliders"); const float spacing = 4; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); @@ -4335,10 +4472,10 @@ static void DemoWindowWidgetsVerticalSliders() static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) { - IMGUI_DEMO_MARKER("Widgets"); //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (!ImGui::CollapsingHeader("Widgets")) return; + // IMGUI_DEMO_MARKER("Widgets"); const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom if (disable_all) @@ -4386,13 +4523,12 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) static void DemoWindowLayout() { - IMGUI_DEMO_MARKER("Layout"); if (!ImGui::CollapsingHeader("Layout & Scrolling")) return; - IMGUI_DEMO_MARKER("Layout/Child windows"); if (ImGui::TreeNode("Child windows")) { + IMGUI_DEMO_MARKER("Layout/Child windows"); ImGui::SeparatorText("Child windows"); HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); @@ -4524,9 +4660,9 @@ static void DemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Widgets Width"); if (ImGui::TreeNode("Widgets Width")) { + IMGUI_DEMO_MARKER("Layout/Widgets Width"); static float f = 0.0f; static bool show_indented_items = true; ImGui::Checkbox("Show indented items", &show_indented_items); @@ -4612,9 +4748,9 @@ static void DemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout"); if (ImGui::TreeNode("Basic Horizontal Layout")) { + IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout"); ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); // Text @@ -4657,23 +4793,21 @@ static void DemoWindowLayout() // Various static float f0 = 1.0f, f1 = 2.0f, f2 = 3.0f; - ImGui::PushItemWidth(80); + ImGui::PushItemWidth(ImGui::CalcTextSize("AAAAAAA").x); const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; static int item = -1; - ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::Combo("Combo", &item, items, IM_COUNTOF(items)); ImGui::SameLine(); ImGui::SliderFloat("X", &f0, 0.0f, 5.0f); ImGui::SameLine(); ImGui::SliderFloat("Y", &f1, 0.0f, 5.0f); ImGui::SameLine(); ImGui::SliderFloat("Z", &f2, 0.0f, 5.0f); - ImGui::PopItemWidth(); - ImGui::PushItemWidth(80); ImGui::Text("Lists:"); static int selection[4] = { 0, 1, 2, 3 }; for (int i = 0; i < 4; i++) { if (i > 0) ImGui::SameLine(); ImGui::PushID(i); - ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::ListBox("", &selection[i], items, IM_COUNTOF(items)); ImGui::PopID(); //ImGui::SetItemTooltip("ListBox %d hovered", i); } @@ -4707,9 +4841,9 @@ static void DemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Groups"); if (ImGui::TreeNode("Groups")) { + IMGUI_DEMO_MARKER("Layout/Groups"); HelpMarker( "BeginGroup() basically locks the horizontal position for new line. " "EndGroup() bundles the whole group so that you can use \"item\" functions such as " @@ -4733,7 +4867,7 @@ static void DemoWindowLayout() // Capture the group size and create widgets using the same size ImVec2 size = ImGui::GetItemRectSize(); const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; - ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + ImGui::PlotHistogram("##values", values, IM_COUNTOF(values), 0, NULL, 0.0f, 1.0f, size); ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); ImGui::SameLine(); @@ -4754,9 +4888,9 @@ static void DemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Text Baseline Alignment"); if (ImGui::TreeNode("Text Baseline Alignment")) { + IMGUI_DEMO_MARKER("Layout/Text Baseline Alignment"); { ImGui::BulletText("Text baseline:"); ImGui::SameLine(); HelpMarker( @@ -4834,7 +4968,7 @@ static void DemoWindowLayout() // Tree // (here the node appears after a button and has odd intent, so we use ImGuiTreeNodeFlags_DrawLinesNone to disable hierarchy outline) const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::Button("Button##1"); + ImGui::Button("Button##1"); // Will make line higher ImGui::SameLine(0.0f, spacing); if (ImGui::TreeNodeEx("Node##1", ImGuiTreeNodeFlags_DrawLinesNone)) { @@ -4844,14 +4978,22 @@ static void DemoWindowLayout() ImGui::TreePop(); } + const float padding = (float)(int)(ImGui::GetFontSize() * 1.20f); // Large padding + ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, padding); + ImGui::Button("Button##2"); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNodeEx("Node##2", ImGuiTreeNodeFlags_DrawLinesNone)) + ImGui::TreePop(); + // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. // Otherwise you can use SmallButton() (smaller fit). ImGui::AlignTextToFramePadding(); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add - // other contents below the node. - bool node_open = ImGui::TreeNode("Node##2"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + // other contents "inside" the node. + bool node_open = ImGui::TreeNode("Node##3"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##3"); if (node_open) { // Placeholder tree data @@ -4861,24 +5003,23 @@ static void DemoWindowLayout() } // Bullet - ImGui::Button("Button##3"); + ImGui::Button("Button##4"); ImGui::SameLine(0.0f, spacing); ImGui::BulletText("Bullet text"); ImGui::AlignTextToFramePadding(); ImGui::BulletText("Node"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##5"); ImGui::Unindent(); } ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Scrolling"); if (ImGui::TreeNode("Scrolling")) { - // Vertical scroll functions IMGUI_DEMO_MARKER("Layout/Scrolling/Vertical"); + // Vertical scroll functions HelpMarker("Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given vertical position."); static int track_item = 50; @@ -5093,7 +5234,7 @@ static void DemoWindowLayout() if (explicit_content_size) { ImGui::SameLine(); - ImGui::SetNextItemWidth(100); + ImGui::SetNextItemWidth(ImGui::CalcTextSize("123456").x); ImGui::DragFloat("##csx", &contents_size_x); ImVec2 p = ImGui::GetCursorScreenPos(); ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 10, p.y + 10), IM_COL32_WHITE); @@ -5164,9 +5305,9 @@ static void DemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Text Clipping"); if (ImGui::TreeNode("Text Clipping")) { + IMGUI_DEMO_MARKER("Layout/Text Clipping"); static ImVec2 size(100.0f, 100.0f); static ImVec2 offset(30.0f, 30.0f); ImGui::DragFloat2("size", (float*)&size, 0.5f, 1.0f, 200.0f, "%.0f"); @@ -5229,9 +5370,9 @@ static void DemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Overlap Mode"); if (ImGui::TreeNode("Overlap Mode")) { + IMGUI_DEMO_MARKER("Layout/Overlap Mode"); static bool enable_allow_overlap = true; HelpMarker( @@ -5267,7 +5408,6 @@ static void DemoWindowLayout() static void DemoWindowPopups() { - IMGUI_DEMO_MARKER("Popups"); if (!ImGui::CollapsingHeader("Popups & Modal windows")) return; @@ -5289,9 +5429,9 @@ static void DemoWindowPopups() // With popups we have to go through a library call (here OpenPopup) to manipulate the visibility state. // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below. - IMGUI_DEMO_MARKER("Popups/Popups"); if (ImGui::TreeNode("Popups")) { + IMGUI_DEMO_MARKER("Popups/Popups"); ImGui::TextWrapped( "When a popup is active, it inhibits interacting with windows that are behind the popup. " "Clicking outside the popup closes it."); @@ -5309,7 +5449,7 @@ static void DemoWindowPopups() if (ImGui::BeginPopup("my_select_popup")) { ImGui::SeparatorText("Aquarium"); - for (int i = 0; i < IM_ARRAYSIZE(names); i++) + for (int i = 0; i < IM_COUNTOF(names); i++) if (ImGui::Selectable(names[i])) selected_fish = i; ImGui::EndPopup(); @@ -5320,7 +5460,7 @@ static void DemoWindowPopups() ImGui::OpenPopup("my_toggle_popup"); if (ImGui::BeginPopup("my_toggle_popup")) { - for (int i = 0; i < IM_ARRAYSIZE(names); i++) + for (int i = 0; i < IM_COUNTOF(names); i++) ImGui::MenuItem(names[i], "", &toggles[i]); if (ImGui::BeginMenu("Sub-menu")) { @@ -5336,7 +5476,7 @@ static void DemoWindowPopups() ImGui::OpenPopup("another popup"); if (ImGui::BeginPopup("another popup")) { - for (int i = 0; i < IM_ARRAYSIZE(names); i++) + for (int i = 0; i < IM_COUNTOF(names); i++) ImGui::MenuItem(names[i], "", &toggles[i]); if (ImGui::BeginMenu("Sub-menu")) { @@ -5382,9 +5522,9 @@ static void DemoWindowPopups() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Popups/Context menus"); if (ImGui::TreeNode("Context menus")) { + IMGUI_DEMO_MARKER("Popups/Context menus"); HelpMarker("\"Context\" functions are simple helpers to associate a Popup to a given Item or Window identifier."); // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: @@ -5458,7 +5598,7 @@ static void DemoWindowPopups() if (ImGui::BeginPopupContextItem()) { ImGui::Text("Edit name:"); - ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + ImGui::InputText("##edit", name, IM_COUNTOF(name)); if (ImGui::Button("Close")) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); @@ -5469,9 +5609,9 @@ static void DemoWindowPopups() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Popups/Modals"); if (ImGui::TreeNode("Modals")) { + IMGUI_DEMO_MARKER("Popups/Modals"); ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside."); if (ImGui::Button("Delete..")) @@ -5546,9 +5686,9 @@ static void DemoWindowPopups() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Popups/Menus inside a regular window"); if (ImGui::TreeNode("Menus inside a regular window")) { + IMGUI_DEMO_MARKER("Popups/Menus inside a regular window"); ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); ImGui::Separator(); @@ -5631,7 +5771,7 @@ struct MyItem // qsort() is instable so always return a way to differentiate items. // Your own compare function may want to avoid fallback on implicit sort specs. // e.g. a Name compare if it wasn't already part of the sort specs. - return (a->ID - b->ID); + return a->ID - b->ID; } }; const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; @@ -5663,13 +5803,13 @@ static void EditTableSizingFlags(ImGuiTableFlags* p_flags) { ImGuiTableFlags_SizingStretchSame, "ImGuiTableFlags_SizingStretchSame", "Columns default to _WidthStretch with same weights." } }; int idx; - for (idx = 0; idx < IM_ARRAYSIZE(policies); idx++) + for (idx = 0; idx < IM_COUNTOF(policies); idx++) if (policies[idx].Value == (*p_flags & ImGuiTableFlags_SizingMask_)) break; - const char* preview_text = (idx < IM_ARRAYSIZE(policies)) ? policies[idx].Name + (idx > 0 ? strlen("ImGuiTableFlags") : 0) : ""; + const char* preview_text = (idx < IM_COUNTOF(policies)) ? policies[idx].Name + (idx > 0 ? strlen("ImGuiTableFlags") : 0) : ""; if (ImGui::BeginCombo("Sizing Policy", preview_text)) { - for (int n = 0; n < IM_ARRAYSIZE(policies); n++) + for (int n = 0; n < IM_COUNTOF(policies); n++) if (ImGui::Selectable(policies[n].Name, idx == n)) *p_flags = (*p_flags & ~ImGuiTableFlags_SizingMask_) | policies[n].Value; ImGui::EndCombo(); @@ -5679,7 +5819,7 @@ static void EditTableSizingFlags(ImGuiTableFlags* p_flags) if (ImGui::BeginItemTooltip()) { ImGui::PushTextWrapPos(ImGui::GetFontSize() * 50.0f); - for (int m = 0; m < IM_ARRAYSIZE(policies); m++) + for (int m = 0; m < IM_COUNTOF(policies); m++) { ImGui::Separator(); ImGui::Text("%s:", policies[m].Name); @@ -5732,7 +5872,6 @@ static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) static void DemoWindowTables() { //ImGui::SetNextItemOpen(true, ImGuiCond_Once); - IMGUI_DEMO_MARKER("Tables"); if (!ImGui::CollapsingHeader("Tables & Columns")) return; @@ -5772,9 +5911,9 @@ static void DemoWindowTables() // Demos if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Basic"); if (ImGui::TreeNode("Basic")) { + IMGUI_DEMO_MARKER("Tables/Basic"); // Here we will showcase three different ways to output a table. // They are very simple variations of a same thing! @@ -5835,9 +5974,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Borders, background"); if (ImGui::TreeNode("Borders, background")) { + IMGUI_DEMO_MARKER("Tables/Borders, background"); // Expose a few Borders related flags interactively enum ContentsType { CT_Text, CT_FillButton }; static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; @@ -5906,9 +6045,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Resizable, stretch"); if (ImGui::TreeNode("Resizable, stretch")) { + IMGUI_DEMO_MARKER("Tables/Resizable, stretch"); // By default, if we don't enable ScrollX the sizing policy for each column is "Stretch" // All columns maintain a sizing weight, and they will occupy all available width. static ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody; @@ -5938,9 +6077,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Resizable, fixed"); if (ImGui::TreeNode("Resizable, fixed")) { + IMGUI_DEMO_MARKER("Tables/Resizable, fixed"); // Here we use ImGuiTableFlags_SizingFixedFit (even though _ScrollX is not set) // So columns will adopt the "Fixed" policy and will maintain a fixed width regardless of the whole available width (unless table is small) // If there is not enough available width to fit all columns, they will however be resized down. @@ -5972,9 +6111,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Resizable, mixed"); if (ImGui::TreeNode("Resizable, mixed")) { + IMGUI_DEMO_MARKER("Tables/Resizable, mixed"); HelpMarker( "Using TableSetupColumn() to alter resizing policy on a per-column basis.\n\n" "When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch."); @@ -6022,9 +6161,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Reorderable, hideable, with headers"); if (ImGui::TreeNode("Reorderable, hideable, with headers")) { + IMGUI_DEMO_MARKER("Tables/Reorderable, hideable, with headers"); HelpMarker( "Click and drag column headers to reorder columns.\n\n" "Right-click on a header to open a context menu."); @@ -6082,9 +6221,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Padding"); if (ImGui::TreeNode("Padding")) { + IMGUI_DEMO_MARKER("Tables/Padding"); // First example: showcase use of padding flags and effect of BorderOuterV/BorderInnerV on X padding. // We don't expose BorderOuterH/BorderInnerH here because they have no effect on X padding. HelpMarker( @@ -6177,7 +6316,7 @@ static void DemoWindowTables() strcpy(text_bufs[cell], "edit me"); ImGui::SetNextItemWidth(-FLT_MIN); ImGui::PushID(cell); - ImGui::InputText("##cell", text_bufs[cell], IM_ARRAYSIZE(text_bufs[cell])); + ImGui::InputText("##cell", text_bufs[cell], IM_COUNTOF(text_bufs[cell])); ImGui::PopID(); } if (!show_widget_frame_bg) @@ -6192,9 +6331,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Explicit widths"); if (ImGui::TreeNode("Sizing policies")) { + IMGUI_DEMO_MARKER("Tables/Explicit widths"); static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags1, ImGuiTableFlags_Resizable); @@ -6290,7 +6429,7 @@ static void DemoWindowTables() case CT_ShowWidth: ImGui::Text("W: %.1f", ImGui::GetContentRegionAvail().x); break; case CT_Button: ImGui::Button(label); break; case CT_FillButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; - case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); break; + case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_COUNTOF(text_buf)); break; } ImGui::PopID(); } @@ -6301,9 +6440,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Vertical scrolling, with clipping"); if (ImGui::TreeNode("Vertical scrolling, with clipping")) { + IMGUI_DEMO_MARKER("Tables/Vertical scrolling, with clipping"); HelpMarker( "Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\n" "We also demonstrate using ImGuiListClipper to virtualize the submission of many items."); @@ -6346,9 +6485,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Horizontal scrolling"); if (ImGui::TreeNode("Horizontal scrolling")) { + IMGUI_DEMO_MARKER("Tables/Horizontal scrolling"); HelpMarker( "When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingFixedFit, " "as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\n" @@ -6437,9 +6576,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Columns flags"); if (ImGui::TreeNode("Columns flags")) { + IMGUI_DEMO_MARKER("Tables/Columns flags"); // Create a first table just to show all the options/flags we want to make visible in our example! const int column_count = 3; const char* column_names[column_count] = { "One", "Two", "Three" }; @@ -6512,9 +6651,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Columns widths"); if (ImGui::TreeNode("Columns widths")) { + IMGUI_DEMO_MARKER("Tables/Columns widths"); HelpMarker("Using TableSetupColumn() to setup default width."); static ImGuiTableFlags flags1 = ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBodyUntilResize; @@ -6581,9 +6720,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Nested tables"); if (ImGui::TreeNode("Nested tables")) { + IMGUI_DEMO_MARKER("Tables/Nested tables"); HelpMarker("This demonstrates embedding a table into another table cell."); if (ImGui::BeginTable("table_nested1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) @@ -6595,7 +6734,7 @@ static void DemoWindowTables() ImGui::TableNextColumn(); ImGui::Text("A0 Row 0"); { - float rows_height = TEXT_BASE_HEIGHT * 2; + float rows_height = (TEXT_BASE_HEIGHT * 2.0f) + (ImGui::GetStyle().CellPadding.y * 2.0f); if (ImGui::BeginTable("table_nested2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { ImGui::TableSetupColumn("B0"); @@ -6626,9 +6765,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Row height"); if (ImGui::TreeNode("Row height")) { + IMGUI_DEMO_MARKER("Tables/Row height"); HelpMarker( "You can pass a 'min_row_height' to TableNextRow().\n\nRows are padded with 'style.CellPadding.y' on top and bottom, " "so effectively the minimum row height will always be >= 'style.CellPadding.y * 2.0f'.\n\n" @@ -6637,7 +6776,7 @@ static void DemoWindowTables() { for (int row = 0; row < 8; row++) { - float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row); + float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row + ImGui::GetStyle().CellPadding.y * 2.0f); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); ImGui::TableNextColumn(); ImGui::Text("min_row_height = %.2f", min_row_height); @@ -6691,9 +6830,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Outer size"); if (ImGui::TreeNode("Outer size")) { + IMGUI_DEMO_MARKER("Tables/Outer size"); // Showcasing use of ImGuiTableFlags_NoHostExtendX and ImGuiTableFlags_NoHostExtendY // Important to that note how the two flags have slightly different behaviors! ImGui::Text("Using NoHostExtendX and NoHostExtendY:"); @@ -6741,9 +6880,10 @@ static void DemoWindowTables() ImGui::SameLine(); if (ImGui::BeginTable("table3", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f))) { + const float rows_height = TEXT_BASE_HEIGHT * 1.5f + ImGui::GetStyle().CellPadding.y * 2.0f; for (int row = 0; row < 3; row++) { - ImGui::TableNextRow(0, TEXT_BASE_HEIGHT * 1.5f); + ImGui::TableNextRow(0, rows_height); for (int column = 0; column < 3; column++) { ImGui::TableNextColumn(); @@ -6758,9 +6898,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Background color"); if (ImGui::TreeNode("Background color")) { + IMGUI_DEMO_MARKER("Tables/Background color"); static ImGuiTableFlags flags = ImGuiTableFlags_RowBg; static int row_bg_type = 1; static int row_bg_target = 1; @@ -6816,9 +6956,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Tree view"); if (ImGui::TreeNode("Tree view")) { + IMGUI_DEMO_MARKER("Tables/Tree view"); static ImGuiTableFlags table_flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; static ImGuiTreeNodeFlags tree_node_flags_base = ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_DrawLinesFull; @@ -6904,9 +7044,9 @@ static void DemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Item width"); if (ImGui::TreeNode("Item width")) { + IMGUI_DEMO_MARKER("Tables/Item width"); HelpMarker( "Showcase using PushItemWidth() and how it is preserved on a per-column basis.\n\n" "Note that on auto-resizing non-resizable fixed columns, querying the content width for " @@ -6951,9 +7091,9 @@ static void DemoWindowTables() // Demonstrate using TableHeader() calls instead of TableHeadersRow() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Custom headers"); if (ImGui::TreeNode("Custom headers")) { + IMGUI_DEMO_MARKER("Tables/Custom headers"); const int COLUMNS_COUNT = 3; if (ImGui::BeginTable("table_custom_headers", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { @@ -7004,11 +7144,11 @@ static void DemoWindowTables() // Demonstrate using ImGuiTableColumnFlags_AngledHeader flag to create angled headers if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Angled headers"); if (ImGui::TreeNode("Angled headers")) { + IMGUI_DEMO_MARKER("Tables/Angled headers"); const char* column_names[] = { "Track", "cabasa", "ride", "smash", "tom-hi", "tom-mid", "tom-low", "hihat-o", "hihat-c", "snare-s", "snare-c", "clap", "rim", "kick" }; - const int columns_count = IM_ARRAYSIZE(column_names); + const int columns_count = IM_COUNTOF(column_names); const int rows_count = 12; static ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_HighlightHoveredColumn; @@ -7073,9 +7213,9 @@ static void DemoWindowTables() // while playing it nice with context menus provided by TableHeadersRow()/TableHeader() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Context menus"); if (ImGui::TreeNode("Context menus")) { + IMGUI_DEMO_MARKER("Tables/Context menus"); HelpMarker( "By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\n" "Using ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body."); @@ -7184,9 +7324,9 @@ static void DemoWindowTables() // Demonstrate creating multiple tables with the same ID if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Synced instances"); if (ImGui::TreeNode("Synced instances")) { + IMGUI_DEMO_MARKER("Tables/Synced instances"); HelpMarker("Multiple tables with the same identifier will share their settings, width, visibility, order etc."); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings; @@ -7227,9 +7367,9 @@ static void DemoWindowTables() }; if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Sorting"); if (ImGui::TreeNode("Sorting")) { + IMGUI_DEMO_MARKER("Tables/Sorting"); // Create item list static ImVector items; if (items.Size == 0) @@ -7237,7 +7377,7 @@ static void DemoWindowTables() items.resize(50, MyItem()); for (int n = 0; n < items.Size; n++) { - const int template_n = n % IM_ARRAYSIZE(template_items_names); + const int template_n = n % IM_COUNTOF(template_items_names); MyItem& item = items[n]; item.ID = n; item.Name = template_items_names[template_n]; @@ -7312,9 +7452,9 @@ static void DemoWindowTables() //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // [DEBUG] if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - IMGUI_DEMO_MARKER("Tables/Advanced"); if (ImGui::TreeNode("Advanced")) { + IMGUI_DEMO_MARKER("Tables/Advanced"); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti @@ -7328,7 +7468,7 @@ static void DemoWindowTables() const char* contents_type_names[] = { "Text", "Button", "SmallButton", "FillButton", "Selectable", "Selectable (span row)" }; static int freeze_cols = 1; static int freeze_rows = 1; - static int items_count = IM_ARRAYSIZE(template_items_names) * 2; + static int items_count = IM_COUNTOF(template_items_names) * 2; static ImVec2 outer_size_value = ImVec2(0.0f, TEXT_BASE_HEIGHT * 12); static float row_min_height = 0.0f; // Auto static float inner_width_with_scroll = 0.0f; // Auto-extend @@ -7446,7 +7586,7 @@ static void DemoWindowTables() ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); ImGui::DragInt("items_count", &items_count, 0.1f, 0, 9999); - ImGui::Combo("items_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); + ImGui::Combo("items_type (first column)", &contents_type, contents_type_names, IM_COUNTOF(contents_type_names)); //filter.Draw("filter"); ImGui::TreePop(); } @@ -7466,7 +7606,7 @@ static void DemoWindowTables() items.resize(items_count, MyItem()); for (int n = 0; n < items_count; n++) { - const int template_n = n % IM_ARRAYSIZE(template_items_names); + const int template_n = n % IM_COUNTOF(template_items_names); MyItem& item = items[n]; item.ID = n; item.Name = template_items_names[template_n]; @@ -7636,7 +7776,6 @@ static void DemoWindowTables() // [2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful BeginTable() API!] static void DemoWindowColumns() { - IMGUI_DEMO_MARKER("Columns (legacy API)"); bool open = ImGui::TreeNode("Legacy Columns API"); ImGui::SameLine(); HelpMarker("Columns() is an old API! Prefer using the more flexible and powerful BeginTable() API!"); @@ -7644,9 +7783,9 @@ static void DemoWindowColumns() return; // Basic columns - IMGUI_DEMO_MARKER("Columns (legacy API)/Basic"); if (ImGui::TreeNode("Basic")) { + IMGUI_DEMO_MARKER("Columns (legacy API)/Basic"); ImGui::Text("Without border:"); ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border ImGui::Separator(); @@ -7689,9 +7828,9 @@ static void DemoWindowColumns() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Columns (legacy API)/Borders"); if (ImGui::TreeNode("Borders")) { + IMGUI_DEMO_MARKER("Columns (legacy API)/Borders"); // NB: Future columns API should allow automatic horizontal borders. static bool h_borders = true; static bool v_borders = true; @@ -7727,9 +7866,9 @@ static void DemoWindowColumns() } // Create multiple items in a same cell before switching to next column - IMGUI_DEMO_MARKER("Columns (legacy API)/Mixed items"); if (ImGui::TreeNode("Mixed items")) { + IMGUI_DEMO_MARKER("Columns (legacy API)/Mixed items"); ImGui::Columns(3, "mixed"); ImGui::Separator(); @@ -7759,9 +7898,9 @@ static void DemoWindowColumns() } // Word wrapping - IMGUI_DEMO_MARKER("Columns (legacy API)/Word-wrapping"); if (ImGui::TreeNode("Word-wrapping")) { + IMGUI_DEMO_MARKER("Columns (legacy API)/Word-wrapping"); ImGui::Columns(2, "word-wrapping"); ImGui::Separator(); ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); @@ -7774,9 +7913,9 @@ static void DemoWindowColumns() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Columns (legacy API)/Horizontal Scrolling"); if (ImGui::TreeNode("Horizontal Scrolling")) { + IMGUI_DEMO_MARKER("Columns (legacy API)/Horizontal Scrolling"); ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); ImVec2 child_size = ImVec2(0, ImGui::GetFontSize() * 20.0f); ImGui::BeginChild("##ScrollingRegion", child_size, ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar); @@ -7800,9 +7939,9 @@ static void DemoWindowColumns() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Columns (legacy API)/Tree"); if (ImGui::TreeNode("Tree")) { + IMGUI_DEMO_MARKER("Columns (legacy API)/Tree"); ImGui::Columns(2, "tree", true); for (int x = 0; x < 3; x++) { @@ -7846,13 +7985,11 @@ static void DemoWindowColumns() static void DemoWindowInputs() { - IMGUI_DEMO_MARKER("Inputs & Focus"); if (ImGui::CollapsingHeader("Inputs & Focus")) { ImGuiIO& io = ImGui::GetIO(); // Display inputs submitted to ImGuiIO - IMGUI_DEMO_MARKER("Inputs & Focus/Inputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); bool inputs_opened = ImGui::TreeNode("Inputs"); ImGui::SameLine(); @@ -7862,16 +7999,17 @@ static void DemoWindowInputs() "- in 'Tools->Debug Log->IO'."); if (inputs_opened) { + IMGUI_DEMO_MARKER("Inputs & Focus/Inputs"); if (ImGui::IsMousePosValid()) ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); else ImGui::Text("Mouse pos: "); ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); ImGui::Text("Mouse down:"); - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + for (int i = 0; i < IM_COUNTOF(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); ImGui::Text("Mouse clicked count:"); - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseClickedCount[i] > 0) { ImGui::SameLine(); ImGui::Text("b%d: %d", i, io.MouseClickedCount[i]); } + for (int i = 0; i < IM_COUNTOF(io.MouseDown); i++) if (io.MouseClickedCount[i] > 0) { ImGui::SameLine(); ImGui::Text("b%d: %d", i, io.MouseClickedCount[i]); } // We iterate both legacy native range and named ImGuiKey ranges. This is a little unusual/odd but this allows // displaying the data for old/new backends. @@ -7887,7 +8025,6 @@ static void DemoWindowInputs() } // Display ImGuiIO output flags - IMGUI_DEMO_MARKER("Inputs & Focus/Outputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); bool outputs_opened = ImGui::TreeNode("Outputs"); ImGui::SameLine(); @@ -7900,6 +8037,7 @@ static void DemoWindowInputs() "rules leading to how those flags are set)."); if (outputs_opened) { + IMGUI_DEMO_MARKER("Inputs & Focus/Outputs"); ImGui::Text("io.WantCaptureMouse: %d", io.WantCaptureMouse); ImGui::Text("io.WantCaptureMouseUnlessPopupClose: %d", io.WantCaptureMouseUnlessPopupClose); ImGui::Text("io.WantCaptureKeyboard: %d", io.WantCaptureKeyboard); @@ -7943,14 +8081,19 @@ static void DemoWindowInputs() // - If you call Shortcut() WITHOUT any routing option, it uses ImGuiInputFlags_RouteFocused. // TL;DR: Most uses will simply be: // - Shortcut(ImGuiMod_Ctrl | ImGuiKey_A); // Use ImGuiInputFlags_RouteFocused policy. - IMGUI_DEMO_MARKER("Inputs & Focus/Shortcuts"); if (ImGui::TreeNode("Shortcuts")) { + IMGUI_DEMO_MARKER("Inputs & Focus/Shortcuts"); static ImGuiInputFlags route_options = ImGuiInputFlags_Repeat; static ImGuiInputFlags route_type = ImGuiInputFlags_RouteFocused; ImGui::CheckboxFlags("ImGuiInputFlags_Repeat", &route_options, ImGuiInputFlags_Repeat); ImGui::RadioButton("ImGuiInputFlags_RouteActive", &route_type, ImGuiInputFlags_RouteActive); ImGui::RadioButton("ImGuiInputFlags_RouteFocused (default)", &route_type, ImGuiInputFlags_RouteFocused); + ImGui::Indent(); + ImGui::BeginDisabled(route_type != ImGuiInputFlags_RouteFocused); + ImGui::CheckboxFlags("ImGuiInputFlags_RouteOverActive##0", &route_options, ImGuiInputFlags_RouteOverActive); + ImGui::EndDisabled(); + ImGui::Unindent(); ImGui::RadioButton("ImGuiInputFlags_RouteGlobal", &route_type, ImGuiInputFlags_RouteGlobal); ImGui::Indent(); ImGui::BeginDisabled(route_type != ImGuiInputFlags_RouteGlobal); @@ -7994,7 +8137,7 @@ static void DemoWindowInputs() // (Commented because the owner-aware version of Shortcut() is still in imgui_internal.h) //char str[16] = "Press Ctrl+A"; //ImGui::Spacing(); - //ImGui::InputText("InputTextB", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly); + //ImGui::InputText("InputTextB", str, IM_COUNTOF(str), ImGuiInputTextFlags_ReadOnly); //ImGuiID item_id = ImGui::GetItemID(); //ImGui::SameLine(); HelpMarker("Internal widgets always use _RouteFocused"); //ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, item_id) ? "PRESSED" : "..."); @@ -8019,7 +8162,7 @@ static void DemoWindowInputs() ImGui::Text("(in PopupF)"); ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); // (Commented because the owner-aware version of Shortcut() is still in imgui_internal.h) - //ImGui::InputText("InputTextG", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly); + //ImGui::InputText("InputTextG", str, IM_COUNTOF(str), ImGuiInputTextFlags_ReadOnly); //ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, ImGui::GetItemID()) ? "PRESSED" : "..."); ImGui::EndPopup(); } @@ -8030,11 +8173,11 @@ static void DemoWindowInputs() } // Display mouse cursors - IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors"); if (ImGui::TreeNode("Mouse Cursors")) { + IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors"); const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "Wait", "Progress", "NotAllowed" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); + IM_ASSERT(IM_COUNTOF(mouse_cursors_names) == ImGuiMouseCursor_COUNT); ImGuiMouseCursor current = ImGui::GetMouseCursor(); const char* cursor_name = (current >= ImGuiMouseCursor_Arrow) && (current < ImGuiMouseCursor_COUNT) ? mouse_cursors_names[current] : "N/A"; @@ -8059,25 +8202,25 @@ static void DemoWindowInputs() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Inputs & Focus/Tabbing"); if (ImGui::TreeNode("Tabbing")) { + IMGUI_DEMO_MARKER("Inputs & Focus/Tabbing"); ImGui::Text("Use Tab/Shift+Tab to cycle through keyboard editable fields."); static char buf[32] = "hello"; - ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); - ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); - ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("1", buf, IM_COUNTOF(buf)); + ImGui::InputText("2", buf, IM_COUNTOF(buf)); + ImGui::InputText("3", buf, IM_COUNTOF(buf)); ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); - ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("4 (tab skip)", buf, IM_COUNTOF(buf)); ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); ImGui::PopItemFlag(); - ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("5", buf, IM_COUNTOF(buf)); ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); if (ImGui::TreeNode("Focus from code")) { + IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); bool focus_3 = ImGui::Button("Focus on 3"); @@ -8085,16 +8228,16 @@ static void DemoWindowInputs() static char buf[128] = "click on a button to set focus"; if (focus_1) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("1", buf, IM_COUNTOF(buf)); if (ImGui::IsItemActive()) has_focus = 1; if (focus_2) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_COUNTOF(buf)); if (ImGui::IsItemActive()) has_focus = 2; ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); if (focus_3) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3 (tab skip)", buf, IM_COUNTOF(buf)); if (ImGui::IsItemActive()) has_focus = 3; ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); ImGui::PopItemFlag(); @@ -8117,9 +8260,9 @@ static void DemoWindowInputs() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Inputs & Focus/Dragging"); if (ImGui::TreeNode("Dragging")) { + IMGUI_DEMO_MARKER("Inputs & Focus/Dragging"); ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); for (int button = 0; button < 3; button++) { @@ -8176,7 +8319,7 @@ void ImGui::ShowAboutWindow(bool* p_open) ImGui::TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding"); ImGui::Separator(); - ImGui::Text("(c) 2014-2025 Omar Cornut"); + ImGui::Text("(c) 2014-2026 Omar Cornut"); ImGui::Text("Developed by Omar Cornut and all Dear ImGui contributors."); ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); ImGui::Text("If your company uses this, please consider funding the project."); @@ -8194,6 +8337,7 @@ void ImGui::ShowAboutWindow(bool* p_open) if (copy_to_clipboard) { ImGui::LogToClipboard(); + ImGui::LogText("// (Copy from the next line. Keep the ``` markers for formatting.)\n"); ImGui::LogText("```cpp\n"); // Back quotes will make text appears without formatting when pasting on GitHub } @@ -8269,8 +8413,12 @@ void ImGui::ShowAboutWindow(bool* p_open) #endif #ifdef __EMSCRIPTEN__ ImGui::Text("define: __EMSCRIPTEN__"); +#ifdef __EMSCRIPTEN_MAJOR__ + ImGui::Text("Emscripten: %d.%d.%d", __EMSCRIPTEN_MAJOR__, __EMSCRIPTEN_MINOR__, __EMSCRIPTEN_TINY__); +#else ImGui::Text("Emscripten: %d.%d.%d", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__); #endif +#endif #ifdef IMGUI_HAS_VIEWPORT ImGui::Text("define: IMGUI_HAS_VIEWPORT"); #endif @@ -8375,9 +8523,9 @@ bool ImGui::ShowStyleSelector(const char* label) static int style_idx = -1; const char* style_names[] = { "Dark", "Light", "Classic" }; bool ret = false; - if (ImGui::BeginCombo(label, (style_idx >= 0 && style_idx < IM_ARRAYSIZE(style_names)) ? style_names[style_idx] : "")) + if (ImGui::BeginCombo(label, (style_idx >= 0 && style_idx < IM_COUNTOF(style_names)) ? style_names[style_idx] : "")) { - for (int n = 0; n < IM_ARRAYSIZE(style_names); n++) + for (int n = 0; n < IM_COUNTOF(style_names); n++) { if (ImGui::Selectable(style_names[n], style_idx == n, ImGuiSelectableFlags_SelectOnNav)) { @@ -8423,6 +8571,11 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) if (ref == NULL) ref = &ref_saved_style; + // The logic behind dynamically changing 'max_border_size' is to not encourage people to increase border size too much: it'll likely reveal lots of subtle rendering artifacts and this isn't a priority right now. + // Note that _MainScale is currently internal PLEASE DO NOT USE IN YOUR CODE. + const float default_border_size = (float)(int)style._MainScale; + const float max_border_size = IM_MAX(default_border_size, 2.0f); + PushItemWidth(GetWindowWidth() * 0.50f); { @@ -8443,19 +8596,19 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); - //BeginDisabled(GetIO().ConfigDpiScaleFonts); + BeginDisabled(GetIO().ConfigDpiScaleFonts); DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 4.0f); - //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); - //EndDisabled(); + SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + EndDisabled(); // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) if (SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding - { bool border = (style.WindowBorderSize > 0.0f); if (Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } + { bool border = (style.WindowBorderSize > 0.0f); if (Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? default_border_size : 0.0f; } } SameLine(); - { bool border = (style.FrameBorderSize > 0.0f); if (Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } + { bool border = (style.FrameBorderSize > 0.0f); if (Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? default_border_size : 0.0f; } } SameLine(); - { bool border = (style.PopupBorderSize > 0.0f); if (Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + { bool border = (style.PopupBorderSize > 0.0f); if (Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? default_border_size : 0.0f; } } } // Save/Revert button @@ -8484,10 +8637,10 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); SeparatorText("Borders"); - SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, max_border_size, "%.0f"); + SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, max_border_size, "%.0f"); + SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, max_border_size, "%.0f"); + SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, max_border_size, "%.0f"); SeparatorText("Rounding"); SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); @@ -8502,9 +8655,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SliderFloat("ScrollbarPadding", &style.ScrollbarPadding, 0.0f, 10.0f, "%.0f"); SeparatorText("Tabs"); - SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); - SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f"); + SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, max_border_size, "%.0f"); + SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, max_border_size, "%.0f"); + SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, IM_MAX(3.0f, max_border_size), "%.0f"); SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); DragFloat("TabMinWidthBase", &style.TabMinWidthBase, 0.5f, 1.0f, 500.0f, "%.0f"); DragFloat("TabMinWidthShrink", &style.TabMinWidthShrink, 0.5f, 1.0f, 500.0f, "%0.f"); @@ -8529,7 +8682,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) style.TreeLinesFlags = option; EndCombo(); } - SliderFloat("TreeLinesSize", &style.TreeLinesSize, 0.0f, 2.0f, "%.0f"); + SliderFloat("TreeLinesSize", &style.TreeLinesSize, 0.0f, max_border_size, "%.0f"); SliderFloat("TreeLinesRounding", &style.TreeLinesRounding, 0.0f, 12.0f, "%.0f"); SeparatorText("Windows"); @@ -8540,16 +8693,19 @@ 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."); SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); + SliderFloat("SeparatorSize", &style.SeparatorSize, 0.0f, 10.0f, "%.0f"); SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); - SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("ImageRounding", &style.ImageRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, max_border_size, "%.0f"); SeparatorText("Docking"); //SetCursorPosX(GetCursorPosX() + CalcItemWidth() - GetFrameHeight()); @@ -8597,7 +8753,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) } LogFinish(); } - SameLine(); SetNextItemWidth(120); Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + SameLine(); SetNextItemWidth(GetFontSize() * 10); Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); SameLine(); Checkbox("Only Modified Colors", &output_only_modified); static ImGuiTextFilter filter; @@ -8770,13 +8926,23 @@ void ImGui::ShowUserGuide() BulletText("Ctrl+Z to undo, Ctrl+Y/Ctrl+Shift+Z to redo."); BulletText("Escape to revert."); Unindent(); - BulletText("With keyboard navigation enabled:"); + BulletText("With Keyboard controls enabled:"); Indent(); BulletText("Arrow keys or Home/End/PageUp/PageDown to navigate."); BulletText("Space to activate a widget."); BulletText("Return to input text into a widget."); BulletText("Escape to deactivate a widget, close popup,\nexit a child window or the menu layer, clear focus."); BulletText("Alt to jump to the menu layer of a window."); + BulletText("Menu or Shift+F10 to open a context menu."); + Unindent(); + BulletText("With Gamepad controls enabled:"); + Indent(); + BulletText("D-Pad: Navigate / Tweak / Resize (in Windowing mode)."); + BulletText("%s Face button: Activate / Open / Toggle. Hold: activate with text input.", io.ConfigNavSwapGamepadButtons ? "East" : "South"); + BulletText("%s Face button: Cancel / Close / Exit.", io.ConfigNavSwapGamepadButtons ? "South" : "East"); + BulletText("West Face button: Toggle Menu. Hold for Windowing mode (Focus/Move/Resize windows)."); + BulletText("North Face button: Open Context Menu."); + BulletText("L1/R1: Tweak Slower/Faster, Focus Previous/Next (in Windowing Mode)."); Unindent(); } @@ -8797,11 +8963,13 @@ static void ShowExampleAppMainMenuBar() { if (ImGui::BeginMenu("File")) { + IMGUI_DEMO_MARKER("Menu/File"); ShowExampleMenuFile(); ImGui::EndMenu(); } if (ImGui::BeginMenu("Edit")) { + IMGUI_DEMO_MARKER("Menu/Edit"); if (ImGui::MenuItem("Undo", "Ctrl+Z")) {} if (ImGui::MenuItem("Redo", "Ctrl+Y", false, false)) {} // Disabled item ImGui::Separator(); @@ -8844,12 +9012,12 @@ static void ShowExampleMenuFile() if (ImGui::MenuItem("Save As..")) {} ImGui::Separator(); - IMGUI_DEMO_MARKER("Examples/Menu/Options"); if (ImGui::BeginMenu("Options")) { + IMGUI_DEMO_MARKER("Examples/Menu/Options"); static bool enabled = true; ImGui::MenuItem("Enabled", "", &enabled); - ImGui::BeginChild("child", ImVec2(0, 60), ImGuiChildFlags_Borders); + ImGui::BeginChild("child", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5.0f), ImGuiChildFlags_Borders); for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i); ImGui::EndChild(); @@ -8861,9 +9029,9 @@ static void ShowExampleMenuFile() ImGui::EndMenu(); } - IMGUI_DEMO_MARKER("Examples/Menu/Colors"); if (ImGui::BeginMenu("Colors")) { + IMGUI_DEMO_MARKER("Examples/Menu/Colors"); float sz = ImGui::GetTextLineHeight(); for (int i = 0; i < ImGuiCol_COUNT; i++) { @@ -8916,7 +9084,6 @@ struct ExampleAppConsole ExampleAppConsole() { - IMGUI_DEMO_MARKER("Examples/Console"); ClearLog(); memset(InputBuf, 0, sizeof(InputBuf)); HistoryPos = -1; @@ -8956,8 +9123,8 @@ struct ExampleAppConsole char buf[1024]; va_list args; va_start(args, fmt); - vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); - buf[IM_ARRAYSIZE(buf)-1] = 0; + vsnprintf(buf, IM_COUNTOF(buf), fmt, args); + buf[IM_COUNTOF(buf)-1] = 0; va_end(args); Items.push_back(Strdup(buf)); } @@ -8970,6 +9137,7 @@ struct ExampleAppConsole ImGui::End(); return; } + IMGUI_DEMO_MARKER("Examples/Console"); // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. // So e.g. IsItemHovered() will return true when hovering the title bar. @@ -9015,7 +9183,8 @@ struct ExampleAppConsole ImGui::Separator(); // Reserve enough left-over height for 1 separator + 1 input text - const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + ImGuiStyle& style = ImGui::GetStyle(); + const float footer_height_to_reserve = style.SeparatorSize + style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar)) { if (ImGui::BeginPopupContextWindow()) @@ -9085,7 +9254,7 @@ struct ExampleAppConsole // Command-line bool reclaim_focus = false; ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; - if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) + if (ImGui::InputText("Input", InputBuf, IM_COUNTOF(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) { char* s = InputBuf; Strtrim(s); @@ -9261,6 +9430,28 @@ static void ShowExampleAppConsole(bool* p_open) } //----------------------------------------------------------------------------- +// [SECTION] Example App: Image Viewer / ShowExampleAppImageViewer() +//----------------------------------------------------------------------------- + +static void ShowExampleAppImageViewer(bool* p_open) +{ + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + ImTextureRef tex_ref = atlas->TexRef; // We don't have access to other textures in this demo! + int tex_w = atlas->TexData->Width; + int tex_h = atlas->TexData->Height; + if (ImGui::Begin("Example: Image Viewer", p_open)) + { + static ExampleImageViewerData image_viewer; + ExampleImageViewer_DrawOptions(&image_viewer); + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); + ImVec2 canvas_min_size = ImGui::IsWindowAppearing() ? ImVec2(3.0f * tex_w, 4.0f * tex_h) : ImVec2(1.0f, 1.0f); + canvas_size = ImVec2(IM_MAX(canvas_size.x, canvas_min_size.x), IM_MAX(canvas_size.y, canvas_min_size.y)); + ExampleImageViewer_DrawCanvas(&image_viewer, canvas_size, tex_ref, tex_w, tex_h); + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- // [SECTION] Example App: Debug Log / ShowExampleAppLog() //----------------------------------------------------------------------------- @@ -9409,8 +9600,8 @@ static void ShowExampleAppLog(bool* p_open) const char* words[] = { "Bumfuzzled", "Cattywampus", "Snickersnee", "Abibliophobia", "Absquatulate", "Nincompoop", "Pauciloquent" }; for (int n = 0; n < 5; n++) { - const char* category = categories[counter % IM_ARRAYSIZE(categories)]; - const char* word = words[counter % IM_ARRAYSIZE(words)]; + const char* category = categories[counter % IM_COUNTOF(categories)]; + const char* word = words[counter % IM_COUNTOF(words)]; log.AddLog("[%05d] [%s] Hello, current time is %.1f, here's a word: '%s'\n", ImGui::GetFrameCount(), category, ImGui::GetTime(), word); counter++; @@ -9500,26 +9691,34 @@ static void ShowExampleAppLayout(bool* p_open) struct ExampleAppPropertyEditor { ImGuiTextFilter Filter; - ExampleTreeNode* VisibleNode = NULL; + ExampleTreeNode* SelectedNode = NULL; + bool UseClipper = false; void Draw(ExampleTreeNode* root_node) { + IMGUI_DEMO_MARKER("Examples/Property editor"); + // Left side: draw tree // - Currently using a table to benefit from RowBg feature + // - Our tree node are all of equal height, facilitating the use of a clipper. if (ImGui::BeginChild("##tree", ImVec2(300, 0), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened)) { + ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImGui::Checkbox("Use Clipper", &UseClipper); + ImGui::SameLine(); + ImGui::Text("(%d root nodes)", root_node->Childs.Size); ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); - ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); - if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_ARRAYSIZE(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) + if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) Filter.Build(); ImGui::PopItemFlag(); - if (ImGui::BeginTable("##bg", 1, ImGuiTableFlags_RowBg)) + if (ImGui::BeginTable("##list", 1, ImGuiTableFlags_RowBg)) { - for (ExampleTreeNode* node : root_node->Childs) - if (Filter.PassFilter(node->Name)) // Filter root node - DrawTreeNode(node); + if (UseClipper) + DrawClippedTree(root_node); + else + DrawTree(root_node); ImGui::EndTable(); } } @@ -9529,7 +9728,7 @@ struct ExampleAppPropertyEditor ImGui::SameLine(); ImGui::BeginGroup(); // Lock X position - if (ExampleTreeNode* node = VisibleNode) + if (ExampleTreeNode* node = SelectedNode) { ImGui::Text("%s", node->Name); ImGui::TextDisabled("UID: 0x%08X", node->UID); @@ -9592,34 +9791,121 @@ struct ExampleAppPropertyEditor ImGui::EndGroup(); } - void DrawTreeNode(ExampleTreeNode* node) + // Custom search filter + // - Here we apply on root node only. + // - This does a case insensitive stristr which is pretty heavy. In a real large-scale app you would likely store a filtered list which in turns would be trivial to linearize. + inline bool IsNodePassingFilter(ExampleTreeNode* node) + { + return node->Parent->Parent != NULL || Filter.PassFilter(node->Name); + } + + // Basic version, recursive. This is how you would generally draw a tree. + // - Simple but going to be noticeably costly if you have a large amount of nodes as DrawTreeNode() is called for all of them. + // - On my desktop PC (2020), for 10K nodes in an optimized build this takes ~1.2 ms + // - Unlike arrays or grids which are very easy to clip, trees are currently more difficult to clip. + void DrawTree(ExampleTreeNode* node) + { + for (ExampleTreeNode* child : node->Childs) + if (IsNodePassingFilter(child) && DrawTreeNode(child)) + { + DrawTree(child); + ImGui::TreePop(); + } + } + + // More advanced version. Use a alternative clipping technique: fast-forwarding through non-visible chunks. + // - On my desktop PC (2020), for 10K nodes in an optimized build this takes ~0.1 ms + // (in ExampleTree_CreateDemoTree(), change 'int ROOT_ITEMS_COUNT = 10000' to try with this amount of root nodes). + // - 1. Use clipper with indeterminate count (items_count = INT_MAX): we need to call SeekCursorForItem() at the end once we know the count. + // - 2. Use SetNextItemStorageID() to specify ID used for open/close storage, making it easy to call TreeNodeGetOpen() on any arbitrary node. + // - 3. Linearize tree during traversal: our tree data structure makes it easy to access sibling and parents. + // - Unlike clipping for a regular array or grid which may be done using random access limited to visible areas, + // this technique requires traversing most accessible nodes. This could be made more optimal with extra work, + // but this is a decent simplicity<>speed trade-off. + // See https://github.com/ocornut/imgui/issues/3823 for discussions about this. + void DrawClippedTree(ExampleTreeNode* root_node) + { + ExampleTreeNode* node = root_node->Childs[0]; // First node + ImGuiListClipper clipper; + clipper.Begin(INT_MAX); + while (clipper.Step()) + while (clipper.UserIndex < clipper.DisplayEnd && node != NULL) + node = DrawClippedTreeNodeAndAdvanceToNext(&clipper, node); + + // Keep going to count nodes and submit final count so we have a reliable scrollbar. + // - One could consider caching this value and only refreshing it occasionally e.g. window is focused and an action occurs. + // - Incorrect but cheap approximation would be to use 'clipper_current_idx = IM_MAX(clipper_current_idx, root_node->Childs.Size)' instead. + // - If either of those is implemented, the general cost will approach zero when scrolling is at the top of the tree. + while (node != NULL) + node = DrawClippedTreeNodeAndAdvanceToNext(&clipper, node); + //clipper.UserIndex = IM_MAX(clipper.UserIndex, root_node->Childs.Size); // <-- Cheap approximation instead of while() loop above. + clipper.SeekCursorForItem(clipper.UserIndex); + } + + ExampleTreeNode* DrawClippedTreeNodeAndAdvanceToNext(ImGuiListClipper* clipper, ExampleTreeNode* node) + { + if (IsNodePassingFilter(node)) + { + // Draw node if within visible range + bool is_open = false; + if (clipper->UserIndex >= clipper->DisplayStart && clipper->UserIndex < clipper->DisplayEnd) + { + is_open = DrawTreeNode(node); + } + else + { + is_open = (node->Childs.Size > 0 && ImGui::TreeNodeGetOpen((ImGuiID)node->UID)); + if (is_open) + ImGui::TreePush(node->Name); + } + clipper->UserIndex++; + + // Next node: recurse into childs + if (is_open) + return node->Childs[0]; + } + + // Next node: next sibling, otherwise move back to parent + while (node != NULL) + { + if (node->IndexInParent + 1 < node->Parent->Childs.Size) + return node->Parent->Childs[node->IndexInParent + 1]; + node = node->Parent; + if (node->Parent == NULL) + break; + ImGui::TreePop(); + } + return NULL; + } + + // To support node with same name we incorporate node->UID into the item ID. + // (this would more naturally be done using PushID(node->UID) + TreeNodeEx(node->Name, tree_flags), + // but it would require in DrawClippedTreeNodeAndAdvanceToNext() to add PushID() before TreePush(), and PopID() after TreePop(), + // so instead we use TreeNodeEx(node->UID, tree_flags, "%s", node->Name) here) + bool DrawTreeNode(ExampleTreeNode* node) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::PushID(node->UID); ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None; - tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;// Standard opening mode as we are likely to want to add selection afterwards + tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsToParent; // Left arrow support tree_flags |= ImGuiTreeNodeFlags_SpanFullWidth; // Span full width for easier mouse reach tree_flags |= ImGuiTreeNodeFlags_DrawLinesToNodes; // Always draw hierarchy outlines - if (node == VisibleNode) - tree_flags |= ImGuiTreeNodeFlags_Selected; + if (node == SelectedNode) + tree_flags |= ImGuiTreeNodeFlags_Selected; // Draw selection highlight if (node->Childs.Size == 0) - tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet; + tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen; // Use _NoTreePushOnOpen + set is_open=false to avoid unnecessarily push/pop on leaves. if (node->DataMyBool == false) ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); - bool node_open = ImGui::TreeNodeEx("", tree_flags, "%s", node->Name); + ImGui::SetNextItemStorageID((ImGuiID)node->UID); // Use node->UID as storage id + bool is_open = ImGui::TreeNodeEx((void*)(intptr_t)node->UID, tree_flags, "%s", node->Name); + if (node->Childs.Size == 0) + is_open = false; if (node->DataMyBool == false) ImGui::PopStyleColor(); if (ImGui::IsItemFocused()) - VisibleNode = node; - if (node_open) - { - for (ExampleTreeNode* child : node->Childs) - DrawTreeNode(child); - ImGui::TreePop(); - } - ImGui::PopID(); + SelectedNode = node; + return is_open; } }; @@ -9798,9 +10084,9 @@ static void ShowExampleAppConstrainedResize(bool* p_open) const bool window_open = ImGui::Begin("Example: Constrained Resize", p_open, window_flags); if (!window_padding) ImGui::PopStyleVar(); + IMGUI_DEMO_MARKER("Examples/Constrained Resizing window"); if (window_open) { - IMGUI_DEMO_MARKER("Examples/Constrained Resizing window"); if (ImGui::GetIO().KeyShift) { // Display a dummy viewport (in your real app you would likely use ImageButton() to display a texture) @@ -9819,7 +10105,7 @@ static void ShowExampleAppConstrainedResize(bool* p_open) if (ImGui::Button("Set 500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); if (ImGui::Button("Set 800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); - ImGui::Combo("Constraint", &type, test_desc, IM_ARRAYSIZE(test_desc)); + ImGui::Combo("Constraint", &type, test_desc, IM_COUNTOF(test_desc)); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); ImGui::Checkbox("Auto-resize", &auto_resize); @@ -9866,7 +10152,7 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background if (ImGui::Begin("Example: Simple overlay", p_open, window_flags)) { - IMGUI_DEMO_MARKER("Examples/Simple Overlay"); + IMGUI_DEMO_MARKER("Examples/Simple overlay"); // Scroll up to the beginning of this function to see overlay flags ImGui::Text("Simple overlay\n" "(right-click to change position)"); ImGui::Separator(); if (ImGui::IsMousePosValid()) @@ -9906,6 +10192,7 @@ static void ShowExampleAppFullscreen(bool* p_open) if (ImGui::Begin("Example: Fullscreen window", p_open, flags)) { + IMGUI_DEMO_MARKER("Examples/Fullscreen window"); ImGui::Checkbox("Use work area instead of main area", &use_work_area); ImGui::SameLine(); HelpMarker("Main Area = entire viewport,\nWork Area = entire viewport minus sections used by the main menu bars, task bars etc.\n\nEnable the main-menu bar in Examples menu to see the difference."); @@ -9942,12 +10229,13 @@ static void ShowExampleAppWindowTitles(bool*) // Using "##" to display same title but have unique identifier. ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 100), ImGuiCond_FirstUseEver); ImGui::Begin("Same title as another window##1"); - IMGUI_DEMO_MARKER("Examples/Manipulating window titles"); + IMGUI_DEMO_MARKER("Examples/Manipulating window titles##1"); ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); ImGui::End(); ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 200), ImGuiCond_FirstUseEver); ImGui::Begin("Same title as another window##2"); + IMGUI_DEMO_MARKER("Examples/Manipulating window titles##2"); ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); ImGui::End(); @@ -9956,6 +10244,7 @@ static void ShowExampleAppWindowTitles(bool*) sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount()); ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 300), ImGuiCond_FirstUseEver); ImGui::Begin(buf); + IMGUI_DEMO_MARKER("Examples/Manipulating window titles##3"); ImGui::Text("This window has a changing title."); ImGui::End(); } @@ -9980,7 +10269,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::End(); return; } - IMGUI_DEMO_MARKER("Examples/Custom Rendering"); + IMGUI_DEMO_MARKER("Examples/Custom rendering"); // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of // overloaded operators, etc. Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your @@ -9991,6 +10280,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) { if (ImGui::BeginTabItem("Primitives")) { + IMGUI_DEMO_MARKER("Examples/Custom rendering/Primitives"); ImGui::PushItemWidth(-ImGui::GetFontSize() * 15); ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -10056,20 +10346,20 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th); x += sz + spacing; // Square + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th); x += sz + spacing; // Square with all rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, corners_tl_br); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th); x += sz + spacing; // Concave Shape - //draw_list->AddPolyline(concave_shape, IM_ARRAYSIZE(concave_shape), col, ImDrawFlags_Closed, th); - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, ImDrawFlags_Closed); x += sz + spacing; // Concave Shape + //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); + draw_list->AddLineH(x, x + sz, y, col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) + draw_list->AddLineV(x, y, y + sz, col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line // Path draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, ImDrawFlags_None, th); + draw_list->PathStroke(col, th); x += sz + spacing; // Quadratic Bezier Curve (3 control points) @@ -10118,6 +10408,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) if (ImGui::BeginTabItem("Canvas")) { + IMGUI_DEMO_MARKER("Examples/Custom rendering/Canvas"); static ImVector points; static ImVec2 scrolling(0.0f, 0.0f); static bool opt_enable_grid = true; @@ -10202,9 +10493,9 @@ static void ShowExampleAppCustomRendering(bool* p_open) { const float GRID_STEP = 64.0f; for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP) - draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40)); + draw_list->AddLineV(canvas_p0.x + x, canvas_p0.y, canvas_p1.y, IM_COL32(200, 200, 200, 40)); for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP) - draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40)); + draw_list->AddLineH(canvas_p0.x, canvas_p1.x, canvas_p0.y + y, IM_COL32(200, 200, 200, 40)); } for (int n = 0; n < points.Size; n += 2) draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); @@ -10215,6 +10506,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) if (ImGui::BeginTabItem("BG/FG draw lists")) { + IMGUI_DEMO_MARKER("Examples/Custom rendering/BG & FG draw lists"); static bool draw_bg = true; static bool draw_fg = true; ImGui::Checkbox("Draw in Background draw list", &draw_bg); @@ -10236,6 +10528,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) // but you can also instantiate your own ImDrawListSplitter if you need to nest them. if (ImGui::BeginTabItem("Draw Channels")) { + IMGUI_DEMO_MARKER("Examples/Custom rendering/Draw Channels"); ImDrawList* draw_list = ImGui::GetWindowDrawList(); { ImGui::Text("Blue shape is drawn first: appears in back"); @@ -10278,37 +10571,36 @@ static void ShowExampleAppCustomRendering(bool* p_open) // [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() //----------------------------------------------------------------------------- -// Demonstrate using DockSpace() to create an explicit docking node within an existing window, with various options. +struct ImGuiDemoDockspaceArgs +{ + bool IsFullscreen = true; + bool KeepWindowPadding = false; // Keep WindowPadding to help understand that DockSpace() is a widget inside the window. + ImGuiDockNodeFlags DockSpaceFlags = ImGuiDockNodeFlags_None; +}; + // THIS IS A DEMO FOR ADVANCED USAGE OF DockSpace(). -// MOST REGULAR APPLICATIONS WHO WANT TO ALLOW DOCKING WINDOWS ON THE EDGE OF YOUR SCREEN CAN SIMPLY USE: -// ImGui::NewFrame(); -// ImGui::DockSpaceOverViewport(); // Create a dockspace in main viewport +// MOST REGULAR APPLICATIONS WANTING TO ALLOW DOCKING WINDOWS ON THE EDGE OF YOUR SCREEN CAN SIMPLY USE: +// ImGui::NewFrame(); + ImGui::DockSpaceOverViewport(); // Create a dockspace in main viewport // OR: -// ImGui::NewFrame(); -// ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode); // Create a dockspace in main viewport, where central node is transparent. +// ImGui::NewFrame(); + ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode); // Create a dockspace in main viewport, where central node is transparent. +// Demonstrate using DockSpace() to create an explicit docking node within an existing window, with various options. // Read https://github.com/ocornut/imgui/wiki/Docking for details. // The reasons we do not use DockSpaceOverViewport() in this demo is because: -// - (1) we allow the host window to be floating/moveable instead of filling the viewport (when opt_fullscreen == false) +// - (1) we allow the host window to be floating/moveable instead of filling the viewport (when args->IsFullscreen == false) // which is mostly to showcase the idea that DockSpace() may be submitted anywhere. -// - (2) we allow the host window to have padding (when opt_padding == true) -// - (3) we expose many flags and need a way to have them visible. -// - (4) we have a local menu bar in the host window (vs. you could use BeginMainMenuBar() + DockSpaceOverViewport() -// in your code, but we don't here because we allow the window to be floating) -void ShowExampleAppDockSpace(bool* p_open) +// Also see 'Demo->Examples->Documents' for a less abstract version of this. +// - (2) we allow the host window to have padding (when args->UsePadding == true) +// - (3) we expose variety of other flags. +static void ShowExampleAppDockSpaceAdvanced(ImGuiDemoDockspaceArgs* args, bool* p_open) { - // TL;DR; this demo is more complicated than what most users you would normally use. - // If we remove all options we are showcasing, this demo would become a simple call to ImGui::DockSpaceOverViewport() !! - // In this specific demo, we are not using DockSpaceOverViewport() because: - - static bool opt_fullscreen = true; - static bool opt_padding = false; - static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; + ImGuiDockNodeFlags dockspace_flags = args->DockSpaceFlags; // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, // because it would be confusing to have two docking targets within each others. - ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - if (opt_fullscreen) + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking; + if (args->IsFullscreen) { + // Fullscreen dockspace: practically the same as calling DockSpaceOverViewport(); const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); ImGui::SetNextWindowSize(viewport->WorkSize); @@ -10317,74 +10609,105 @@ void ShowExampleAppDockSpace(bool* p_open) ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + window_flags |= ImGuiWindowFlags_NoBackground; } else { + // Floating dockspace dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; } - // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background - // and handle the pass-thru hole, so we ask Begin() to not render a background. - if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) - window_flags |= ImGuiWindowFlags_NoBackground; - // Important: note that we proceed even if Begin() returns false (aka window is collapsed). // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, // all active windows docked into it will lose their parent and become undocked. // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. - if (!opt_padding) + if (!args->KeepWindowPadding) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("DockSpace Demo", p_open, window_flags); - if (!opt_padding) + ImGui::Begin("Window with a DockSpace", p_open, window_flags); + if (!args->KeepWindowPadding) ImGui::PopStyleVar(); - if (opt_fullscreen) + if (args->IsFullscreen) ImGui::PopStyleVar(2); - // Submit the DockSpace - // REMINDER: THIS IS A DEMO FOR ADVANCED USAGE OF DockSpace()! - // MOST REGULAR APPLICATIONS WILL SIMPLY WANT TO CALL DockSpaceOverViewport(). READ COMMENTS ABOVE. - ImGuiIO& io = ImGui::GetIO(); - if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) - { - ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); - ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); - } + // Submit the DockSpace widget inside our window + // - Note that the id here is different from the one used by DockSpaceOverViewport(), so docking state won't get transfered between "Basic" and "Advanced" demos. + // - If we made the ShowExampleAppDockSpaceBasic() calculate its own ID and pass it to DockSpaceOverViewport() the ID could easily match. + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + + ImGui::End(); +} + +static void ShowExampleAppDockSpaceBasic(ImGuiDockNodeFlags flags) +{ + // Basic version which you can use in many apps: + // e.g: + // ImGui::DockSpaceOverViewport(); + // or: + // ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode); // Central node will be transparent + // or: + // ImGuiViewport* viewport = ImGui::GetMainViewport(); + // ImGui::DockSpaceOverViewport(0, viewport, ImGuiDockNodeFlags_None); + + ImGui::DockSpaceOverViewport(0, nullptr, flags); +} + +void ShowExampleAppDockSpace(bool* p_open) +{ + static int opt_demo_mode = 0; + static bool opt_demo_mode_changed = false; + static ImGuiDemoDockspaceArgs args; + + if (opt_demo_mode == 0) + ShowExampleAppDockSpaceBasic(args.DockSpaceFlags); else + ShowExampleAppDockSpaceAdvanced(&args, p_open); + + // Refocus our window to minimize perceived loss of focus when changing mode (caused by the fact that each use a different window, which would not happen in a real app) + if (opt_demo_mode_changed) + ImGui::SetNextWindowFocus(); + ImGui::Begin("Examples: Dockspace", p_open, ImGuiWindowFlags_MenuBar); + opt_demo_mode_changed = false; + opt_demo_mode_changed |= ImGui::RadioButton("Basic demo mode", &opt_demo_mode, 0); + opt_demo_mode_changed |= ImGui::RadioButton("Advanced demo mode", &opt_demo_mode, 1); + + ImGui::SeparatorText("Options"); + + if ((ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) == 0) { ShowDockingDisabledMessage(); } + else if (opt_demo_mode == 0) + { + args.DockSpaceFlags &= ImGuiDockNodeFlags_PassthruCentralNode; // Allowed flags + ImGui::CheckboxFlags("Flag: PassthruCentralNode", &args.DockSpaceFlags, ImGuiDockNodeFlags_PassthruCentralNode); + } + else if (opt_demo_mode == 1) + { + ImGui::Checkbox("Fullscreen", &args.IsFullscreen); + ImGui::Checkbox("Keep Window Padding", &args.KeepWindowPadding); + ImGui::SameLine(); + HelpMarker("This is mostly exposed to facilitate understanding that a DockSpace() is _inside_ a window."); + ImGui::BeginDisabled(args.IsFullscreen == false); + ImGui::CheckboxFlags("Flag: PassthruCentralNode", &args.DockSpaceFlags, ImGuiDockNodeFlags_PassthruCentralNode); + ImGui::EndDisabled(); + ImGui::CheckboxFlags("Flag: NoDockingOverCentralNode", &args.DockSpaceFlags, ImGuiDockNodeFlags_NoDockingOverCentralNode); + ImGui::CheckboxFlags("Flag: NoDockingSplit", &args.DockSpaceFlags, ImGuiDockNodeFlags_NoDockingSplit); + ImGui::CheckboxFlags("Flag: NoUndocking", &args.DockSpaceFlags, ImGuiDockNodeFlags_NoUndocking); + ImGui::CheckboxFlags("Flag: NoResize", &args.DockSpaceFlags, ImGuiDockNodeFlags_NoResize); + ImGui::CheckboxFlags("Flag: AutoHideTabBar", &args.DockSpaceFlags, ImGuiDockNodeFlags_AutoHideTabBar); + } // Show demo options and help if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("Options")) - { - // Disabling fullscreen would allow the window to be moved to the front of other windows, - // which we can't undo at the moment without finer window depth/z control. - ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen); - ImGui::MenuItem("Padding", NULL, &opt_padding); - ImGui::Separator(); - - if (ImGui::MenuItem("Flag: NoDockingOverCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingOverCentralNode) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingOverCentralNode; } - if (ImGui::MenuItem("Flag: NoDockingSplit", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingSplit) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingSplit; } - if (ImGui::MenuItem("Flag: NoUndocking", "", (dockspace_flags & ImGuiDockNodeFlags_NoUndocking) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoUndocking; } - if (ImGui::MenuItem("Flag: NoResize", "", (dockspace_flags & ImGuiDockNodeFlags_NoResize) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoResize; } - if (ImGui::MenuItem("Flag: AutoHideTabBar", "", (dockspace_flags & ImGuiDockNodeFlags_AutoHideTabBar) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_AutoHideTabBar; } - if (ImGui::MenuItem("Flag: PassthruCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0, opt_fullscreen)) { dockspace_flags ^= ImGuiDockNodeFlags_PassthruCentralNode; } - ImGui::Separator(); - - if (ImGui::MenuItem("Close", NULL, false, p_open != NULL)) - *p_open = false; - ImGui::EndMenu(); - } if (ImGui::BeginMenu("Help")) { ImGui::TextUnformatted( - "This demo has nothing to do with enabling docking!" "\n" - "This demo only demonstrate the use of ImGui::DockSpace() which allows you to manually\ncreate a docking node _within_ another window." "\n" - "Most application can simply call ImGui::DockSpaceOverViewport() and be done with it."); + "This demonstrates the use of ImGui::DockSpace() which allows you to manually\ncreate a docking node _within_ another window." "\n" + "The \"Basic\" version uses the ImGui::DockSpaceOverViewport() helper. Most applications can probably use this."); ImGui::Separator(); ImGui::TextUnformatted("When docking is enabled, you can ALWAYS dock MOST window into another! Try it now!" "\n" "- Drag from window title bar or their tab to dock/undock." "\n" @@ -10550,6 +10873,7 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::End(); return; } + IMGUI_DEMO_MARKER("Examples/Documents"); // Menu if (ImGui::BeginMenuBar()) @@ -10708,7 +11032,7 @@ void ShowExampleAppDocuments(bool* p_open) if (ImGui::BeginPopup("Rename")) { ImGui::SetNextItemWidth(ImGui::GetFontSize() * 30); - if (ImGui::InputText("###Name", app.RenamingDoc->Name, IM_ARRAYSIZE(app.RenamingDoc->Name), ImGuiInputTextFlags_EnterReturnsTrue)) + if (ImGui::InputText("###Name", app.RenamingDoc->Name, IM_COUNTOF(app.RenamingDoc->Name), ImGuiInputTextFlags_EnterReturnsTrue)) { ImGui::CloseCurrentPopup(); app.RenamingDoc = NULL; @@ -10828,7 +11152,7 @@ struct ExampleAsset if (delta < 0) return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; } - return ((int)a->ID - (int)b->ID); + return (int)a->ID - (int)b->ID; } }; const ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL; @@ -10838,12 +11162,14 @@ struct ExampleAssetsBrowser // Options bool ShowTypeOverlay = true; bool AllowSorting = true; - bool AllowDragUnselected = false; - bool AllowBoxSelect = true; - float IconSize = 32.0f; + bool AllowBoxSelect = true; // Will set ImGuiMultiSelectFlags_BoxSelect2d + bool AllowBoxSelectInsideSelection = false; // Will set ImGuiMultiSelectFlags_SelectOnClickAlways + bool AllowDragUnselected = false; // Will set ImGuiMultiSelectFlags_SelectOnClickRelease + float IconSize = 0; 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. + 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. bool StretchSpacing = true; + bool UseScrollX = false; // Debug: submit twice the number of items per line (overflow horizontally to exercise ScrollX + box-select) // State ImVector Items; // Our items @@ -10894,12 +11220,15 @@ struct ExampleAssetsBrowser // Layout: calculate number of icon per line and number of lines LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize)); LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1); - LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer. if (StretchSpacing && LayoutColumnCount > 1) LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount; + if (UseScrollX) + LayoutColumnCount *= 2; + LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; + LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing); LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f); LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f); @@ -10907,12 +11236,16 @@ struct ExampleAssetsBrowser void Draw(const char* title, bool* p_open) { + if (IconSize <= 0.0f) + IconSize = ImGui::CalcTextSize("99999").x; + ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver); if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar)) { ImGui::End(); return; } + IMGUI_DEMO_MARKER("Examples/Assets Browser"); // Menu bar if (ImGui::BeginMenuBar()) @@ -10943,8 +11276,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"); @@ -10952,6 +11288,7 @@ struct ExampleAssetsBrowser ImGui::SliderInt("Icon Spacing", &IconSpacing, 0, 32); ImGui::SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32); ImGui::Checkbox("Stretch Spacing", &StretchSpacing); + ImGui::Checkbox("Use ScrollX", &UseScrollX); ImGui::PopItemWidth(); ImGui::EndMenu(); } @@ -10981,7 +11318,7 @@ struct ExampleAssetsBrowser ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.y + LayoutItemSpacing))); - if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove)) + if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_HorizontalScrollbar)) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -11000,9 +11337,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: @@ -11106,7 +11445,7 @@ struct ExampleAssetsBrowser draw_list->AddRectFilled(box_min, box_max, icon_bg_color); // Background color if (ShowTypeOverlay && item_data->Type != 0) { - ImU32 type_col = icon_type_overlay_colors[item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)]; + ImU32 type_col = icon_type_overlay_colors[item_data->Type % IM_COUNTOF(icon_type_overlay_colors)]; draw_list->AddRectFilled(ImVec2(box_max.x - 2 - icon_type_overlay_size.x, box_min.y + 2), ImVec2(box_max.x - 2, box_min.y + 2 + icon_type_overlay_size.y), type_col); } if (display_label) @@ -11123,6 +11462,8 @@ struct ExampleAssetsBrowser } } clipper.End(); + if (Items.Size == 0) + ImGui::Dummy(ImVec2(0, 0)); ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing // Context menu diff --git a/src/Libs/imgui/imgui_draw.cpp b/src/Libs/imgui/imgui_draw.cpp index 402727a..b5a93b1 100644 --- a/src/Libs/imgui/imgui_draw.cpp +++ b/src/Libs/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (drawing and font code) /* @@ -17,10 +17,11 @@ Index of this file: // [SECTION] ImFontAtlas: backend for stb_truetype // [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder -// [SECTION] ImFont +// [SECTION] ImFontBaked, ImFont // [SECTION] ImGui Internal Render Helpers // [SECTION] Decompression code // [SECTION] Default font data (ProggyClean.ttf) +// [SECTION] Default font data (ProggyForever.ttf) */ @@ -81,6 +82,7 @@ Index of this file: #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif //------------------------------------------------------------------------- @@ -103,6 +105,7 @@ namespace IMGUI_STB_NAMESPACE #pragma warning (push) #pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration #pragma warning (disable: 6011) // (stb_rectpack) Dereferencing NULL pointer 'cur->next'. +#pragma warning (disable: 5262) // (stb_truetype) implicit fall-through occurs here; are you missing a break statement? #pragma warning (disable: 6385) // (stb_truetype) Reading invalid data from 'buffer': the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read. #pragma warning (disable: 28182) // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did. #endif @@ -205,6 +208,7 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_CheckboxSelectedBg] = ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBgHovered], 0.65f); colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); @@ -274,6 +278,7 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_CheckboxSelectedBg] = ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBgActive], 0.65f); colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); @@ -344,6 +349,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_CheckboxSelectedBg] = ImVec4(0.95f, 0.97f, 1.00f, 1.00f); colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); @@ -395,11 +401,11 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) ImDrawListSharedData::ImDrawListSharedData() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); InitialFringeScale = 1.0f; - for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++) + for (int i = 0; i < IM_COUNTOF(ArcFastVtx); i++) { - const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx); + const float a = ((float)i * 2 * IM_PI) / (float)IM_COUNTOF(ArcFastVtx); ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); @@ -417,7 +423,7 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) IM_ASSERT(max_error > 0.0f); CircleSegmentMaxError = max_error; - for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++) + for (int i = 0; i < IM_COUNTOF(CircleSegmentCounts); i++) { const float radius = (float)i; CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX); @@ -427,7 +433,7 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); _SetDrawListSharedData(shared_data); } @@ -450,10 +456,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); @@ -528,9 +537,14 @@ void ImDrawList::_PopUnusedDrawCmd() void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size) { + IM_ASSERT(callback != NULL); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (callback == ImDrawCallback_ResetRenderState && _Data->Context != NULL && _Data->Context->PlatformIO.DrawCallback_ResetRenderState != NULL) + callback = _Data->Context->PlatformIO.DrawCallback_ResetRenderState; // == ImGui::GetPlatformIO().DrawCallback_ResetRenderState +#endif + IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - IM_ASSERT(callback != NULL); IM_ASSERT(curr_cmd->UserCallback == NULL); if (curr_cmd->ElemCount != 0) { @@ -649,7 +663,7 @@ int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const { // Automatic segment count const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy - if (radius_idx >= 0 && radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) + if (radius_idx >= 0 && radius_idx < IM_COUNTOF(_Data->CircleSegmentCounts)) return _Data->CircleSegmentCounts[radius_idx]; // Use cached value else return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); @@ -662,7 +676,7 @@ void ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool i if (intersect_with_current_clip_rect) { ImVec4 current = _CmdHeader.ClipRect; - if (cr.x < current.x) cr.x = current.x; + if (cr.x < current.x) cr.x = current.x; // = ClipWith(). Note that passing inverted range wouldn't be fixed here. if (cr.y < current.y) cr.y = current.y; if (cr.z > current.z) cr.z = current.z; if (cr.w > current.w) cr.w = current.w; @@ -806,7 +820,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c // TODO: Thickness anti-aliased lines cap are missing their AA fringe. // We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness) +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; @@ -815,6 +829,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw const bool thick_line = (thickness > _FringeScale); + IM_ASSERT((flags & ImDrawFlags_InvalidMask_) == 0 && "Incorrect parameter. Did you swapped 'thickness' and 'flags'?"); if (Flags & ImDrawListFlags_AntiAliasedLines) { @@ -1312,7 +1327,7 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa { const float arc_length = ImAbs(a_max - a_min); const int circle_segment_count = _CalcCircleAutoSegmentCount(radius); - const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), (int)(2.0f * IM_PI / arc_length)); + const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), 1); _PathArcToN(center, radius, a_min, a_max, arc_segment_count); } } @@ -1432,35 +1447,13 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, } } -static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) -{ - /* - IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // Obsoleted in 1.82 (from February 2021). This code was stripped/simplified and mostly commented in 1.90 (from September 2023) - // - Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) - if (flags == ~0) { return ImDrawFlags_RoundCornersAll; } - // - Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations). Read details in older version of this code. - if (flags >= 0x01 && flags <= 0x0F) { return (flags << 4); } - // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' -#endif - */ - // If this assert triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. - // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway. - // See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in "API BREAKING CHANGES" section. - IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); - - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; - - return flags; -} - void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { if (rounding >= 0.5f) { - flags = FixRectCornerFlags(flags); + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); } @@ -1490,20 +1483,48 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th return; PathLineTo(p1 + ImVec2(0.5f, 0.5f)); PathLineTo(p2 + ImVec2(0.5f, 0.5f)); - PathStroke(col, 0, thickness); + PathStroke(col, thickness); +} + +void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(ImVec2(min_x + 0.5f, y + 0.5f)); // Same as AddLine() above. + PathLineTo(ImVec2(max_x + 0.5f, y + 0.5f)); + PathStroke(col, thickness); +} + +void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(ImVec2(x + 0.5f, min_y + 0.5f)); // Same as AddLine() above. + PathLineTo(ImVec2(x + 0.5f, max_y + 0.5f)); + PathStroke(col, thickness); } // p_min = upper-left, p_max = lower-right // Note we don't render 1 pixels sized rectangles properly. -void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) -{ +void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) +{ + // If this assert triggers on legacy code: + // - 1.92.8 (2025/04): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundCornersXXX values. + // If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! + // - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll. + // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. + // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'. + // See "API BREAKING CHANGES" section for 1.82 and 1.90. + IM_ASSERT((flags & ImDrawFlags_InvalidMask_) == 0 && "Incorrect parameter. Did you swapped 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values + if ((col & IM_COL32_A_MASK) == 0) return; if (Flags & ImDrawListFlags_AntiAliasedLines) PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); else PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) @@ -1547,7 +1568,7 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c PathLineTo(p2); PathLineTo(p3); PathLineTo(p4); - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -1570,7 +1591,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -1605,7 +1626,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); } - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) @@ -1641,7 +1662,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); - PathStroke(col, ImDrawFlags_Closed, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } // Guaranteed to honor 'num_segments' @@ -1668,7 +1689,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); - PathStroke(col, true, thickness); + PathStroke(col, thickness, ImDrawFlags_Closed); } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) @@ -1693,7 +1714,7 @@ void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2 PathLineTo(p1); PathBezierCubicCurveTo(p2, p3, p4, num_segments); - PathStroke(col, 0, thickness); + PathStroke(col, thickness); } // Quadratic Bezier takes 3 controls points @@ -1704,7 +1725,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathLineTo(p1); PathBezierQuadraticCurveTo(p2, p3, num_segments); - PathStroke(col, 0, thickness); + PathStroke(col, thickness); } void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) @@ -1776,7 +1797,10 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons if ((col & IM_COL32_A_MASK) == 0) return; - flags = FixRectCornerFlags(flags); + IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); // If this assert triggers on legacy code: see comments in ImDrawList::PathRect(). + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); @@ -2417,10 +2441,11 @@ void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, in // FIXME-NEWATLAS: Oversample specification could be more dynamic. For now, favoring automatic selection. ImFontConfig::ImFontConfig() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); FontDataOwnedByAtlas = true; OversampleH = 0; // Auto == 1 or 2 depending on size OversampleV = 0; // Auto == 1 + ExtraSizeScale = 1.0f; GlyphMaxAdvanceX = FLT_MAX; RasterizerMultiply = 1.0f; RasterizerDensity = 1.0f; @@ -2500,10 +2525,11 @@ void ImTextureData::DestroyPixels() // - Default texture data encoded in ASCII // - ImFontAtlas() // - ImFontAtlas::Clear() -// - ImFontAtlas::CompactCache() +// - ImFontAtlas::ClearFonts() // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() -// - ImFontAtlas::ClearFonts() +// - ImFontAtlas::CompactCache() +// - ImFontAtlas::SetFontLoader() //----------------------------------------------------------------------------- // - ImFontAtlasUpdateNewFrame() // - ImFontAtlasTextureBlockConvert() @@ -2519,6 +2545,8 @@ void ImTextureData::DestroyPixels() //----------------------------------------------------------------------------- // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() +// - ImFontAtlas::AddFontDefaultBitmap() +// - ImFontAtlas::AddFontDefaultVector() // - ImFontAtlas::AddFontFromFileTTF() // - ImFontAtlas::AddFontFromMemoryTTF() // - ImFontAtlas::AddFontFromMemoryCompressedTTF() @@ -2638,7 +2666,7 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 ImFontAtlas::ImFontAtlas() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; TexMinWidth = 512; @@ -2662,7 +2690,9 @@ ImFontAtlas::~ImFontAtlas() TexData = NULL; } -// If you call this mid-frame, you would need to add new font and bind them! +// You probably should not call this directly. It is not well specified. +// If you want to replace all your fonts mid-frame, most likely you should instead call ClearFonts() then load the new fonts. +// Calling this mid-frame will discard the CPU-side copy of the texture data which is generally unreliable as you may have textures queued for creation or updates. void ImFontAtlas::Clear() { bool backup_renderer_has_textures = RendererHasTextures; @@ -2672,20 +2702,27 @@ void ImFontAtlas::Clear() RendererHasTextures = backup_renderer_has_textures; } -void ImFontAtlas::CompactCache() -{ - ImFontAtlasTextureCompact(this); -} - -void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +void ImFontAtlas::ClearFonts() { - ImFontAtlasBuildSetupFontLoader(this, font_loader); + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + for (ImFont* font : Fonts) + ImFontAtlasBuildNotifySetFont(this, font, NULL); + ImFontAtlasBuildDestroy(this); + ClearInputData(); + Fonts.clear_delete(); + TexIsBuilt = false; + for (ImDrawListSharedData* shared_data : DrawListSharedDatas) + if (shared_data->FontAtlas == this) + { + shared_data->Font = NULL; + shared_data->FontScale = shared_data->FontSize = 0.0f; + } } void ImFontAtlas::ClearInputData() { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - for (ImFont* font : Fonts) ImFontAtlasFontDestroyOutput(this, font); for (ImFontConfig& font_cfg : Sources) @@ -2709,22 +2746,14 @@ void ImFontAtlas::ClearTexData() //Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it. } -void ImFontAtlas::ClearFonts() +void ImFontAtlas::CompactCache() { - // FIXME-NEWATLAS: Illegal to remove currently bound font. - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - for (ImFont* font : Fonts) - ImFontAtlasBuildNotifySetFont(this, font, NULL); - ImFontAtlasBuildDestroy(this); - ClearInputData(); - Fonts.clear_delete(); - TexIsBuilt = false; - for (ImDrawListSharedData* shared_data : DrawListSharedDatas) - if (shared_data->FontAtlas == this) - { - shared_data->Font = NULL; - shared_data->FontScale = shared_data->FontSize = 0.0f; - } + ImFontAtlasTextureCompact(this); +} + +void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +{ + ImFontAtlasBuildSetupFontLoader(this, font_loader); } static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) @@ -2958,12 +2987,17 @@ void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, I memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); } -// Queue texture block update for renderer backend void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) { + ImTextureDataQueueUpload(tex, x, y, w, h); + atlas->TexIsBuilt = false; +} + +// Queue texture block update for renderer backend +void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h) +{ IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed); IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); - IM_UNUSED(atlas); ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); @@ -2976,7 +3010,6 @@ void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y); tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x); tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y); - atlas->TexIsBuilt = false; // No need to queue if status is == ImTextureStatus_WantCreate if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) @@ -3034,6 +3067,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) ImFontAtlasBuildInit(this); // Create new font + const bool is_first_font = (Fonts.Size == 0); ImFont* font; if (!font_cfg_in->MergeMode) { @@ -3046,8 +3080,9 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } else { - IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font!"); // When using MergeMode make sure that a font has already been added before. font = font_cfg_in->DstFont ? font_cfg_in->DstFont : Fonts.back(); + ImFontAtlasFontDiscardBakes(this, font, 0); // Need to discard bakes if the font was already used, because baked->FontLoaderDatas[] will change size. (#9162) } // Add to list @@ -3058,12 +3093,6 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) font->Sources.push_back(font_cfg); ImFontAtlasBuildUpdatePointers(this); // Pointers to Sources are otherwise dangling after we called Sources.push_back(). - if (font_cfg->FontDataOwnedByAtlas == false) - { - font_cfg->FontDataOwnedByAtlas = true; - font_cfg->FontData = ImMemdup(font_cfg->FontData, (size_t)font_cfg->FontDataSize); - } - // Sanity check // We don't round cfg.SizePixels yet as relative size of merged fonts are used afterwards. if (font_cfg->GlyphExcludeRanges != NULL) @@ -3079,6 +3108,11 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. } + // | Target w/ Implicit RefSize | Target w/ Explicit RefSize | + // Adding w/ Implicit RefSize: | OK (same scale) | OK (same scale) | + // Adding w/ Explicit RefSize: | KO | OK (custom scale) | + if (font_cfg_in->MergeMode && font_cfg_in->SizePixels > 0) + IM_ASSERT((font->Flags & ImFontFlags_ImplicitRefSize) == 0 && "Cannot use MergeMode with an explicit reference size when the destination font used an implicit reference size!"); IM_ASSERT(font_cfg->FontLoaderData == NULL); if (!ImFontAtlasFontSourceInit(this, font_cfg)) @@ -3096,6 +3130,8 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } ImFontAtlasFontSourceAddToFont(this, font, font_cfg); + if (is_first_font) + ImFontAtlasBuildNotifySetFont(this, NULL, font); return font; } @@ -3114,32 +3150,78 @@ static void Decode85(const unsigned char* src, unsigned char* dst) } } #ifndef IMGUI_DISABLE_DEFAULT_FONT -static const char* GetDefaultCompressedFontDataTTF(int* out_size); +static const char* GetDefaultCompressedFontDataProggyClean(int* out_size); +static const char* GetDefaultCompressedFontDataProggyForever(int* out_size); #endif -// Load embedded ProggyClean.ttf at size 13, disable oversampling -// If you want a similar font which may be better scaled, consider using ProggyVector from the same author! -ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +// This duplicates some of the logic in UpdateFontsNewFrame() which is a bit chicken-and-eggy/tricky to extract due to variety of codepaths and possible initialization ordering. +static float GetExpectedContextFontSize(ImGuiContext* ctx) +{ + return ((ctx->Style.FontSizeBase > 0.0f) ? ctx->Style.FontSizeBase : 13.0f) * ctx->Style.FontScaleMain * ctx->Style.FontScaleDpi; +} + +// Legacy function with heuristic to select Pixel or Vector font. +// The selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold at the time of adding the default font. +// Prefer calling AddFontDefaultVector() or AddFontDefaultBitmap() based on your own logic. +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg) +{ + if (OwnerContext == NULL || GetExpectedContextFontSize(OwnerContext) >= 15.0f) + return AddFontDefaultVector(font_cfg); + else + return AddFontDefaultBitmap(font_cfg); +} + +// Load embedded ProggyClean.ttf. Default size 13, disable oversampling. +// If you want a similar font which may be better scaled, consider using AddFontDefaultVector(). +ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template) { #ifndef IMGUI_DISABLE_DEFAULT_FONT ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (!font_cfg_template) + font_cfg.PixelSnapH = true; // Prevents sub-integer scaling factors at lower-level layers. + if (font_cfg.SizePixels <= 0.0f) { - font_cfg.OversampleH = font_cfg.OversampleV = 1; - font_cfg.PixelSnapH = true; + font_cfg.SizePixels = 13.0f; // This only serves (1) as a reference for GlyphOffset.y setting and (2) as a default for pre-1.92 backend. + font_cfg.Flags |= ImFontFlags_ImplicitRefSize; } - if (font_cfg.SizePixels <= 0.0f) - font_cfg.SizePixels = 13.0f * 1.0f; if (font_cfg.Name[0] == '\0') - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf"); + ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; - font_cfg.GlyphOffset.y += 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + font_cfg.GlyphOffset.y += 1.0f * (font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + + int ttf_compressed_size = 0; + const char* ttf_compressed = GetDefaultCompressedFontDataProggyClean(&ttf_compressed_size); + return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg); +#else + IM_ASSERT(0 && "Function is disabled in this build."); + IM_UNUSED(font_cfg_template); + return NULL; +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT +} + +// Load a minimal version of ProggyForever, designed to match our good old ProggyClean, but nicely scalable. +// (See build script in https://github.com/ocornut/proggyforever for details) +ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template) +{ +#ifndef IMGUI_DISABLE_DEFAULT_FONT + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + font_cfg.PixelSnapH = true; // Precisely match ProggyClean, but prevents sub-integer scaling factors at lower-level layers. + if (font_cfg.SizePixels <= 0.0f) + { + font_cfg.SizePixels = 13.0f; + font_cfg.Flags |= ImFontFlags_ImplicitRefSize; + } + if (font_cfg.Name[0] == '\0') + ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyForever.ttf"); + font_cfg.ExtraSizeScale *= 1.015f; // Match ProggyClean + font_cfg.GlyphOffset.y += 0.5f * (font_cfg.SizePixels / 16.0f); // Closer match ProggyClean + avoid descenders going too high (with current code). int ttf_compressed_size = 0; - const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size); + const char* ttf_compressed = GetDefaultCompressedFontDataProggyForever(&ttf_compressed_size); return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg); #else - IM_ASSERT(0 && "AddFontDefault() disabled in this build."); + IM_ASSERT(0 && "Function is disabled in this build."); IM_UNUSED(font_cfg_template); return NULL; #endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT @@ -3162,15 +3244,15 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (font_cfg.Name[0] == '\0') { - // Store a short copy of filename into into the font name for convenience + // Store a short copy of filename into the font name for convenience const char* p; for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s", p); + ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "%s", p); } return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); } -// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +// NB: Transfer ownership of 'font_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); @@ -3217,6 +3299,11 @@ void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* shared_data->Font = new_font; if (ImGuiContext* ctx = shared_data->Context) { + // While this should work either way, we save ourselves the bother / debugging confusion of running ImGui code so early when it is not needed. + // Also fixes erroneously rewriting style.FontSizeBase during init if adding default fonts. + if (old_font == NULL && ctx->Font == NULL && ctx->FontSizeBase == 0.0f) + continue; + if (ctx->IO.FontDefault == old_font) ctx->IO.FontDefault = new_font; if (ctx->Font == old_font) @@ -3239,7 +3326,6 @@ void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* void ImFontAtlas::RemoveFont(ImFont* font) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - font->ClearOutputData(); ImFontAtlasFontDestroyOutput(this, font); for (ImFontConfig* src : font->Sources) @@ -3402,6 +3488,7 @@ void ImFontAtlasBuildMain(ImFontAtlas* atlas) void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v) { + // (Only used by stb_truetype builder) // Automatically disable horizontal oversampling over size 36 const float raster_size = baked->Size * baked->RasterizerDensity * src->RasterizerDensity; *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (raster_size > 36.0f || src->PixelSnapH) ? 1 : 2; @@ -3616,6 +3703,12 @@ void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) } } +void ImFontAtlasFontRebuildOutput(ImFontAtlas* atlas, ImFont* font) +{ + ImFontAtlasFontDestroyOutput(atlas, font); + ImFontAtlasFontInitOutput(atlas, font); +} + //----------------------------------------------------------------------------------------------------------------------------- bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src) @@ -3644,7 +3737,7 @@ void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src) IM_UNUSED(atlas); // IF YOU GET A CRASH IN THE IM_FREE() CALL HERE AND USED AddFontFromMemoryTTF(): // - DUE TO LEGACY REASON AddFontFromMemoryTTF() TRANSFERS MEMORY OWNERSHIP BY DEFAULT. - // - IT WILL THEREFORE CRASH WHEN PASSED DATA WHICH MAY NOT BE FREEED BY IMGUI. + // - IT WILL THEREFORE CRASH WHEN PASSED DATA WHICH MAY NOT BE FREED BY IMGUI. // - USE `ImFontConfig font_cfg; font_cfg.FontDataOwnedByAtlas = false; io.Fonts->AddFontFromMemoryTTF(....., &cfg);` to disable passing ownership/ // WE WILL ADDRESS THIS IN A FUTURE REWORK OF THE API. if (src->FontDataOwnedByAtlas) @@ -4015,7 +4108,7 @@ static void ImFontAtlasDebugWriteTexToDisk(ImTextureData* tex, const char* descr { ImGuiContext& g = *GImGui; char buf[128]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "[%05d] Texture #%03d - %s.png", g.FrameCount, tex->UniqueID, description); + ImFormatString(buf, IM_COUNTOF(buf), "[%05d] Texture #%03d - %s.png", g.FrameCount, tex->UniqueID, description); stbi_write_png(buf, tex->Width, tex->Height, tex->BytesPerPixel, tex->Pixels, tex->GetPitch()); // tex->BytesPerPixel is technically not component, but ok for the formats we support. } #endif @@ -4244,6 +4337,10 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) ImFontAtlasUpdateDrawListsSharedData(atlas); //atlas->TexIsBuilt = true; + + // Lazily initialize char/text classifier + // FIXME: This could be practically anywhere, and should eventually be parameters to CalcTextSize/word-wrapping code, but there's no obvious spot now. + ImTextInitClassifiers(); } // Destroy builder and all cached glyphs. Do not destroy actual fonts. @@ -4550,16 +4647,16 @@ void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) if (tex->Status == ImTextureStatus_WantCreate) IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: create %dx%d\n", tex->UniqueID, tex->Width, tex->Height); else if (tex->Status == ImTextureStatus_WantDestroy) - IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, IM_TEXTUREID_TO_U64(tex->TexID), tex->BackendUserData); + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, ImGui::DebugTextureIDToU64(tex->TexID), tex->BackendUserData); else if (tex->Status == ImTextureStatus_WantUpdates) { - IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, IM_TEXTUREID_TO_U64(tex->TexID), (ImU64)(intptr_t)tex->BackendUserData); + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, ImGui::DebugTextureIDToU64(tex->TexID), (ImU64)(intptr_t)tex->BackendUserData); for (const ImTextureRect& r : tex->Updates) { IM_UNUSED(r); IM_ASSERT(r.x >= 0 && r.y >= 0); IM_ASSERT(r.x + r.w <= tex->Width && r.y + r.h <= tex->Height); // In theory should subtract PackPadding but it's currently part of atlas and mid-frame change would wreck assert. - //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, IM_TEXTUREID_TO_U64(tex->TexID), (ImU64)(intptr_t)tex->BackendUserData); + //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, ImGui::DebugTextureIDToU64(tex->TexID), (ImU64)(intptr_t)tex->BackendUserData); } } } @@ -4589,14 +4686,14 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* IM_ASSERT(src->FontLoaderData == NULL); // Initialize helper structure for font loading and verify that the TTF/OTF data is correct - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); + const int font_offset = stbtt_GetFontOffsetForIndex((const unsigned char*)src->FontData, src->FontNo); if (font_offset < 0) { IM_DELETE(bd_font_data); IM_ASSERT_USER_ERROR(0, "stbtt_GetFontOffsetForIndex(): FontData is incorrect, or FontNo cannot be found."); return false; } - if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) + if (!stbtt_InitFont(&bd_font_data->FontInfo, (const unsigned char*)src->FontData, font_offset)) { IM_DELETE(bd_font_data); IM_ASSERT_USER_ERROR(0, "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); @@ -4608,12 +4705,10 @@ 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 + bd_font_data->ScaleFactor *= src->ExtraSizeScale; return true; } @@ -4646,7 +4741,7 @@ static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig { // FIXME-NEWFONTS: reevaluate how to use sizing metrics // FIXME-NEWFONTS: make use of line gap value - float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + const float scale_for_layout = bd_font_data->ScaleFactor * baked->Size / src->ExtraSizeScale; int unscaled_ascent, unscaled_descent, unscaled_line_gap; stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); @@ -4721,12 +4816,8 @@ static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontC const float ref_size = baked->OwnerFont->Sources[0]->SizePixels; const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; - float font_off_x = (src->GlyphOffset.x * offsets_scale); - float font_off_y = (src->GlyphOffset.y * offsets_scale); - if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. - font_off_x = IM_ROUND(font_off_x); - if (src->PixelSnapV) - font_off_y = IM_ROUND(font_off_y); + float font_off_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f); // Snap scaled offset. + float font_off_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f); font_off_x += sub_x; font_off_y += sub_y + IM_ROUND(baked->Ascent); float recip_h = 1.0f / (oversample_h * rasterizer_density); @@ -4898,11 +4989,11 @@ const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() 0xFF00, 0xFFEF, // Half-width characters 0xFFFD, 0xFFFD // Invalid }; - static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 }; + static ImWchar full_ranges[IM_COUNTOF(base_ranges) + IM_COUNTOF(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 }; if (!full_ranges[0]) { memcpy(full_ranges, base_ranges, sizeof(base_ranges)); - UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_COUNTOF(accumulative_offsets_from_0x4E00), full_ranges + IM_COUNTOF(base_ranges)); } return &full_ranges[0]; } @@ -4988,11 +5079,11 @@ const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() 0xFF00, 0xFFEF, // Half-width characters 0xFFFD, 0xFFFD // Invalid }; - static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 }; + static ImWchar full_ranges[IM_COUNTOF(base_ranges) + IM_COUNTOF(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 }; if (!full_ranges[0]) { memcpy(full_ranges, base_ranges, sizeof(base_ranges)); - UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_COUNTOF(accumulative_offsets_from_0x4E00), full_ranges + IM_COUNTOF(base_ranges)); } return &full_ranges[0]; } @@ -5081,12 +5172,12 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) } //----------------------------------------------------------------------------- -// [SECTION] ImFont +// [SECTION] ImFontBaked, ImFont //----------------------------------------------------------------------------- ImFontBaked::ImFontBaked() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); FallbackGlyphIndex = -1; } @@ -5103,7 +5194,7 @@ void ImFontBaked::ClearOutputData() ImFont::ImFont() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS Scale = 1.0f; #endif @@ -5118,7 +5209,7 @@ void ImFont::ClearOutputData() { if (ImFontAtlas* atlas = OwnerAtlas) ImFontAtlasFontDiscardBakes(atlas, this, 0); - FallbackChar = EllipsisChar = 0; + //FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); LastBaked = NULL; } @@ -5379,22 +5470,69 @@ 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; } +void ImTextClassifierClear(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class) +{ + for (unsigned int c = codepoint_min; c < codepoint_end; c++) + ImTextClassifierSetCharClass(bits, codepoint_min, codepoint_end, char_class, c); +} + +void ImTextClassifierSetCharClass(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class, unsigned int c) +{ + IM_ASSERT(c >= codepoint_min && c < codepoint_end); + IM_UNUSED(codepoint_end); + c -= codepoint_min; + const ImU32 shift = (c & 15) << 1; + bits[c >> 4] = (bits[c >> 4] & ~(0x03 << shift)) | (char_class << shift); +} + +void ImTextClassifierSetCharClassFromStr(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class, const char* s) +{ + const char* s_end = s + strlen(s); + while (*s) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, s_end); + ImTextClassifierSetCharClass(bits, codepoint_min, codepoint_end, char_class, c); + } +} + +#define ImTextClassifierGet(_BITS, _CHAR_OFFSET) ((_BITS[(_CHAR_OFFSET) >> 4] >> (((_CHAR_OFFSET) & 15) << 1)) & 0x03) + +// 2-bit per character +static ImU32 g_CharClassifierIsSeparator_0000_007f[128 / 16] = {}; +static ImU32 g_CharClassifierIsSeparator_3000_300f[ 16 / 16] = {}; + +void ImTextInitClassifiers() +{ + if (ImTextClassifierGet(g_CharClassifierIsSeparator_0000_007f, ',') != 0) + return; + + // List of hardcoded separators: .,;!?'" + // Making this dynamic given known ranges is trivial BUT requires us to standardize where you pass them as parameters. (#3002, #8503) + ImTextClassifierClear(g_CharClassifierIsSeparator_0000_007f, 0, 128, ImWcharClass_Other); + ImTextClassifierSetCharClassFromStr(g_CharClassifierIsSeparator_0000_007f, 0, 128, ImWcharClass_Blank, " \t"); + ImTextClassifierSetCharClassFromStr(g_CharClassifierIsSeparator_0000_007f, 0, 128, ImWcharClass_Punct, ".,;!?\""); + + ImTextClassifierClear(g_CharClassifierIsSeparator_3000_300f, 0x3000, 0x300F, ImWcharClass_Other); + ImTextClassifierSetCharClass(g_CharClassifierIsSeparator_3000_300f, 0x3000, 0x300F, ImWcharClass_Blank, 0x3000); + ImTextClassifierSetCharClass(g_CharClassifierIsSeparator_3000_300f, 0x3000, 0x300F, ImWcharClass_Punct, 0x3001); + ImTextClassifierSetCharClass(g_CharClassifierIsSeparator_3000_300f, 0x3000, 0x300F, ImWcharClass_Punct, 0x3002); +} + // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. -// FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) +// Refer to imgui_test_suite's "drawlist_text_wordwrap_1" for tests. const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* text, const char* text_end, float wrap_width, ImDrawTextFlags flags) { // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" // ^ ^ ^ ^ ^__ ^ ^ - // List of hardcoded separators: .,;!?'" - // Skip extra blanks after a line returns (that includes not counting them in width computation) // e.g. "Hello world" --> "Hello" "World" @@ -5405,16 +5543,20 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t const float scale = size / baked->Size; float line_width = 0.0f; - float word_width = 0.0f; float blank_width = 0.0f; wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters - const char* word_end = text; - const char* prev_word_end = NULL; - bool inside_word = true; - const char* s = text; IM_ASSERT(text_end != NULL); + + int prev_type = ImWcharClass_Other; + const bool keep_blanks = (flags & ImDrawTextFlags_WrapKeepBlanks) != 0; + + // Find next wrapping point + //const char* span_begin = s; + const char* span_end = s; + float span_width = 0.0f; + while (s < text_end) { unsigned int c = (unsigned int)*s; @@ -5430,7 +5572,7 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t return s; // Direct return, skip "Wrap_width is too small to fit anything" path. if (c == '\r') { - s = next_s; + s = next_s; // Fast-skip continue; } } @@ -5440,46 +5582,56 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t if (char_width < 0.0f) char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); - if (ImCharIsBlankW(c)) + // Classify current character + int curr_type; + if (c < 128) + curr_type = ImTextClassifierGet(g_CharClassifierIsSeparator_0000_007f, c); + else if (c >= 0x3000 && c < 0x3010) + curr_type = ImTextClassifierGet(g_CharClassifierIsSeparator_3000_300f, c & 15); //-V578 + else + curr_type = ImWcharClass_Other; + + if (curr_type == ImWcharClass_Blank) { - if (inside_word) + // End span: 'A ' or '. ' + if (prev_type != ImWcharClass_Blank && !keep_blanks) { - line_width += blank_width; - blank_width = 0.0f; - word_end = s; + span_end = s; + line_width += span_width; + span_width = 0.0f; } blank_width += char_width; - inside_word = false; } else { - word_width += char_width; - if (inside_word) + // End span: '.X' unless X is a digit + if (prev_type == ImWcharClass_Punct && curr_type != ImWcharClass_Punct && !(c >= '0' && c <= '9')) // FIXME: Digit checks might be removed if allow custom separators (#8503) { - word_end = next_s; + span_end = s; + line_width += span_width + blank_width; + span_width = blank_width = 0.0f; } - else + // End span: 'A ' or '. ' + else if (prev_type == ImWcharClass_Blank && keep_blanks) { - prev_word_end = word_end; - line_width += word_width + blank_width; - if ((flags & ImDrawTextFlags_WrapKeepBlanks) && line_width <= wrap_width) - prev_word_end = s; - word_width = blank_width = 0.0f; + span_end = s; + line_width += span_width + blank_width; + span_width = blank_width = 0.0f; } - - // Allow wrapping after punctuation. - inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"' && c != 0x3001 && c != 0x3002); + span_width += char_width; } - // We ignore blank width at the end of the line (they can be skipped) - if (line_width + word_width > wrap_width) + if (span_width + blank_width + line_width > wrap_width) { - // Words that cannot possibly fit within an entire line will be cut anywhere. - if (word_width < wrap_width) - s = prev_word_end ? prev_word_end : word_end; - break; + if (span_width + blank_width > wrap_width) + break; + // FIXME: Narrow wrapping e.g. "A quick brown" -> "Quic|k br|own", would require knowing if span is going to be longer than wrap_width. + //if (span_width > wrap_width && !is_blank && !was_blank) + // return s; + return span_end; } + prev_type = curr_type; s = next_s; } @@ -5631,7 +5783,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -// DO NOT CALL DIRECTLY THIS WILL CHANGE WILDLY IN 2025-2025. Use ImDrawList::AddText(). +// DO NOT CALL DIRECTLY THIS WILL CHANGE WILDLY IN 2026. Use ImDrawList::AddText(). void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, ImDrawTextFlags flags) { // Align to be pixel perfect @@ -5846,7 +5998,7 @@ begin: // - RenderCheckMark() // - RenderArrowDockMenu() // - RenderArrowPointingAt() -// - RenderRectFilledRangeH() +// - RenderRectFilledInRangeH() // - RenderRectFilledWithHole() //----------------------------------------------------------------------------- // Function in need of a redesign (legacy mess) @@ -5904,7 +6056,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx - third, by - third)); draw_list->PathLineTo(ImVec2(bx, by)); draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); - draw_list->PathStroke(col, 0, thickness); + draw_list->PathStroke(col, thickness); } // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. @@ -5937,15 +6089,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 fill_x0, float fill_x1, float rounding) { - if (x_end_norm == x_start_norm) + if (fill_x0 > fill_x1) 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(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); @@ -6187,11 +6339,8 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i //----------------------------------------------------------------------------- // [SECTION] Default font data (ProggyClean.ttf) //----------------------------------------------------------------------------- -// ProggyClean.ttf -// Copyright (c) 2004, 2005 Tristan Grimmer -// MIT license (see License.txt in http://www.proggyfonts.net/index.php?menu=download) -// Download and more information at http://www.proggyfonts.net or http://upperboundsinteractive.com/fonts.php -// If you want a similar font which may be better scaled, consider using ProggyVector from the same author! +// MIT License / Copyright (c) 2004, 2005 Tristan Grimmer +// Download and more information at https://github.com/bluescan/proggyfonts //----------------------------------------------------------------------------- #ifndef IMGUI_DISABLE_DEFAULT_FONT @@ -6369,11 +6518,274 @@ static const unsigned char proggy_clean_ttf_compressed_data[9583] = 239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247, }; -static const char* GetDefaultCompressedFontDataTTF(int* out_size) +static const char* GetDefaultCompressedFontDataProggyClean(int* out_size) { *out_size = proggy_clean_ttf_compressed_size; return (const char*)proggy_clean_ttf_compressed_data; } + +//----------------------------------------------------------------------------- +// [SECTION] Default font data (ProggyForever-Regular-minimal.ttf) +//----------------------------------------------------------------------------- +// Based on ProggyForever: https://github.com/ocornut/proggyforever +// MIT license / Copyright (c) 2026 Disco Hello, Copyright (c) 2019,2023 Tristan Grimmer +//----------------------------------------------------------------------------- + +// File: 'output/ProggyForever-Regular-minimal.ttf' (18556 bytes) +// Exported using binary_to_compressed_c.exe -u8 "output/ProggyForever-Regular-minimal.ttf" proggy_forever_minimal_ttf +static const unsigned int proggy_forever_minimal_ttf_compressed_size = 14562; +static const unsigned char proggy_forever_minimal_ttf_compressed_data[14562] = +{ + 87,188,0,0,0,0,0,0,0,0,72,124,0,4,0,0,55,0,1,0,0,0,14,0,128,0,3,0,96,70,70,84,77,176,111,174,190,0,0,72,96,130,21,40,28,71,68,69,70,0,136,0,105,130,15,32,64,130,15,44,30,79,83,47,50, + 104,97,19,194,0,0,1,104,130,15,44,96,99,109,97,112,177,173,221,139,0,0,3,80,130,19,44,114,99,118,116,32,0,33,2,121,0,0,4,196,130,31,38,4,103,97,115,112,255,255,130,89,34,0,72,56,130, + 15,56,8,103,108,121,102,239,245,108,207,0,0,6,76,0,0,62,224,104,101,97,100,44,57,58,3,130,27,32,236,130,3,33,54,104,130,16,35,4,62,0,230,130,75,32,36,130,15,39,36,104,109,116,120,24, + 22,19,130,95,33,1,200,130,19,40,136,108,111,99,97,80,9,64,114,130,95,131,15,39,130,109,97,120,112,1,7,0,131,31,32,72,130,47,44,32,110,97,109,101,10,160,159,151,0,0,69,44,130,47,44, + 68,112,111,115,116,70,77,175,253,0,0,70,112,130,15,32,197,132,235,32,1,130,9,42,224,136,151,95,15,60,245,0,11,3,232,130,55,36,0,229,175,187,66,132,7,42,178,59,232,255,225,255,68,1, + 215,2,176,130,15,34,8,0,2,130,5,131,2,130,51,39,2,131,255,71,0,0,1,184,130,31,34,226,1,215,132,73,131,25,135,3,32,4,132,17,37,192,0,90,0,5,0,131,0,33,2,0,130,44,132,19,34,64,0,46,130, + 11,38,0,0,4,1,184,1,144,131,29,35,2,138,2,187,130,17,32,140,133,7,38,1,223,0,49,1,2,0,138,0,37,128,0,0,7,16,0,136,123,33,0,88,130,0,37,0,64,0,32,32,172,133,131,34,2,175,0,131,63,33, + 3,0,130,0,35,1,133,2,6,130,6,38,32,0,1,1,184,0,33,130,9,130,161,32,0,132,3,39,0,166,0,116,255,249,0,49,130,113,36,4,0,185,0,135,130,1,38,27,0,22,0,154,0,53,130,25,40,39,0,41,0,79,0, + 50,0,45,130,17,32,49,130,11,33,46,0,131,17,36,166,0,143,0,24,132,1,34,48,255,225,130,55,34,38,0,47,130,23,44,52,0,58,0,35,0,42,0,66,0,65,0,19,130,79,32,19,130,47,38,35,0,44,0,13,0, + 38,130,21,38,9,0,37,0,13,255,250,130,121,36,5,0,33,0,104,130,47,40,104,0,37,255,242,0,124,0,48,130,95,32,59,130,3,34,37,0,40,130,5,36,62,0,139,0,100,130,57,36,133,0,20,0,62,130,55, + 32,50,130,19,40,69,0,69,0,87,0,54,0,29,130,167,32,20,130,107,36,64,0,63,0,188,130,3,36,22,0,21,0,159,130,7,36,40,0,55,0,5,130,17,34,64,0,109,130,33,34,92,0,50,130,5,42,109,0,101,0, + 24,0,115,0,109,0,131,130,121,32,48,130,39,36,143,0,114,0,83,130,77,32,19,130,157,34,18,0,73,130,49,32,5,136,3,32,2,130,85,33,52,0,133,1,33,66,0,133,1,32,17,130,125,32,35,130,209,131, + 3,36,46,0,1,0,46,132,1,32,47,130,51,32,42,130,25,32,38,130,213,135,3,34,5,0,59,130,99,32,37,132,3,34,51,0,68,132,1,32,42,130,189,33,43,0,135,1,36,24,0,12,0,61,134,1,32,26,130,131,38, + 26,0,30,0,0,0,3,134,3,32,28,130,93,130,10,35,0,108,0,3,132,9,36,28,0,4,0,80,130,16,34,16,0,16,132,35,46,126,0,133,0,171,0,210,0,211,0,255,32,172,255,255,130,25,32,32,130,17,34,161, + 0,174,130,17,32,212,131,17,45,255,227,255,221,255,194,255,192,255,95,255,191,224,19,132,67,130,36,139,2,34,1,6,0,136,22,35,1,2,0,0,66,53,9,32,0,133,0,32,1,130,142,8,148,4,5,6,7,8,9, + 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69, + 70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,0,132,133,135,137,145,149,155,160,159,161,163,162,164,166,168,167,169,170,172,171,173,174,176,178, + 177,179,181,180,185,184,186,187,0,112,100,101,105,0,118,158,110,107,0,116,106,0,134,151,0,113,0,0,103,117,132,158,38,108,122,0,165,183,127,99,132,11,38,109,123,0,0,128,131,148,132, + 11,130,4,37,182,0,190,60,0,191,130,8,34,0,0,119,130,5,47,130,138,129,139,136,141,142,143,140,50,147,0,146,153,154,152,130,18,32,111,130,3,32,120,130,3,130,2,34,33,2,121,130,5,33,42, + 0,133,1,9,155,68,0,86,0,134,0,210,1,14,1,104,1,116,1,148,1,180,1,212,1,232,2,6,2,20,2,38,2,54,2,100,2,122,2,172,2,232,3,2,3,60,3,116,3,134,3,210,4,10,4,40,4,82,4,102,4,122,4,142,4, + 204,5,36,5,60,5,118,5,158,5,184,5,208,5,228,6,24,6,44,6,68,6,94,6,118,6,134,6,160,6,182,6,224,7,14,7,64,7,106,7,182,7,200,7,238,8,0,8,28,8,52,8,72,8,96,8,114,8,130,8,148,8,166,8,180, + 8,194,9,12,9,58,9,92,9,140,9,182,9,214,10,14,10,44,10,74,10,110,10,134,10,150,10,198,10,230,11,4,11,50,11,96,11,130,11,188,11,216,11,250,12,14,12,42,12,66,12,114,12,142,12,198,12,210, + 13,10,13,62,13,126,13,152,13,198,13,238,14,34,14,72,14,90,14,198,14,232,15,32,15,106,15,134,15,200,15,214,15,244,16,16,16,56,16,114,16,128,16,182,16,212,16,230,17,2,17,22,17,60,17, + 86,17,132,17,194,18,20,18,76,18,108,18,140,18,176,18,228,19,24,19,74,19,110,19,168,19,198,19,228,20,6,20,56,20,86,20,116,20,150,20,200,20,252,21,44,21,92,21,144,21,212,22,24,22,50, + 22,112,22,148,22,186,22,226,23,28,23,56,23,90,23,160,23,246,24,76,24,164,25,18,25,124,25,226,26,94,26,150,26,198,26,248,27,44,27,114,27,144,27,174,27,208,28,2,28,74,28,136,28,174,28, + 212,28,254,29,60,29,118,29,172,29,230,30,16,30,58,30,104,30,166,30,206,30,244,31,48,31,112,0,0,0,2,0,33,0,0,1,42,2,154,0,3,0,7,0,46,177,1,0,47,60,178,7,4,0,237,50,177,6,5,220,60,178, + 3,2,130,10,34,0,177,3,131,22,32,5,131,22,39,178,7,6,1,252,60,178,1,131,23,52,51,17,33,17,39,51,17,35,33,1,9,232,199,199,2,154,253,102,33,2,88,131,83,38,166,255,254,1,18,2,35,130,81, + 61,13,0,0,54,50,22,20,6,34,38,52,55,35,3,55,51,21,198,44,32,32,44,32,85,59,14,1,83,105,31,131,11,36,122,1,4,91,91,130,135,38,116,1,70,1,68,2,7,132,135,37,0,1,35,53,51,7,130,3,8,40, + 1,67,64,64,142,65,65,1,70,192,192,192,0,2,255,248,0,3,1,191,2,3,0,3,0,31,0,0,63,1,35,7,37,35,7,51,21,35,7,35,55,132,3,34,53,51,55,130,51,35,55,51,7,51,131,3,53,254,30,92,30,1,29,104, + 31,92,106,38,59,38,92,38,58,38,96,110,31,98,112,134,11,34,90,201,115,130,0,33,54,143,130,0,35,54,115,54,144,130,0,32,0,130,85,8,37,49,255,241,1,135,2,22,0,6,0,11,0,52,0,0,55,62,1,53, + 52,38,47,1,53,6,21,20,23,30,3,21,20,7,21,35,53,46,1,130,16,8,109,30,2,23,53,46,3,53,52,54,55,53,51,21,30,1,31,1,21,38,39,21,50,249,32,41,36,37,60,72,137,22,40,46,28,141,60,36,67,15, + 15,6,21,69,37,31,44,43,22,75,65,60,25,55,15,15,43,67,3,81,7,37,31,30,32,10,73,135,12,61,44,30,4,15,28,50,33,114,15,44,43,1,14,6,6,63,4,11,22,3,162,6,15,26,42,29,53,67,9,45,42,2,11, + 5,5,62,29,4,150,0,5,130,1,36,11,1,178,1,250,130,161,52,11,0,19,0,27,0,35,0,0,1,51,1,35,36,50,54,52,38,34,6,20,65,97,7,34,2,20,22,132,17,65,111,5,53,54,50,1,74,71,254,222,71,1,16,41, + 30,30,41,29,7,84,60,60,84,59,174,130,9,34,29,41,122,130,9,51,59,84,1,249,254,21,47,27,39,27,27,39,115,56,79,56,56,79,1,26,131,11,33,27,7,131,11,8,59,56,0,0,0,3,0,4,255,246,1,179,2, + 21,0,9,0,28,0,62,0,0,55,50,55,46,1,39,6,21,20,22,3,28,2,30,5,23,62,1,53,52,38,35,34,6,1,23,35,38,39,6,35,34,38,53,52,54,55,46,2,130,5,8,101,51,50,22,21,20,14,2,7,22,23,54,53,55,20, + 206,52,37,25,110,24,65,80,41,1,2,3,6,8,12,7,37,37,31,23,24,35,1,2,67,72,13,21,60,89,72,104,52,44,22,19,15,63,60,61,51,15,32,25,24,89,58,36,61,44,36,28,131,28,54,59,48,62,1,125,1,7, + 3,7,5,10,9,13,16,9,25,40,24,21,25,32,254,143,79,15,24,49,96,66,50,74,33,25,130,17,56,41,69,55,49,20,36,34,20,17,107,69,65,72,1,108,0,1,0,185,1,70,0,255,2,8,130,189,42,0,19,35,53,51, + 254,68,68,1,71,193,130,23,8,59,135,255,193,1,49,2,62,0,17,0,0,1,14,1,20,22,23,35,46,4,52,62,2,63,1,1,49,43,55,55,43,60,4,15,38,29,24,23,32,33,11,11,2,61,60,177,161,177,60,6,21,68,70, + 103,97,102,75,61,16,16,130,50,32,0,138,63,34,19,30,4,130,220,40,15,1,35,62,1,52,38,39,195,137,57,32,60,131,73,44,2,61,6,22,68,71,103,97,101,75,60,16,16,132,73,32,0,131,63,38,27,0,69, + 1,157,1,199,132,127,8,41,7,23,7,39,21,35,53,7,39,55,39,55,23,53,51,21,55,1,157,131,131,33,128,64,129,32,131,131,32,129,64,128,1,77,71,71,50,70,142,142,70,50,134,7,49,0,1,0,22,0,74, + 1,162,1,185,0,11,0,0,19,51,21,35,130,62,130,221,37,53,51,251,166,166,62,130,2,38,1,31,59,154,154,59,154,130,39,39,154,255,129,1,30,0,107,0,130,180,32,54,65,133,5,8,37,15,1,39,54,55, + 46,1,53,52,209,45,31,21,31,30,11,11,26,55,10,17,25,107,32,22,32,61,40,32,7,7,37,48,41,1,31,21,22,131,163,54,53,0,227,1,131,1,32,0,3,0,0,37,33,53,33,1,131,254,178,1,78,228,60,132,191, + 40,166,255,255,1,18,0,105,0,7,67,209,9,67,203,5,37,105,31,44,31,31,44,132,35,38,39,255,222,1,144,2,39,131,63,49,9,1,35,1,1,144,254,221,69,1,34,2,39,253,184,2,72,0,130,21,8,73,41,255, + 247,1,143,2,14,0,10,0,21,0,27,0,0,55,50,62,3,53,52,39,7,22,19,34,14,3,21,20,23,55,38,39,50,16,35,34,16,220,17,28,30,21,14,4,190,27,57,17,28,31,20,14,4,189,26,57,178,178,178,51,7,26, + 44,78,53,40,34,230,52,1,161,8,131,10,39,39,33,229,52,58,253,234,2,130,186,41,0,1,0,79,0,0,1,104,2,7,130,91,40,0,19,39,55,51,17,51,21,33,130,5,50,81,1,106,68,105,254,233,1,105,1,103, + 81,78,254,55,61,61,1,122,130,43,32,50,130,43,37,134,2,17,0,31,0,130,43,49,62,2,51,50,30,2,21,20,6,15,1,33,21,33,53,52,55,54,67,247,5,8,58,35,34,6,7,55,1,8,29,81,37,48,72,36,17,44,59, + 147,1,1,254,173,13,1,165,37,40,58,45,26,75,24,1,159,65,5,16,27,30,48,46,21,40,78,58,146,61,46,14,12,1,165,38,80,33,33,47,27,13,0,130,94,41,0,45,255,246,1,139,2,16,0,41,130,12,32,22, + 130,93,38,35,34,38,47,1,53,30,130,108,40,54,53,52,38,43,1,53,51,50,131,8,130,101,37,15,1,53,54,51,50,131,35,8,62,1,26,112,107,85,34,78,22,23,7,25,80,41,57,65,65,49,64,64,46,56,55,45, + 33,72,20,20,74,68,78,98,51,1,22,30,109,65,84,13,7,6,69,4,12,19,51,49,46,52,58,45,38,37,43,14,7,7,64,23,74,59,50,55,130,119,34,2,0,22,130,111,45,162,2,7,0,2,0,13,0,0,37,17,3,59,1,66, + 44,6,54,19,51,1,16,179,237,88,88,67,241,208,100,181,1,18,254,238,58,123,123,73,1,66,130,46,33,0,49,130,171,32,135,130,51,37,40,0,0,19,17,33,130,47,130,143,37,30,3,21,20,14,2,137,181, + 36,62,2,53,52,46,130,15,8,68,6,7,72,1,33,221,24,41,25,48,51,38,24,35,61,67,38,43,70,14,14,7,24,76,38,21,39,39,23,23,40,44,25,28,56,14,1,2,1,4,59,127,11,10,25,38,63,40,49,72,38,18,11, + 6,5,71,4,11,19,11,25,48,33,33,48,25,12,13,7,130,157,55,41,255,248,1,143,2,8,0,9,0,37,0,0,55,50,53,52,35,34,6,21,20,22,19,65,139,5,130,119,42,53,52,62,2,51,50,22,31,1,21,38,131,26,8, + 58,54,224,101,101,41,59,59,51,31,56,49,29,91,82,87,98,36,63,79,48,24,46,11,10,31,59,69,89,36,48,119,121,57,64,63,56,1,39,20,40,68,43,90,89,114,136,73,110,63,31,8,4,3,66,25,95,98,72, + 0,130,0,38,1,0,46,0,0,1,138,130,227,32,6,130,227,8,42,53,33,21,3,35,19,46,1,91,197,78,190,1,203,59,29,254,23,1,202,0,0,3,0,39,255,246,1,145,2,17,0,15,0,31,0,52,0,0,54,50,62,2,130,243, + 38,34,14,2,20,30,1,18,132,6,32,2,132,18,35,1,23,30,1,65,210,5,35,53,52,55,38,68,239,7,8,50,199,42,32,32,18,18,33,32,40,32,33,18,18,32,72,37,29,29,16,17,29,28,36,28,30,16,16,29,33,48, + 52,104,76,77,104,99,84,95,71,70,96,45,7,20,42,63,42,19,7,7,19,130,6,43,20,1,166,6,17,37,53,37,16,6,6,16,130,6,59,17,191,16,72,52,69,78,77,70,107,33,30,91,62,69,70,61,90,0,2,0,41,0, + 0,1,143,2,16,65,43,5,43,19,34,21,20,51,50,54,53,52,38,3,34,69,108,11,65,170,5,32,22,131,26,8,44,6,215,100,100,42,59,60,51,30,57,48,29,91,82,86,99,36,64,79,47,24,46,11,11,32,59,68,89, + 35,1,216,120,120,56,64,64,56,254,217,20,39,68,44,89,90,65,44,6,35,3,4,65,24,65,44,5,55,2,0,166,0,81,1,18,1,171,0,7,0,15,0,0,18,34,38,52,54,50,22,20,6,71,199,6,38,242,44,32,32,44,32, + 76,132,5,39,1,65,31,44,31,31,44,165,132,5,32,0,130,0,36,2,0,143,255,209,134,59,32,24,140,59,35,21,20,14,2,68,137,8,37,243,45,31,31,45,31,130,67,41,22,30,31,11,10,27,55,10,17,25,135, + 74,37,32,22,33,60,41,31,68,150,10,8,37,0,1,0,24,0,84,1,160,1,176,0,6,0,0,19,37,21,13,1,21,37,24,1,135,254,200,1,56,254,121,1,30,145,62,111,112,62,145,132,123,38,24,0,152,1,159,1,114, + 72,67,5,34,37,33,53,132,1,33,1,159,130,37,32,135,131,3,35,153,59,99,59,130,39,141,79,37,5,21,5,53,45,1,131,79,32,121,130,79,36,200,1,176,145,57,131,81,132,79,42,48,0,0,1,135,2,31,0, + 34,0,42,130,121,33,50,22,130,195,44,3,29,1,35,53,52,62,4,53,52,38,35,34,132,209,34,62,4,16,65,25,6,58,226,83,82,29,42,42,29,70,21,33,38,33,22,52,43,25,45,29,22,4,5,52,2,10,34,38,61, + 132,239,8,33,2,31,68,52,28,47,33,30,33,17,41,41,22,39,28,29,23,31,16,30,33,16,23,23,8,8,34,4,14,35,26,22,254,75,65,71,9,47,255,225,255,233,1,215,2,32,0,7,0,64,0,0,54,50,71,233,5,39, + 5,23,14,4,35,34,46,3,71,98,10,46,21,35,53,6,35,34,38,52,54,51,50,22,31,1,50,132,151,8,122,6,21,20,30,2,51,50,62,3,214,60,43,43,60,43,1,5,38,2,11,38,43,71,37,63,103,67,46,20,141,105, + 105,128,15,24,21,10,55,36,43,55,79,80,55,28,49,10,11,45,84,70,81,118,39,61,75,39,35,64,40,30,14,168,56,79,57,57,79,120,46,4,11,27,21,17,33,55,74,79,42,120,163,78,67,24,35,17,7,1,198, + 19,31,93,131,94,26,13,13,28,34,57,124,104,53,87,55,30,13,20,20,13,0,0,0,2,0,5,0,0,1,178,2,7,130,9,8,38,10,0,0,55,51,3,55,19,35,39,35,7,35,19,141,158,79,45,169,77,41,193,40,77,169,192, + 1,9,61,253,250,136,136,2,6,0,3,0,38,130,47,32,146,130,47,36,8,0,17,0,39,130,49,32,50,69,22,5,36,21,17,21,22,55,131,10,42,35,23,30,4,21,20,14,2,43,1,17,69,159,6,8,61,220,65,52,62,45, + 121,71,39,84,49,42,77,12,23,34,25,18,30,50,57,31,194,177,43,62,32,15,49,58,47,52,47,43,189,1,147,156,1,2,4,76,42,33,183,3,7,20,27,48,31,38,57,32,14,2,5,25,43,46,24,42,55,130,110,47, + 0,47,255,248,1,137,2,18,0,26,0,0,37,21,6,35,67,62,7,58,23,21,38,35,34,14,2,21,20,30,1,51,50,1,136,49,79,56,86,50,24,112,104,77,51,51,77,130,65,55,15,26,66,50,77,93,73,28,42,74,95,58, + 117,151,32,71,45,33,59,74,46,62,91,59,130,233,130,183,33,1,144,130,195,34,6,0,13,131,193,33,53,52,130,172,58,19,50,17,16,35,7,17,199,125,125,84,84,201,200,160,58,202,201,254,109,1, + 204,254,253,254,254,1,130,247,34,1,0,52,130,136,32,131,130,51,8,34,11,0,0,19,33,21,33,23,51,21,35,21,33,21,33,53,1,77,254,253,1,227,228,1,4,254,178,2,6,59,153,59,188,59,0,130,0,38, + 1,0,58,0,0,1,126,130,47,32,9,132,47,34,35,31,1,130,47,40,35,58,1,67,249,1,181,182,74,130,41,35,158,1,59,241,130,34,33,0,35,130,219,32,148,130,219,32,36,130,219,38,39,35,53,51,21,14, + 4,149,224,47,62,1,1,78,1,99,170,2,7,27,32,54,30,56,85,51,130,233,33,78,50,132,233,47,14,25,66,50,27,42,11,67,141,58,223,2,7,18,13,11,143,241,37,11,7,0,1,0,42,130,108,32,141,133,191, + 57,51,17,51,21,51,53,51,17,35,53,35,21,43,75,204,75,75,204,2,6,212,212,253,250,247,246,130,39,32,66,130,39,32,117,133,39,36,19,53,33,21,35,130,43,55,5,53,55,17,67,1,50,116,116,254, + 206,115,1,203,59,59,254,112,58,1,57,1,1,145,132,231,36,65,255,249,1,118,130,47,32,13,130,231,8,33,51,17,20,6,35,7,53,51,50,54,53,17,35,158,216,66,78,164,147,48,37,140,2,6,254,158,80, + 90,1,57,48,58,1,47,130,50,39,0,1,0,19,0,0,1,165,137,139,59,55,51,7,19,35,3,7,21,19,75,233,85,212,221,90,180,57,2,6,230,230,212,254,206,1,4,57,202,130,48,34,1,0,49,130,47,32,135,130, + 47,50,5,0,0,55,17,51,17,33,21,49,75,1,10,1,2,5,254,53,58,132,31,131,79,32,164,130,31,56,12,0,0,51,17,51,23,55,51,17,7,17,3,35,3,17,20,100,99,101,100,65,107,57,106,130,130,42,254,253, + 251,1,1,197,254,239,1,19,254,130,52,34,1,0,41,130,83,32,142,65,159,5,35,51,3,51,19,130,86,130,48,38,45,3,98,186,72,95,186,130,46,55,90,1,166,253,250,1,166,254,90,0,0,2,0,35,255,250, + 1,149,2,18,0,5,0,25,130,229,63,50,16,35,34,16,18,50,62,3,52,46,3,34,14,3,20,30,2,220,184,184,184,167,34,27,30,20,13,13,20,30,27,131,8,33,14,14,130,22,50,17,253,234,2,22,254,37,8,26, + 43,78,106,78,44,26,8,8,26,44,130,8,33,43,26,130,179,38,2,0,44,255,255,1,139,132,179,32,30,131,83,71,166,5,45,43,1,21,55,50,22,21,20,6,43,1,28,1,30,130,3,8,47,49,35,17,50,206,32,46, + 22,10,10,21,42,30,93,99,85,91,85,84,108,1,1,75,174,1,10,19,31,31,17,17,31,31,18,195,252,80,70,71,89,5,21,55,48,50,29,2,6,130,90,45,0,2,0,13,255,184,1,171,2,15,0,12,0,32,131,91,39,17, + 20,7,23,21,35,39,6,143,182,39,198,184,40,84,48,82,40,58,147,187,40,14,254,245,123,67,97,44,91,27,149,193,38,2,0,38,0,0,1,145,130,191,34,9,0,24,130,99,34,35,21,22,73,125,5,35,15,1,35, + 17,73,20,5,8,45,7,23,35,39,38,213,99,68,39,34,51,53,139,3,72,179,79,83,57,44,122,79,111,97,1,205,184,1,2,2,46,45,43,47,242,218,2,5,89,69,45,69,16,229,217,1,130,184,42,1,0,42,255,246, + 1,141,2,17,0,54,130,91,44,21,46,2,35,34,6,21,20,30,7,23,30,3,73,135,16,32,39,69,75,6,8,95,30,2,23,1,109,6,21,67,35,62,61,6,14,11,25,12,31,9,33,1,23,42,48,29,101,89,43,78,18,18,7,27, + 86,45,45,66,61,69,34,48,47,24,103,87,18,40,32,27,8,1,245,71,4,13,21,41,48,11,18,15,11,10,5,7,3,5,1,4,17,33,58,39,78,74,16,9,8,73,5,16,27,46,45,46,37,14,6,17,31,50,34,72,82,5,8,9,84, + 93,5,32,9,130,143,32,175,130,235,32,7,130,233,56,53,33,21,35,17,7,17,10,1,164,172,75,1,203,59,59,254,54,1,1,203,0,1,0,37,130,187,32,147,130,35,32,24,66,163,5,32,20,74,49,5,8,50,17, + 51,17,20,14,3,34,46,3,37,76,6,19,43,32,67,48,75,6,22,38,68,95,70,38,22,7,166,1,96,254,153,26,35,34,18,54,67,1,95,254,173,33,48,53,33,22,21,32,50,44,130,75,32,13,130,111,32,171,130, + 75,8,32,6,0,0,51,3,51,27,1,51,3,174,161,73,134,134,72,161,2,6,254,58,1,198,253,250,0,1,255,249,0,0,1,190,130,35,32,12,135,35,131,38,47,35,11,1,75,81,63,58,63,83,64,57,64,81,73,72,73, + 130,47,44,99,1,157,254,98,1,158,253,250,1,184,254,72,130,55,32,255,130,55,32,185,67,123,5,8,33,19,39,51,23,55,51,7,19,35,39,7,35,184,160,80,121,123,81,165,177,80,141,141,80,1,19,243, + 195,195,243,254,237,218,218,130,139,32,5,130,47,32,178,130,47,36,8,0,0,55,3,131,47,50,3,21,35,182,176,79,135,134,80,177,75,234,1,28,228,228,254,227,233,130,34,33,0,33,130,4,32,151, + 130,39,32,9,65,35,5,60,1,33,21,5,53,1,41,1,102,254,224,1,40,254,138,1,24,1,203,59,53,254,106,58,1,56,1,147,0,130,42,41,0,104,255,189,1,80,2,73,0,7,130,12,55,39,17,51,21,35,17,51,1, + 79,162,162,231,231,2,18,1,253,226,56,2,140,0,1,0,76,151,10,41,19,1,35,1,109,1,35,70,254,222,76,150,7,130,31,138,67,52,19,51,17,35,53,51,17,7,104,231,231,163,163,2,73,253,116,56,2,30, + 1,130,90,37,0,37,1,63,1,147,65,75,5,33,19,23,131,233,45,55,252,150,82,100,101,82,150,2,6,198,141,141,198,131,139,55,255,241,255,190,1,199,255,247,0,3,0,0,5,33,53,33,1,198,254,44,1, + 212,66,56,131,27,39,0,124,1,160,1,60,2,57,131,27,131,63,39,207,108,82,109,2,57,153,153,130,27,8,175,2,0,48,255,246,1,136,1,143,0,17,0,54,0,0,55,50,62,3,53,34,35,38,14,4,21,20,22,39, + 52,62,3,23,48,60,1,46,6,35,7,53,50,54,51,21,50,30,2,21,7,35,54,39,6,35,34,38,204,32,48,23,13,2,10,21,24,45,39,32,22,13,51,118,28,50,69,81,45,2,3,7,9,15,18,26,16,122,18,58,34,38,58, + 52,29,1,61,2,2,38,99,64,79,44,26,33,49,27,17,1,2,3,10,18,28,20,37,35,72,36,51,28,16,1,1,14,7,17,10,16,10,12,7,5,2,54,2,1,12,33,65,47,241,25,27,61,63,0,0,2,0,50,255,247,1,134,2,49,0, + 9,0,30,0,0,54,50,54,53,52,38,34,6,21,20,39,62,4,67,150,5,130,127,8,57,47,1,7,35,17,51,172,112,50,51,110,52,3,1,5,18,23,42,25,71,87,88,59,32,62,16,15,7,61,68,45,82,68,67,83,83,67,68, + 212,3,8,20,16,12,102,101,98,106,29,15,15,49,2,47,0,1,0,59,130,91,32,125,130,239,35,20,0,0,1,131,84,36,22,51,50,55,21,72,197,7,8,34,23,21,38,1,13,65,72,72,65,62,49,47,78,85,110,110, + 85,78,47,49,1,89,74,76,76,74,43,65,32,106,196,106,32,65,43,139,159,32,7,133,159,133,158,38,19,51,17,35,39,14,4,130,155,8,35,53,52,54,51,50,30,2,31,1,122,200,48,104,48,200,68,62,6,3, + 8,26,27,41,20,59,88,87,70,25,42,24,17,3,4,45,150,131,158,51,1,109,253,209,49,3,7,21,15,13,106,98,101,102,12,17,17,6,7,0,130,0,34,2,0,37,130,163,32,147,130,163,37,5,0,26,0,0,19,130, + 165,35,51,52,7,20,130,168,32,54,133,169,132,95,8,42,22,29,1,32,227,47,69,224,227,62,67,42,62,46,70,82,89,109,105,79,91,90,254,218,1,89,68,48,116,165,56,79,18,21,64,29,109,95,93,110, + 97,73,48,130,83,42,1,0,40,0,3,1,144,2,36,0,19,132,247,37,29,1,51,21,35,17,130,1,33,53,51,130,77,61,59,1,21,1,40,35,31,125,125,68,122,122,65,64,108,1,237,25,37,75,57,254,217,1,39,57, + 52,78,62,55,133,147,36,50,255,70,1,134,130,147,32,9,73,49,5,77,51,8,44,51,17,20,6,43,1,53,51,50,62,2,61,1,137,254,8,60,22,23,222,99,99,53,51,51,159,61,90,81,130,136,28,42,23,10,1,5, + 18,24,42,25,71,91,91,60,48,52,28,53,142,150,82,68,65,77,1,80,254,110,86,86,58,22,36,37,18,59,2,6,16,12,10,101,100,98,101,20,29,130,162,41,0,62,0,1,1,122,2,49,0,18,74,253,5,8,45,17, + 35,53,52,38,35,34,6,29,1,35,17,51,21,54,253,53,72,68,40,37,47,56,68,68,37,1,146,78,67,255,0,250,45,48,61,59,223,2,47,218,60,0,2,0,139,130,59,38,45,2,30,0,11,0,17,130,74,8,40,35,34, + 61,1,52,59,1,50,29,1,20,7,53,51,17,35,17,1,35,56,10,10,56,9,161,161,68,1,202,10,64,9,9,64,10,127,59,254,123,1,74,130,232,34,2,0,100,130,231,32,84,130,59,34,12,0,24,130,121,130,47,32, + 20,132,221,35,54,53,17,55,138,72,39,165,174,127,112,105,29,37,59,132,72,43,1,76,58,254,98,161,59,52,50,1,100,126,133,79,39,0,1,0,41,0,2,1,142,132,131,38,0,55,23,35,39,7,21,130,183, + 53,17,55,51,210,188,81,154,51,70,70,181,80,245,243,201,45,156,2,27,254,201,161,130,118,130,47,32,133,130,179,36,51,2,8,0,5,130,117,131,164,41,35,133,173,69,104,2,7,253,250,1,72,63, + 5,46,20,255,254,1,164,1,143,0,30,0,0,1,50,22,21,130,198,36,52,35,34,6,7,138,7,48,51,21,54,51,50,23,62,1,1,68,43,52,61,52,26,26,3,132,4,52,62,62,31,42,52,26,13,48,1,143,69,53,254,234, + 1,5,84,34,37,254,238,130,6,42,33,36,254,236,1,133,18,29,49,22,27,130,128,32,1,65,111,5,33,1,147,65,111,20,32,23,65,111,9,33,64,4,65,112,12,35,1,133,48,60,66,31,5,42,42,255,247,1,141, + 1,144,0,9,0,17,67,179,11,32,18,130,171,8,40,20,32,53,52,174,92,58,59,90,59,21,166,94,254,158,44,72,80,80,70,70,80,80,1,28,102,102,205,205,102,0,2,0,50,255,78,1,134,1,136,0,67,79,6, + 38,54,53,52,34,21,20,19,131,57,8,62,6,35,34,46,2,47,1,21,35,17,51,23,62,4,176,103,48,199,107,66,89,87,71,25,41,24,18,3,3,68,61,7,1,6,20,25,41,38,82,67,148,148,67,1,16,105,99,101,103, + 12,18,17,6,6,221,2,48,49,2,8,20,16,13,152,91,35,51,17,35,53,67,171,13,38,160,104,48,200,216,62,68,66,165,5,41,70,87,89,66,23,41,26,19,4,4,134,91,44,6,253,208,221,2,8,21,15,13,103,101, + 99,105,132,99,36,0,0,1,0,69,130,4,36,115,1,142,0,19,130,7,35,50,22,31,1,71,239,5,33,29,1,130,173,8,36,7,62,1,1,12,29,52,11,11,4,16,56,31,56,71,68,68,2,17,69,1,142,15,7,8,65,5,12,20, + 60,35,245,1,133,53,27,35,130,54,46,0,69,255,254,1,115,1,151,0,38,0,0,55,30,1,130,250,34,39,46,1,80,126,9,39,46,2,35,34,21,20,22,23,79,243,7,8,78,47,1,69,53,109,71,91,83,57,14,32,65, + 44,33,62,14,15,6,19,63,33,90,36,57,70,69,88,73,31,70,20,20,88,27,14,32,35,49,15,15,53,44,22,38,36,20,11,6,6,62,4,11,17,57,25,29,8,11,46,55,57,66,12,6,6,0,0,0,1,0,87,0,6,1,97,2,4,0, + 17,130,115,41,51,21,35,34,46,3,53,17,51,21,130,9,57,21,20,216,136,125,25,33,41,24,16,68,162,162,65,58,3,15,26,50,34,1,124,118,58,204,70,132,55,40,54,255,246,1,130,1,133,0,21,130,55, + 33,53,51,80,10,5,34,55,51,19,65,74,6,8,32,54,68,86,44,62,3,68,1,64,1,3,17,23,45,27,79,73,145,244,241,102,53,39,251,254,124,42,2,7,19,13,12,74,132,67,32,29,130,128,32,155,130,67,53, + 6,0,0,1,51,3,35,3,51,19,1,84,70,148,86,147,71,120,1,133,254,123,130,3,32,186,130,38,39,0,1,255,248,255,255,1,192,130,39,33,12,0,132,39,33,39,7,131,42,48,55,51,23,1,124,67,97,63,68, + 65,65,97,68,68,53,76,60,130,47,34,123,215,215,130,5,34,208,192,192,132,55,33,0,20,130,55,32,163,130,55,35,11,0,0,37,71,43,5,57,39,51,23,55,51,1,1,162,78,122,120,78,162,149,76,110,109, + 76,203,203,155,155,204,185,141,141,77,151,5,38,68,1,137,1,134,0,31,130,12,32,18,77,244,5,44,53,51,50,62,4,53,52,53,35,34,38,53,19,130,230,59,22,51,23,3,1,134,3,12,24,47,32,195,176, + 16,23,13,8,3,1,133,71,72,1,68,29,41,137,1,130,122,56,135,48,17,47,52,36,59,12,24,22,34,17,14,4,2,78,49,1,6,229,49,50,1,1,73,132,239,32,64,130,143,32,119,130,95,8,41,14,0,0,55,51,23, + 33,53,62,3,39,35,53,33,21,2,137,237,1,254,202,15,50,105,69,1,229,1,45,238,52,52,59,17,58,122,82,1,50,60,254,235,130,54,8,32,0,1,0,63,255,178,1,120,2,84,0,41,0,0,1,21,20,7,22,29,1,20, + 22,59,1,21,34,35,34,46,3,61,1,83,197,7,130,9,33,62,3,130,22,8,61,35,34,6,1,11,72,72,38,42,29,31,13,38,54,27,14,3,38,43,51,51,43,38,3,14,27,54,38,44,29,42,38,1,215,100,95,17,17,96,99, + 34,29,62,20,26,44,30,23,86,43,32,66,32,42,87,23,30,44,26,19,62,29,130,98,53,0,188,255,200,0,252,2,63,0,3,0,0,23,35,17,51,252,64,64,55,2,117,141,135,32,19,84,60,5,36,50,51,50,30,3,134, + 142,130,119,130,9,33,14,3,69,35,5,130,141,55,55,38,172,37,42,29,30,14,37,54,27,15,2,38,44,51,51,44,38,2,15,27,54,37,130,132,41,37,72,72,1,115,100,33,29,62,19,131,129,33,87,42,130,129, + 45,43,86,24,29,44,26,20,62,29,33,100,96,17,16,130,248,42,1,0,22,0,177,1,161,1,93,0,33,130,148,34,23,14,4,131,238,39,7,14,1,15,1,39,62,4,131,119,54,55,62,1,55,1,103,58,5,5,17,21,40, + 25,25,43,30,28,31,16,20,25,3,3,143,16,62,1,66,13,21,21,45,24,20,26,35,36,21,2,3,45,21,21,13,21,22,44,24,21,26,36,35,22,3,3,45,20,130,229,32,21,130,95,45,163,0,106,0,15,0,31,0,47,0, + 0,55,50,22,130,200,36,6,43,1,34,38,130,197,34,54,59,1,157,15,43,51,104,5,7,7,5,71,4,8,8,4,222,131,9,32,70,131,4,32,221,136,9,35,105,7,5,81,131,12,150,4,42,0,0,2,0,159,255,235,1,25, + 2,9,90,167,5,33,18,34,82,231,5,59,7,51,23,7,35,53,245,50,35,35,50,35,84,46,29,1,101,1,145,35,49,35,35,49,105,196,155,155,130,51,42,63,255,147,1,121,1,242,0,5,0,28,130,177,48,17,14, + 1,21,20,19,17,54,55,21,6,7,21,35,53,46,1,90,19,6,8,62,22,23,21,38,243,50,58,146,57,39,45,51,38,78,102,100,80,38,61,35,40,49,1,38,6,73,67,134,1,25,254,215,2,25,59,21,3,100,101,6,107, + 92,95,104,4,97,98,5,20,60,25,0,1,0,40,255,255,1,144,2,16,0,27,130,89,38,51,21,33,53,51,53,35,130,3,81,218,6,32,21,71,122,5,8,39,51,21,35,202,197,254,153,87,73,73,82,79,33,52,9,10,48, + 42,50,50,136,136,59,59,59,161,50,82,88,88,10,5,5,64,30,53,63,88,50,0,131,223,38,55,0,63,1,128,1,125,130,223,35,31,0,0,54,82,67,6,8,41,54,20,7,23,7,39,6,34,39,7,39,55,38,52,55,39,55, + 23,54,50,23,55,23,7,187,65,46,46,65,46,204,22,60,34,62,31,73,31,62,34,60,22,137,10,49,148,44,61,43,43,61,67,73,30,57,35,60,19,19,60,35,57,30,137,10,75,219,10,36,24,0,0,1,7,130,160, + 33,7,21,88,161,9,32,39,130,194,8,37,39,51,23,55,1,178,120,91,115,33,148,148,75,148,148,33,115,91,119,79,135,134,2,6,194,39,53,11,39,182,182,39,11,53,39,194,228,228,130,178,41,0,2,0, + 188,255,206,0,252,2,57,83,195,5,36,23,35,17,51,53,67,9,5,40,64,64,50,1,8,92,1,7,0,130,35,59,64,255,188,1,119,2,16,0,15,0,75,0,0,37,62,1,53,52,38,39,38,39,14,1,21,20,22,23,69,167,11, + 33,53,22,85,40,5,130,28,35,46,4,53,52,89,5,5,32,54,86,84,10,133,47,8,53,4,21,20,6,1,10,21,28,48,46,25,22,22,27,48,45,25,61,25,21,69,72,25,57,16,16,58,51,36,42,37,47,8,3,28,25,39,17, + 14,38,33,25,21,68,74,24,54,14,14,51,51,37,40,36,130,22,33,29,24,131,22,42,142,12,35,15,26,44,25,14,13,12,36,132,7,63,37,18,34,23,44,66,10,5,5,57,26,32,25,20,30,26,4,2,16,14,28,23,34, + 19,28,50,16,18,34,24,43,59,131,25,33,27,26,133,25,39,15,15,28,23,33,20,28,50,133,251,42,109,1,212,1,75,2,27,0,11,0,23,73,51,13,33,43,1,73,63,9,39,1,66,58,9,9,58,9,155,131,5,37,8,1, + 213,9,52,9,136,2,48,0,0,3,255,248,0,40,1,192,1,218,0,7,0,15,0,36,65,245,9,33,18,50,92,21,5,33,22,52,130,255,35,23,21,38,35,75,119,11,8,53,142,156,111,111,156,111,95,188,133,133,188, + 134,92,78,62,57,30,43,34,47,52,52,47,46,31,36,51,61,79,105,147,105,105,147,1,34,127,179,127,127,179,152,126,66,15,38,21,48,49,49,47,18,36,17,131,179,8,41,3,0,92,0,162,1,91,2,18,0,3, + 0,18,0,53,0,0,37,35,53,51,39,34,35,34,14,4,21,20,22,51,50,54,7,52,62,2,23,60,1,46,2,88,232,5,8,122,62,6,51,50,22,29,1,35,39,6,35,34,38,1,91,247,247,55,5,8,25,24,37,19,20,8,36,22,49, + 41,201,37,58,70,33,8,15,32,22,26,50,11,12,2,18,7,17,12,17,18,9,57,73,45,8,39,54,47,59,163,43,178,1,3,6,11,18,13,24,24,61,16,33,43,14,7,4,8,10,22,14,11,11,6,6,44,1,6,2,6,2,3,1,57,64, + 159,38,45,44,0,2,0,50,0,49,1,134,1,112,0,6,0,13,0,0,1,21,7,23,21,39,53,55,133,5,50,1,134,112,112,174,7,111,111,173,173,1,112,67,93,93,66,145,28,79,132,5,35,146,0,0,4,65,59,8,56,9,0, + 27,0,35,0,43,0,0,19,22,62,2,53,52,38,43,1,23,30,1,23,35,38,39,130,9,32,21,130,224,36,50,21,20,14,1,67,78,6,65,88,7,54,175,21,36,27,15,29,21,49,86,14,25,46,52,30,17,21,24,27,47,92,103, + 34,147,65,89,10,53,1,13,1,2,6,18,15,19,20,102,5,33,72,44,26,33,103,242,74,21,32,172,65,94,11,130,172,41,0,109,1,183,1,75,1,235,0,3,130,12,130,106,38,1,74,220,220,1,183,51,66,31,5,45, + 101,1,50,1,82,2,16,0,7,0,15,0,0,18,93,253,14,56,190,59,42,42,59,41,22,98,69,69,98,69,1,94,39,55,39,39,55,138,65,91,65,65,91,133,59,40,24,255,255,1,159,1,151,0,11,131,59,92,65,11,56, + 19,33,53,33,251,164,164,62,165,165,62,164,254,121,1,135,1,36,59,115,115,59,114,254,105,87,43,5,40,115,1,133,1,69,2,173,0,24,130,143,130,245,37,7,51,21,35,53,54,85,161,5,8,47,34,6,15, + 1,53,54,51,50,22,1,66,17,62,70,152,210,77,32,42,37,28,17,42,12,13,39,51,57,58,2,94,20,33,64,61,39,38,72,29,40,36,18,25,11,6,6,41,20,130,197,32,1,130,223,38,130,1,75,2,176,0,39,131, + 79,32,6,88,180,10,91,20,23,8,51,7,22,1,75,68,55,21,49,15,14,50,42,26,48,43,31,31,31,29,36,30,27,21,44,12,12,47,44,49,63,33,30,71,1,214,37,47,7,4,4,42,18,26,25,25,27,38,22,21,18,22, + 8,130,13,38,13,42,33,28,29,7,13,130,252,39,1,0,131,1,181,1,53,2,79,123,5,41,51,7,35,235,73,120,56,2,57,131,130,26,51,0,1,0,37,255,107,1,146,1,132,0,36,0,0,37,50,54,63,1,21,133,147, + 33,14,4,130,154,36,39,21,35,17,51,66,199,5,8,63,53,55,51,16,21,20,1,120,7,12,4,3,27,29,19,26,4,3,1,4,16,22,42,27,26,47,11,60,64,45,37,43,59,3,65,49,4,2,2,52,15,26,13,13,2,7,18,14,11, + 28,26,192,2,24,241,48,53,53,38,251,254,226,24,29,130,108,42,1,0,48,255,185,1,136,2,7,0,16,76,95,7,47,16,21,6,34,47,1,17,34,38,52,54,216,175,52,70,1,130,60,8,34,69,99,99,2,6,253,182, + 2,32,253,229,5,2,1,1,1,39,85,121,85,0,0,1,0,159,0,198,1,25,1,63,0,7,0,0,66,114,7,39,195,50,35,35,50,35,1,63,130,4,33,35,50,131,35,36,143,255,119,1,41,130,43,130,95,35,33,22,21,20,65, + 92,8,52,39,48,58,1,1,0,40,91,14,31,8,8,24,27,91,73,21,22,41,37,59,130,173,34,46,11,94,132,151,40,114,1,132,1,70,2,167,0,10,131,151,37,21,35,53,51,53,7,130,239,56,250,75,204,76,83,85, + 51,1,171,38,38,212,14,40,13,0,3,0,83,0,162,1,101,2,16,130,9,37,13,0,21,0,0,37,130,41,8,47,2,34,6,21,20,22,50,54,53,52,22,32,53,52,54,50,22,21,1,86,249,249,88,69,46,45,71,45,56,254, + 239,73,127,73,163,43,1,25,47,54,54,49,49,54,54,197,143,71,130,0,130,114,67,163,15,33,55,21,130,112,35,39,53,51,23,132,7,48,223,173,111,111,166,174,174,112,112,222,28,145,66,93,93,67, + 146,132,6,41,0,4,0,19,255,162,1,165,2,64,130,127,36,6,0,17,0,28,130,242,38,23,5,39,23,51,53,55,70,111,8,33,55,3,130,72,32,51,131,14,8,44,53,51,1,155,9,254,120,9,198,102,51,43,43,50, + 144,134,187,82,87,48,75,203,75,1,47,37,91,37,203,144,43,188,37,65,65,42,183,1,86,14,39,13,252,39,39,130,81,137,91,34,28,0,39,133,89,32,5,66,236,15,34,39,54,51,130,224,34,20,6,1,143, + 100,34,1,73,86,66,254,7,42,41,13,12,1,39,51,57,59,28,254,252,138,113,52,190,77,39,39,71,30,39,36,19,25,12,6,6,42,19,51,27,23,45,1,226,134,123,34,4,0,18,132,215,32,70,134,215,32,56, + 133,125,34,23,51,39,138,215,33,39,20,67,43,10,67,42,26,132,243,34,202,102,1,130,244,8,32,49,145,134,45,72,55,21,48,13,13,41,46,36,42,38,34,33,30,31,37,33,25,24,45,10,10,46,44,50,62, + 33,30,72,65,11,13,8,68,168,36,47,7,4,4,41,18,29,47,28,37,23,21,20,20,8,4,4,42,12,41,32,29,29,8,14,0,0,2,0,73,255,247,1,111,2,27,0,30,0,38,0,0,55,50,54,63,1,21,14,2,35,34,38,53,52,62, + 4,61,1,51,21,20,14,3,21,20,22,73,68,7,8,56,235,29,66,18,19,7,24,69,34,82,78,19,29,34,29,19,71,26,37,38,26,47,71,44,32,32,44,32,48,24,11,12,65,5,12,21,65,54,24,43,30,30,24,31,17,69, + 61,30,48,34,31,39,21,31,36,1,129,91,1,6,45,3,0,5,0,0,1,179,2,131,0,3,0,6,0,130,128,36,1,35,39,51,3,90,85,9,37,1,9,56,84,68,52,90,90,9,46,2,38,92,254,61,1,10,61,253,250,135,135,2,6, + 0,130,53,143,63,34,7,35,55,139,63,36,59,84,56,72,106,132,63,39,40,193,41,77,169,2,130,92,140,64,36,3,0,5,255,255,132,127,32,6,79,77,5,32,19,130,63,34,51,23,35,138,66,38,220,61,51,78, + 69,78,51,90,223,5,133,68,33,101,62,130,69,32,151,138,134,131,135,33,255,254,130,71,61,123,0,20,0,23,0,31,0,0,19,34,6,21,35,52,54,51,50,22,51,50,54,53,55,51,20,6,35,34,38,138,85,49, + 169,12,12,47,36,34,26,62,15,11,13,1,46,34,36,28,58,45,138,164,45,81,19,16,33,43,34,17,9,8,25,51,35,254,109,139,103,32,4,136,175,38,2,0,10,0,22,0,34,91,131,12,81,53,11,72,74,11,138, + 187,32,147,72,84,9,33,9,190,136,89,32,56,72,93,13,32,0,136,207,38,149,0,2,0,14,0,30,132,101,52,39,20,22,23,51,62,1,53,52,38,34,6,23,19,35,39,35,7,35,19,38,68,11,5,32,20,130,97,52,56, + 18,16,44,16,19,33,47,34,112,159,77,40,193,41,77,159,47,60,84,60,130,98,55,110,17,26,6,6,26,17,21,31,31,101,254,25,135,135,1,231,28,51,40,55,55,40,51,130,89,38,2,255,255,1,182,2,7,130, + 109,32,19,130,97,40,17,35,3,55,21,51,21,35,53,131,88,8,40,37,7,35,23,55,7,235,37,75,186,128,202,127,37,68,150,1,23,1,122,1,111,1,190,1,13,254,244,55,188,59,134,133,2,5,2,61,153,1,60, + 0,78,87,5,38,119,1,137,2,16,0,40,130,12,91,224,8,49,55,21,6,7,22,21,20,6,35,34,39,53,22,51,50,53,52,39,88,3,6,8,66,23,21,38,1,8,38,57,32,14,25,66,50,77,51,37,56,33,45,38,45,25,24,31, + 46,26,54,81,48,23,112,104,77,51,51,1,213,32,60,74,46,62,91,59,46,73,21,5,36,32,31,30,9,45,11,31,22,32,3,42,74,93,56,117,151,32,71,45,130,187,38,52,0,3,1,132,2,135,71,103,5,8,41,55, + 33,21,33,17,33,21,35,31,1,21,35,19,35,39,51,127,1,4,254,178,1,71,253,1,241,242,142,57,84,68,63,59,2,6,59,153,1,59,1,48,93,130,175,36,2,0,52,0,4,150,59,34,7,35,55,138,59,36,173,84,56, + 72,64,135,59,33,140,93,130,60,135,59,32,136,130,119,34,18,0,0,140,119,66,185,5,138,62,38,86,61,52,78,70,78,52,136,65,33,110,62,131,66,33,0,3,130,127,32,0,130,127,32,133,130,67,34,23, + 0,35,74,129,25,32,3,134,213,35,23,51,21,35,74,141,11,33,9,58,137,222,39,2,63,8,52,9,9,52,8,133,5,33,253,252,132,173,42,59,0,2,0,66,255,254,1,118,2,131,65,31,5,8,49,1,35,17,23,21,33, + 53,51,17,35,53,33,39,35,39,51,1,117,115,115,254,206,116,116,1,50,106,56,84,68,1,202,254,111,1,57,59,1,144,59,33,92,0,0,0,2,0,66,0,3,130,59,65,91,6,130,59,32,51,132,59,32,39,130,59, + 130,227,56,1,117,116,116,254,206,115,115,1,50,60,84,56,72,1,207,254,112,59,57,1,145,1,59,125,65,31,5,133,59,65,31,6,143,59,34,51,23,35,137,62,32,148,65,30,5,137,65,32,95,65,31,7,137, + 187,65,31,28,34,23,35,17,131,153,32,55,131,213,65,31,12,32,188,135,161,33,2,61,67,88,11,32,115,132,170,34,1,145,59,131,227,45,17,0,4,1,167,2,11,0,18,0,37,0,0,55,92,29,5,8,79,43,1,21, + 51,21,35,21,50,19,50,30,3,20,14,3,43,1,53,35,53,51,53,50,200,19,36,42,30,20,20,30,42,36,19,62,99,99,62,1,31,56,61,45,29,29,45,61,56,30,139,46,46,138,62,9,27,42,75,98,75,42,27,8,162, + 53,188,1,205,12,35,55,95,125,96,54,35,11,245,53,220,131,103,42,42,0,0,1,142,2,130,0,20,0,30,68,149,22,36,23,51,17,35,3,130,2,33,51,19,68,148,16,41,140,72,95,186,71,4,98,186,2,88,68, + 145,10,32,82,92,232,5,37,2,6,254,90,0,3,92,235,6,38,135,0,5,0,9,0,29,92,237,7,35,55,35,39,51,92,241,16,37,238,57,84,68,3,33,92,245,5,34,29,28,34,133,8,42,31,2,17,253,234,2,22,25,93, + 253,175,92,247,19,135,95,32,136,130,95,32,12,92,157,5,36,16,35,34,16,55,66,235,5,32,2,93,84,15,32,184,65,210,5,32,77,150,101,37,88,62,93,93,254,11,151,103,42,251,1,149,2,129,0,20,0, + 26,0,46,65,41,14,33,55,53,69,191,5,33,23,50,93,202,16,38,169,11,13,47,36,35,25,69,199,6,39,35,29,58,34,184,184,184,168,145,127,32,87,65,64,10,32,70,131,241,44,254,37,8,26,44,78,105, + 78,44,27,7,7,27,132,8,36,26,0,0,0,4,65,79,6,131,239,36,17,0,29,0,49,65,81,7,32,37,87,16,10,69,218,11,65,101,16,33,1,30,69,225,10,32,19,145,136,32,17,131,124,32,49,66,150,11,33,253, + 244,65,17,17,8,32,1,0,46,0,65,1,137,1,137,0,11,0,0,1,7,23,7,39,7,39,55,39,55,23,55,1,137,129,129,45,128,129,45,130,5,38,129,128,1,94,121,121,42,135,2,47,0,3,0,1,255,234,1,183,2,34, + 0,10,0,21,0,39,103,247,10,46,39,20,23,55,38,35,34,14,3,37,7,22,21,16,35,80,146,5,51,53,16,51,50,23,55,220,17,27,30,20,13,7,184,26,49,7,185,26,59,132,12,40,1,70,65,30,184,83,46,54,36, + 133,6,32,54,131,157,54,53,56,42,248,58,208,56,42,249,58,8,26,44,78,205,88,65,105,254,245,56,72,25,130,7,48,1,11,56,72,0,0,2,0,46,255,246,1,138,2,131,0,17,130,123,40,0,1,51,17,20,35, + 34,53,17,130,6,8,44,22,51,50,62,2,53,3,35,39,51,1,62,75,170,176,75,39,61,30,40,19,7,43,57,84,69,2,6,254,160,176,189,1,83,254,160,67,52,18,33,36,25,1,135,92,161,71,34,7,35,55,139,71, + 35,12,84,57,72,144,71,35,227,92,92,0,130,0,139,147,33,24,0,146,147,66,196,5,139,78,32,98,66,191,5,139,81,33,17,34,130,153,33,198,62,130,82,34,3,0,47,136,227,34,29,0,41,146,81,32,19, + 66,36,22,44,1,62,76,171,176,75,40,61,30,39,19,7,5,66,30,10,144,103,32,158,72,7,15,32,2,72,111,6,35,130,0,8,0,86,29,5,38,21,35,55,3,51,23,19,130,187,44,1,98,80,177,75,1,177,79,135,90, + 84,57,73,130,74,39,227,233,234,1,28,229,1,97,130,170,41,0,2,0,42,255,255,1,142,2,6,130,55,32,21,99,245,10,8,41,55,50,22,21,20,43,1,21,35,17,51,21,50,210,49,61,63,47,93,103,83,95,178, + 103,75,75,103,186,43,48,47,42,180,240,75,74,150,127,2,6,92,0,130,222,130,67,32,242,130,67,8,127,28,0,48,0,0,19,20,30,3,21,20,14,1,38,39,53,22,51,50,54,53,52,46,3,53,52,62,2,63,1,38, + 54,46,2,35,34,6,21,17,35,17,52,54,51,50,21,34,6,234,34,48,48,33,51,78,88,38,49,51,41,45,33,47,47,33,24,35,34,12,12,1,3,10,14,38,27,46,41,68,73,82,155,47,72,1,50,20,33,27,32,53,35,43, + 57,20,5,16,56,21,36,29,24,37,25,27,45,30,28,44,24,15,2,2,1,15,23,22,16,48,50,254,123,1,133,77,74,152,44,130,138,41,0,3,0,38,255,245,1,146,2,69,130,9,34,18,0,61,130,156,49,35,39,51, + 19,50,62,2,39,48,42,1,35,14,1,21,20,22,55,130,3,50,31,1,35,38,39,14,1,35,34,38,53,52,54,59,1,60,2,46,5,81,15,9,8,99,1,12,57,118,72,24,36,52,26,10,1,36,47,11,60,52,51,225,10,5,5,68, + 9,7,21,76,38,65,80,91,91,92,2,6,8,15,20,30,18,38,71,16,17,3,25,11,24,17,24,26,13,168,1,194,131,253,230,29,47,50,26,2,32,44,38,36,183,116,25,55,15,15,22,37,32,37,64,60,62,70,1,15,9, + 18,13,16,11,10,5,18,10,9,63,1,9,3,8,3,4,2,149,171,35,7,35,55,3,131,171,33,42,2,167,170,37,84,118,57,103,80,37,131,170,33,1,35,167,171,34,2,69,131,176,172,65,87,9,39,6,0,21,0,64,0,0, + 19,130,171,34,51,23,35,132,174,65,90,41,39,220,66,52,90,56,90,52,97,65,92,45,38,2,26,88,131,131,254,105,65,94,20,33,63,69,65,94,20,135,175,46,51,0,24,0,39,0,82,0,0,1,34,46,2,35,34, + 75,234,5,42,30,1,51,50,54,61,1,51,20,14,2,174,191,53,1,13,21,31,16,23,11,26,45,36,37,21,34,30,13,12,13,46,5,13,30,101,173,205,50,1,209,17,20,17,54,43,55,27,26,26,13,14,15,29,33,21, + 254,90,148,217,66,56,24,32,4,66,227,6,40,29,0,11,0,23,0,38,0,81,74,25,25,32,19,66,77,45,32,63,68,115,10,32,6,66,85,46,33,1,214,68,147,11,33,254,85,181,211,52,114,0,7,0,15,0,58,0,73, + 0,0,18,34,6,20,22,50,54,52,6,34,88,7,5,32,19,67,183,31,67,58,12,44,240,46,33,33,46,33,14,84,60,60,84,60,63,67,191,33,32,194,139,237,46,2,70,31,44,31,31,44,118,56,80,56,56,80,254,249, + 67,204,32,33,254,155,136,236,67,215,5,55,5,255,245,1,179,1,143,0,8,0,19,0,89,0,0,1,34,6,23,51,54,39,46,1,130,161,33,1,39,85,233,5,40,37,21,35,28,2,30,5,51,22,79,184,7,34,46,2,47,96, + 42,8,32,59,113,200,6,83,206,5,8,164,30,2,31,1,62,4,51,50,22,23,30,1,21,1,56,31,31,3,121,1,2,3,27,217,26,26,14,1,28,53,44,37,1,75,182,2,4,6,10,14,21,12,34,52,9,10,5,15,48,25,25,42,22, + 16,2,3,1,4,16,21,36,22,57,60,70,68,50,42,30,19,46,13,14,44,65,19,30,18,12,2,2,1,3,14,19,36,22,58,48,8,2,1,1,90,54,64,23,22,35,38,254,209,18,59,59,32,38,35,31,136,1,1,28,10,28,13,23, + 11,14,6,1,17,9,10,60,3,10,16,11,17,16,6,5,2,7,19,15,12,60,56,57,65,28,50,39,14,8,7,58,24,9,13,13,4,5,2,6,15,11,10,59,76,18,42,12,130,248,42,1,0,59,255,119,1,125,1,142,0,37,132,243, + 42,21,20,22,51,50,55,21,14,1,15,1,77,223,14,130,222,34,54,51,50,98,88,10,35,14,43,15,15,77,222,8,33,82,106,98,99,5,56,88,74,76,76,74,43,65,11,15,2,2,36,32,31,30,8,46,12,32,22,31,3, + 105,96,98,98,114,5,41,0,3,0,37,255,247,1,147,2,69,130,9,34,9,0,30,69,235,5,32,23,98,25,22,42,21,23,32,1,19,57,118,72,55,46,70,98,30,12,40,92,89,1,254,217,1,194,131,236,98,33,17,145, + 95,35,7,35,55,7,154,95,36,93,119,56,102,49,147,95,34,2,69,131,147,96,33,0,0,138,195,36,6,0,12,0,33,69,87,8,154,102,39,225,67,52,90,57,90,52,64,148,104,36,26,88,131,131,105,145,202, + 33,0,4,65,43,6,68,51,5,34,29,0,50,68,51,25,65,65,27,44,69,57,9,9,57,9,155,58,9,9,58,9,39,147,128,68,5,13,32,125,146,137,8,58,0,0,2,0,51,255,252,1,117,2,59,0,13,0,17,0,0,37,51,21,34, + 35,34,38,61,1,35,53,51,21,20,17,35,39,51,1,37,80,24,71,54,65,90,158,57,119,73,51,54,78,63,201,50,251,86,1,132,131,0,130,0,34,2,0,68,130,59,32,116,149,59,48,19,7,35,55,1,37,79,23,72, + 53,66,90,158,106,119,57,103,135,60,34,2,7,131,130,61,135,59,36,69,0,6,0,20,65,105,8,32,19,140,126,39,186,66,52,90,56,90,51,40,134,68,70,121,5,32,113,134,131,130,127,32,3,134,127,38, + 26,0,11,0,23,0,37,65,69,25,141,86,33,1,44,65,56,5,132,5,32,130,134,92,33,1,212,69,49,12,32,95,135,101,54,2,0,42,255,244,1,142,2,27,0,14,0,47,0,0,55,50,54,53,52,38,47,1,90,34,6,38,19, + 20,30,6,21,20,6,72,186,5,8,96,23,46,1,47,1,7,39,55,39,51,23,55,23,7,22,220,45,60,20,10,10,18,50,43,59,60,145,20,6,19,8,13,6,5,89,88,88,89,107,100,5,27,10,11,103,12,88,67,81,47,106, + 13,93,36,42,70,71,38,67,15,14,9,69,73,73,69,1,89,1,27,10,29,19,33,29,38,19,86,107,107,94,93,102,8,8,31,12,11,31,34,27,69,49,32,34,28,34,65,115,5,32,62,130,231,44,122,2,32,0,18,0,43, + 0,0,19,50,22,21,100,121,12,34,23,54,55,71,88,17,54,35,34,46,2,253,53,72,68,39,38,47,56,68,64,4,37,3,26,45,36,37,20,35,71,51,5,40,14,29,21,21,31,17,22,1,145,100,165,9,51,1,132,47,60, + 99,54,44,54,27,26,26,14,13,15,29,33,21,17,20,17,130,124,40,3,0,43,255,244,1,141,2,69,86,227,7,36,1,35,39,51,18,102,253,8,99,73,7,37,1,15,56,119,73,5,99,78,9,37,1,194,131,253,229,71, + 99,82,9,35,204,204,102,0,130,65,143,75,35,7,35,55,2,145,75,36,86,118,57,103,96,137,75,34,2,69,131,145,76,137,151,36,6,0,16,0,24,66,77,8,145,78,72,242,6,32,112,138,80,37,26,88,131,131, + 254,104,143,158,136,159,32,32,130,79,34,34,0,42,72,179,24,145,99,53,1,15,20,32,16,22,12,25,46,36,38,20,35,29,14,12,13,46,5,14,30,118,137,113,36,1,190,17,20,17,65,92,11,33,254,108,142, + 125,32,4,65,103,6,66,215,5,34,33,0,41,66,217,25,146,126,93,1,10,33,9,11,138,118,66,224,13,32,86,142,115,32,3,130,231,36,49,1,159,1,136,130,249,42,19,0,35,0,0,37,33,53,33,39,35,96,108, + 13,32,3,142,15,48,1,159,254,121,1,135,161,70,4,7,7,4,70,5,6,6,5,136,9,37,192,59,56,6,5,63,131,18,130,4,33,254,255,130,11,131,35,130,11,32,0,130,0,44,3,0,12,255,222,1,172,1,166,0,7, + 0,15,131,107,39,55,50,54,53,52,39,7,22,78,242,6,37,6,37,7,22,21,20,78,241,7,8,65,52,54,51,50,23,55,22,222,47,57,10,169,27,56,9,168,27,46,45,59,1,54,61,33,178,75,44,57,34,62,29,94,83, + 71,46,55,34,43,72,80,42,32,189,37,152,42,31,189,33,70,121,68,52,81,204,40,63,26,70,50,81,102,102,39,63,27,67,55,5,51,61,255,246,1,123,2,59,0,3,0,26,0,0,1,35,39,51,23,51,17,105,15,6, + 38,61,1,51,21,20,22,51,130,133,8,41,1,13,57,119,73,144,68,63,6,2,5,20,23,38,21,77,61,67,38,43,44,53,1,184,131,182,254,124,57,2,10,23,18,14,72,82,244,240,52,52,59,50,130,84,33,2,0,141, + 83,34,7,35,55,148,83,36,83,119,56,102,44,143,83,34,2,59,131,147,84,135,83,36,69,0,6,0,29,66,201,8,32,23,145,169,32,55,66,203,6,32,92,139,171,41,42,45,53,2,2,26,88,131,131,61,143,174, + 32,235,130,175,32,3,134,175,95,67,5,32,46,66,85,25,148,197,35,63,58,8,8,95,89,6,33,9,128,140,205,130,117,95,106,13,32,80,143,125,42,0,0,2,0,26,255,107,1,158,2,59,80,107,7,34,2,7,14, + 99,148,6,8,49,55,3,51,27,1,7,35,55,1,86,72,140,39,14,32,36,25,18,54,46,30,32,22,160,72,123,105,119,56,102,1,133,254,175,98,35,44,18,5,54,46,54,1,127,254,208,1,230,131,131,130,203,34, + 2,0,50,130,79,39,134,2,32,0,9,0,23,0,107,131,10,33,19,50,66,59,5,53,21,35,17,51,21,54,174,89,55,57,86,56,99,80,91,171,67,32,68,68,31,42,66,245,11,37,65,202,2,180,208,61,130,75,32,3, + 134,155,32,17,130,155,38,29,0,41,0,0,1,51,142,157,80,34,23,144,177,32,100,71,147,10,144,185,32,118,80,40,14,42,1,0,30,255,249,1,153,2,20,0,43,130,115,40,34,7,51,7,35,6,21,20,21,130, + 6,35,30,3,51,50,107,75,6,38,39,35,55,51,38,52,55,130,5,8,76,62,1,51,50,23,21,46,1,1,44,110,20,178,18,163,1,132,17,111,6,27,38,37,23,20,61,27,47,61,88,103,14,65,18,44,2,2,62,18,47,14, + 103,83,65,48,28,61,1,220,144,36,30,4,17,15,37,44,60,29,10,19,24,71,28,104,95,37,29,9,28,36,95,105,29,72,24,21,130,246,130,2,36,14,0,174,0,1,130,7,131,2,34,1,0,4,134,11,32,1,130,7,32, + 10,134,11,32,2,130,7,32,16,134,11,36,3,0,29,0,78,134,11,131,43,32,112,134,11,36,5,0,9,0,134,134,11,32,6,130,7,44,148,0,3,0,1,4,9,0,0,0,2,0,0,134,11,32,1,130,11,32,6,134,11,32,2,130, + 11,32,12,134,11,36,3,0,58,0,18,134,11,32,4,130,23,32,108,134,11,32,5,130,21,32,114,134,11,32,6,130,23,35,144,0,46,0,143,2,39,70,0,111,0,110,0,116,0,131,7,40,114,0,103,0,101,0,32,0, + 50,130,36,32,48,130,7,32,58,130,3,32,46,134,7,36,49,0,49,0,45,130,25,131,3,32,48,130,7,51,54,0,0,70,111,110,116,70,111,114,103,101,32,50,46,48,32,58,32,46,130,3,39,49,49,45,50,45,50, + 48,50,130,30,133,107,32,86,130,81,36,114,0,115,0,105,132,103,32,32,130,89,40,0,86,101,114,115,105,111,110,32,136,140,33,2,0,139,0,65,75,7,137,19,32,192,130,10,131,251,60,3,0,4,0,5, + 0,6,0,7,0,8,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,130,235,8,50,19,0,20,0,21,0,22,0,23,0,24,0,25,0,26,0,27,0,28,0,29,0,30,0,31,0,32,0,33,0,34,0,35,0,36,0,37,0,38,0,39,0,40,0,41, + 0,42,0,43,0,44,130,211,36,46,0,47,0,48,130,221,9,48,50,0,51,0,52,0,53,0,54,0,55,0,56,0,57,0,58,0,59,0,60,0,61,0,62,0,63,0,64,0,65,0,66,0,67,0,68,0,69,0,70,0,71,0,72,0,73,0,74,0,75, + 0,76,0,77,0,78,0,79,0,80,0,81,0,82,0,83,0,84,0,85,0,86,0,87,0,88,0,89,0,90,0,91,0,92,0,93,0,94,1,2,0,96,0,97,1,3,0,163,0,132,0,133,0,189,0,150,0,232,0,134,0,142,0,139,0,157,0,169,0, + 138,1,4,0,131,0,147,0,242,0,243,0,141,0,151,0,136,0,195,0,222,0,241,0,158,0,170,0,245,0,244,0,246,0,162,0,173,0,201,0,199,0,174,0,98,0,99,0,144,0,100,0,203,0,101,0,200,0,202,0,207, + 0,204,0,205,0,206,0,233,0,102,0,211,0,209,0,175,0,103,0,240,0,145,0,214,0,212,0,213,0,104,0,235,0,237,0,137,0,106,0,105,0,107,0,109,0,108,0,110,0,160,0,111,0,113,0,112,0,114,0,115, + 0,117,0,116,0,118,0,119,0,234,0,120,0,122,0,121,0,123,0,125,0,124,0,184,0,161,0,127,0,126,0,128,0,129,0,236,0,238,0,186,1,5,11,118,101,114,116,105,99,97,108,98,97,114,7,117,110,105, + 48,48,56,53,9,111,130,20,42,115,99,111,114,101,4,69,117,114,111,0,132,0,38,1,255,255,0,2,0,1,130,11,32,12,130,3,32,22,130,3,131,13,32,98,130,183,34,1,0,4,132,13,130,4,130,2,32,1,130, + 3,36,0,229,13,183,147,132,7,42,175,187,66,0,0,0,0,229,178,59,232,5,250,48,120,202,241, +}; + +static const char* GetDefaultCompressedFontDataProggyForever(int* out_size) +{ + *out_size = proggy_forever_minimal_ttf_compressed_size; + return (const char*)proggy_forever_minimal_ttf_compressed_data; +} + #endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT #endif // #ifndef IMGUI_DISABLE diff --git a/src/Libs/imgui/imgui_internal.h b/src/Libs/imgui/imgui_internal.h index 23389ab..e7a9547 100644 --- a/src/Libs/imgui/imgui_internal.h +++ b/src/Libs/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -60,7 +60,7 @@ Index of this file: #include // INT_MIN, INT_MAX // Enable SSE intrinsics if available -#if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) +#if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) && !defined(_M_ARM64) && !defined(_M_ARM64EC) #define IMGUI_ENABLE_SSE #include #if (defined __AVX__ || defined __SSE4_2__) @@ -106,6 +106,7 @@ Index of this file: #pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif // In 1.89.4, we moved the implementation of "courtesy maths operators" from imgui_internal.h in imgui.h @@ -262,6 +263,9 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +// Debug options (also see ones on top of imgui.cpp) +//#define IMGUI_DEBUG_BOXSELECT + // Static Asserts #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") @@ -287,11 +291,9 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_MEMALIGN(_OFF,_ALIGN) (((_OFF) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 -#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // ImTrunc() is not inlined in MSVC debug builds -#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023) -#endif +#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! ImTrunc() is not inlined in MSVC debug builds +#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! +//#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023) // Hint for branch prediction #if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L)) @@ -346,7 +348,6 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_PRIu64 "llu" #define IM_PRIX64 "llX" #endif -#define IM_TEXTUREID_TO_U64(_TEXID) ((ImU64)(intptr_t)(_TEXID)) //----------------------------------------------------------------------------- // [SECTION] Generic helpers @@ -453,6 +454,16 @@ IMGUI_API ImVec2 ImFontCalcTextSizeEx(ImFont* font, float size, float max IMGUI_API const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* text, const char* text_end, float wrap_width, ImDrawTextFlags flags = 0); IMGUI_API const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_end, ImDrawTextFlags flags = 0); // trim trailing space and find beginning of next line +// Character classification for word-wrapping logic +enum ImWcharClass +{ + ImWcharClass_Blank, ImWcharClass_Punct, ImWcharClass_Other +}; +IMGUI_API void ImTextInitClassifiers(); +IMGUI_API void ImTextClassifierClear(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class); +IMGUI_API void ImTextClassifierSetCharClass(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class, unsigned int c); +IMGUI_API void ImTextClassifierSetCharClassFromStr(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class, const char* s); + // Helpers: File System #ifdef IMGUI_DISABLE_FILE_FUNCTIONS #define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS @@ -509,7 +520,8 @@ inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } -template T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template T ImLerp(double a, double b, float t) { return (T)(a + (b - a) * (double)t); } +template T ImLerp(T a, T b, float t) { return (T)((float)a + (float)(b - a) * t); } template void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } template T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } template T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } @@ -529,7 +541,7 @@ inline ImVec2 ImTrunc(const ImVec2& v) { return inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } inline float ImTrunc64(float f) { return (float)(ImS64)(f); } -inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } +inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only. inline int ImModPositive(int a, int b) { return (a + b) % b; } inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } @@ -537,7 +549,7 @@ inline float ImLinearSweep(float current, float target, float speed) { if (cu inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } -inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / n; avg += sample / n; return avg; } +inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / (float)n; avg += sample / (float)n; return avg; } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry @@ -606,6 +618,8 @@ struct IMGUI_API ImRect bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void AddX(float x) { if (Min.x > x) Min.x = x; if (Max.x < x) Max.x = x; } + void AddY(float y) { if (Min.y > y) Min.y = y; if (Max.y < y) Max.y = y; } void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; } @@ -613,7 +627,6 @@ struct IMGUI_API ImRect void TranslateY(float dy) { Min.y += dy; Max.y += dy; } void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. - void Floor() { Min.x = IM_TRUNC(Min.x); Min.y = IM_TRUNC(Min.y); Max.x = IM_TRUNC(Max.x); Max.y = IM_TRUNC(Max.y); } bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); } const ImVec4& AsVec4() const { return *(const ImVec4*)&Min.x; } @@ -647,15 +660,15 @@ typedef ImU32* ImBitArrayPtr; // Name for use in structs template struct ImBitArray { - ImU32 Storage[(BITCOUNT + 31) >> 5]; + ImU32 Data[(BITCOUNT + 31) >> 5]; ImBitArray() { ClearAllBits(); } - void ClearAllBits() { memset(Storage, 0, sizeof(Storage)); } - void SetAllBits() { memset(Storage, 255, sizeof(Storage)); } - bool TestBit(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); } - void SetBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Storage, n); } - void ClearBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Storage, n); } - void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2) - bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); } + void ClearAllBits() { memset(Data, 0, sizeof(Data)); } + void SetAllBits() { memset(Data, 255, sizeof(Data)); } + bool TestBit(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Data, n); } + void SetBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Data, n); } + void ClearBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Data, n); } + void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Data, n, n2); } // Works on range [n..n2) + bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Data, n); } }; // Helper: ImBitVector @@ -712,7 +725,7 @@ struct ImSpanAllocator int Offsets[CHUNKS]; int Sizes[CHUNKS]; - ImSpanAllocator() { memset(this, 0, sizeof(*this)); } + ImSpanAllocator() { memset((void*)this, 0, sizeof(*this)); } inline void Reserve(int n, size_t sz, int a=4) { IM_ASSERT(n == CurrIdx && n < CHUNKS); CurrOff = IM_MEMALIGN(CurrOff, a); Offsets[n] = CurrOff; Sizes[n] = (int)sz; CurrIdx++; CurrOff += (int)sz; } inline int GetArenaSizeInBytes() { return CurrOff; } inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } @@ -723,10 +736,10 @@ struct ImSpanAllocator }; // Helper: ImStableVector<> -// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear(). +// Allocating chunks of BLOCKSIZE items. Objects pointers are never invalidated when growing, only by clear(). // Important: does not destruct anything! // Implemented only the minimum set of functions we need for it. -template +template struct ImStableVector { int Size = 0; @@ -740,19 +753,19 @@ struct ImStableVector inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; } inline void reserve(int new_cap) { - new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE); - int old_count = Capacity / BLOCK_SIZE; - int new_count = new_cap / BLOCK_SIZE; + new_cap = IM_MEMALIGN(new_cap, BLOCKSIZE); + int old_count = Capacity / BLOCKSIZE; + int new_count = new_cap / BLOCKSIZE; if (new_count <= old_count) return; Blocks.resize(new_count); for (int n = old_count; n < new_count; n++) - Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE); + Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCKSIZE); Capacity = new_cap; } - inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } - inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } - inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; } + inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCKSIZE][i % BLOCKSIZE]; } + inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCKSIZE][i % BLOCKSIZE]; } + inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCKSIZE); void* ptr = &Blocks[i / BLOCKSIZE][i % BLOCKSIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; } }; // Helper: ImPool<> @@ -893,7 +906,7 @@ struct ImDrawDataBuilder ImVector* Layers[2]; // Pointers to global layers for: regular, tooltip. LayersP[0] is owned by DrawData. ImVector LayerData1; - ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } + ImDrawDataBuilder() { memset((void*)this, 0, sizeof(*this)); } }; struct ImFontStackData @@ -967,7 +980,6 @@ enum ImGuiDataTypePrivate_ enum ImGuiItemFlagsPrivate_ { // Controlled by user - ImGuiItemFlags_Disabled = 1 << 10, // false // Disable interactions (DOES NOT affect visuals. DO NOT mix direct use of this with BeginDisabled(). See BeginDisabled()/EndDisabled() for full disable feature, and github #211). ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() @@ -1004,6 +1016,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid. ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. + ImGuiItemStatusFlags_EditedInternal = 1 << 11, // Similar to ImGuiItemStatusFlags_Edited but bypassing ImGuiItemFlags_NoMarkEdited. // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -1028,7 +1041,7 @@ enum ImGuiInputTextFlagsPrivate_ { // [Internal] ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() - ImGuiInputTextFlags_MergedItem = 1 << 27, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. + ImGuiInputTextFlags_TempInput = 1 << 27, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 28, // For internal use by InputScalar() and TempInputScalar() }; @@ -1038,12 +1051,11 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_PressedOnClick = 1 << 4, // return true on click (mouse down event) ImGuiButtonFlags_PressedOnClickRelease = 1 << 5, // [Default] return true on click + release on same item <-- this is what the majority of Button are using ImGuiButtonFlags_PressedOnClickReleaseAnywhere = 1 << 6, // return true on click + release even if the release event is not done while hovering the item - ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release). Prior to 2026/03/20 this implied ImGuiButtonFlags_NoHoldingActiveId but they are separate now. ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8, // return true on double-click (default requires click+release) 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 @@ -1169,7 +1181,7 @@ struct IMGUI_API ImGuiComboPreviewData float BackupPrevLineTextBaseOffset; ImGuiLayoutType BackupLayout; - ImGuiComboPreviewData() { memset(this, 0, sizeof(*this)); } + ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); } }; // Stacked storage data for BeginGroup()/EndGroup() @@ -1203,19 +1215,20 @@ struct IMGUI_API ImGuiMenuColumns ImU16 OffsetMark; ImU16 Widths[4]; // Width of: Icon, Label, Shortcut, Mark (accumulators for current frame) - ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); } + ImGuiMenuColumns() { memset((void*)this, 0, sizeof(*this)); } void Update(float spacing, bool window_reappearing); float DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark); void CalcNextTotalWidth(bool update_offsets); }; // Internal temporary state for deactivating InputText() instances. +// Store as part of ImGuiDeactivatedItemData? struct IMGUI_API ImGuiInputTextDeactivatedState { ImGuiID ID; // widget id owning the text state (which just got deactivated) ImVector TextA; // text buffer - ImGuiInputTextDeactivatedState() { memset(this, 0, sizeof(*this)); } + ImGuiInputTextDeactivatedState() { memset((void*)this, 0, sizeof(*this)); } void ClearFreeMemory() { ID = 0; TextA.clear(); } }; @@ -1239,7 +1252,7 @@ struct IMGUI_API ImGuiInputTextState ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. ImGuiID ID; // widget id owning the text state int TextLen; // UTF-8 length of the string in TextA (in bytes) - const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). Field only set and valid _inside_ the call InputText() call. + const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). For _ReadOnly fields, pointer will be null outside the InputText() call. ImVector TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1). ImVector TextToRevertTo; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) ImVector CallbackTextBackup; // temporary storage for callback to support automatic reconcile of undo-stack @@ -1251,7 +1264,8 @@ struct IMGUI_API ImGuiInputTextState bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool CursorCenterY; // set when we want scrolling to be centered over the cursor position (while resizing a word-wrapping field) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection - bool Edited; // edited this frame + bool EditedBefore; // edited since activated + bool EditedThisFrame; // edited this frame bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. ImS8 LastMoveDirectionLR; // ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s int ReloadSelectionStart; @@ -1264,6 +1278,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(); @@ -1273,6 +1288,7 @@ struct IMGUI_API ImGuiInputTextState int GetCursorPos() const; int GetSelectionStart() const; int GetSelectionEnd() const; + void SetSelection(int start, int end); void SelectAll(); // Reload user buf (WIP #2890) @@ -1348,18 +1364,19 @@ struct ImGuiNextWindowData ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?) ImGuiWindowRefreshFlags RefreshFlagsVal; - ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } + ImGuiNextWindowData() { memset((void*)this, 0, sizeof(*this)); } inline void ClearFlags() { HasFlags = ImGuiNextWindowDataFlags_None; } }; enum ImGuiNextItemDataFlags_ { - ImGuiNextItemDataFlags_None = 0, - ImGuiNextItemDataFlags_HasWidth = 1 << 0, - ImGuiNextItemDataFlags_HasOpen = 1 << 1, - ImGuiNextItemDataFlags_HasShortcut = 1 << 2, - ImGuiNextItemDataFlags_HasRefVal = 1 << 3, - ImGuiNextItemDataFlags_HasStorageID = 1 << 4, + ImGuiNextItemDataFlags_None = 0, + ImGuiNextItemDataFlags_HasWidth = 1 << 0, + ImGuiNextItemDataFlags_HasOpen = 1 << 1, + ImGuiNextItemDataFlags_HasShortcut = 1 << 2, + ImGuiNextItemDataFlags_HasRefVal = 1 << 3, + ImGuiNextItemDataFlags_HasStorageID = 1 << 4, + ImGuiNextItemDataFlags_HasColorMarker = 1 << 5, }; struct ImGuiNextItemData @@ -1377,8 +1394,9 @@ struct ImGuiNextItemData ImU8 OpenCond; // Set by SetNextItemOpen() ImGuiDataTypeStorage RefVal; // Not exposed yet, for ImGuiInputTextFlags_ParseEmptyAsRefVal ImGuiID StorageId; // Set by SetNextItemStorageID() + ImU32 ColorMarker; // Set by SetNextItemColorMarker(). Not exposed yet, supported by DragScalar,SliderScalar and for ImGuiSliderFlags_ColorMarkers. - ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } + ImGuiNextItemData() { memset((void*)this, 0, sizeof(*this)); SelectionUserData = -1; } inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! }; @@ -1395,7 +1413,7 @@ struct ImGuiLastItemData ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set.. ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set.. - ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } + ImGuiLastItemData() { memset((void*)this, 0, sizeof(*this)); } }; // Store data emitted by TreeNode() for usage by TreePop() @@ -1428,7 +1446,7 @@ struct IMGUI_API ImGuiErrorRecoveryState short SizeOfBeginPopupStack; short SizeOfDisabledStack; - ImGuiErrorRecoveryState() { memset(this, 0, sizeof(*this)); } + ImGuiErrorRecoveryState() { memset((void*)this, 0, sizeof(*this)); } }; // Data saved for each window pushed into the stack @@ -1458,6 +1476,7 @@ struct ImGuiPtrOrIndex }; // Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions +// Also see ImGuiInputTextDeactivatedState which is an extension for this for InputText() struct ImGuiDeactivatedItemData { ImGuiID ID; @@ -1489,7 +1508,7 @@ struct ImGuiPopupData ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup - ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } + ImGuiPopupData() { memset((void*)this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } }; //----------------------------------------------------------------------------- @@ -1518,8 +1537,8 @@ typedef ImBitArray ImBitAr #define ImGuiKey_NavGamepadTweakFast ImGuiKey_GamepadR1 #define ImGuiKey_NavGamepadActivate (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceRight : ImGuiKey_GamepadFaceDown) #define ImGuiKey_NavGamepadCancel (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight) -#define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft -#define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp +#define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft // Toggle menu layer. Hold to enable Windowing. +#define ImGuiKey_NavGamepadContextMenu ImGuiKey_GamepadFaceUp // Open context menu (same as Shift+F10) enum ImGuiInputEventType { @@ -1570,7 +1589,7 @@ struct ImGuiInputEvent }; bool AddedByTestEngine; - ImGuiInputEvent() { memset(this, 0, sizeof(*this)); } + ImGuiInputEvent() { memset((void*)this, 0, sizeof(*this)); } }; // Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior. @@ -1602,7 +1621,7 @@ struct ImGuiKeyRoutingTable ImVector EntriesNext; // Double-buffer to avoid reallocation (could use a shared buffer) ImGuiKeyRoutingTable() { Clear(); } - void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); } + void Clear() { for (int n = 0; n < IM_COUNTOF(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); } }; // This extends ImGuiKeyData but only for named keys (legacy keys don't support the new features) @@ -1685,7 +1704,7 @@ struct ImGuiListClipperData int ItemsFrozen; ImVector Ranges; - ImGuiListClipperData() { memset(this, 0, sizeof(*this)); } + ImGuiListClipperData() { memset((void*)this, 0, sizeof(*this)); } void Reset(ImGuiListClipper* clipper) { ListClipper = clipper; StepNo = ItemsFrozen = 0; Ranges.resize(0); } }; @@ -1820,7 +1839,7 @@ struct IMGUI_API ImGuiTypingSelectState float LastRequestTime = 0.0f; bool SingleCharModeLock = false; // After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing. - ImGuiTypingSelectState() { memset(this, 0, sizeof(*this)); } + ImGuiTypingSelectState() { memset((void*)this, 0, sizeof(*this)); } void Clear() { SearchBuffer[0] = 0; SingleCharModeLock = false; } // We preserve remaining data for easier debugging }; @@ -1856,7 +1875,7 @@ struct ImGuiOldColumnData ImGuiOldColumnFlags Flags; // Not exposed ImRect ClipRect; - ImGuiOldColumnData() { memset(this, 0, sizeof(*this)); } + ImGuiOldColumnData() { memset((void*)this, 0, sizeof(*this)); } }; struct ImGuiOldColumns @@ -1877,7 +1896,7 @@ struct ImGuiOldColumns ImVector Columns; ImDrawListSplitter Splitter; - ImGuiOldColumns() { memset(this, 0, sizeof(*this)); } + ImGuiOldColumns() { memset((void*)this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -1902,10 +1921,11 @@ struct ImGuiBoxSelectState // Temporary/Transient data bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. + ImRect UnclipRects[2]; // Per-axis versions. ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) ImRect BoxSelectRectCurr; - ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); } + ImGuiBoxSelectState() { memset((void*)this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -1924,7 +1944,8 @@ struct IMGUI_API ImGuiMultiSelectTempData ImGuiMultiSelectFlags Flags; ImVec2 ScopeRectMin; ImVec2 BackupCursorMaxPos; - ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + //ImGuiSelectionUserData CurrSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + //ImGuiSelectionUserData PrevSubmittedItem; // Copy of previous submitted item data, used to merge output ranges. ImGuiID BoxSelectId; ImGuiKeyChord KeyMods; ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. @@ -2012,7 +2033,7 @@ enum ImGuiDockNodeState ImGuiDockNodeState_HostWindowVisible, }; -// sizeof() 156~192 +// sizeof() 176~216 struct IMGUI_API ImGuiDockNode { ImGuiID ID; @@ -2029,8 +2050,8 @@ struct IMGUI_API ImGuiDockNode ImVec2 Size; // Current size ImVec2 SizeRef; // [Split node only] Last explicitly written-to size (overridden when using a splitter affecting the node), used to calculate Size. ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y) - ImGuiWindowClass WindowClass; // [Root node only] ImU32 LastBgColor; + ImGuiWindowClass WindowClass; // [Root node only] ImGuiWindow* HostWindow; ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window. @@ -2106,7 +2127,7 @@ struct ImGuiDockContext ImVector Requests; ImVector NodesSettings; bool WantFullRebuild; - ImGuiDockContext() { memset(this, 0, sizeof(*this)); } + ImGuiDockContext() { memset((void*)this, 0, sizeof(*this)); } }; #endif // #ifdef IMGUI_HAS_DOCK @@ -2130,7 +2151,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 @@ -2147,7 +2168,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; } @@ -2184,7 +2205,7 @@ struct ImGuiWindowSettings bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) bool WantDelete; // Set to invalidate/delete the settings entry - ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); DockOrder = -1; } + ImGuiWindowSettings() { memset((void*)this, 0, sizeof(*this)); DockOrder = -1; } char* GetName() { return (char*)(this + 1); } }; @@ -2200,7 +2221,7 @@ struct ImGuiSettingsHandler void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf' void* UserData; - ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } + ImGuiSettingsHandler() { memset((void*)this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -2242,7 +2263,9 @@ struct ImGuiLocEntry // - See 'Demo->Configuration->Error Handling' and ImGuiIO definitions for details on error handling. // - Read https://github.com/ocornut/imgui/wiki/Error-Handling for details on error handling. #ifndef IM_ASSERT_USER_ERROR -#define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0) // Recoverable User Error +#define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR)) { if (ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } } while (0) // Recoverable User Error +#define IM_ASSERT_USER_ERROR_RET(_EXPR,_MSG) do { if (!(_EXPR)) { if (ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } return; } } while (0) // Recoverable User Error +#define IM_ASSERT_USER_ERROR_RETV(_EXPR,_RETV,_MSG) do { if (!(_EXPR)) { if (ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } return _RETV; } } while (0) // Recoverable User Error #endif // The error callback is currently not public, as it is expected that only advanced users will rely on it. @@ -2272,7 +2295,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 @@ -2289,7 +2313,7 @@ struct ImGuiDebugAllocInfo ImS16 LastEntriesIdx; // Current index in buffer ImGuiDebugAllocEntry LastEntriesBuf[6]; // Track last 6 frames that had allocations - ImGuiDebugAllocInfo() { memset(this, 0, sizeof(*this)); } + ImGuiDebugAllocInfo() { memset((void*)this, 0, sizeof(*this)); } }; struct ImGuiMetricsConfig @@ -2319,7 +2343,7 @@ struct ImGuiStackLevelInfo ImS8 DataType; // ImGuiDataType int DescOffset; // -1 or offset into parent's ResultsPathsBuf - ImGuiStackLevelInfo() { memset(this, 0, sizeof(*this)); DataType = -1; DescOffset = -1; } + ImGuiStackLevelInfo() { memset((void*)this, 0, sizeof(*this)); DataType = -1; DescOffset = -1; } }; struct ImGuiDebugItemPathQuery @@ -2332,7 +2356,7 @@ struct ImGuiDebugItemPathQuery ImGuiTextBuffer ResultsDescBuf; ImGuiTextBuffer ResultPathBuf; - ImGuiDebugItemPathQuery() { memset(this, 0, sizeof(*this)); } + ImGuiDebugItemPathQuery() { memset((void*)this, 0, sizeof(*this)); } }; // State for ID Stack tool queries @@ -2343,7 +2367,7 @@ struct ImGuiIDStackTool int LastActiveFrame; float CopyToClipboardLastTime; - ImGuiIDStackTool() { memset(this, 0, sizeof(*this)); LastActiveFrame = -1; OptHexEncodeNonAsciiChars = true; CopyToClipboardLastTime = -FLT_MAX; } + ImGuiIDStackTool() { memset((void*)this, 0, sizeof(*this)); LastActiveFrame = -1; OptHexEncodeNonAsciiChars = true; CopyToClipboardLastTime = -FLT_MAX; } }; //----------------------------------------------------------------------------- @@ -2361,9 +2385,11 @@ struct ImGuiContextHook ImGuiContextHookCallback Callback; void* UserData; - ImGuiContextHook() { memset(this, 0, sizeof(*this)); } + ImGuiContextHook() { memset((void*)this, 0, sizeof(*this)); } }; +typedef void (*ImGuiDemoMarkerCallback)(const char* file, int line, const char* section); + //----------------------------------------------------------------------------- // [SECTION] ImGuiContext (main Dear ImGui context) //----------------------------------------------------------------------------- @@ -2395,6 +2421,7 @@ struct ImGuiContext float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; ImGuiID WithinEndChildID; // Set within EndChild() + ImGuiID WithinEndPopupID; // Set within EndPopup() void* TestEngine; // Test engine user data // Inputs @@ -2517,6 +2544,7 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) + ImGuiItemFlags NavIdItemFlags; ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) @@ -2524,6 +2552,8 @@ struct ImGuiContext ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; + ImGuiID NavOpenContextMenuItemId; + ImGuiID NavOpenContextMenuWindowId; ImGuiID NavNextActivateId; // Set by ActivateItemByID(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Gamepad @@ -2546,7 +2576,7 @@ struct ImGuiContext ImGuiDir NavMoveDirForDebug; ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring. - ImRect NavScoringNoClipRect; // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted + ImRect NavScoringNoClipRect; // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted. Unset/invalid if inverted. int NavScoringDebugCount; // Metrics for debugging int NavTabbingDir; // Generally -1 or +1, 0 when tabbing without a nav id int NavTabbingCounter; // >0 when counting items for tabbing @@ -2563,10 +2593,15 @@ struct ImGuiContext bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. - // Navigation: Windowing (Ctrl+Tab for list, or Menu button + keys or directional pads to move/resize) + // Navigation: extra config options (will be made public eventually) + // - Tabbing (Tab, Shift+Tab) and Windowing (Ctrl+Tab, Ctrl+Shift+Tab) are enabled REGARDLESS of ImGuiConfigFlags_NavEnableKeyboard being set. + // - Ctrl+Tab is reconfigurable because it is the only shortcut that may be polled when no window are focused. It also doesn't work e.g. Web platforms. + bool ConfigNavEnableTabbing; // = true. Enable tabbing (Tab, Shift+Tab). PLEASE LET ME KNOW IF YOU USE THIS. bool ConfigNavWindowingWithGamepad; // = true. Enable Ctrl+Tab by holding ImGuiKey_GamepadFaceLeft (== ImGuiKey_NavGamepadMenu). When false, the button may still be used to toggle Menu layer. - ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828) + ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). Set to 0 to disable. For reconfiguration (see #4828) ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X) + + // Navigation: Windowing (Ctrl+Tab for list, or Menu button + keys or directional pads to move/resize) ImGuiWindow* NavWindowingTarget; // Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the fade-out can stay on it. ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the Ctrl+Tab contents @@ -2648,6 +2683,7 @@ struct ImGuiContext ImGuiInputTextDeactivatedState InputTextDeactivatedState; ImFontBaked InputTextPasswordFontBackupBaked; ImFontFlags InputTextPasswordFontBackupFlags; + ImGuiID InputTextReactivateId; // ID of InputText to reactivate on next frame (for io.ConfigInputTextEnterKeepActive behavior) ImGuiID TempInputId; // Temporary text input when using Ctrl+Click on a slider, etc. ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; @@ -2695,8 +2731,11 @@ struct ImGuiContext ImVector SettingsHandlers; // List of .ini settings handlers ImChunkStream SettingsWindows; // ImGuiWindow .ini settings entries ImChunkStream SettingsTables; // ImGuiTable .ini settings entries + + // Hooks ImVector Hooks; // Hooks for extensions (e.g. test engine) ImGuiID HookIdNext; // Next available HookId + ImGuiDemoMarkerCallback DemoMarkerCallback; // Localization const char* LocalizationTable[ImGuiLocKey_COUNT]; @@ -2771,6 +2810,8 @@ struct ImGuiContext // [SECTION] ImGuiWindowTempData, ImGuiWindow //----------------------------------------------------------------------------- +#define IMGUI_WINDOW_HARD_MIN_SIZE 4.0f + // Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. // (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..) // (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin) @@ -2825,6 +2866,7 @@ struct IMGUI_API ImGuiWindowTempData // Local parameters stacks // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. float ItemWidth; // Current item width (>0.0: width in pixels, <0.0: align xx pixels to the right of window). + float ItemWidthDefault; float TextWrapPos; // Current text wrap pos. ImVector ItemWidthStack; // Store item widths to restore (attention: .back() is not == ItemWidth) ImVector TextWrapPosStack; // Store text wrap pos to restore (attention: .back() is not == TextWrapPos) @@ -2923,7 +2965,6 @@ struct IMGUI_API ImGuiWindow int LastFrameActive; // Last frame number the window was Active. int LastFrameJustFocused; // Last frame number the window was made Focused. float LastTimeActive; // Last timestamp the window was Active (using float as we don't need high precision there) - float ItemWidthDefault; ImGuiStorage StateStorage; ImVector ColumnsStorage; float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() @@ -3012,7 +3053,7 @@ struct ImGuiTabItem ImGuiWindow* Window; // When TabItem is part of a DockNode's TabBar, we hold on to a window. int LastFrameVisible; int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance - float Offset; // Position relative to beginning of tab + float Offset; // Position relative to beginning of tab bar float Width; // Width currently displayed float ContentWidth; // Width of label + padding, stored during BeginTabItem() call (misnamed as "Content" would normally imply width of label only) float RequestedWidth; // Width optionally requested by caller, -1.0f is unused @@ -3021,7 +3062,7 @@ struct ImGuiTabItem ImS16 IndexDuringLayout; // Index only used during TabBarLayout(). Tabs gets reordered so 'Tabs[n].IndexDuringLayout == n' but may mismatch during additions. bool WantClose; // Marked as closed by SetTabItemClosed() - ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } + ImGuiTabItem() { memset((void*)this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } }; // Storage for a tab bar (sizeof() 160 bytes) @@ -3033,6 +3074,7 @@ struct IMGUI_API ImGuiTabBar ImGuiID ID; // Zero for tab-bars used by docking ImGuiID SelectedTabId; // Selected tab/window ImGuiID NextSelectedTabId; // Next selected tab/window. Will also trigger a scrolling animation + ImGuiID NextScrollToTabId; ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for Ctrl+Tab preview) int CurrFrameVisible; int PrevFrameVisible; @@ -3125,7 +3167,7 @@ struct ImGuiTableColumn ImGuiTableColumn() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); StretchWeight = WidthRequest = -1.0f; NameOffset = -1; DisplayOrder = IndexWithinEnabledSet = -1; @@ -3249,8 +3291,9 @@ struct IMGUI_API ImGuiTable ImGuiTableColumnIdx ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. ImGuiTableColumnIdx LastResizedColumn; // Index of column being resized from previous frame. ImGuiTableColumnIdx HeldHeaderColumn; // Index of column header being held. + ImGuiTableColumnIdx LastHeldHeaderColumn; // Index of column header being held from previous frame. ImGuiTableColumnIdx ReorderColumn; // Index of column being reordered. (not cleared) - ImGuiTableColumnIdx ReorderColumnDir; // -1 or +1 + ImGuiTableColumnIdx ReorderColumnDstOrder; // Requested display order of column being reordered. ImGuiTableColumnIdx LeftMostEnabledColumn; // Index of left-most non-hidden column. ImGuiTableColumnIdx RightMostEnabledColumn; // Index of right-most non-hidden column. ImGuiTableColumnIdx LeftMostStretchedColumn; // Index of left-most stretched column. @@ -3286,7 +3329,7 @@ struct IMGUI_API ImGuiTable bool MemoryCompacted; bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis - ImGuiTable() { memset(this, 0, sizeof(*this)); LastFrameActive = -1; } + ImGuiTable() { memset((void*)this, 0, sizeof(*this)); LastFrameActive = -1; } ~ImGuiTable() { IM_FREE(RawData); } }; @@ -3315,7 +3358,7 @@ struct IMGUI_API ImGuiTableTempData float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable() int HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable() - ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; } + ImGuiTableTempData() { memset((void*)this, 0, sizeof(*this)); LastTimeActive = -1.0f; } }; // sizeof() ~ 16 @@ -3352,7 +3395,7 @@ struct ImGuiTableSettings ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) - ImGuiTableSettings() { memset(this, 0, sizeof(*this)); } + ImGuiTableSettings() { memset((void*)this, 0, sizeof(*this)); } ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } }; @@ -3370,6 +3413,7 @@ namespace ImGui // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. IMGUI_API ImGuiIO& GetIO(ImGuiContext* ctx); IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx); + inline float GetScale() { ImGuiContext& g = *GImGui; return g.Style._MainScale; } // FIXME-DPI: I don't want to formalize this just yet. Because reasons. Please don't use. inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); @@ -3407,7 +3451,7 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing - IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL: DO NOT USE YET. + IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL. IMGUI_API void UnregisterUserTexture(ImTextureData* tex); IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); @@ -3426,6 +3470,12 @@ namespace ImGui IMGUI_API void Initialize(); IMGUI_API void Shutdown(); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + // Context name & generic context hooks + IMGUI_API void SetContextName(ImGuiContext* ctx, const char* name); + IMGUI_API ImGuiID AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook); + IMGUI_API void RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_to_remove); + IMGUI_API void CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType type); + // NewFrame IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs); IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos); @@ -3436,11 +3486,6 @@ namespace ImGui IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); - // Generic context hooks - IMGUI_API ImGuiID AddContextHook(ImGuiContext* context, const ImGuiContextHook* hook); - IMGUI_API void RemoveContextHook(ImGuiContext* context, ImGuiID hook_to_remove); - IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); - // Viewports IMGUI_API void TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos, const ImVec2& old_size, const ImVec2& new_size); IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); @@ -3484,7 +3529,6 @@ namespace ImGui // Basic Accessors inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } - inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.ItemFlags; } inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); @@ -3525,6 +3569,7 @@ namespace ImGui // Childs IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags); + IMGUI_API ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window); // Popups, Modals IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); @@ -3540,6 +3585,9 @@ namespace ImGui IMGUI_API ImGuiWindow* FindBlockingModal(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); + IMGUI_API ImGuiMouseButton GetMouseButtonFromPopupFlags(ImGuiPopupFlags flags); + IMGUI_API bool IsPopupOpenRequestForItem(ImGuiPopupFlags flags, ImGuiID id); + IMGUI_API bool IsPopupOpenRequestForWindow(ImGuiPopupFlags flags); // Tooltips IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); @@ -3627,7 +3675,7 @@ namespace ImGui IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } @@ -3730,6 +3778,7 @@ namespace ImGui // We don't use the ID Stack for this as it is common to want them separate. IMGUI_API void PushFocusScope(ImGuiID id); IMGUI_API void PopFocusScope(); + IMGUI_API bool IsInNavFocusRoute(ImGuiID focus_scope_id); inline ImGuiID GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; } // Focus scope we are outputting into, set by PushFocusScope() // Drag and Drop @@ -3739,7 +3788,7 @@ namespace ImGui IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); IMGUI_API void RenderDragDropTargetRectForItem(const ImRect& bb); - IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb); + IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding); // Typing-Select API // (provide Windows Explorer style "select items by typing partial name" + "cycle through items by typing same letter" feature) @@ -3796,12 +3845,14 @@ namespace ImGui IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); + IMGUI_API void TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect); IMGUI_API void TableDrawBorders(ImGuiTable* table); IMGUI_API void TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display); IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable* table); IMGUI_API void TableMergeDrawChannels(ImGuiTable* table); inline ImGuiTableInstanceData* TableGetInstanceData(ImGuiTable* table, int instance_no) { if (instance_no == 0) return &table->InstanceDataFirst; return &table->InstanceDataExtra[instance_no - 1]; } inline ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no) { return TableGetInstanceData(table, instance_no)->TableInstanceID; } + IMGUI_API void TableFixDisplayOrder(ImGuiTable* table); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column); @@ -3817,6 +3868,8 @@ namespace ImGui IMGUI_API float TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n); IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); + IMGUI_API void TableSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order); + IMGUI_API void TableQueueSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order); IMGUI_API void TableRemove(ImGuiTable* table); IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table); @@ -3867,6 +3920,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(const ImRect& bb, ImU32 col, 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 @@ -3881,7 +3935,7 @@ namespace ImGui 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 RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, 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 fill_x0, float fill_x1, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); IMGUI_API ImDrawFlags CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold); @@ -3908,6 +3962,7 @@ namespace ImGui IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners IMGUI_API ImGuiID GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir); + IMGUI_API void ExtendHitBoxWhenNearViewportEdge(ImGuiWindow* window, ImRect* bb, float threshold, ImGuiAxis axis); // Widgets low-level behaviors IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); @@ -3920,15 +3975,14 @@ namespace ImGui IMGUI_API void TreeNodeDrawLineToChildNode(const ImVec2& target_pos); IMGUI_API void TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data); IMGUI_API void TreePushOverrideID(ImGuiID id); - IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); IMGUI_API bool TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags); // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging. // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). // e.g. " extern template IMGUI_API float RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " - template IMGUI_API float ScaleRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size); - template IMGUI_API T ScaleValueFromRatioT(ImGuiDataType data_type, float t, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size); + template IMGUI_API float ScaleRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, float logarithmic_zero_epsilon, float zero_deadzone_size); + template IMGUI_API T ScaleValueFromRatioT(ImGuiDataType data_type, float t, T v_min, T v_max, float logarithmic_zero_epsilon, float zero_deadzone_size); template IMGUI_API bool DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, T v_min, T v_max, const char* format, ImGuiSliderFlags flags); template IMGUI_API bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, T v_min, T v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); template IMGUI_API T RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v); @@ -3946,9 +4000,9 @@ namespace ImGui // InputText IMGUI_API bool InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); IMGUI_API void InputTextDeactivateHook(ImGuiID id); - IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags); + IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL); - inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); } + inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return g.ActiveId == id && g.TempInputId == id; } inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data); inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field. @@ -3957,6 +4011,7 @@ namespace ImGui IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags); + inline void SetNextItemColorMarker(ImU32 col) { ImGuiContext& g = *GImGui; g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasColorMarker; g.NextItemData.ColorMarker = col; } // Plot IMGUI_API int PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg); @@ -3981,6 +4036,9 @@ namespace ImGui IMGUI_API bool BeginErrorTooltip(); IMGUI_API void EndErrorTooltip(); + // Demo Doc Marker for e.g. imgui_explorer + IMGUI_API void DemoMarker(const char* file, int line, const char* section); + // Debug Tools IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free IMGUI_API void DebugDrawCursorPos(ImU32 col = IM_COL32(255, 0, 0, 255)); @@ -3994,13 +4052,14 @@ namespace ImGui IMGUI_API bool DebugBreakButton(const char* label, const char* description_of_location); IMGUI_API void DebugBreakButtonTooltip(bool keyboard_only, const char* description_of_location); IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); + IMGUI_API ImU64 DebugTextureIDToU64(ImTextureID tex_id); IMGUI_API void DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end); IMGUI_API void DebugNodeColumns(ImGuiOldColumns* columns); IMGUI_API void DebugNodeDockNode(ImGuiDockNode* node, const char* label); IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); - IMGUI_API void DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask); + IMGUI_API void DebugNodeFontGlyphsForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); @@ -4059,14 +4118,14 @@ struct ImFontLoader // FIXME: At this point the two other types of buffers may be managed by core to be consistent? size_t FontBakedSrcLoaderDataSize; - ImFontLoader() { memset(this, 0, sizeof(*this)); } + ImFontLoader() { memset((void*)this, 0, sizeof(*this)); } }; #ifdef IMGUI_ENABLE_STB_TRUETYPE IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); #endif #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper. +typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92.0] The types are not actually compatible but we provide this as a compile-time error report helper. #endif //----------------------------------------------------------------------------- @@ -4156,7 +4215,7 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; - ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } + ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } }; IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); @@ -4185,6 +4244,7 @@ IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, I IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasFontRebuildOutput(ImFontAtlas* atlas, ImFont* font); IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames); IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density); @@ -4216,6 +4276,7 @@ IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); +IMGUI_API void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h); IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); diff --git a/src/Libs/imgui/imgui_tables.cpp b/src/Libs/imgui/imgui_tables.cpp index 41fdf6a..135410a 100644 --- a/src/Libs/imgui/imgui_tables.cpp +++ b/src/Libs/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (tables and columns code) /* @@ -240,6 +240,8 @@ Index of this file: #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' #pragma GCC diagnostic ignored "-Wstrict-overflow" #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value +#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif //----------------------------------------------------------------------------- @@ -335,7 +337,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[]. const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; const ImVec2 avail_size = GetContentRegionAvail(); - const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f)); + const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window ? ImMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) : 0.0f)); const ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); const bool outer_window_is_measuring_size = (outer_window->AutoFitFramesX > 0) || (outer_window->AutoFitFramesY > 0); // Doesn't apply to AlwaysAutoResize windows! if (use_child_window && IsClippedEx(outer_rect, 0) && !outer_window_is_measuring_size) @@ -564,9 +566,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG const int old_columns_count = table->Columns.size(); if (old_columns_count != 0 && old_columns_count != columns_count) { - // Attempt to preserve width on column count change (#4046) + // Attempt to preserve width and other settings on column count/specs change (#4046) old_columns_to_preserve = table->Columns.Data; - old_columns_raw_data = table->RawData; + old_columns_raw_data = table->RawData; // Free at end of function table->RawData = NULL; } if (table->RawData == NULL) @@ -584,7 +586,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934) table->InstanceInteracted = -1; table->ContextPopupColumn = -1; - table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1; + table->ReorderColumn = table->ReorderColumnDstOrder = table->ResizedColumn = table->LastResizedColumn = -1; table->AutoFitSingleColumn = -1; table->HoveredColumnBody = table->HoveredColumnBorder = -1; for (int n = 0; n < columns_count; n++) @@ -592,7 +594,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG ImGuiTableColumn* column = &table->Columns[n]; if (old_columns_to_preserve && n < old_columns_count) { - // FIXME: We don't attempt to preserve column order in this path. *column = old_columns_to_preserve[n]; } else @@ -602,8 +603,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG column->WidthAuto = width_auto; column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker column->IsEnabled = column->IsUserEnabled = column->IsUserEnabledNextFrame = true; + column->DisplayOrder = (ImGuiTableColumnIdx)n; } - column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n; + table->DisplayOrderToIndex[n] = column->DisplayOrder; } } if (old_columns_raw_data) @@ -698,37 +700,23 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) } // Handle reordering request - // Note: we don't clear ReorderColumn after handling the request. + //// Note: we don't clear ReorderColumn after handling the request (FIXME: clarify why or add a test). if (table->InstanceCurrent == 0) { - if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) - table->ReorderColumn = -1; + table->LastHeldHeaderColumn = table->HeldHeaderColumn; table->HeldHeaderColumn = -1; - if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) + if (table->ReorderColumn != -1 && table->ReorderColumnDstOrder != -1) { - // We need to handle reordering across hidden columns. - // In the configuration below, moving C to the right of E will lead to: - // ... C [D] E ---> ... [D] E C (Column name/index) - // ... 2 3 4 ... 2 3 4 (Display order) - const int reorder_dir = table->ReorderColumnDir; - IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); - IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); - ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; - ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn]; - IM_UNUSED(dst_column); - const int src_order = src_column->DisplayOrder; - const int dst_order = dst_column->DisplayOrder; - src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order; - for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) - table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir; - IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); - - // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former. - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; - table->ReorderColumnDir = 0; - table->IsSettingsDirty = true; + TableSetColumnDisplayOrder(table, table->ReorderColumn, table->ReorderColumnDstOrder); + table->ReorderColumnDstOrder = -1; } + + // Release + ImGuiContext& g = *GImGui; + if (g.ActiveId == 0) // FIXME: Need to revisit. See 38f5e5a. + table->ReorderColumn = -1; + //if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) + // table->ReorderColumn = -1; } // Handle display order reset request @@ -741,6 +729,63 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) } } +// Apply immediately. See TableQueueSetColumnDisplayOrder() for additional checks/constraints. +void ImGui::TableSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order) +{ + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + IM_ASSERT(dst_order >= 0 && dst_order < table->ColumnsCount); + + ImGuiTableColumn* src_column = &table->Columns[column_n]; + const int src_order = src_column->DisplayOrder; + if (src_order == dst_order) + return; + const int reorder_dir = (dst_order < src_order) ? -1 : +1; + + src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order; + for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) + table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir; + //IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); + + // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former. + // FIXME-OPT: If this is called multiple times we'd effectively have a O(N^2) thing going on. + for (int n = 0; n < table->ColumnsCount; n++) + table->DisplayOrderToIndex[table->Columns[n].DisplayOrder] = (ImGuiTableColumnIdx)n; + table->IsSettingsDirty = true; +} + +static int TableGetMaxDisplayOrderAllowed(ImGuiTable* table, int src_order, int dst_order) +{ + dst_order = ImClamp(dst_order, 0, table->ColumnsCount - 1); + if (src_order == dst_order) + return dst_order; + + // Cannot cross over the frozen column limit when interactively reordering. + // TableSetupScrollFreeze() enforce a display order range for frozen columns. Reordering across the frozen column barrier is illegal and will be undone. + if (table->FreezeColumnsRequest > 0) + dst_order = (src_order < table->FreezeColumnsRequest) ? ImMin(dst_order, (int)table->FreezeColumnsRequest - 1) : ImMax(dst_order, (int)table->FreezeColumnsRequest); + + // Cannot cross over a column with the ImGuiTableColumnFlags_NoReorder flag. + int reorder_dir = (src_order < dst_order) ? +1 : -1; + for (int order_n = src_order; (src_order < dst_order && order_n <= dst_order) || (dst_order < src_order && order_n >= dst_order); order_n += reorder_dir) + if (table->Columns[table->DisplayOrderToIndex[order_n]].Flags & ImGuiTableColumnFlags_NoReorder) + { + dst_order = (order_n == src_order) ? src_order : order_n - reorder_dir; + break; + } + return dst_order; +} + +// Reorder requested by user interaction. +void ImGui::TableQueueSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order) +{ + const int src_order = table->Columns[column_n].DisplayOrder; + table->ReorderColumn = (ImGuiTableColumnIdx)column_n; + table->ReorderColumnDstOrder = (ImGuiTableColumnIdx)-1; + dst_order = TableGetMaxDisplayOrderAllowed(table, src_order, dst_order); + if (dst_order != src_order) + table->ReorderColumnDstOrder = (ImGuiTableColumnIdx)dst_order; +} + // Adjust flags: default width mode + stretch columns are not allowed when auto extending static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in) { @@ -1048,7 +1093,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping. - int visible_n = 0; bool has_at_least_one_column_requesting_output = false; bool offset_x_frozen = (table->FreezeColumnsCount > 0); float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; @@ -1063,7 +1107,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Initial nav layer: using FreezeRowsCount, NOT FreezeRowsRequest, so Header line changes layer when frozen column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : (ImGuiNavLayer)table->NavLayer); - if (offset_x_frozen && table->FreezeColumnsCount == visible_n) + if (offset_x_frozen && table->FreezeColumnsCount == order_n) { offset_x += work_rect.Min.x - table->OuterRect.Min.x; offset_x_frozen = false; @@ -1183,11 +1227,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->CannotSkipItemsQueue >>= 1; } - if (visible_n < table->FreezeColumnsCount) + if (order_n < table->FreezeColumnsCount) host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x); offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; - visible_n++; } // In case the table is visible (e.g. decorations) but all columns clipped, we keep a column visible. @@ -1268,19 +1311,37 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 13] Setup inner window decoration size (for scrolling / nav tracking to properly take account of frozen rows/columns) if (table->FreezeColumnsRequest > 0) - table->InnerWindow->DecoInnerSizeX1 = table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsRequest - 1]].MaxX - table->OuterRect.Min.x; + table->InnerWindow->DecoInnerSizeX1 = table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsRequest - 1]].MaxX - table->OuterRect.Min.x; // FIXME-FROZEN if (table->FreezeRowsRequest > 0) table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight; table_instance->LastFrozenHeight = 0.0f; - // Initial state ImGuiWindow* inner_window = table->InnerWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (bs->Window == inner_window && bs->UnclipMode) + TableApplyExternalUnclipRect(table, bs->UnclipRect); + + // Initial state if (table->Flags & ImGuiTableFlags_NoClip) table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); else inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect? } +// When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields. +void ImGui::TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect) +{ + if (rect.IsInverted()) + return; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsRequestOutput) + if (rect.Overlaps(ImRect(column->MinX, table->WorkRect.Min.y, column->MaxX, FLT_MAX))) + column->IsRequestOutput = true; + } +} + // Process hit-testing on resizing borders. Actual size change will be applied in EndTable() // - Set table->HoveredColumnBorder with a short delay/timer to reduce visual feedback noise. void ImGui::TableUpdateBorders(ImGuiTable* table) @@ -1348,11 +1409,7 @@ void ImGui::EndTable() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "EndTable() call should only be done while in BeginTable() scope!"); - return; - } + IM_ASSERT_USER_ERROR_RET(table != NULL, "EndTable() call should only be done while in BeginTable() scope!"); // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) @@ -1383,7 +1440,7 @@ void ImGui::EndTable() inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize; inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize; inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos; - const float inner_content_max_y = table->RowPosY2; + const float inner_content_max_y = ImCeil(table->RowPosY2); // Rounding final position is important as we currently don't round row height ('Demo->Tables->Outer Size' demo uses non-integer heights) IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y); if (inner_window != outer_window) inner_window->DC.CursorMaxPos.y = inner_content_max_y; @@ -1398,12 +1455,12 @@ void ImGui::EndTable() if (table->Flags & ImGuiTableFlags_ScrollX) { const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; - float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x; + float max_pos_x = inner_window->DC.CursorMaxPos.x; if (table->RightMostEnabledColumn != -1) max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); if (table->ResizedColumn != -1) max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); - table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; + inner_window->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; } // Pop clipping rect @@ -1512,7 +1569,7 @@ void ImGui::EndTable() } else { - table->InnerWindow->DC.TreeDepth--; + inner_window->DC.TreeDepth--; ItemSize(table->OuterRect.GetSize()); ItemAdd(table->OuterRect, 0); } @@ -1527,13 +1584,12 @@ void ImGui::EndTable() } else if (temp_data->UserOuterSize.x <= 0.0f) { - // Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block - // - Checking for ImGuiTableFlags_ScrollX/ScrollY flag makes us a frame ahead when disabling those flags. - // - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback. - const float inner_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; // Slightly misleading name but used for code symmetry with inner_content_max_y - const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.x : 0.0f); - outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, inner_content_max_x + decoration_size - temp_data->UserOuterSize.x); - outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, inner_content_max_x + decoration_size)); + // Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352 + // - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer' + const float outer_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; + const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((inner_window != outer_window) ? inner_window->ScrollbarSizes.x : 0.0f); + outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data->UserOuterSize.x); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, outer_content_max_x + decoration_size)); } else { @@ -1541,9 +1597,12 @@ void ImGui::EndTable() } if (temp_data->UserOuterSize.y <= 0.0f) { - const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.y : 0.0f; - outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y); - outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y + decoration_size)); + // (same comment as above) + const float outer_content_size_y = (inner_window == outer_window) ? (inner_content_max_y - table->InnerRect.Min.y) : (inner_content_max_y - inner_window->DC.CursorStartPos.y); + const float outer_content_max_y = table->OuterRect.Min.y + outer_content_size_y; + const float decoration_size = (inner_window != outer_window ? inner_window->ScrollbarSizes.y : 0.0f); + outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data->UserOuterSize.y); + outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, outer_content_max_y + decoration_size)); } else { @@ -1601,18 +1660,10 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } - IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); + IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!"); + IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); // Table layout is locked when submitting a row or when calling BeginMultiSelect() with box-select. IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); - if (table->DeclColumnsCount >= table->ColumnsCount) - { - IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, "Called TableSetupColumn() too many times!"); - return; - } ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; table->DeclColumnsCount++; @@ -1620,7 +1671,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa. // Give a grace to users of ImGuiTableFlags_ScrollX. if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0) - IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column."); + IM_ASSERT_USER_ERROR_RET(init_width_or_weight <= 0.0f, "TableSetupColumn(): can only specify width/weight if sizing policy is set explicitly in either Table or Column."); // When passing a width automatically enforce WidthFixed policy // (whereas TableSetupColumnFlags would default to WidthAuto if table is not resizable) @@ -1662,12 +1713,8 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } - IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); + IM_ASSERT(table->IsLayoutLocked == false && "TableSetupColumn(): need to call before first row!"); IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit @@ -1676,18 +1723,6 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0; table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b - - // Ensure frozen columns are ordered in their section. We still allow multiple frozen columns to be reordered. - // FIXME-TABLE: This work for preserving 2143 into 21|43. How about 4321 turning into 21|43? (preserve relative order in each section) - for (int column_n = 0; column_n < table->FreezeColumnsRequest; column_n++) - { - int order_n = table->DisplayOrderToIndex[column_n]; - if (order_n != column_n && order_n >= table->FreezeColumnsRequest) - { - ImSwap(table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder, table->Columns[table->DisplayOrderToIndex[column_n]].DisplayOrder); - ImSwap(table->DisplayOrderToIndex[order_n], table->DisplayOrderToIndex[column_n]); - } - } } //----------------------------------------------------------------------------- @@ -1743,11 +1778,7 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above if (column_n < 0) column_n = table->CurrentColumn; @@ -1825,12 +1856,8 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); IM_ASSERT(target != ImGuiTableBgTarget_None); - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } if (color == IM_COL32_DISABLE) color = 0; @@ -2047,11 +2074,11 @@ void ImGui::TableEndRow(ImGuiTable* table) // Draw top border if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), top_border_col, border_size); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, border_size); // Draw bottom border at the row unfreezing mark (always strong) if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, border_size); } // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) @@ -2128,11 +2155,7 @@ bool ImGui::TableSetColumnIndex(int column_n) { if (table->CurrentColumn != -1) TableEndCell(table); - if ((column_n >= 0 && column_n < table->ColumnsCount) == false) - { - IM_ASSERT_USER_ERROR(column_n >= 0 && column_n < table->ColumnsCount, "TableSetColumnIndex() invalid column index!"); - return false; - } + IM_ASSERT_USER_ERROR_RETV(column_n >= 0 && column_n < table->ColumnsCount, false, "TableSetColumnIndex() invalid column index!"); TableBeginCell(table, column_n); } @@ -2458,6 +2481,11 @@ void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) // - TableDrawBorders() [Internal] //------------------------------------------------------------------------- + +// FIXME: This could be abstracted and merged with PushColumnsBackground(), by creating a generic struct +// with storage for backup cliprect + backup channel + storage for splitter pointer, new clip rect. +// This would slightly simplify caller code. + // Bg2 is used by Selectable (and possibly other widgets) to render to the background. // Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect. void ImGui::TablePushBackgroundChannel() @@ -2619,7 +2647,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) const int size_for_masks_bitarrays_one = (int)ImBitArrayGetStorageSizeInBytes(max_draw_channels); g.TempBuffer.reserve(size_for_masks_bitarrays_one * 5); memset(g.TempBuffer.Data, 0, size_for_masks_bitarrays_one * 5); - for (int n = 0; n < IM_ARRAYSIZE(merge_groups); n++) + for (int n = 0; n < IM_COUNTOF(merge_groups); n++) merge_groups[n].ChannelsMask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * n)); ImBitArrayPtr remaining_mask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * 4)); @@ -2676,7 +2704,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) // [DEBUG] Display merge groups #if 0 if (g.IO.KeyShift) - for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + for (int merge_group_n = 0; merge_group_n < IM_COUNTOF(merge_groups); merge_group_n++) { MergeGroup* merge_group = &merge_groups[merge_group_n]; if (merge_group->ChannelsCount == 0) @@ -2704,7 +2732,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS); //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect; ImRect host_rect = table->HostClipRect; - for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + for (int merge_group_n = 0; merge_group_n < IM_COUNTOF(merge_groups); merge_group_n++) { if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) { @@ -2827,7 +2855,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if ((table->Flags & (ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_NoBordersInBody)) == 0) draw_y2 = draw_y2_body; if (draw_y2 > draw_y1) - inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), TableGetColumnBorderCol(table, order_n, column_n), border_size); + inner_drawlist->AddLineV(column->MaxX, draw_y1, draw_y2, TableGetColumnBorderCol(table, order_n, column_n), border_size); } } @@ -2844,17 +2872,17 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const ImU32 outer_col = table->BorderColorStrong; if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { - inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterV) { - inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); - inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Max.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { - inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); - inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y, outer_col, border_size); } } if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) @@ -2862,7 +2890,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw bottom-most row border between it is above outer border. const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) - inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); + inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size); } inner_drawlist->PopClipRect(); @@ -3113,11 +3141,7 @@ void ImGui::TableHeadersRow() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. @@ -3134,7 +3158,7 @@ void ImGui::TableHeadersRow() const int columns_count = TableGetColumnCount(); for (int column_n = 0; column_n < columns_count; column_n++) { - if (!TableSetColumnIndex(column_n)) + if (!TableSetColumnIndex(column_n) && table->LastHeldHeaderColumn != column_n) continue; // Push an id to allow empty/unnamed headers. This is also idiomatic as it ensure there is a consistent ID path to access columns (for e.g. automation) @@ -3162,12 +3186,7 @@ void ImGui::TableHeader(const char* label) return; ImGuiTable* table = g.CurrentTable; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } - + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -3176,7 +3195,7 @@ void ImGui::TableHeader(const char* label) if (label == NULL) label = ""; const char* label_end = FindRenderedTextEnd(label); - ImVec2 label_size = CalcTextSize(label, label_end, true); + ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 label_pos = window->DC.CursorPos; // If we already got a row height, there's use that. @@ -3197,7 +3216,7 @@ void ImGui::TableHeader(const char* label) sort_arrow = true; if (column->SortOrder > 0) { - ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1); + ImFormatString(sort_order_suf, IM_COUNTOF(sort_order_suf), "%d", column->SortOrder + 1); w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; } } @@ -3242,21 +3261,19 @@ void ImGui::TableHeader(const char* label) // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) { - // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x - table->ReorderColumn = (ImGuiTableColumnIdx)column_n; + // - While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x + // - We need to handle reordering across hidden columns. + // In the configuration below, moving C to the right of E will lead to: + // ... C [D] E ---> ... [D] E C (Column name/index) + // ... 2 3 4 ... 2 3 4 (Display order) + // - The other constraints are enforced by TableQueueSetColumnDisplayOrder() which might early out. table->InstanceInteracted = table->InstanceCurrent; - - // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL) - if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) - if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) - table->ReorderColumnDir = -1; + TableQueueSetColumnDisplayOrder(table, column_n, prev_column->DisplayOrder); if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) if (ImGuiTableColumn* next_column = (column->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL) - if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) - if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) - table->ReorderColumnDir = +1; + TableQueueSetColumnDisplayOrder(table, column_n, next_column->DisplayOrder); } // Sort order arrow @@ -3295,8 +3312,10 @@ void ImGui::TableHeader(const char* label) SetItemTooltip("%.*s", (int)(label_end - label), label); // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden - if (IsMouseReleased(1) && IsItemHovered()) + if (IsPopupOpenRequestForItem(ImGuiPopupFlags_None, id)) TableOpenContextMenu(column_n); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); } // Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets. @@ -3312,7 +3331,7 @@ void ImGui::TableAngledHeadersRow() // Which column needs highlight? const ImGuiID row_id = GetID("##AngledHeaders"); ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); - int highlight_column_n = table->HighlightColumnHeader; + int highlight_column_n = (table->LastHeldHeaderColumn != -1) ? table->LastHeldHeaderColumn : table->HighlightColumnHeader; if (highlight_column_n == -1 && table->HoveredColumnBody != -1) if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) highlight_column_n = table->HoveredColumnBody; @@ -3342,11 +3361,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ImGuiTable* table = g.CurrentTable; ImGuiWindow* window = g.CurrentWindow; ImDrawList* draw_list = window->DrawList; - if (table == NULL) - { - IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); - return; - } + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); IM_ASSERT(table->CurrentRow == -1 && "Must be first row"); if (max_label_width == 0.0f) @@ -3370,13 +3385,14 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); // vector from bottom-left to top-left, and from bottom-right to top-right // Declare row, override and draw our own background + // FIXME-TABLE: Generally broken when overlapping frozen columns limit. TableNextRow(ImGuiTableRowFlags_Headers, row_height); TableNextColumn(); const ImRect row_r(table->WorkRect.Min.x, table->BgClipRect.Min.y, table->WorkRect.Max.x, table->RowPosY2); table->DrawSplitter->SetCurrentChannel(draw_list, TABLE_DRAW_CHANNEL_BG0); float clip_rect_min_x = table->BgClipRect.Min.x; if (table->FreezeColumnsCount > 0) - clip_rect_min_x = ImMax(clip_rect_min_x, table->Columns[table->FreezeColumnsCount - 1].MaxX); + clip_rect_min_x = ImMax(clip_rect_min_x, table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsCount - 1]].MaxX); TableSetBgColor(ImGuiTableBgTarget_RowBg0, 0); // Cancel PushClipRect(table->BgClipRect.Min, table->BgClipRect.Max, false); // Span all columns draw_list->AddRectFilled(ImVec2(table->BgClipRect.Min.x, row_r.Min.y), ImVec2(table->BgClipRect.Max.x, row_r.Max.y), GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color. @@ -3512,6 +3528,36 @@ bool ImGui::TableBeginContextMenuPopup(ImGuiTable* table) return false; } +// FIXME: Copied from MenuItem() for the purpose of being able to pass _SelectOnRelease (#9312) +static bool MenuItemForColumnReorder(const char* label, bool selected, bool enabled) +{ + using namespace ImGui; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImGuiMenuColumns* offsets = &window->DC.MenuColumns; + float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); + float min_w = offsets->DeclColumns(0.0f, label_size.x, 0.0f, checkmark_w); // Feedback for next frame + float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + + ImGuiID id = GetID(label); + ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SpanAvailWidth; + if (g.ActiveId == id) + selectable_flags |= ImGuiSelectableFlags_Highlight; // Stays highlighted while dragging. + const bool has_been_moved = (g.ActiveId == id) && g.ActiveIdHasBeenEditedBefore; // But disable toggling once moved. + + BeginDisabled(!enabled); // Don't use ImGuiSelectableFlags_Disabled so that Check mark is also affected. + bool ret = Selectable(label, false, selectable_flags, ImVec2(min_w, label_size.y)) && !has_been_moved; // Can't use IsMouseDragging(0) as button is released already. + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && selected) + RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); + EndDisabled(); + + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + return ret; +} + // Output context menu into current window (generally a popup) // FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? // Sections to display are pulled from 'flags_for_section_to_display', which is typically == table->Flags. @@ -3528,17 +3574,17 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags return; bool want_separator = false; - const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; - ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL; + const int context_column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; + ImGuiTableColumn* context_column = (context_column_n != -1) ? &table->Columns[context_column_n] : NULL; // Sizing if (flags_for_section_to_display & ImGuiTableFlags_Resizable) { - if (column != NULL) + if (context_column != NULL) { - const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled; + const bool can_resize = !(context_column->Flags & ImGuiTableColumnFlags_NoResize) && context_column->IsEnabled; if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableSizeOne), NULL, false, can_resize)) // "###SizeOne" - TableSetColumnWidthAutoSingle(table, column_n); + TableSetColumnWidthAutoSingle(table, context_column_n); } const char* size_all_desc; @@ -3587,23 +3633,47 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags Separator(); want_separator = true; + // While reordering: we calculate min/max allowed range once here so we can avoid a O(N log N) in the loop (because the query itself does a sweep scan). + // This assume that reordering constraints output a single range, otherwise would need to either call TableGetMaxDisplayOrderAllowed() for each item below, or cache this once per frame into columns. + const bool is_reordering = (g.ActiveId != 0 && g.ActiveIdWindow == g.CurrentWindow && table->ReorderColumn != -1 && g.ActiveIdHasBeenEditedBefore); // FIXME: This is a bit of a hack. + const int reorder_src_order = is_reordering ? table->Columns[table->ReorderColumn].DisplayOrder : -1; + const int reorder_min_order = is_reordering ? TableGetMaxDisplayOrderAllowed(table, reorder_src_order, 0) : 0; + const int reorder_max_order = is_reordering ? TableGetMaxDisplayOrderAllowed(table, reorder_src_order, table->ColumnsCount - 1) : table->ColumnsCount - 1; PushItemFlag(ImGuiItemFlags_AutoClosePopups, false); - for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - ImGuiTableColumn* other_column = &table->Columns[other_column_n]; - if (other_column->Flags & ImGuiTableColumnFlags_Disabled) + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->Flags & ImGuiTableColumnFlags_Disabled) continue; - const char* name = TableGetColumnName(table, other_column_n); + const char* name = TableGetColumnName(table, column_n); if (name == NULL || name[0] == 0) name = ""; // Make sure we can't hide the last active column - bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; - if (other_column->IsUserEnabled && table->ColumnsEnabledCount <= 1) - menu_item_active = false; - if (MenuItem(name, NULL, other_column->IsUserEnabled, menu_item_active)) - other_column->IsUserEnabledNextFrame = !other_column->IsUserEnabled; + bool menu_item_enabled = (column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; + if (column->IsUserEnabled && table->ColumnsEnabledCount <= 1) + menu_item_enabled = false; + if (is_reordering && (column->DisplayOrder < reorder_min_order || column->DisplayOrder > reorder_max_order)) + menu_item_enabled = false; + if (MenuItemForColumnReorder(name, column->IsUserEnabled, menu_item_enabled)) + column->IsUserEnabledNextFrame = !column->IsUserEnabled; + + // Drag to reorder + // FIXME: It is currently not possible to reorder columns marked with ImGuiTableColumnFlags_NoHide. + if (IsItemActive() && IsMouseDragging(0) && g.ActiveIdSource == ImGuiInputSource_Mouse && (table->Flags & ImGuiTableFlags_Reorderable)) + { + g.ActiveIdHasBeenEditedBefore = true; // Disable toggle in MenuItemForColumnReorder() + start dimming to display allowed reorder targets. + table->ReorderColumn = (ImGuiTableColumnIdx)column_n; + if (!IsItemHovered()) + { + int reorder_dir = (g.IO.MousePos.y < (g.LastItemData.Rect.Min.y + g.LastItemData.Rect.Max.y) * 0.5f) ? -1 : +1; + float reorder_amount = (reorder_dir < 0 ? g.LastItemData.Rect.Min.y - g.IO.MousePos.y : g.IO.MousePos.y - g.LastItemData.Rect.Max.y) / g.LastItemData.Rect.GetHeight(); + int dst_order = column->DisplayOrder + (int)ImCeil(reorder_amount) * reorder_dir; // Estimated target order, will be validated and clamped. + TableQueueSetColumnDisplayOrder(table, column_n, dst_order); + } + } } PopItemFlag(); } @@ -3787,7 +3857,6 @@ void ImGui::TableLoadSettings(ImGuiTable* table) // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); - ImU64 display_order_mask = 0; for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) { int column_n = column_settings->Index; @@ -3804,24 +3873,51 @@ void ImGui::TableLoadSettings(ImGuiTable* table) } if (settings->SaveFlags & ImGuiTableFlags_Reorderable) column->DisplayOrder = column_settings->DisplayOrder; - display_order_mask |= (ImU64)1 << column->DisplayOrder; if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1) column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1; column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } - // Validate and fix invalid display order data - const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1; - if (display_order_mask != expected_display_order_mask) - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n; - - // Rebuild index + // Fix display order and build index + if (settings->SaveFlags & ImGuiTableFlags_Reorderable) + TableFixDisplayOrder(table); for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; } +struct ImGuiTableFixDisplayOrderColumnData +{ + ImGuiTableColumnIdx Idx; + ImGuiTable* Table; // This is unfortunate but we don't have userdata in qsort api. +}; + +// Sort by DisplayOrder and then Index +static int IMGUI_CDECL TableFixDisplayOrderComparer(const void* lhs, const void* rhs) +{ + const ImGuiTable* table = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Table; + const ImGuiTableColumnIdx lhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Idx; + const ImGuiTableColumnIdx rhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)rhs)->Idx; + const int order_delta = (table->Columns[lhs_idx].DisplayOrder - table->Columns[rhs_idx].DisplayOrder); + return (order_delta > 0) ? +1 : (order_delta < 0) ? -1 : (lhs_idx > rhs_idx) ? +1 : -1; +} + +// Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2) +void ImGui::TableFixDisplayOrder(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + g.TempBuffer.reserve((int)(sizeof(ImGuiTableFixDisplayOrderColumnData) * table->ColumnsCount)); // FIXME: Maybe wrap those two lines as a helper. + ImGuiTableFixDisplayOrderColumnData* fdo_columns = (ImGuiTableFixDisplayOrderColumnData*)(void*)g.TempBuffer.Data; + for (int n = 0; n < table->ColumnsCount; n++) + { + fdo_columns[n].Idx = (ImGuiTableColumnIdx)n; + fdo_columns[n].Table = table; + } + ImQsort(fdo_columns, (size_t)table->ColumnsCount, sizeof(ImGuiTableFixDisplayOrderColumnData), TableFixDisplayOrderComparer); + for (int n = 0; n < table->ColumnsCount; n++) + table->Columns[fdo_columns[n].Idx].DisplayOrder = (ImGuiTableColumnIdx)n; +} + static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { ImGuiContext& g = *ctx; @@ -4047,7 +4143,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) BulletText("ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsGivenWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX); BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); - BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); + BulletText("ResizedColumn: %d, HeldHeaderColumn: %d, ReorderColumn: %d", table->LastResizedColumn, table->LastHeldHeaderColumn, table->ReorderColumn); for (int n = 0; n < table->InstanceCurrent + 1; n++) { ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, n); @@ -4063,7 +4159,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[n]; const char* name = TableGetColumnName(table, n); char buf[512]; - ImFormatString(buf, IM_ARRAYSIZE(buf), + ImFormatString(buf, IM_COUNTOF(buf), "Column %d order %d '%s': offset %+.2f to %+.2f%s\n" "Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n" "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n" @@ -4522,7 +4618,7 @@ void ImGui::EndColumns() // Draw column const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); const float xi = IM_TRUNC(x); - window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); + window->DrawList->AddLineV(xi, y1 + 1.0f, y2, col); } // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. diff --git a/src/Libs/imgui/imgui_widgets.cpp b/src/Libs/imgui/imgui_widgets.cpp index cc0f47a..c255b5d 100644 --- a/src/Libs/imgui/imgui_widgets.cpp +++ b/src/Libs/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.5 +// dear imgui, v1.92.8 // (widgets code) /* @@ -92,6 +92,8 @@ Index of this file: #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value +#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result #endif //------------------------------------------------------------------------- @@ -134,7 +136,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); //------------------------------------------------------------------------- // For InputTextEx() -static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); +static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0); //------------------------------------------------------------------------- @@ -400,7 +402,8 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const char* value_text_begin, *value_text_end; ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 pos = window->DC.CursorPos; const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); @@ -412,7 +415,7 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) // Render RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label, label_end, false); } void ImGui::BulletText(const char* fmt, ...) @@ -652,6 +655,14 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } } + if (flags & ImGuiButtonFlags_PressedOnRelease) + { + // FIXME: Traditionally ImGuiButtonFlags_PressedOnRelease never took ActiveId. Adding it in 2026-03-20 since ImGuiButtonFlags_NoHoldingActiveId can always be added. + // We don't yet perform an explicit ClearActiveID() to reduce scope of change, but this possibility could be investigated. + if (!(flags & ImGuiButtonFlags_NoHoldingActiveId)) + SetActiveID(id, window); // Hold on ID + g.ActiveIdMouseButton = (ImS8)mouse_button_clicked; + } } if (flags & ImGuiButtonFlags_PressedOnRelease) { @@ -779,7 +790,8 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 pos = window->DC.CursorPos; if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) @@ -801,7 +813,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags if (g.LogEnabled) LogSetNextTextDecoration("[", "]"); - RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, label_end, &label_size, style.ButtonTextAlign, &bb); // Automatically close popups //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) @@ -836,11 +848,10 @@ bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiBut if (window->SkipItems) return false; - // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. - IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); + // Ensure zero-size fits to contents + ImVec2 size = CalcItemSize(ImVec2(size_arg.x != 0.0f ? size_arg.x : -FLT_MIN, size_arg.y != 0.0f ? size_arg.y : -FLT_MIN), 0.0f, 0.0f); const ImGuiID id = window->GetID(str_id); - ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav)) @@ -919,7 +930,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) const ImU32 cross_col = GetColorU32(ImGuiCol_Text); const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; - const float cross_thickness = 1.0f; // FIXME-DPI + const float cross_thickness = 1.0f * (float)(int)g.Style._MainScale; // FIXME-DPI window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness); @@ -973,13 +984,23 @@ ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) IM_ASSERT(scrollbar_size >= 0.0f); const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f); - const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f; + const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : (window->Flags & ImGuiWindowFlags_NoTitleBar) ? border_size : 0; if (axis == ImGuiAxis_X) return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); else return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); } +void ImGui::ExtendHitBoxWhenNearViewportEdge(ImGuiWindow* window, ImRect* bb, float threshold, ImGuiAxis axis) +{ + ImRect window_rect = window->RootWindow->Rect(); + ImRect viewport_rect = window->Viewport->GetMainRect(); + if (window_rect.Min[axis] == viewport_rect.Min[axis] && bb->Min[axis] > window_rect.Min[axis] && bb->Min[axis] - threshold <= window_rect.Min[axis]) + bb->Min[axis] = window_rect.Min[axis]; + if (window_rect.Max[axis] == viewport_rect.Max[axis] && bb->Max[axis] < window_rect.Max[axis] && bb->Max[axis] + threshold >= window_rect.Max[axis]) + bb->Max[axis] = window_rect.Max[axis]; +} + void ImGui::Scrollbar(ImGuiAxis axis) { ImGuiContext& g = *GImGui; @@ -988,20 +1009,8 @@ void ImGui::Scrollbar(ImGuiAxis axis) // Calculate scrollbar bounding box ImRect bb = GetWindowScrollbarRect(window, axis); - ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; - if (axis == ImGuiAxis_X) - { - rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; - if (!window->ScrollbarY) - rounding_corners |= ImDrawFlags_RoundCornersBottomRight; - } - else - { - if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) - rounding_corners |= ImDrawFlags_RoundCornersTopRight; - if (!window->ScrollbarX) - rounding_corners |= ImDrawFlags_RoundCornersBottomRight; - } + ImRect host_rect = (window->DockIsActive ? window->DockNode->HostWindow : window)->Rect(); + ImDrawFlags rounding_corners = CalcRoundingFlagsForRectInRect(bb, host_rect, g.Style.WindowBorderSize); float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; ImS64 scroll = (ImS64)window->Scroll[axis]; @@ -1043,20 +1052,26 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); + if (scrollbar_size_v < 1.0f) + return false; // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) // But we maintain a minimum size in pixel to allow for the user to still aim inside. IM_ASSERT(ImMax(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) + ImRect bb_hit = bb_frame; + ExtendHitBoxWhenNearViewportEdge(window, &bb_hit, g.Style.WindowBorderSize, (ImGuiAxis)(axis ^ 1)); + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). bool held = false; bool hovered = false; ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav); - ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + ButtonBehavior(bb_hit, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v); float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); @@ -1135,11 +1150,15 @@ void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const Im return; // Render - if (g.Style.ImageBorderSize > 0.0f) - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize); + float rounding = g.Style.ImageRounding; if (bg_col.w > 0.0f) - window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col), rounding); + if (rounding > 0.0f) + window->DrawList->AddImageRounded(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col), rounding); + else + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + if (g.Style.ImageBorderSize > 0.0f) + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, g.Style.ImageBorderSize); } void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) @@ -1179,10 +1198,14 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_ // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderNavCursor(bb, id); - RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + float image_rounding = ImMax(g.Style.FrameRounding - ImMax(padding.x, padding.y), g.Style.ImageRounding); + if (image_rounding > 0.0f) + window->DrawList->AddImageRounded(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col), image_rounding); + else + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } @@ -1230,7 +1253,8 @@ bool ImGui::Checkbox(const char* label, bool* v) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; @@ -1271,8 +1295,9 @@ bool ImGui::Checkbox(const char* label, bool* v) if (is_visible) { RenderNavCursor(total_bb, id); - RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : (mixed_value || checked) ? ImGuiCol_CheckboxSelectedBg : ImGuiCol_FrameBg); ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); + RenderFrame(check_bb.Min, check_bb.Max, bg_col, true, style.FrameRounding); if (mixed_value) { // Undocumented tristate/mixed/indeterminate checkbox (#2644) @@ -1290,7 +1315,7 @@ bool ImGui::Checkbox(const char* label, bool* v) if (g.LogEnabled) LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); if (is_visible && label_size.x > 0.0f) - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return pressed; @@ -1352,7 +1377,8 @@ bool ImGui::RadioButton(const char* label, bool active) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; @@ -1391,7 +1417,7 @@ bool ImGui::RadioButton(const char* label, bool active) if (g.LogEnabled) LogRenderedText(&label_pos, active ? "(x)" : "( )"); if (label_size.x > 0.0f) - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; @@ -1444,7 +1470,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 @@ -1453,14 +1482,14 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over { if (!overlay) { - ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); + ImFormatString(overlay_buf, IM_COUNTOF(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); overlay = overlay_buf; } 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); } } @@ -1504,7 +1533,7 @@ bool ImGui::TextLink(const char* label) const char* label_end = FindRenderedTextEnd(label); ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - ImVec2 size = CalcTextSize(label, label_end, true); + ImVec2 size = CalcTextSize(label, label_end, false); ImRect bb(pos, pos + size); ItemSize(size, 0.0f); if (!ItemAdd(bb, id)) @@ -1535,10 +1564,10 @@ bool ImGui::TextLink(const char* label) } float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f); - window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI + window->DrawList->AddLineH(bb.Min.x, bb.Max.x, line_y, GetColorU32(line_colf), 1.0f * (float)(int)g.Style._MainScale); // FIXME-TEXT: Underline mode // FIXME-DPI PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); - RenderText(bb.Min, label, label_end); + RenderText(bb.Min, label, label_end, false); PopStyleColor(); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); @@ -1670,9 +1699,13 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) // We don't provide our width to the layout so that it doesn't get feed back into AutoFit // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell) - const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. + + // Between 1.71 and 1.92.7, we maintained a hack where a 1.0f thin Separator() would not impact layout. + // This was mostly chosen to allow backward compatibility with user's code assuming zero-height when calculating height for layout (e.g. bottom alignment of a status bar). + // In order to handle scaling we need to scale separator thickness and it would not makes sense to have a disparity depending on height. + ////float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness)); - ItemSize(ImVec2(0.0f, thickness_for_layout)); + ItemSize(ImVec2(0.0f, thickness)); if (ItemAdd(bb, 0)) { @@ -1698,14 +1731,13 @@ void ImGui::Separator() return; // Those flags should eventually be configurable by the user - // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f. ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; // Only applies to legacy Columns() api as they relied on Separator() a lot. if (window->DC.CurrentColumns) flags |= ImGuiSeparatorFlags_SpanAllColumns; - SeparatorEx(flags, 1.0f); + SeparatorEx(flags, ImMax(g.Style.SeparatorSize, 1.0f)); } void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) @@ -1742,9 +1774,9 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x2 = label_pos.x - style.ItemSpacing.x; const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) - window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness); if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) - window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); + window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness); if (g.LogEnabled) LogSetNextTextDecoration("---", NULL); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size); @@ -1754,7 +1786,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end if (g.LogEnabled) LogText("---"); if (separator_thickness > 0.0f) - window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness); } } @@ -1838,7 +1870,7 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs; if (int d = (int)(b->Width - a->Width)) return d; - return (b->Index - a->Index); + return b->Index - a->Index; } // Shrink excess width from a set of item, by removing width from the larger items first. @@ -1925,8 +1957,9 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f; + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, false).x : 0.0f; const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth()); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -1977,7 +2010,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); } if (label_size.x > 0) - RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label, label_end, false); if (!popup_open) return false; @@ -2020,7 +2053,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags // This is essentially a specialized version of BeginPopupEx() char name[16]; - ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth + ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth // Set position given a custom constraint (peak into expected window size so we can position it) // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? @@ -2055,8 +2088,12 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags void ImGui::EndCombo() { ImGuiContext& g = *GImGui; - EndPopup(); g.BeginComboDepth--; + char name[16]; + ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // FIXME: Move those to helpers? + if (strcmp(g.CurrentWindow->Name, name) != 0) + IM_ASSERT_USER_ERROR_RET(0, "Calling EndCombo() in wrong window!"); + EndPopup(); } // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements @@ -2204,30 +2241,6 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa return value_changed; } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - -struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); }; -static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx) -{ - ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data; - const char* s = NULL; - data->OldCallback(data->UserData, idx, &s); - return s; -} - -bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items) -{ - ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; - return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items); -} -bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items) -{ - ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; - return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items); -} - -#endif - //------------------------------------------------------------------------- // [SECTION] Data Type and Data Formatting Helpers [Internal] //------------------------------------------------------------------------- @@ -2241,6 +2254,11 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void* // - RoundScalarWithFormat<>() //------------------------------------------------------------------------- +static const ImU32 GDefaultRgbaColorMarkers[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) +}; + static const ImGuiDataTypeInfo GDataTypeInfo[] = { { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8 @@ -2261,7 +2279,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool { 0, "char*","%s", "%s" }, // ImGuiDataType_String }; -IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); +IM_STATIC_ASSERT(IM_COUNTOF(GDataTypeInfo) == ImGuiDataType_COUNT); const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) { @@ -2365,12 +2383,17 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void // Sanitize format // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf - // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %. char format_sanitized[32]; if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + { format = type_info->ScanFmt; + } else - format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized)); + { + format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_COUNTOF(format_sanitized)); + if (format[0] == '\0') + format = type_info->ScanFmt; // Format doesn't want us to show the number currently, but we still need to parse the resulting input + } // Small types need a 32-bit buffer to receive the result from scanf() int v32 = 0; @@ -2461,7 +2484,7 @@ static float GetMinimumStepAtDecimalPrecision(int decimal_precision) static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; if (decimal_precision < 0) return FLT_MIN; - return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); + return (decimal_precision < IM_COUNTOF(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); } template @@ -2475,12 +2498,12 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, // Sanitize format char fmt_sanitized[32]; - ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized)); + ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_COUNTOF(fmt_sanitized)); fmt_start = fmt_sanitized; // Format value with our rounding, and read back char v_str[64]; - ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); + ImFormatString(v_str, IM_COUNTOF(v_str), fmt_start, v); const char* p = v_str; while (*p == ' ') p++; @@ -2582,9 +2605,9 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); // Convert to parametric space, apply delta, convert back - float v_old_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float v_old_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); float v_new_parametric = v_old_parametric + g.DragCurrentAccum; - v_cur = ScaleValueFromRatioT(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + v_cur = ScaleValueFromRatioT(data_type, v_new_parametric, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); v_old_ref_for_accum_remainder = v_old_parametric; } else @@ -2601,7 +2624,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const if (is_logarithmic) { // Convert to parametric space, apply delta, convert back - float v_new_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float v_new_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder); } else @@ -2677,6 +2700,20 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v return false; } +// Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) +static bool TempInputIsClampEnabled(ImGuiSliderFlags flags, ImGuiDataType data_type, const void* p_min, const void* p_max) +{ + if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL)) + { + const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? ImGui::DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max + if (p_min == NULL || p_max == NULL || clamp_range_dir < 0) + return true; + if (clamp_range_dir == 0) + return ImGui::DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true; + } + return false; +} + // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional. // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) @@ -2689,8 +2726,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); + const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -2741,23 +2780,17 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (temp_input_is_active) { - // Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) - bool clamp_enabled = false; - if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL)) - { - const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max - if (p_min == NULL || p_max == NULL || clamp_range_dir < 0) - clamp_enabled = true; - else if (clamp_range_dir == 0) - clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true; - } + const bool clamp_enabled = TempInputIsClampEnabled(flags, data_type, p_min, p_max); return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } // 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 (color_marker != 0 && style.ColorMarkerSize > 0.0f) + RenderColorComponentMarker(frame_bb, GetColorU32(color_marker), 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); @@ -2766,13 +2799,13 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; @@ -2795,6 +2828,8 @@ bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); + if (flags & ImGuiSliderFlags_ColorMarkers) + SetNextItemColorMarker(GDefaultRgbaColorMarkers[i]); value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); PopID(); PopItemWidth(); @@ -2945,14 +2980,14 @@ bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_ // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT) template -float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) +float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) { if (v_min == v_max) return 0.0f; IM_UNUSED(data_type); const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); - if (is_logarithmic) + if (logarithmic_zero_epsilon > 0.0f) // == is_logarithmic from caller { bool flipped = v_max < v_min; @@ -3002,7 +3037,7 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT) template -TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) +TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) { // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. @@ -3012,7 +3047,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T return v_max; TYPE result = (TYPE)0; - if (is_logarithmic) + if (logarithmic_zero_epsilon > 0.0f) // == is_logarithmic from caller { // Fudge min/max to avoid getting silly results close to zero FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; @@ -3117,7 +3152,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ const float mouse_abs_pos = g.IO.MousePos[axis]; if (g.ActiveIdIsJustActivated) { - float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) grab_t = 1.0f - grab_t; const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); @@ -3172,7 +3207,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } else if (g.SliderCurrentAccumDirty) { - clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits { @@ -3186,10 +3221,10 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ clicked_t = ImSaturate(clicked_t + delta); // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) v_new = RoundScalarWithFormatT(format, data_type, v_new); - float new_clicked_t = ScaleRatioFromValueT(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float new_clicked_t = ScaleRatioFromValueT(data_type, v_new, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (delta > 0) g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta); @@ -3207,7 +3242,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ if (set_new_value) { - TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); // Round to user desired precision based on format string if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) @@ -3229,7 +3264,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ else { // Output grab position so it can be displayed by the caller - float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) grab_t = 1.0f - grab_t; const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); @@ -3293,8 +3328,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); + const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -3336,14 +3373,17 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (temp_input_is_active) { // Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) - const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; + const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; // Don't use TempInputIsClampEnabled() return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } // 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 (color_marker != 0 && style.ColorMarkerSize > 0.0f) + RenderColorComponentMarker(frame_bb, GetColorU32(color_marker), style.FrameRounding); + RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding); // Slider behavior ImRect grab_bb; @@ -3357,13 +3397,13 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; @@ -3387,6 +3427,8 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); + if (flags & ImGuiSliderFlags_ColorMarkers) + SetNextItemColorMarker(GDefaultRgbaColorMarkers[i]); value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); PopID(); PopItemWidth(); @@ -3466,7 +3508,8 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -3508,11 +3551,12 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return value_changed; } @@ -3535,7 +3579,8 @@ bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, // - ImParseFormatSanitizeForPrinting() [Internal] // - ImParseFormatSanitizeForScanning() [Internal] // - ImParseFormatPrecision() [Internal] -// - TempInputTextScalar() [Internal] +// - TempInputText() [Internal] +// - TempInputScalar() [Internal] // - InputScalar() // - InputScalarN() // - InputFloat() @@ -3673,27 +3718,33 @@ int ImParseFormatPrecision(const char* fmt, int default_precision) } // Create text input in place of another active widget (e.g. used when doing a Ctrl+Click on drag/slider widgets) +// - This must be submitted right after the item it is overlaying. // FIXME: Facilitate using this in variety of other situations. -// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but -// the expected relationship between TempInputXXX functions and LastItemData is a little fishy. -bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags) +bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id. // We clear ActiveID on the first frame to allow the InputText() taking it back. ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const bool init = (g.TempInputId != id); if (init) ClearActiveID(); - g.CurrentWindow->DC.CursorPos = bb.Min; - g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; - bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); + ImVec2 backup_pos = window->DC.CursorPos; + window->DC.CursorPos = bb.Min; + g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; // Using ImGuiInputTextFlags_MergedItem above will skip ItemAdd() so we poke here. + bool value_changed = InputTextEx(label, NULL, buf, (int)buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_TempInput | ImGuiInputTextFlags_AutoSelectAll, callback, user_data); + KeepAliveID(id); // Not done because of ImGuiInputTextFlags_TempInput if (init) { // First frame we started displaying the InputText widget, we expect it to take the active id. IM_ASSERT(g.ActiveId == id); g.TempInputId = g.ActiveId; } + if (g.ActiveId != id) + g.TempInputId = 0; + window->DC.CursorPos = backup_pos; return value_changed; } @@ -3708,37 +3759,36 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); char fmt_buf[32]; char data_buf[32]; - format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_COUNTOF(fmt_buf)); if (format[0] == 0) format = type_info->PrintFmt; - DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); + DataTypeFormatString(data_buf, IM_COUNTOF(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. - bool value_changed = false; - if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) - { - // Backup old value - size_t data_type_size = type_info->Size; - ImGuiDataTypeStorage data_backup; - memcpy(&data_backup, p_data, data_type_size); + if (!TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags)) + return false; - // Apply new value (or operations) then clamp - DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL); - if (p_clamp_min || p_clamp_max) - { - if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) - ImSwap(p_clamp_min, p_clamp_max); - DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); - } + // Backup old value + size_t data_type_size = type_info->Size; + ImGuiDataTypeStorage data_backup; + memcpy(&data_backup, p_data, data_type_size); - // Only mark as edited if new value is different - g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; - value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; - if (value_changed) - MarkItemEdited(id); + // Apply new value (or operations) then clamp + DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL); + if (p_clamp_min || p_clamp_max) + { + if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) + ImSwap(p_clamp_min, p_clamp_max); + DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); } + + // Only mark as edited if new value is different + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; + bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (value_changed) + MarkItemEdited(id); return value_changed; } @@ -3759,7 +3809,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; - IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! + //IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; @@ -3770,31 +3820,38 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0) buf[0] = 0; else - DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + DataTypeFormatString(buf, IM_COUNTOF(buf), data_type, p_data, format); // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; - bool value_changed = false; - if (p_step == NULL) - { - if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) - value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); - } - else + const bool has_step_buttons = (p_step != NULL); + const float button_size = has_step_buttons ? GetFrameHeight() : 0.0f; + bool ret; + if (has_step_buttons) { - 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_ARRAYSIZE(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); + } + + // Apply + bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. + bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false; - // Step buttons + // Step buttons + if (has_step_buttons) + { const ImVec2 backup_frame_padding = style.FramePadding; style.FramePadding.x = style.FramePadding.y; if (flags & ImGuiInputTextFlags_ReadOnly) @@ -3804,13 +3861,13 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data if (ButtonEx("-", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); - value_changed = true; + value_changed = ret = true; } SameLine(0, style.ItemInnerSpacing.x); if (ButtonEx("+", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); - value_changed = true; + value_changed = ret = true; } PopItemFlag(); if (flags & ImGuiInputTextFlags_ReadOnly) @@ -3832,6 +3889,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data if (value_changed) MarkItemEdited(g.LastItemData.ID); + if (flags & ImGuiInputTextFlags_EnterReturnsTrue) + return ret; return value_changed; } @@ -4152,7 +4211,7 @@ static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) char* dst = obj->TextA.Data + pos; char* src = obj->TextA.Data + pos + n; memmove(dst, src, obj->TextLen - n - pos + 1); - obj->Edited = true; + obj->EditedBefore = obj->EditedThisFrame = true; obj->TextLen -= n; } @@ -4182,7 +4241,7 @@ static int STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const cha memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos)); memcpy(text + pos, new_text, (size_t)new_text_len); - obj->Edited = true; + obj->EditedBefore = obj->EditedThisFrame = true; obj->TextLen += new_text_len; obj->TextA[obj->TextLen] = '\0'; @@ -4236,7 +4295,7 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st // We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators. ImGuiInputTextState::ImGuiInputTextState() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); Stb = IM_NEW(ImStbTexteditState); memset(Stb, 0, sizeof(*Stb)); } @@ -4277,6 +4336,7 @@ void ImGuiInputTextState::ClearSelection() { Stb->select_start int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; } int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; } int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; } +void ImGuiInputTextState::SetSelection(int start, int end) { Stb->select_start = start; Stb->cursor = Stb->select_end = end; } float ImGuiInputTextState::GetPreferredOffsetX() const { return Stb->has_preferred_x ? Stb->preferred_x : -1; } void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; } void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } @@ -4285,7 +4345,7 @@ void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); } // Public API to manipulate UTF-8 text from within a callback. @@ -4381,9 +4441,11 @@ void ImGui::PopPasswordFont() } // Return false to discard a character. -static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) +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; // Filter non-printable (NB: isprint is unreliable! see #2467) bool apply_named_filters = true; @@ -4473,9 +4535,14 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im ImGuiContext& g = *GImGui; ImGuiInputTextCallbackData callback_data; callback_data.Ctx = &g; + callback_data.ID = state->ID; + callback_data.Flags = flags; callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; callback_data.EventChar = (ImWchar)c; - callback_data.Flags = flags; + callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.CursorPos = state->Stb->cursor; + callback_data.SelectionStart = state->Stb->select_start; + callback_data.SelectionEnd = state->Stb->select_end; callback_data.UserData = user_data; if (callback(&callback_data) != 0) return false; @@ -4524,6 +4591,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) ImGuiInputTextState* state = &g.InputTextState; if (id == 0 || state->ID != id) return; + //IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id); g.InputTextDeactivatedState.ID = state->ID; if (state->Flags & ImGuiInputTextFlags_ReadOnly) { @@ -4565,6 +4633,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) @@ -4585,6 +4654,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) { @@ -4593,6 +4663,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; } } @@ -4603,11 +4674,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; } @@ -4658,7 +4726,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar) BeginGroup(); const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); @@ -4672,7 +4741,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { ImVec2 backup_pos = window->DC.CursorPos; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) + bool no_clip = (g.InputTextDeactivatedState.ID == id) || (g.ActiveId == id) || (id == g.NavActivateId); // Mimic some of ItemAdd() logic + add InputTextDeactivatedState.ID check. + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable) && !no_clip) { EndGroup(); return false; @@ -4698,7 +4768,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.NavActivateId = backup_activate_id; PopStyleVar(3); PopStyleColor(); - if (!child_visible) + if (!child_visible && !no_clip) { EndChild(); EndGroup(); @@ -4708,12 +4778,17 @@ 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 { // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd) ItemSize(total_bb, style.FramePadding.y); - if (!(flags & ImGuiInputTextFlags_MergedItem)) + if (!(flags & ImGuiInputTextFlags_TempInput)) if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) return false; } @@ -4744,20 +4819,22 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_wordwrap) wrap_width = ImMax(1.0f, GetContentRegionAvail().x + (draw_window->ScrollbarY ? 0.0f : -g.Style.ScrollbarSize)); - const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard))); - 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 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 input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput)); + 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; float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; 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); - const bool init_state = (init_make_active || user_scroll_active); + const bool init_changed_specs_multiline = (state != NULL && (state->Stb->single_line != !is_multiline)); // state != NULL means its our state. + const bool init_changed_specs_readonly = (state != NULL && ((state->Flags ^ flags) & ImGuiInputTextFlags_ReadOnly)); // state != NULL means its our state. + const bool init_make_active = (input_requested_by_user || input_requested_by_nav || input_requested_by_reactivate || user_scroll_finish); if (init_reload_from_user_buf) { int new_len = (int)ImStrlen(buf); @@ -4770,7 +4847,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_multiline || init_changed_specs_readonly) { // Access state even if we don't own it yet. state = &g.InputTextState; @@ -4783,18 +4860,22 @@ 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? - bool recycle_state = (state->ID == id && !init_changed_specs); - if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) + bool recycle_state = (state->ID == id && !init_changed_specs_multiline); + if (recycle_state && !init_changed_specs_readonly && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; // Start edition state->ID = id; state->TextLen = buf_len; + state->EditedBefore = false; if (!is_readonly) { state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. @@ -4826,12 +4907,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } const bool is_osx = io.ConfigMacOSXBehaviors; - if (g.ActiveId != id && init_make_active) + if (init_make_active && g.ActiveId != id) { IM_ASSERT(state && state->ID == id); SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); + if (input_requested_by_nav) + SetNavCursorVisibleAfterMove(); } if (g.ActiveId == id) { @@ -4878,7 +4961,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 @@ -4895,22 +4978,25 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); - // Word-wrapping: attempt to keep cursor in view while resizing frame/parent - // FIXME-WORDWRAP: It would be better to preserve same relative offset. - if (is_wordwrap && state != NULL && state->ID == id && state->WrapWidth != wrap_width) + if (state != NULL && state->ID == id) { - state->CursorCenterY = true; - state->WrapWidth = wrap_width; - render_cursor = true; + state->Flags = flags; + + // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset) + if (is_wordwrap && state->WrapWidth != wrap_width) + { + state->CursorCenterY = true; + state->WrapWidth = wrap_width; + render_cursor = true; + } } // Process mouse inputs and character inputs if (g.ActiveId == id) { IM_ASSERT(state != NULL); - state->Edited = false; + state->EditedThisFrame = false; state->BufCapacity = buf_size; - state->Flags = flags; state->WrapWidth = wrap_width; // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. @@ -4990,7 +5076,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id)) { unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) + if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data)) state->OnCharPressed(c); } // FIXME: Implement Shift+Tab @@ -5013,7 +5099,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ unsigned int c = (unsigned int)io.InputQueueCharacters[n]; if (c == '\t') // Skip Tab, see above. continue; - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) + if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data)) state->OnCharPressed(c); } @@ -5049,7 +5135,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool is_enter = Shortcut(ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiKey_KeypadEnter, f_repeat, id); const bool is_ctrl_enter = Shortcut(ImGuiMod_Ctrl | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_KeypadEnter, f_repeat, id); - const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); + const bool is_shift_enter = Shortcut(ImGuiMod_Shift | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_KeypadEnter, f_repeat, id); + const bool is_gamepad_validate = nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, false); const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id)); // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. @@ -5083,23 +5170,26 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } - else if (is_enter || is_ctrl_enter || is_gamepad_validate) + else if (is_enter || is_ctrl_enter || is_shift_enter || is_gamepad_validate) { // Determine if we turn Enter into a \n character bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; - if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line != is_ctrl_enter)) + bool is_new_line = is_multiline && !is_gamepad_validate && (is_shift_enter || (is_enter && !ctrl_enter_for_new_line) || (is_ctrl_enter && ctrl_enter_for_new_line)); + if (!is_new_line) { - validated = true; + validated = clear_active_id = true; if (io.ConfigInputTextEnterKeepActive && !is_multiline) + { + // Queue reactivation, so that e.g. IsItemDeactivatedAfterEdit() will work. (#9001) state->SelectAll(); // No need to scroll - else - clear_active_id = true; + g.InputTextReactivateId = id; // Mark for reactivation on next frame + } } else if (!is_readonly) { // Insert new line unsigned int c = '\n'; - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) + if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data)) state->OnCharPressed(c); } } @@ -5168,7 +5258,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ unsigned int c; int in_len = ImTextCharFromUtf8(&c, s, clipboard_end); s += in_len; - if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) + if (!InputTextFilterCharacter(&g, state, &c, callback, callback_user_data, true)) continue; char c_utf8[5]; ImTextCharToUtf8(c_utf8, c); @@ -5189,7 +5279,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) @@ -5219,108 +5309,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; - } + 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->EditedThisFrame) + { + event_flag = ImGuiInputTextFlags_CallbackEdit; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + { + event_flag = ImGuiInputTextFlags_CallbackAlways; + } - if (event_flag) + 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) { - ImGuiInputTextCallbackData callback_data; - callback_data.Ctx = &g; - callback_data.EventFlag = event_flag; - callback_data.Flags = flags; - 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(); - } + // 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 - if (!is_readonly && strcmp(state->TextSrc, buf) != 0) - { - apply_new_text = state->TextSrc; - apply_new_text_length = state->TextLen; - value_changed = true; - } + // 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) @@ -5333,19 +5414,23 @@ 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) { ImGuiInputTextCallbackData callback_data; callback_data.Ctx = &g; - callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.ID = id; callback_data.Flags = flags; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + 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); @@ -5375,7 +5460,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; - ImVec2 text_size(0.0f, 0.0f); ImRect clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size if (is_multiline) clip_rect.ClipWith(draw_window->ClipRect); @@ -5433,7 +5517,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ line_visible_n1 = ImMin(line_visible_n1, line_count); // Store text height (we don't need width) - text_size = ImVec2(inner_size.x, line_count * g.FontSize); + float text_size_y = line_count * g.FontSize; //GetForegroundDrawList()->AddRect(draw_pos + ImVec2(0, line_visible_n0 * g.FontSize), draw_pos + ImVec2(frame_size.x, line_visible_n1 * g.FontSize), IM_COL32(255, 0, 0, 255)); // Calculate blinking cursor position @@ -5493,7 +5577,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (new_scroll_y != scroll_y) { - const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); + const float scroll_max_y = ImMax((text_size_y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y); draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->Scroll.y = scroll_y; @@ -5506,7 +5590,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 @@ -5535,7 +5619,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); } @@ -5543,7 +5627,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Find render position for right alignment (single-line only) - if (g.ActiveId != id && flags & ImGuiInputTextFlags_ElideLeft) + if (g.ActiveId != id && (flags & ImGuiInputTextFlags_ElideLeft) && !render_cursor && !render_selection) draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive? @@ -5564,7 +5648,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031) + draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), 1.0f * (float)(int)style._MainScale); // FIXME-DPI: Cursor thickness (#7031) // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) // This is required for some backends (SDL3) to start emitting character/text inputs. @@ -5586,7 +5670,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) { // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)... - Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); + Dummy(ImVec2(0.0f, text_size_y + style.FramePadding.y)); g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; EndChild(); item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); @@ -5601,7 +5685,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.LastItemData.StatusFlags = item_data_backup.StatusFlags; } } - if (state) + if (state && is_readonly) state->TextSrc = NULL; // Log as text @@ -5612,7 +5696,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); if (value_changed) MarkItemEdited(id); @@ -5785,8 +5869,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] = { @@ -5801,6 +5887,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA }; const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1; + const ImGuiSliderFlags drag_flags = draw_color_marker ? ImGuiSliderFlags_ColorMarkers : ImGuiSliderFlags_None; float prev_split = 0.0f; for (int n = 0; n < components; n++) @@ -5810,16 +5897,18 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag float next_split = IM_TRUNC(w_items * (n + 1) / components); SetNextItemWidth(ImMax(next_split - prev_split, 1.0f)); prev_split = next_split; + if (draw_color_marker) + SetNextItemColorMarker(GDefaultRgbaColorMarkers[n]); // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. 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); @@ -5830,11 +5919,11 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // RGB Hexadecimal Input char buf[64]; if (alpha) - ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255)); + ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255)); else - ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255)); + ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255)); SetNextItemWidth(w_inputs); - if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase)) + if (InputText("##Text", buf, IM_COUNTOF(buf), ImGuiInputTextFlags_CharsUppercase)) { value_changed = true; char* p = buf; @@ -6273,7 +6362,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; const int vert_start_idx = draw_list->VtxBuffer.Size; draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); - draw_list->PathStroke(col_white, 0, wheel_thickness); + draw_list->PathStroke(col_white, wheel_thickness); const int vert_end_idx = draw_list->VtxBuffer.Size; // Paint colors over existing vertices @@ -6417,7 +6506,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (g.Style.FrameBorderSize > 0.0f) RenderFrameBorder(bb.Min, bb.Max, rounding); else - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border // FIXME-DPI + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding, 1.0f * (float)(int)g.Style._MainScale); // Color buttons are often in need of some sort of border // FIXME-DPI } // Drag and Drop Source @@ -6442,6 +6531,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl } // Initialize/override default color options +// FIXME: Could be moved to a simple IO field. void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; @@ -6528,18 +6618,18 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) { int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); char buf[64]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + ImFormatString(buf, IM_COUNTOF(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if (Selectable(buf)) SetClipboardText(buf); - ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + ImFormatString(buf, IM_COUNTOF(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); if (Selectable(buf)) SetClipboardText(buf); - ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb); + ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X", cr, cg, cb); if (Selectable(buf)) SetClipboardText(buf); if (!(flags & ImGuiColorEditFlags_NoAlpha)) { - ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca); + ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca); if (Selectable(buf)) SetClipboardText(buf); } @@ -6696,6 +6786,8 @@ bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char return TreeNodeBehavior(id, flags, label, label_end); } +// The reason those two functions are not yet in public API is because I would like to design a more feature-full and generic API for this. +// They are otherwise function (cc: #3823, #9251, #7553, #6754, #5423, #2958, #2079, #1947, #1131, #722) bool ImGui::TreeNodeGetOpen(ImGuiID storage_id) { ImGuiContext& g = *GImGui; @@ -6703,15 +6795,16 @@ bool ImGui::TreeNodeGetOpen(ImGuiID storage_id) return storage->GetInt(storage_id, 0) != 0; } -void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open) +void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool is_open) { ImGuiContext& g = *GImGui; ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; - storage->SetInt(storage_id, open ? 1 : 0); + storage->SetInt(storage_id, is_open ? 1 : 0); } bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) { + // Leaf node always open a new tree/id scope. If you never use it, add ImGuiTreeNodeFlags_NoTreePushOnOpen. if (flags & ImGuiTreeNodeFlags_Leaf) return true; @@ -6780,7 +6873,6 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } -// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); @@ -6789,26 +6881,28 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; + + // When not framed, we vertically increase height up to typical framed widget height const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; - const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); + const bool use_frame_padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)); + const ImVec2 padding = use_frame_padding ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); if (!label_end) label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing - const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it + const float text_offset_y = use_frame_padding ? ImMax(style.FramePadding.y, window->DC.CurrLineTextBaseOffset) : window->DC.CurrLineTextBaseOffset; // Latch before ItemSize changes it const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow - // We vertically grow up to current line height up the typical widget height. - const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); + const float frame_height = label_size.y + padding.y * 2; const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL); ImRect frame_bb; frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; - frame_bb.Min.y = window->DC.CursorPos.y; + frame_bb.Min.y = window->DC.CursorPos.y + (text_offset_y - padding.y); frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; - frame_bb.Max.y = window->DC.CursorPos.y + frame_height; + frame_bb.Max.y = window->DC.CursorPos.y + (text_offset_y - padding.y) + frame_height; if (display_frame) { const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits @@ -7085,11 +7179,11 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); if (x1 < x2) window->DrawList->PathLineTo(ImVec2(x2, y)); - window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize); + window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } else { - window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7115,7 +7209,7 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) float x = ImTrunc(data->DrawLinesX1); if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); - window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); if (data->DrawLinesTableColumn != -1) TablePopColumnChannel(); } @@ -7274,7 +7368,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. ImGuiID id = window->GetID(label); - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); ImVec2 pos = window->DC.CursorPos; pos.y += window->DC.CurrLineTextBaseOffset; @@ -7429,7 +7524,11 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns. if (is_visible) - RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); + RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb); + +#ifdef IMGUI_DEBUG_BOXSELECT + if (g.BoxSelectState.UnclipMode) { GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label, label_end); } +#endif // Automatically close popups if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) @@ -7491,7 +7590,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f } // Append to buffer - const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1; + const int buffer_max_len = IM_COUNTOF(data->SearchBuffer) - 1; int buffer_len = (int)ImStrlen(data->SearchBuffer); bool select_request = false; for (ImWchar w : g.IO.InputQueueCharacters) @@ -7746,7 +7845,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI return false; // Current frame absolute prev/current rectangles are used to toggle selection. - // They are derived from positions relative to scrolling space. + // They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates. ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already ImVec2 curr_end_pos_abs = g.IO.MousePos; @@ -7756,20 +7855,69 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + //IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs->StartPosRel.x, bs->StartPosRel.y, bs->EndPosRel.x, bs->EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y); - // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) - // Storing an extra rect used by widgets supporting box-select. - if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) - if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + // Box-select 2D mode detects change of the rectangle. + // Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them). + // To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry. + if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + // For both sides, compute the area differing between Prev and Curr rectangles. + bs->UnclipRects[0] = bs->UnclipRects[1] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int side = 0; side < 2; side++) { - bs->UnclipMode = true; - bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. - bs->UnclipRect.Add(bs->BoxSelectRectCurr); + ImVec2 d_min = (side == 0) ? ImMin(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMin(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max); + ImVec2 d_max = (side == 0) ? ImMax(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMax(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max); + if (d_min.x != d_max.x) + { + bs->UnclipRects[0].AddX(d_min.x); + bs->UnclipRects[0].AddX(d_max.x); + } + if (d_min.y != d_max.y) + { + bs->UnclipRects[1].AddY(d_min.y); + bs->UnclipRects[1].AddY(d_max.y); + } } - //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + ImRect box_select_intersection = bs->BoxSelectRectPrev; + box_select_intersection.Add(bs->BoxSelectRectCurr); + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + { + bs->UnclipRects[0].AddY(box_select_intersection.Min.y); + bs->UnclipRects[0].AddY(box_select_intersection.Max.y); + } + if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + if (bs->BoxSelectRectPrev.Min.y != bs->BoxSelectRectCurr.Min.y || bs->BoxSelectRectPrev.Max.y != bs->BoxSelectRectCurr.Max.y) + { + bs->UnclipRects[1].AddX(box_select_intersection.Min.x); + bs->UnclipRects[1].AddX(box_select_intersection.Max.x); + } + + // Merge both rectangles into one. + // FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should + // ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation. + bs->UnclipRect = bs->UnclipRects[0]; + bs->UnclipRect.Add(bs->UnclipRects[1]); + if (!bs->UnclipRect.IsInverted() && (!window->ClipRect.Contains(bs->UnclipRect.Min) || !window->ClipRect.Contains(bs->UnclipRect.Max))) // !! Don't use Contains(ImRect) + bs->UnclipMode = true; + if (bs->UnclipMode && g.CurrentTable != NULL) + TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect); // No need submitting both + } + +#ifdef IMGUI_DEBUG_BOXSELECT + //GetForegroundDrawList()->AddRect(scope_rect.Min, scope_rect.Max, IM_COL32(0, 255, 0, 200), 0.0f, 0, 4.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); + if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + for (ImRect& unclip_r : bs->UnclipRects) + if (!unclip_r.IsInverted()) + GetForegroundDrawList()->AddRect(unclip_r.Min, unclip_r.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 4.0f); + GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 2.0f); + } +#endif return true; } @@ -7785,8 +7933,9 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view ImRect box_select_r = bs->BoxSelectRectCurr; box_select_r.ClipWith(scope_rect); - window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling - window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling + ImGuiWindow* draw_window = FindFrontMostVisibleChildWindow(window); + draw_window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling + draw_window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling // Scroll const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; @@ -7826,18 +7975,18 @@ static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSe static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) { - ImGuiContext& g = *GImGui; if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) { // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + // This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect(). return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin)); } else { - // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + //// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) ImRect scope_rect = window->InnerClipRect; - if (g.CurrentTable != NULL) - scope_rect = g.CurrentTable->HostClipRect; + //if (g.CurrentTable != NULL) + // scope_rect = g.CurrentTable->HostClipRect; // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max); @@ -7873,18 +8022,24 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) // They should perhaps be stacked properly? if (ImGuiTable* table = g.CurrentTable) - if (table->CurrentColumn != -1) + { + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + else if (table->CurrentColumn != -1) TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + } // FIXME: BeginFocusScope() const ImGuiID id = window->IDStack.back(); ms->Clear(); ms->FocusScopeId = id; ms->Flags = flags; - ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId); ms->BackupCursorMaxPos = window->DC.CursorMaxPos; - ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; + ms->ScopeRectMin = window->DC.CursorPos; + if (flags & ImGuiMultiSelectFlags_ScopeRect) + window->DC.CursorMaxPos = ms->ScopeRectMin; // CalcScopeRect() for ImGuiMultiSelectFlags_ScopeRect will measure in EndMultiSelect(). PushFocusScope(ms->FocusScopeId); + ms->IsFocused = IsInNavFocusRoute(g.CurrentFocusScopeId); if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; @@ -7966,7 +8121,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel storage->LastSelectionSize = 0; } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; - ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + //ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); @@ -8012,7 +8167,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() // Clear selection when clicking void? // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! // The InnerRect test is necessary for non-child/decorated windows. - bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos); + bool scope_hovered = window->InnerRect.Contains(g.IO.MousePos) && IsWindowHovered(ImGuiHoveredFlags_ChildWindows); if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)) scope_hovered &= scope_rect.Contains(g.IO.MousePos); if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0) @@ -8038,10 +8193,13 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) { IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope - ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + NavMoveRequestTryWrapping(GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); } // Unwind + if (ImGuiTable* table = g.CurrentTable) + if (table->IsInsideRow) + TableEndRow(table); window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); PopFocusScope(); @@ -8069,6 +8227,8 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; if (ms->IO.RangeSrcItem == selection_user_data) ms->RangeSrcPassedBy = true; + //ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + //ms->CurrSubmittedItem = selection_user_data; } else { @@ -8134,10 +8294,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; } } @@ -8214,8 +8377,31 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) if (ms->BoxSelectId != 0) if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) { - const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); - const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect); + ImRect item_rect = g.LastItemData.Rect; + if (!window->DC.NavIsScrollPushableX) // FIXME: Rename to be more generic. + if (ImGuiTable* table = g.CurrentTable) + if (table->CurrentColumn != -1) + { + // FIXME: We cannot solely use current ClipRect as it includes HostClipRect. + // However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns) + // A more generic version would be nice, but window->WorkRect.Min/Max exclude CellPadding. (#7994, #9383) + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + float clip_min_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Min.x : window->ClipRect.Min.x; + float clip_max_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Max.x : window->ClipRect.Max.x; + if (clip_min_x != clip_max_x) // When zero sized we expect that bounds have been clamped and thus are unreliable + { + item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column->MinX, clip_min_x)); + item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column->MaxX, clip_max_x)); + } + else + { + item_rect.Min.x = ImMax(item_rect.Min.x, column->MinX); + item_rect.Max.x = ImMin(item_rect.Max.x, column->MaxX); + } + //GetForegroundDrawList()->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255)); + } + const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(item_rect); + const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(item_rect); if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr)) { if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce) @@ -8227,6 +8413,9 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) { selected = !selected; MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); +#ifdef IMGUI_DEBUG_BOXSELECT + GetForegroundDrawList()->AddRectFilled(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, selected ? IM_COL32(0, 255, 0, 200) : IM_COL32(255, 0, 0, 200)); +#endif } storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); } @@ -8257,7 +8446,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); //---------------------------------------------------------------------------------------- @@ -8340,7 +8529,6 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } if (storage->NavIdItem == item_data) ms->NavIdPassedBy = true; - ms->LastSubmittedItem = item_data; *p_selected = selected; *p_pressed = pressed; @@ -8356,15 +8544,20 @@ void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) { // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + // FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value). + // The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem. + // User can always perform this merge if required. +#if 0 if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) { ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; - if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->PrevSubmittedItem && prev->Selected == selected) { prev->RangeLastItem = last_item; return; } } +#endif ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; ms->IO.Requests.push_back(req); // Add new request @@ -8602,7 +8795,8 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) const ImGuiStyle& style = g.Style; const ImGuiID id = GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); // Size default to hold ~7.25 items. // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. @@ -8625,7 +8819,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) if (label_size.x > 0.0f) { ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); AlignTextToFramePadding(); } @@ -8706,9 +8900,7 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)( // - PlotHistogram() //------------------------------------------------------------------------- // Plot/Graph widgets are not very good. -// Consider writing your own, or using a third-party one, see: -// - ImPlot https://github.com/epezent/implot -// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions +// Consider using ImPlot (https://github.com/epezent/implot) which is much better! //------------------------------------------------------------------------- int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg) @@ -8721,7 +8913,8 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); @@ -8820,10 +9013,11 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label, label_end, false); // Return hovered index or -1 if none are hovered. // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return idx_hovered; } @@ -8853,6 +9047,7 @@ void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } +// Plot Histogram (the data provided _is_ histogram data. it doesn't compute the histogram of your data) void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) { ImGuiPlotArrayGetterData data(values, stride); @@ -8891,7 +9086,7 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) if (float_format) { char fmt[64]; - ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + ImFormatString(fmt, IM_COUNTOF(fmt), "%%s: %s", float_format); Text(fmt, prefix, v); } else @@ -8930,7 +9125,7 @@ void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets) { ImU16 offset = 0; bool want_spacing = false; - for (int i = 0; i < IM_ARRAYSIZE(Widths); i++) + for (int i = 0; i < IM_COUNTOF(Widths); i++) { ImU16 width = Widths[i]; if (want_spacing && width > 0) @@ -9028,6 +9223,10 @@ void ImGui::EndMenuBar() NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat } } + else + { + NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_WrapX); + } PopClipRect(); PopID(); @@ -9117,11 +9316,7 @@ bool ImGui::BeginMainMenuBar() void ImGui::EndMainMenuBar() { ImGuiContext& g = *GImGui; - if (!g.CurrentWindow->DC.MenuBarAppending) - { - IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar - return; - } + IM_ASSERT_USER_ERROR_RET(g.CurrentWindow->DC.MenuBarAppending, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar EndMenuBar(); g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356) @@ -9190,7 +9385,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu g.MenusIdSubmittedThisFrame.push_back(id); - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window) // This is only done for items for the menu set and not the full parent window. @@ -9201,52 +9397,60 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. - ImVec2 popup_pos, pos = window->DC.CursorPos; + ImVec2 popup_pos; + ImVec2 pos = window->DC.CursorPos; PushID(label); if (!enabled) BeginDisabled(); - const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; + bool pressed; - // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoAutoClosePopups | (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnClick; + ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Menu inside a horizontal menu bar // Selectable extend their highlight by half ItemSpacing in each direction. // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() - popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); - float w = label_size.x; - ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, selectable_flags, label_size); LogSetNextTextDecoration("[", "]"); - RenderText(text_pos, label); + RenderText(text_pos, label, label_end, false); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight); } else { // Menu inside a regular/vertical menu // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.) - popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); - float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame + float min_w = offsets->DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); - ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); LogSetNextTextDecoration("", ">"); - RenderText(text_pos, label); + RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label, label_end, false); if (icon_w > 0.0f) - RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); - RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); + RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon); + RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); + popup_pos = ImVec2(pos.x, text_pos.y - style.WindowPadding.y); } if (!enabled) EndDisabled(); + // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394) + // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here. + if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0)) + { + ClearActiveID(); + SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner); + } + const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; if (menuset_is_open) PopItemFlag(); @@ -9327,6 +9531,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); PopID(); + if (g.ActiveId == id && want_open) + g.ActiveIdNoClearOnFocusLoss = true; + if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame. @@ -9380,7 +9587,8 @@ void ImGui::EndMenu() // Nav: When a left move request our menu failed, close ourselves. ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls + IM_ASSERT_USER_ERROR_RET((window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu), "Calling EndMenu() in wrong window!"); + ImGuiWindow* parent_window = window->ParentWindow; // Should always be != NULL is we passed assert. if (window->BeginCount == window->BeginCountPreviousFrame) if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet()) @@ -9402,7 +9610,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); @@ -9417,20 +9626,19 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut BeginDisabled(); // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover; - const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; + const ImGuiSelectableFlags selectable_flags = (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnRelease | (ImGuiSelectableFlags)ImGuiSelectableFlags_SetNavIdOnHover; + ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. - float w = label_size.x; window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); - pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f)); + pressed = Selectable("", selected, selectable_flags, ImVec2(label_size.x, 0.0f)); PopStyleVar(); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) - RenderText(text_pos, label); + RenderText(text_pos, label, label_end, false); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). } else @@ -9441,25 +9649,37 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f; float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); - float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame + float min_w = offsets->DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + ImVec2 text_pos(pos.x, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) { - RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label, label_end, false); if (icon_w > 0.0f) - RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); if (shortcut_w > 0.0f) { PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); LogSetNextTextDecoration("(", ")"); - RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); + RenderText(text_pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } if (selected) - RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); + RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); } } + + // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394) + // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here. + const ImGuiID id = g.LastItemData.ID; + if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0)) + { + ClearActiveID(); + SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner); + } + + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); if (!enabled) EndDisabled(); @@ -9520,7 +9740,7 @@ struct ImGuiTabBarSection float WidthAfterShrinkMinWidth; float Spacing; // Horizontal spacing at the end of the section. - ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } + ImGuiTabBarSection() { memset((void*)this, 0, sizeof(*this)); } }; namespace ImGui @@ -9536,7 +9756,7 @@ namespace ImGui ImGuiTabBar::ImGuiTabBar() { - memset(this, 0, sizeof(*this)); + memset((void*)this, 0, sizeof(*this)); CurrFrameVisible = PrevFrameVisible = -1; LastTabItemIdx = -1; } @@ -9617,7 +9837,7 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG return false; IM_ASSERT(tab_bar->ID != 0); - if ((flags & ImGuiTabBarFlags_DockNode) == 0) + if ((flags & ImGuiTabBarFlags_DockNode) == 0) // Already done PushOverrideID(tab_bar->ID); // Add to stack @@ -9679,11 +9899,7 @@ void ImGui::EndTabBar() return; ImGuiTabBar* tab_bar = g.CurrentTabBar; - if (tab_bar == NULL) - { - IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!"); - return; - } + IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!"); // Fallback in case no TabItem have been submitted if (tab_bar->WantLayout) @@ -9704,7 +9920,7 @@ void ImGui::EndTabBar() window->DC.CursorPos = tab_bar->BackupCursorPos; tab_bar->LastTabItemIdx = -1; - if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) // Already done PopID(); g.CurrentTabBarStack.pop_back(); @@ -9772,11 +9988,17 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); // Calculate spacing between sections - sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + const float tab_spacing = g.Style.ItemInnerSpacing.x; + sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? tab_spacing : 0.0f; + sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? tab_spacing : 0.0f; // Setup next selected tab ImGuiID scroll_to_tab_id = 0; + if (tab_bar->NextScrollToTabId) + { + scroll_to_tab_id = tab_bar->NextScrollToTabId; + tab_bar->NextScrollToTabId = 0; + } if (tab_bar->NextSelectedTabId) { tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; @@ -9834,8 +10056,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) int section_n = TabItemGetSectionIdx(tab); ImGuiTabBarSection* section = §ions[section_n]; - section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); - section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); + section->Width += tab->ContentWidth + (section_n == curr_section_n ? tab_spacing : 0.0f); + section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? tab_spacing : 0.0f); curr_section_n = section_n; // Store data so we can build an array sorted by width if we need to shrink tabs down @@ -10181,6 +10403,7 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) return; + const float tab_spacing = g.Style.ItemInnerSpacing.x; const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); @@ -10199,8 +10422,8 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s dst_idx = i; // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered. - const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x; - const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x; + const float x1 = bar_offset + dst_tab->Offset - tab_spacing; + const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + tab_spacing; //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2)) break; @@ -10363,11 +10586,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f return false; ImGuiTabBar* tab_bar = g.CurrentTabBar; - if (tab_bar == NULL) - { - IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); - return false; - } + IM_ASSERT_USER_ERROR_RETV(tab_bar != NULL, false, "Needs to be called between BeginTabBar() and EndTabBar()!"); IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); @@ -10387,11 +10606,7 @@ void ImGui::EndTabItem() return; ImGuiTabBar* tab_bar = g.CurrentTabBar; - if (tab_bar == NULL) - { - IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); - return; - } + IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); IM_ASSERT(tab_bar->LastTabItemIdx >= 0); ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) @@ -10406,11 +10621,7 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) return false; ImGuiTabBar* tab_bar = g.CurrentTabBar; - if (tab_bar == NULL) - { - IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); - return false; - } + IM_ASSERT_USER_ERROR_RETV(tab_bar != NULL, false, "Needs to be called between BeginTabBar() and EndTabBar()!"); return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL); } @@ -10422,11 +10633,7 @@ void ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float return; ImGuiTabBar* tab_bar = g.CurrentTabBar; - if (tab_bar == NULL) - { - IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); - return; - } + IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); SetNextItemWidth(width); TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL); } @@ -10560,7 +10767,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); if (want_clip_rect) - PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); + PushClipRect(ImVec2(ImClamp(bb.Min.x, tab_bar->ScrollingRectMinX, tab_bar->ScrollingRectMaxX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y); @@ -10575,7 +10782,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; @@ -10684,7 +10891,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, float rounding = style.TabRounding; display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9); display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11); - display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize); + display_draw_list->PathStroke(overline_col, style.TabBarOverlineSize); } else { @@ -10802,7 +11009,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); - draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize); } } @@ -10811,7 +11018,8 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped) { ImGuiContext& g = *GImGui; - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); if (out_just_closed) *out_just_closed = false; @@ -10896,7 +11104,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, } } LogSetNextTextDecoration("/", "\\"); - RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size); + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, label_end, &label_size); #if 0 if (!is_contents_visible) diff --git a/src/Libs/imgui/imstb_rectpack.h b/src/Libs/imgui/imstb_rectpack.h index f6917e7..ad08921 100644 --- a/src/Libs/imgui/imstb_rectpack.h +++ b/src/Libs/imgui/imstb_rectpack.h @@ -315,7 +315,7 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0 if (node->y > min_y) { // raise min_y higher. // we've accounted for all waste up to min_y, - // but we'll now add more waste for everything we've visted + // but we'll now add more waste for everything we've visited waste_area += visited_width * (node->y - min_y); min_y = node->y; // the first time through, visited_width might be reduced @@ -470,7 +470,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i // insert the new node into the right starting point, and // let 'cur' point to the remaining nodes needing to be - // stiched back in + // stitched back in cur = *res.prev_link; if (cur->x < res.x) { diff --git a/src/Libs/imgui/imstb_truetype.h b/src/Libs/imgui/imstb_truetype.h index cf33289..ec4d42c 100644 --- a/src/Libs/imgui/imstb_truetype.h +++ b/src/Libs/imgui/imstb_truetype.h @@ -886,7 +886,7 @@ STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, fl // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// the same as stbtt_GetCodepointBitmap, but you can specify a subpixel // shift for the character STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);