diff --git a/external/imgui/imgui/LICENSE.txt b/external/imgui/imgui/LICENSE.txt deleted file mode 100644 index 4023e0ca..00000000 --- a/external/imgui/imgui/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014-2022 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 -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/external/imgui/imgui/backends/imgui_impl_sdl.cpp b/external/imgui/imgui/backends/imgui_impl_sdl2.cpp similarity index 82% rename from external/imgui/imgui/backends/imgui_impl_sdl.cpp rename to external/imgui/imgui/backends/imgui_impl_sdl2.cpp index 9a5ca1d5..86be801c 100644 --- a/external/imgui/imgui/backends/imgui_impl_sdl.cpp +++ b/external/imgui/imgui/backends/imgui_impl_sdl2.cpp @@ -8,8 +8,7 @@ // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// Missing features: -// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. +// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. // 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. @@ -18,6 +17,16 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644) +// 2023-02-07: Implement IME handler (io.SetPlatformImeDataFn will call SDL_SetTextInputRect()/SDL_StartTextInput()). +// 2023-02-07: *BREAKING CHANGE* Renamed this backend file from imgui_impl_sdl.cpp/.h to imgui_impl_sdl2.cpp/.h in prevision for the future release of SDL3. +// 2023-02-02: Avoid calling SDL_SetCursor() when cursor has not changed, as the function is surprisingly costly on Mac with latest SDL (may be fixed in next SDL version). +// 2023-02-02: Added support for SDL 2.0.18+ preciseX/preciseY mouse wheel data for smooth scrolling + Scaling X value on Emscripten (bug?). (#4019, #6096) +// 2023-02-02: Removed SDL_MOUSEWHEEL value clamping, as values seem correct in latest Emscripten. (#4019) +// 2023-02-01: Flipping SDL_MOUSEWHEEL 'wheel.x' value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463) +// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. +// 2022-09-26: Inputs: Disable SDL 2.0.22 new "auto capture" (SDL_HINT_MOUSE_AUTO_CAPTURE) which prevents drag and drop across windows for multi-viewport support + don't capture when drag and dropping. (#5710) +// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-03-22: Inputs: Fix mouse position issues when dragging outside of boundaries. SDL_CaptureMouse() erroneously still gives out LEAVE events when hovering OS decorations. // 2022-03-22: Inputs: Added support for extra mouse buttons (SDL_BUTTON_X1/SDL_BUTTON_X2). // 2022-02-04: Added SDL_Renderer* parameter to ImGui_ImplSDL2_InitForSDLRenderer(), so we can use SDL_GetRendererOutputSize() instead of SDL_GL_GetDrawableSize() when bound to a SDL_Renderer. @@ -61,7 +70,7 @@ // 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. #include "imgui.h" -#include "imgui_impl_sdl.h" +#include "imgui_impl_sdl2.h" // SDL #include @@ -75,7 +84,6 @@ #else #define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 0 #endif -#define SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH SDL_VERSION_ATLEAST(2,0,5) #define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) // SDL Data @@ -84,8 +92,10 @@ struct ImGui_ImplSDL2_Data SDL_Window* Window; SDL_Renderer* Renderer; Uint64 Time; + Uint32 MouseWindowID; int MouseButtonsDown; SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT]; + SDL_Cursor* LastMouseCursor; int PendingMouseLeaveFrame; char* ClipboardTextData; bool MouseCanUseGlobalState; @@ -99,7 +109,7 @@ struct ImGui_ImplSDL2_Data // FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context. static ImGui_ImplSDL2_Data* ImGui_ImplSDL2_GetBackendData() { - return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : NULL; + return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } // Functions @@ -117,6 +127,25 @@ static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text) SDL_SetClipboardText(text); } +// Note: native IME will only display if user calls SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1") _before_ SDL_CreateWindow(). +static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport*, ImGuiPlatformImeData* data) +{ + if (data->WantVisible) + { + SDL_Rect r; + r.x = (int)data->InputPos.x; + r.y = (int)data->InputPos.y; + r.w = 1; + r.h = (int)data->InputLineHeight; + SDL_SetTextInputRect(&r); + SDL_StartTextInput(); + } + else + { + SDL_StopTextInput(); + } +} + static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode) { switch (keycode) @@ -233,10 +262,10 @@ static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode) static void ImGui_ImplSDL2_UpdateKeyModifiers(SDL_Keymod sdl_key_mods) { ImGuiIO& io = ImGui::GetIO(); - io.AddKeyEvent(ImGuiKey_ModCtrl, (sdl_key_mods & KMOD_CTRL) != 0); - io.AddKeyEvent(ImGuiKey_ModShift, (sdl_key_mods & KMOD_SHIFT) != 0); - io.AddKeyEvent(ImGuiKey_ModAlt, (sdl_key_mods & KMOD_ALT) != 0); - io.AddKeyEvent(ImGuiKey_ModSuper, (sdl_key_mods & KMOD_GUI) != 0); + io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & KMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & KMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & KMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & KMOD_GUI) != 0); } // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. @@ -253,13 +282,23 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) { case SDL_MOUSEMOTION: { - io.AddMousePosEvent((float)event->motion.x, (float)event->motion.y); + ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y); + io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); return true; } case SDL_MOUSEWHEEL: { - float wheel_x = (event->wheel.x > 0) ? 1.0f : (event->wheel.x < 0) ? -1.0f : 0.0f; - float wheel_y = (event->wheel.y > 0) ? 1.0f : (event->wheel.y < 0) ? -1.0f : 0.0f; + //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY); +#if SDL_VERSION_ATLEAST(2,0,18) // If this fails to compile on Emscripten: update to latest Emscripten! + float wheel_x = -event->wheel.preciseX; + float wheel_y = event->wheel.preciseY; +#else + float wheel_x = -(float)event->wheel.x; + float wheel_y = (float)event->wheel.y; +#endif +#ifdef __EMSCRIPTEN__ + wheel_x /= 100.0f; +#endif io.AddMouseWheelEvent(wheel_x, wheel_y); return true; } @@ -301,7 +340,10 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) // we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details. Uint8 window_event = event->window.event; if (window_event == SDL_WINDOWEVENT_ENTER) + { + bd->MouseWindowID = event->window.windowID; bd->PendingMouseLeaveFrame = 0; + } if (window_event == SDL_WINDOWEVENT_LEAVE) bd->PendingMouseLeaveFrame = ImGui::GetFrameCount() + 1; if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED) @@ -317,7 +359,7 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer) { ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!"); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); // Check and store if we are on a SDL backend that supports global mouse position // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) @@ -333,7 +375,7 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer) // Setup backend capabilities flags ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)(); io.BackendPlatformUserData = (void*)bd; - io.BackendPlatformName = "imgui_impl_sdl"; + io.BackendPlatformName = "imgui_impl_sdl2"; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) @@ -343,7 +385,8 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer) io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText; io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText; - io.ClipboardUserData = NULL; + io.ClipboardUserData = nullptr; + io.SetPlatformImeDataFn = ImGui_ImplSDL2_SetPlatformImeData; // Load mouse cursors bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); @@ -357,31 +400,48 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer) bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); // Set platform dependent data in viewport -#ifdef _WIN32 + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->PlatformHandleRaw = nullptr; SDL_SysWMinfo info; SDL_VERSION(&info.version); if (SDL_GetWindowWMInfo(window, &info)) - ImGui::GetMainViewport()->PlatformHandleRaw = (void*)info.info.win.window; -#else - (void)window; + { +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + main_viewport->PlatformHandleRaw = (void*)info.info.win.window; +#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA) + main_viewport->PlatformHandleRaw = (void*)info.info.cocoa.window; #endif + } - // Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event. + // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event. // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered. // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application. // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click: // you can ignore SDL_MOUSEBUTTONDOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) -#if SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH +#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); #endif + // From 2.0.18: Enable native IME. + // IMPORTANT: This is used at the time of SDL_CreateWindow() so this will only affects secondary windows, if any. + // For the main window to be affected, your application needs to call this manually before calling SDL_CreateWindow(). +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710) +#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); +#endif + return true; } bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context) { IM_UNUSED(sdl_gl_context); // Viewport branch will need this. - return ImGui_ImplSDL2_Init(window, NULL); + return ImGui_ImplSDL2_Init(window, nullptr); } bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window) @@ -389,7 +449,7 @@ bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window) #if !SDL_HAS_VULKAN IM_ASSERT(0 && "Unsupported"); #endif - return ImGui_ImplSDL2_Init(window, NULL); + return ImGui_ImplSDL2_Init(window, nullptr); } bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window) @@ -397,12 +457,12 @@ bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window) #if !defined(_WIN32) IM_ASSERT(0 && "Unsupported"); #endif - return ImGui_ImplSDL2_Init(window, NULL); + return ImGui_ImplSDL2_Init(window, nullptr); } bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window) { - return ImGui_ImplSDL2_Init(window, NULL); + return ImGui_ImplSDL2_Init(window, nullptr); } bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer) @@ -413,16 +473,17 @@ bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* rendere void ImGui_ImplSDL2_Shutdown() { ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); - IM_ASSERT(bd != NULL && "No platform backend to shutdown, or already shutdown?"); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); if (bd->ClipboardTextData) SDL_free(bd->ClipboardTextData); for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) SDL_FreeCursor(bd->MouseCursors[cursor_n]); + bd->LastMouseCursor = NULL; - io.BackendPlatformName = NULL; - io.BackendPlatformUserData = NULL; + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; IM_DELETE(bd); } @@ -434,7 +495,7 @@ 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 imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside - SDL_CaptureMouse(bd->MouseButtonsDown != 0 ? SDL_TRUE : SDL_FALSE); + SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE); SDL_Window* focused_window = SDL_GetKeyboardFocus(); const bool is_app_focused = (bd->Window == focused_window); #else @@ -473,7 +534,12 @@ static void ImGui_ImplSDL2_UpdateMouseCursor() else { // Show OS mouse cursor - SDL_SetCursor(bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]); + SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]; + if (bd->LastMouseCursor != expected_cursor) + { + SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113) + bd->LastMouseCursor = expected_cursor; + } SDL_ShowCursor(SDL_TRUE); } } @@ -527,7 +593,7 @@ static void ImGui_ImplSDL2_UpdateGamepads() void ImGui_ImplSDL2_NewFrame() { ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); - IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDL2_Init()?"); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplSDL2_Init()?"); ImGuiIO& io = ImGui::GetIO(); // Setup display size (every frame to accommodate for window resizing) @@ -536,7 +602,7 @@ void ImGui_ImplSDL2_NewFrame() SDL_GetWindowSize(bd->Window, &w, &h); if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED) w = h = 0; - if (bd->Renderer != NULL) + if (bd->Renderer != nullptr) SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h); else SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h); @@ -545,15 +611,19 @@ void ImGui_ImplSDL2_NewFrame() io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) + // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644) static Uint64 frequency = SDL_GetPerformanceFrequency(); 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); bd->Time = current_time; if (bd->PendingMouseLeaveFrame && bd->PendingMouseLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0) { - io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + bd->MouseWindowID = 0; bd->PendingMouseLeaveFrame = 0; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } ImGui_ImplSDL2_UpdateMouseData(); diff --git a/external/imgui/imgui/backends/imgui_impl_sdl.h b/external/imgui/imgui/backends/imgui_impl_sdl2.h similarity index 90% rename from external/imgui/imgui/backends/imgui_impl_sdl.h rename to external/imgui/imgui/backends/imgui_impl_sdl2.h index dc18c2cc..68da36a8 100644 --- a/external/imgui/imgui/backends/imgui_impl_sdl.h +++ b/external/imgui/imgui/backends/imgui_impl_sdl2.h @@ -7,8 +7,7 @@ // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// Missing features: -// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. +// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. // 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. diff --git a/external/imgui/imgui/backends/imgui_impl_vulkan.cpp b/external/imgui/imgui/backends/imgui_impl_vulkan.cpp index e5a9491f..bfc488c2 100644 --- a/external/imgui/imgui/backends/imgui_impl_vulkan.cpp +++ b/external/imgui/imgui/backends/imgui_impl_vulkan.cpp @@ -2,8 +2,8 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. // [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. +// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. // Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'. // This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. @@ -30,6 +30,10 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2023-01-02: Vulkan: Fixed sampler passed to ImGui_ImplVulkan_AddTexture() not being honored + removed a bunch of duplicate code. +// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. +// 2022-10-04: Vulkan: Added experimental ImGui_ImplVulkan_RemoveTexture() for api symetry. (#914, #5738). +// 2022-01-20: Vulkan: Added support for ImTextureID as VkDescriptorSet. User need to call ImGui_ImplVulkan_AddTexture(). Building for 32-bit targets requires '#define ImTextureID ImU64'. (#914). // 2021-10-15: Vulkan: Call vkCmdSetScissor() at the end of render a full-viewport to reduce likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling vkCmdSetScissor() explicitly every frame. // 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). // 2021-03-22: Vulkan: Fix mapped memory validation error when buffer sizes are not multiple of VkPhysicalDeviceLimits::nonCoherentAtomSize. @@ -113,7 +117,7 @@ struct ImGui_ImplVulkan_Data VkDeviceMemory UploadBufferMemory; VkBuffer UploadBuffer; - // Render buffers + // Render buffers for main window ImGui_ImplVulkanH_WindowRenderBuffers MainWindowRenderBuffers; ImGui_ImplVulkan_Data() @@ -189,6 +193,7 @@ static bool g_FunctionsLoaded = true; IMGUI_VULKAN_FUNC_MAP_MACRO(vkDeviceWaitIdle) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkFlushMappedMemoryRanges) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeCommandBuffers) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeDescriptorSets) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeMemory) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetBufferMemoryRequirements) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetImageMemoryRequirements) \ @@ -325,7 +330,7 @@ static uint32_t __glsl_shader_frag_spv[] = // FIXME: multi-context support is not tested and probably dysfunctional in this backend. static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() { - return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : NULL; + return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) @@ -444,7 +449,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Allocate array to store enough vertex/index buffers ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &bd->MainWindowRenderBuffers; - if (wrb->FrameRenderBuffers == NULL) + if (wrb->FrameRenderBuffers == nullptr) { wrb->Index = 0; wrb->Count = v->ImageCount; @@ -466,8 +471,8 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm CreateOrResizeBuffer(rb->IndexBuffer, rb->IndexBufferMemory, rb->IndexBufferSize, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); // Upload vertex/index data into a single contiguous GPU buffer - ImDrawVert* vtx_dst = NULL; - ImDrawIdx* idx_dst = NULL; + ImDrawVert* vtx_dst = nullptr; + ImDrawIdx* idx_dst = nullptr; VkResult err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, rb->VertexBufferSize, 0, (void**)(&vtx_dst)); check_vk_result(err); err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, rb->IndexBufferSize, 0, (void**)(&idx_dst)); @@ -510,7 +515,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback != NULL) + if (pcmd->UserCallback != nullptr) { // 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.) @@ -549,7 +554,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->FontDescriptorSet); desc_set[0] = bd->FontDescriptorSet; } - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, desc_set, 0, NULL); + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, desc_set, 0, nullptr); // Draw vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); @@ -562,7 +567,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Note: at this point both vkCmdSetViewport() and vkCmdSetScissor() have been called. // Our last values will leak into user/application rendering IF: // - Your app uses a pipeline with VK_DYNAMIC_STATE_VIEWPORT or VK_DYNAMIC_STATE_SCISSOR dynamic state - // - And you forgot to call vkCmdSetViewport() and vkCmdSetScissor() yourself to explicitely set that state. + // - And you forgot to call vkCmdSetViewport() and vkCmdSetScissor() yourself to explicitly set that state. // If you use VK_DYNAMIC_STATE_VIEWPORT or VK_DYNAMIC_STATE_SCISSOR you are responsible for setting the values before rendering. // In theory we should aim to backup/restore those values but I am not sure this is possible. // We perform a call to vkCmdSetScissor() to set back a full viewport which is likely to fix things for 99% users but technically this is not perfect. (See github #4644) @@ -654,7 +659,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) // Upload to Buffer: { - char* map = NULL; + char* map = nullptr; err = vkMapMemory(v->Device, bd->UploadBufferMemory, 0, upload_size, 0, (void**)(&map)); check_vk_result(err); memcpy(map, pixels, upload_size); @@ -680,7 +685,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copy_barrier[0].subresourceRange.levelCount = 1; copy_barrier[0].subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, copy_barrier); VkBufferImageCopy region = {}; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -702,7 +707,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; use_barrier[0].subresourceRange.levelCount = 1; use_barrier[0].subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, use_barrier); } // Store our identifier @@ -735,72 +740,6 @@ static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAlloca } } -static void ImGui_ImplVulkan_CreateFontSampler(VkDevice device, const VkAllocationCallbacks* allocator) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->FontSampler) - return; - - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. - VkSamplerCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - info.magFilter = VK_FILTER_LINEAR; - info.minFilter = VK_FILTER_LINEAR; - info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - info.minLod = -1000; - info.maxLod = 1000; - info.maxAnisotropy = 1.0f; - VkResult err = vkCreateSampler(device, &info, allocator, &bd->FontSampler); - check_vk_result(err); -} - -static void ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDevice device, const VkAllocationCallbacks* allocator) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->DescriptorSetLayout) - return; - - ImGui_ImplVulkan_CreateFontSampler(device, allocator); - VkSampler sampler[1] = { bd->FontSampler }; - VkDescriptorSetLayoutBinding binding[1] = {}; - binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - binding[0].descriptorCount = 1; - binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - binding[0].pImmutableSamplers = sampler; - VkDescriptorSetLayoutCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = 1; - info.pBindings = binding; - VkResult err = vkCreateDescriptorSetLayout(device, &info, allocator, &bd->DescriptorSetLayout); - check_vk_result(err); -} - -static void ImGui_ImplVulkan_CreatePipelineLayout(VkDevice device, const VkAllocationCallbacks* allocator) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->PipelineLayout) - return; - - // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix - ImGui_ImplVulkan_CreateDescriptorSetLayout(device, allocator); - VkPushConstantRange push_constants[1] = {}; - push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - push_constants[0].offset = sizeof(float) * 0; - push_constants[0].size = sizeof(float) * 4; - VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; - VkPipelineLayoutCreateInfo layout_info = {}; - layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - layout_info.setLayoutCount = 1; - layout_info.pSetLayouts = set_layout; - layout_info.pushConstantRangeCount = 1; - layout_info.pPushConstantRanges = push_constants; - VkResult err = vkCreatePipelineLayout(device, &layout_info, allocator, &bd->PipelineLayout); - check_vk_result(err); -} - static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline* pipeline, uint32_t subpass) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -885,8 +824,6 @@ static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationC dynamic_state.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states); dynamic_state.pDynamicStates = dynamic_states; - ImGui_ImplVulkan_CreatePipelineLayout(device, allocator); - VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; info.flags = bd->PipelineCreateFlags; @@ -915,6 +852,7 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() if (!bd->FontSampler) { + // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. VkSamplerCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; info.magFilter = VK_FILTER_LINEAR; @@ -932,12 +870,10 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() if (!bd->DescriptorSetLayout) { - VkSampler sampler[1] = {bd->FontSampler}; VkDescriptorSetLayoutBinding binding[1] = {}; binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; binding[0].descriptorCount = 1; binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - binding[0].pImmutableSamplers = sampler; VkDescriptorSetLayoutCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.bindingCount = 1; @@ -1012,7 +948,7 @@ bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const ch #ifdef VK_NO_PROTOTYPES #define IMGUI_VULKAN_FUNC_LOAD(func) \ func = reinterpret_cast(loader_func(#func, user_data)); \ - if (func == NULL) \ + if (func == nullptr) \ return false; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_LOAD) #undef IMGUI_VULKAN_FUNC_LOAD @@ -1029,7 +965,7 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); // Setup backend capabilities flags ImGui_ImplVulkan_Data* bd = IM_NEW(ImGui_ImplVulkan_Data)(); @@ -1058,19 +994,19 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend void ImGui_ImplVulkan_Shutdown() { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); ImGui_ImplVulkan_DestroyDeviceObjects(); - io.BackendRendererName = NULL; - io.BackendRendererUserData = NULL; + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; IM_DELETE(bd); } void ImGui_ImplVulkan_NewFrame() { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - IM_ASSERT(bd != NULL && "Did you call ImGui_ImplVulkan_Init()?"); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplVulkan_Init()?"); IM_UNUSED(bd); } @@ -1119,11 +1055,18 @@ VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image write_desc[0].descriptorCount = 1; write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write_desc[0].pImageInfo = desc_image; - vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL); + vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, nullptr); } return descriptor_set; } +void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + vkFreeDescriptorSets(v->Device, v->DescriptorPool, 1, &descriptor_set); +} + //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers // (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own app.) @@ -1143,7 +1086,7 @@ VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space) { IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); - IM_ASSERT(request_formats != NULL); + IM_ASSERT(request_formats != nullptr); IM_ASSERT(request_formats_count > 0); // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation @@ -1151,7 +1094,7 @@ VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physic // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40, // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used. uint32_t avail_count; - vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, NULL); + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, nullptr); ImVector avail_format; avail_format.resize((int)avail_count); vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, avail_format.Data); @@ -1188,12 +1131,12 @@ VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physic VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count) { IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); - IM_ASSERT(request_modes != NULL); + IM_ASSERT(request_modes != nullptr); IM_ASSERT(request_modes_count > 0); // Request a certain mode and confirm that it is available. If not use VK_PRESENT_MODE_FIFO_KHR which is mandatory uint32_t avail_count = 0; - vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, NULL); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, nullptr); ImVector avail_modes; avail_modes.resize((int)avail_count); vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, avail_modes.Data); @@ -1285,8 +1228,8 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); - wd->Frames = NULL; - wd->FrameSemaphores = NULL; + wd->Frames = nullptr; + wd->FrameSemaphores = nullptr; wd->ImageCount = 0; if (wd->RenderPass) vkDestroyRenderPass(device, wd->RenderPass, allocator); @@ -1333,7 +1276,7 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V } err = vkCreateSwapchainKHR(device, &info, allocator, &wd->Swapchain); check_vk_result(err); - err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, NULL); + err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, nullptr); check_vk_result(err); VkImage backbuffers[16] = {}; IM_ASSERT(wd->ImageCount >= min_image_count); @@ -1341,7 +1284,7 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers); check_vk_result(err); - IM_ASSERT(wd->Frames == NULL); + IM_ASSERT(wd->Frames == nullptr); wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount); wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->ImageCount); memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount); @@ -1456,8 +1399,8 @@ void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); - wd->Frames = NULL; - wd->FrameSemaphores = NULL; + wd->Frames = nullptr; + wd->FrameSemaphores = nullptr; vkDestroyPipeline(device, wd->Pipeline, allocator); vkDestroyRenderPass(device, wd->RenderPass, allocator); vkDestroySwapchainKHR(device, wd->Swapchain, allocator); @@ -1501,7 +1444,7 @@ void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVul for (uint32_t n = 0; n < buffers->Count; n++) ImGui_ImplVulkanH_DestroyFrameRenderBuffers(device, &buffers->FrameRenderBuffers[n], allocator); IM_FREE(buffers->FrameRenderBuffers); - buffers->FrameRenderBuffers = NULL; + buffers->FrameRenderBuffers = nullptr; buffers->Index = 0; buffers->Count = 0; } diff --git a/external/imgui/imgui/backends/imgui_impl_vulkan.h b/external/imgui/imgui/backends/imgui_impl_vulkan.h index 52d9ffe7..64cc887c 100644 --- a/external/imgui/imgui/backends/imgui_impl_vulkan.h +++ b/external/imgui/imgui/backends/imgui_impl_vulkan.h @@ -2,8 +2,8 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. // [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. +// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. // Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'. // See imgui_impl_vulkan.cpp file for details. @@ -73,12 +73,14 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontUploadObjects(); IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated) // Register a texture (VkDescriptorSet == ImTextureID) -// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem, please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions. +// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem +// Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions. IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); +IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); // Optional: load Vulkan functions with a custom function loader // This is only useful with IMGUI_IMPL_VULKAN_NO_PROTOTYPES / VK_NO_PROTOTYPES -IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = NULL); +IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = nullptr); //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers @@ -87,8 +89,8 @@ IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*l // You probably do NOT need to use or care about those functions. // Those functions only exist because: // 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. -// 2) the upcoming multi-viewport feature will need them internally. -// Generally we avoid exposing any kind of superfluous high-level helpers in the backends, +// 2) the multi-viewport / platform window implementation needs them internally. +// Generally we avoid exposing any kind of superfluous high-level helpers in the bindings, // but it is too much code to duplicate everywhere so we exceptionally expose them. // // Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). diff --git a/external/imgui/imgui/imconfig.h b/external/imgui/imgui/imconfig.h index 7de64f11..876cf32f 100644 --- a/external/imgui/imgui/imconfig.h +++ b/external/imgui/imgui/imconfig.h @@ -90,6 +90,8 @@ constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ operator MyVec4() const { return MyVec4(x,y,z,w); } */ +//---- ...Or use Dear ImGui's own very basic math operators. +//#define IMGUI_DEFINE_MATH_OPERATORS //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). @@ -108,11 +110,6 @@ //#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK __debugbreak() -//---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), -// (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) -// This adds a small runtime cost which is why it is not enabled by default. -//#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX - //---- Debug Tools: Enable slower asserts //#define IMGUI_DEBUG_PARANOID @@ -123,4 +120,3 @@ namespace ImGui void MyFunction(const char* name, const MyMatrix44& v); } */ -#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES \ No newline at end of file diff --git a/external/imgui/imgui/imgui.cpp b/external/imgui/imgui/imgui.cpp index bbbbe8be..b7a099c2 100644 --- a/external/imgui/imgui/imgui.cpp +++ b/external/imgui/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (main code and documentation) // Help: @@ -11,7 +11,7 @@ // - FAQ http://dearimgui.org/faq // - Homepage & latest https://github.com/ocornut/imgui // - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/5243 (please post your screenshots/video there!) +// - Gallery https://github.com/ocornut/imgui/issues/5886 (please post your screenshots/video there!) // - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Glossary https://github.com/ocornut/imgui/wiki/Glossary // - Issues & support https://github.com/ocornut/imgui/issues @@ -65,10 +65,11 @@ CODE // [SECTION] MISC HELPERS/UTILITIES (Color functions) // [SECTION] ImGuiStorage // [SECTION] ImGuiTextFilter -// [SECTION] ImGuiTextBuffer +// [SECTION] ImGuiTextBuffer, ImGuiTextIndex // [SECTION] ImGuiListClipper // [SECTION] STYLING // [SECTION] RENDER HELPERS +// [SECTION] INITIALIZATION, SHUTDOWN // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) // [SECTION] INPUTS // [SECTION] ERROR CHECKING @@ -80,7 +81,8 @@ CODE // [SECTION] DRAG AND DROP // [SECTION] LOGGING/CAPTURING // [SECTION] SETTINGS -// [SECTION] VIEWPORTS +// [SECTION] LOCALIZATION +// [SECTION] VIEWPORTS, PLATFORM WINDOWS // [SECTION] PLATFORM DEPENDENT HELPERS // [SECTION] METRICS/DEBUGGER WINDOW // [SECTION] DEBUG LOG WINDOW @@ -126,7 +128,7 @@ CODE - Hold SHIFT or use mouse to select text. - CTRL+Left/Right to word jump. - CTRL+Shift+Left/Right to select words. - - CTRL+A our Double-Click to select all. + - CTRL+A or Double-Click to select all. - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ - CTRL+Z,CTRL+Y to undo/redo. - ESCAPE to revert text to its original value. @@ -290,7 +292,7 @@ CODE --------------------------------------------- The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. - void void MyImGuiRenderFunction(ImDrawData* draw_data) + void MyImGuiRenderFunction(ImDrawData* draw_data) { // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. @@ -384,6 +386,44 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2023/02/15 (1.89.4) - moved the optional "courtesy maths operators" implementation from imgui_internal.h in imgui.h. + Even though we encourage using your own maths types and operators by setting up IM_VEC2_CLASS_EXTRA, + it has been frequently requested by people to use our own. We had an opt-in define which was + previously fulfilled in imgui_internal.h. It is now fulfilled in imgui.h. (#6164) + - OK: #define IMGUI_DEFINE_MATH_OPERATORS / #include "imgui.h" / #include "imgui_internal.h" + - Error: #include "imgui.h" / #define IMGUI_DEFINE_MATH_OPERATORS / #include "imgui_internal.h" + - 2023/02/07 (1.89.3) - backends: renamed "imgui_impl_sdl.cpp" to "imgui_impl_sdl2.cpp" and "imgui_impl_sdl.h" to "imgui_impl_sdl2.h". (#6146) This is in prevision for the future release of SDL3. + - 2022/10/26 (1.89) - commented out redirecting OpenPopupContextItem() which was briefly the name of OpenPopupOnItemClick() from 1.77 to 1.79. + - 2022/10/12 (1.89) - removed runtime patching of invalid "%f"/"%0.f" format strings for DragInt()/SliderInt(). This was obsoleted in 1.61 (May 2018). See 1.61 changelog for details. + - 2022/09/26 (1.89) - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline redirection enums (will obsolete). + - ImGuiKey_ModCtrl and ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl + - ImGuiKey_ModShift and ImGuiModFlags_Shift -> ImGuiMod_Shift + - ImGuiKey_ModAlt and ImGuiModFlags_Alt -> ImGuiMod_Alt + - ImGuiKey_ModSuper and ImGuiModFlags_Super -> ImGuiMod_Super + the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends. + the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only by third-party extensions. + exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence schedule to reduce confusion and because they were not meant to be used anyway. + - 2022/09/20 (1.89) - ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers. + this will require uses of legacy backend-dependent indices to be casted, e.g. + - with imgui_impl_glfw: IsKeyPressed(GLFW_KEY_A) -> IsKeyPressed((ImGuiKey)GLFW_KEY_A); + - with imgui_impl_win32: IsKeyPressed('A') -> IsKeyPressed((ImGuiKey)'A') + - etc. However if you are upgrading code you might well use the better, backend-agnostic IsKeyPressed(ImGuiKey_A) now! + - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly. NULL/nullptr is ok but require cast, e.g. TreePush((void*)nullptr); + - 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June 2020): + - DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f. + - SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f. + - BeginPopupContextWindow(const char*, ImGuiMouseButton, bool) -> use BeginPopupContextWindow(const char*, ImGuiPopupFlags) + - 2022/09/02 (1.89) - obsoleted using SetCursorPos()/SetCursorScreenPos() to extend parent window/cell boundaries. + this relates to when moving the cursor position beyond current boundaries WITHOUT submitting an item. + - previously this would make the window content size ~200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); + - instead, please submit an item: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); + - alternative: + Begin(...) + Dummy(ImVec2(200,200)) + End(); + - content size is now only extended when submitting an item! + - with '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will now be detected and assert. + - without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we obsolete it. - 2022/08/03 (1.89) - changed signature of ImageButton() function. Kept redirection function (will obsolete). - added 'const char* str_id' parameter + removed 'int frame_padding = -1' parameter. - old signature: bool ImageButton(ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), int frame_padding = -1, ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1)); @@ -415,7 +455,7 @@ CODE - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX) - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes). - - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiKey_ModXXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiKey_ModXXX values.* + - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.* - one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock native indices (e.g. "io.KeyMap[ImGuiKey_A] = ImGuiKey_A") because those values are now larger than the legacy KeyDown[] array. Will assert. - inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper. - 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept redirection enum. @@ -776,7 +816,7 @@ CODE - 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? - Q: How can I display an image? What is ImTextureID, how does it works? + 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/ImVec4? Q: How can I interact with standard C++ types (such as std::string and std::vector)? Q: How can I display custom shapes? (using low-level ImDrawList API) @@ -826,16 +866,15 @@ CODE #define _CRT_SECURE_NO_WARNINGS #endif -#include "imgui.h" -#ifndef IMGUI_DISABLE - #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE #include "imgui_internal.h" // System includes -#include // toupper #include // vsnprintf, sscanf, printf #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier #include // intptr_t @@ -882,7 +921,7 @@ CODE #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types #endif -#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif @@ -905,7 +944,7 @@ CODE #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #elif defined(__GNUC__) -// We disable -Wpragmas because GCC doesn't provide an has_warning equivalent and some forks/patches may not following the warning/version association. +// We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size @@ -929,7 +968,7 @@ static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time // Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). 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 = 2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. +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. //------------------------------------------------------------------------- // [SECTION] FORWARD DECLARATIONS @@ -985,14 +1024,17 @@ static void ErrorCheckEndFrameSanityChecks(); static void UpdateDebugToolItemPicker(); static void UpdateDebugToolStackQueries(); -// Misc -static void UpdateSettings(); +// Inputs static void UpdateKeyboardInputs(); static void UpdateMouseInputs(); static void UpdateMouseWheel(); +static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); + +// Misc +static void UpdateSettings(); static bool UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); -static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size); +static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size); static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open); static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col); static void RenderDimmedBackgrounds(); @@ -1079,10 +1121,13 @@ ImGuiStyle::ImGuiStyle() LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. - TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. 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; // Thickkness 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. DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. @@ -1120,6 +1165,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) LogSliderDeadzone = ImFloor(LogSliderDeadzone * scale_factor); TabRounding = ImFloor(TabRounding * scale_factor); TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImFloor(TabMinWidthForCloseButton * scale_factor) : FLT_MAX; + SeparatorTextPadding = ImFloor(SeparatorTextPadding * scale_factor); DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); @@ -1147,6 +1193,8 @@ ImGuiIO::ImGuiIO() #endif KeyRepeatDelay = 0.275f; KeyRepeatRate = 0.050f; + HoverDelayNormal = 0.30f; + HoverDelayShort = 0.10f; UserData = NULL; Fonts = NULL; @@ -1251,16 +1299,17 @@ void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) { unsigned int c = 0; utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL); - if (c != 0) - AddInputCharacter(c); + AddInputCharacter(c); } } +// FIXME: Perhaps we could clear queued events as well? void ImGuiIO::ClearInputCharacters() { InputQueueCharacters.resize(0); } +// FIXME: Perhaps we could clear queued events as well? void ImGuiIO::ClearInputKeys() { #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -1273,7 +1322,31 @@ void ImGuiIO::ClearInputKeys() KeysData[n].DownDurationPrev = -1.0f; } KeyCtrl = KeyShift = KeyAlt = KeySuper = false; - KeyMods = ImGuiModFlags_None; + KeyMods = ImGuiMod_None; + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++) + { + MouseDown[n] = false; + MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f; + } + MouseWheel = MouseWheelH = 0.0f; +} + +static ImGuiInputEvent* FindLatestInputEvent(ImGuiInputEventType type, int arg = -1) +{ + ImGuiContext& g = *GImGui; + for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--) + { + ImGuiInputEvent* e = &g.InputEventsQueue[n]; + if (e->Type != type) + continue; + if (type == ImGuiInputEventType_Key && e->Key.Key != arg) + continue; + if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg) + continue; + return e; + } + return NULL; } // Queue a new key down/up event. @@ -1287,8 +1360,9 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) return; ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); - IM_ASSERT(ImGui::IsNamedKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API. + IM_ASSERT(ImGui::IsNamedKeyOrModKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API. IM_ASSERT(!ImGui::IsAliasKey(key)); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events. + IM_ASSERT(key != ImGuiMod_Shortcut); // We could easily support the translation here but it seems saner to not accept it (TestEngine perform a translation itself) // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data. #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -1301,17 +1375,13 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) if (ImGui::IsGamepadKey(key)) BackendUsingLegacyNavInputArray = false; - // Partial filter of duplicates (not strictly needed, but makes data neater in particular for key mods and gamepad values which are most commonly spmamed) - ImGuiKeyData* key_data = ImGui::GetKeyData(key); - if (key_data->Down == down && key_data->AnalogValue == analog_value) - { - bool found = false; - for (int n = g.InputEventsQueue.Size - 1; n >= 0 && !found; n--) - if (g.InputEventsQueue[n].Type == ImGuiInputEventType_Key && g.InputEventsQueue[n].Key.Key == key) - found = true; - if (!found) - return; - } + // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_Key, (int)key); + const ImGuiKeyData* key_data = ImGui::GetKeyData(key); + const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down; + const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue; + if (latest_key_down == down && latest_key_analog == analog_value) + return; // Add event ImGuiInputEvent e; @@ -1338,14 +1408,14 @@ void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native if (key == ImGuiKey_None) return; IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512 - IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey(native_legacy_index)); // >= 0 && <= 511 + IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511 IM_UNUSED(native_keycode); // Yet unused IM_UNUSED(native_scancode); // Yet unused // Build native->imgui map so old user code can still call key functions with native 0..511 values. #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode; - if (!ImGui::IsLegacyKey(legacy_key)) + if (!ImGui::IsLegacyKey((ImGuiKey)legacy_key)) return; KeyMap[legacy_key] = key; KeyMap[key] = legacy_key; @@ -1369,11 +1439,20 @@ void ImGuiIO::AddMousePosEvent(float x, float y) if (!AppAcceptingEvents) return; + // Apply same flooring as UpdateMouseInputs() + ImVec2 pos((x > -FLT_MAX) ? ImFloorSigned(x) : x, (y > -FLT_MAX) ? ImFloorSigned(y) : y); + + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_MousePos); + const ImVec2 latest_pos = latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos; + if (latest_pos.x == pos.x && latest_pos.y == pos.y) + return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MousePos; e.Source = ImGuiInputSource_Mouse; - e.MousePos.PosX = x; - e.MousePos.PosY = y; + e.MousePos.PosX = pos.x; + e.MousePos.PosY = pos.y; g.InputEventsQueue.push_back(e); } @@ -1385,6 +1464,12 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) if (!AppAcceptingEvents) return; + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_MouseButton, (int)mouse_button); + const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button]; + if (latest_button_down == down) + return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseButton; e.Source = ImGuiInputSource_Mouse; @@ -1393,12 +1478,14 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) g.InputEventsQueue.push_back(e); } -// Queue a mouse wheel event (most mouse/API will only have a Y component) +// Queue a mouse wheel event (some mouse/API may only have a Y component) void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y) { ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); - if ((wheel_x == 0.0f && wheel_y == 0.0f) || !AppAcceptingEvents) + + // Filter duplicate (unlike most events, wheel values are relative and easy to filter) + if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f)) return; ImGuiInputEvent e; @@ -1414,6 +1501,12 @@ void ImGuiIO::AddFocusEvent(bool focused) ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_Focus); + const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost; + if (latest_focused == focused) + return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_Focus; e.AppFocused.Focused = focused; @@ -1548,14 +1641,14 @@ ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, int ImStricmp(const char* str1, const char* str2) { int d; - while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + while ((d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } int ImStrnicmp(const char* str1, const char* str2, size_t count) { int d = 0; - while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + while (count > 0 && (d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; count--; } return d; } @@ -1622,14 +1715,14 @@ const char* ImStristr(const char* haystack, const char* haystack_end, const char if (!needle_end) needle_end = needle + strlen(needle); - const char un0 = (char)toupper(*needle); + const char un0 = (char)ImToUpper(*needle); while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) { - if (toupper(*haystack) == un0) + if (ImToUpper(*haystack) == un0) { const char* b = needle + 1; for (const char* a = haystack + 1; b < needle_end; a++, b++) - if (toupper(*a) != toupper(*b)) + if (ImToUpper(*a) != ImToUpper(*b)) break; if (b == needle_end) return haystack; @@ -1723,18 +1816,36 @@ void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, ImGuiContext& g = *GImGui; va_list args; va_start(args, fmt); - int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); - *out_buf = g.TempBuffer.Data; - if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + { + const char* buf = va_arg(args, const char*); // Skip formatting when using "%s" + *out_buf = buf; + if (out_buf_end) { *out_buf_end = buf + strlen(buf); } + } + else + { + int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); + *out_buf = g.TempBuffer.Data; + if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } + } va_end(args); } void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args) { ImGuiContext& g = *GImGui; - int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); - *out_buf = g.TempBuffer.Data; - if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + { + const char* buf = va_arg(args, const char*); // Skip formatting when using "%s" + *out_buf = buf; + if (out_buf_end) { *out_buf_end = buf + strlen(buf); } + } + else + { + int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); + *out_buf = g.TempBuffer.Data; + if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } + } } // CRC32 needs a 1KB lookup table (not cache friendly) @@ -1763,7 +1874,7 @@ static const ImU32 GCrc32LookupTable[256] = // Known size hash // It is ok to call ImHashData on a string with known length but the ### operator won't be supported. // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. -ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed) +ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) { ImU32 crc = ~seed; const unsigned char* data = (const unsigned char*)data_p; @@ -1779,7 +1890,7 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed) // - 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) // 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, ImU32 seed) +ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) { seed = ~seed; ImU32 crc = seed; @@ -1821,7 +1932,7 @@ ImFileHandle ImFileOpen(const char* filename, const char* mode) // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); - ImVector buf; + ImVector buf; buf.resize(filename_wsize + mode_wsize); ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t*)&buf[0], filename_wsize); ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t*)&buf[filename_wsize], mode_wsize); @@ -1884,6 +1995,8 @@ void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_f // [SECTION] MISC HELPERS/UTILITIES (ImText* functions) //----------------------------------------------------------------------------- +IM_MSVC_RUNTIME_CHECKS_OFF + // Convert UTF-8 to 32-bit character, process single character input. // A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8). // We handle UTF-8 decoding error by skipping forward. @@ -1895,7 +2008,7 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* static const int shiftc[] = { 0, 18, 12, 6, 0 }; static const int shifte[] = { 0, 6, 4, 2, 0 }; int len = lengths[*(const unsigned char*)in_text >> 3]; - int wanted = len + !len; + int wanted = len + (len ? 0 : 1); if (in_text_end == NULL) in_text_end = in_text + wanted; // Max length, nulls will be taken into account. @@ -1947,8 +2060,6 @@ int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const cha { unsigned int c; in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); - if (c == 0) - break; *buf_out++ = (ImWchar)c; } *buf_out = 0; @@ -1964,8 +2075,6 @@ int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) { unsigned int c; in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); - if (c == 0) - break; char_count++; } return char_count; @@ -2059,6 +2168,7 @@ int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_e } return bytes_count; } +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (Color functions) @@ -2393,7 +2503,7 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const } //----------------------------------------------------------------------------- -// [SECTION] ImGuiTextBuffer +// [SECTION] ImGuiTextBuffer, ImGuiTextIndex //----------------------------------------------------------------------------- // On some platform vsnprintf() takes va_list by reference and modifies it. @@ -2461,6 +2571,20 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) va_end(args_copy); } +void ImGuiTextIndex::append(const char* base, int old_size, int new_size) +{ + IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset); + if (old_size == new_size) + return; + if (EndOffset == 0 || base[EndOffset - 1] == '\n') + LineOffsets.push_back(EndOffset); + const char* base_end = base + new_size; + for (const char* p = base + old_size; (p = (const char*)memchr(p, '\n', base_end - p)) != 0; ) + if (++p < base_end) // Don't push a trailing offset on last \n + LineOffsets.push_back((int)(intptr_t)(p - base)); + EndOffset = ImMax(EndOffset, new_size); +} + //----------------------------------------------------------------------------- // [SECTION] ImGuiListClipper // This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed @@ -2477,7 +2601,7 @@ static bool GetSkipItemForListClipping() #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy helper to calculate coarse clipping of large list of evenly sized items. -// This legacy API is not ideal because it assume we will return a single contiguous rectangle. +// This legacy API is not ideal because it assumes we will return a single contiguous rectangle. // Prefer using ImGuiListClipper which can returns non-contiguous ranges. void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) { @@ -2592,13 +2716,11 @@ ImGuiListClipper::~ImGuiListClipper() End(); } -// Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1 -// Use case B: Begin() called from constructor with items_height>0 -// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. void ImGuiListClipper::Begin(int items_count, float items_height) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Begin(%d,%.2f) in '%s'\n", items_count, items_height, window->Name); if (ImGuiTable* table = g.CurrentTable) if (table->IsInsideRow) @@ -2625,6 +2747,7 @@ void ImGuiListClipper::End() if (ImGuiListClipperData* data = (ImGuiListClipperData*)TempData) { // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user. + IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name); if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0) ImGuiListClipper_SeekCursorForItem(this, ItemsCount); @@ -2650,11 +2773,11 @@ void ImGuiListClipper::ForceDisplayRangeByIndices(int item_min, int item_max) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_min, item_max)); } -bool ImGuiListClipper::Step() +static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; + ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData; IM_ASSERT(data != NULL && "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?"); ImGuiTable* table = g.CurrentTable; @@ -2662,18 +2785,17 @@ bool ImGuiListClipper::Step() ImGui::TableEndRow(table); // No items - if (ItemsCount == 0 || GetSkipItemForListClipping()) - return (void)End(), false; + if (clipper->ItemsCount == 0 || GetSkipItemForListClipping()) + return false; // While we are in frozen row state, keep displaying items one by one, unclipped // FIXME: Could be stored as a table-agnostic state. if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows) { - DisplayStart = data->ItemsFrozen; - DisplayEnd = data->ItemsFrozen + 1; - if (DisplayStart >= ItemsCount) - return (void)End(), false; - data->ItemsFrozen++; + clipper->DisplayStart = data->ItemsFrozen; + clipper->DisplayEnd = ImMin(data->ItemsFrozen + 1, clipper->ItemsCount); + if (clipper->DisplayStart < clipper->DisplayEnd) + data->ItemsFrozen++; return true; } @@ -2681,15 +2803,13 @@ bool ImGuiListClipper::Step() bool calc_clipping = false; if (data->StepNo == 0) { - StartPosY = window->DC.CursorPos.y; - if (ItemsHeight <= 0.0f) + clipper->StartPosY = window->DC.CursorPos.y; + if (clipper->ItemsHeight <= 0.0f) { // Submit the first item (or range) so we can measure its height (generally the first range is 0..1) data->Ranges.push_front(ImGuiListClipperRange::FromIndices(data->ItemsFrozen, data->ItemsFrozen + 1)); - DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen); - DisplayEnd = ImMin(data->Ranges[0].Max, ItemsCount); - if (DisplayStart == DisplayEnd) - return (void)End(), false; + clipper->DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen); + clipper->DisplayEnd = ImMin(data->Ranges[0].Max, clipper->ItemsCount); data->StepNo = 1; return true; } @@ -2697,29 +2817,29 @@ bool ImGuiListClipper::Step() } // Step 1: Let the clipper infer height from first range - if (ItemsHeight <= 0.0f) + if (clipper->ItemsHeight <= 0.0f) { IM_ASSERT(data->StepNo == 1); if (table) - IM_ASSERT(table->RowPosY1 == StartPosY && table->RowPosY2 == window->DC.CursorPos.y); + IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); - ItemsHeight = (window->DC.CursorPos.y - StartPosY) / (float)(DisplayEnd - DisplayStart); - bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); + clipper->ItemsHeight = (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); + bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) - ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. + clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. - IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); + IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } // Step 0 or 1: Calculate the actual ranges of visible elements. - const int already_submitted = DisplayEnd; + const int already_submitted = clipper->DisplayEnd; if (calc_clipping) { if (g.LogEnabled) { // If logging is active, do not perform any clipping - data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, ItemsCount)); + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, clipper->ItemsCount)); } else { @@ -2728,7 +2848,7 @@ bool ImGuiListClipper::Step() if (is_nav_request) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, 0, 0)); if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) && g.NavTabbingDir == -1) - data->Ranges.push_back(ImGuiListClipperRange::FromIndices(ItemsCount - 1, ItemsCount)); + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount)); // Add focused/active item ImRect nav_rect_abs = ImGui::WindowRectRelToAbs(window, window->NavRectRel[0]); @@ -2748,10 +2868,10 @@ bool ImGuiListClipper::Step() for (int i = 0; i < data->Ranges.Size; i++) if (data->Ranges[i].PosToIndexConvert) { - int m1 = (int)(((double)data->Ranges[i].Min - window->DC.CursorPos.y - data->LossynessOffset) / ItemsHeight); - int m2 = (int)((((double)data->Ranges[i].Max - window->DC.CursorPos.y - data->LossynessOffset) / ItemsHeight) + 0.999999f); - data->Ranges[i].Min = ImClamp(already_submitted + m1 + data->Ranges[i].PosToIndexOffsetMin, already_submitted, ItemsCount - 1); - data->Ranges[i].Max = ImClamp(already_submitted + m2 + data->Ranges[i].PosToIndexOffsetMax, data->Ranges[i].Min + 1, ItemsCount); + int m1 = (int)(((double)data->Ranges[i].Min - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight); + int m2 = (int)((((double)data->Ranges[i].Max - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight) + 0.999999f); + data->Ranges[i].Min = ImClamp(already_submitted + m1 + data->Ranges[i].PosToIndexOffsetMin, already_submitted, clipper->ItemsCount - 1); + data->Ranges[i].Max = ImClamp(already_submitted + m2 + data->Ranges[i].PosToIndexOffsetMax, data->Ranges[i].Min + 1, clipper->ItemsCount); data->Ranges[i].PosToIndexConvert = false; } ImGuiListClipper_SortAndFuseRanges(data->Ranges, data->StepNo); @@ -2760,23 +2880,45 @@ bool ImGuiListClipper::Step() // Step 0+ (if item height is given in advance) or 1+: Display the next range in line. if (data->StepNo < data->Ranges.Size) { - DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); - DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, ItemsCount); - if (DisplayStart > already_submitted) //-V1051 - ImGuiListClipper_SeekCursorForItem(this, DisplayStart); + clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); + clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount); + if (clipper->DisplayStart > already_submitted) //-V1051 + ImGuiListClipper_SeekCursorForItem(clipper, clipper->DisplayStart); data->StepNo++; return true; } // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), // Advance the cursor to the end of the list and then returns 'false' to end the loop. - if (ItemsCount < INT_MAX) - ImGuiListClipper_SeekCursorForItem(this, ItemsCount); + if (clipper->ItemsCount < INT_MAX) + ImGuiListClipper_SeekCursorForItem(clipper, clipper->ItemsCount); - End(); return false; } +bool ImGuiListClipper::Step() +{ + ImGuiContext& g = *GImGui; + bool need_items_height = (ItemsHeight <= 0.0f); + bool ret = ImGuiListClipper_StepInternal(this); + if (ret && (DisplayStart == DisplayEnd)) + ret = false; + if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false) + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): inside frozen table row.\n"); + if (need_items_height && ItemsHeight > 0.0f) + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): computed ItemsHeight: %.2f.\n", ItemsHeight); + if (ret) + { + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): display %d to %d.\n", DisplayStart, DisplayEnd); + } + else + { + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): End.\n"); + End(); + } + return ret; +} + //----------------------------------------------------------------------------- // [SECTION] STYLING //----------------------------------------------------------------------------- @@ -2843,6 +2985,11 @@ void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) void ImGui::PopStyleColor(int count) { ImGuiContext& g = *GImGui; + if (g.ColorStack.Size < count) + { + IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times: stack underflow."); + count = g.ColorStack.Size; + } while (count > 0) { ImGuiColorMod& backup = g.ColorStack.back(); @@ -2887,6 +3034,9 @@ static const ImGuiStyleVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, SeparatorTextBorderSize) },// ImGuiStyleVar_SeparatorTextBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding }; static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) @@ -2927,6 +3077,11 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) void ImGui::PopStyleVar(int count) { ImGuiContext& g = *GImGui; + if (g.StyleVarStack.Size < count) + { + IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times: stack underflow."); + count = g.StyleVarStack.Size; + } while (count > 0) { // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. @@ -3111,7 +3266,6 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons LogRenderedText(&pos_min, text, text_display_end); } - // Another overly complex function until we reorganize everything into a nice all-in-one helper. // This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_ the ellipsis is, from actual clipping of text and limit of the ellipsis display. // This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move. @@ -3135,30 +3289,12 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con const ImFont* font = draw_list->_Data->Font; const float font_size = draw_list->_Data->FontSize; + const float font_scale = font_size / font->FontSize; const char* text_end_ellipsis = NULL; - - ImWchar ellipsis_char = font->EllipsisChar; - int ellipsis_char_count = 1; - if (ellipsis_char == (ImWchar)-1) - { - ellipsis_char = font->DotChar; - ellipsis_char_count = 3; - } - const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char); - - float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side - float ellipsis_total_width = ellipsis_glyph_width; // Full width of entire ellipsis - - if (ellipsis_char_count > 1) - { - // Full ellipsis size without free spacing after it. - const float spacing_between_dots = 1.0f * (draw_list->_Data->FontSize / font->FontSize); - ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots; - ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - spacing_between_dots; - } + const float ellipsis_width = font->EllipsisWidth * font_scale; // 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_total_width) - pos_min.x, 1.0f); + 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; if (text == text_end_ellipsis && text_end_ellipsis < text_end_full) { @@ -3175,13 +3311,10 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con // Render text, render ellipsis RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); - float ellipsis_x = pos_min.x + text_size_clipped_x; - if (ellipsis_x + ellipsis_total_width <= ellipsis_max_x) - for (int i = 0; i < ellipsis_char_count; i++) - { - font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_char); - ellipsis_x += ellipsis_glyph_width; - } + ImVec2 ellipsis_pos = ImFloor(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y)); + if (ellipsis_pos.x + ellipsis_width <= ellipsis_max_x) + for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale) + font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar); } else { @@ -3254,27 +3387,238 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso { ImGuiContext& g = *GImGui; IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT); + ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas; for (int n = 0; n < g.Viewports.Size; n++) { - ImGuiViewportP* viewport = g.Viewports[n]; - ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas; + // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor. ImVec2 offset, size, uv[4]; - if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) - { - const ImVec2 pos = base_pos - offset; - const float scale = base_scale; - ImTextureID tex_id = font_atlas->TexID; - draw_list->PushTextureID(tex_id); - draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); - draw_list->PopTextureID(); - } + if (!font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) + continue; + ImGuiViewportP* viewport = g.Viewports[n]; + const ImVec2 pos = base_pos - offset; + const float scale = base_scale; + if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) + continue; + ImDrawList* draw_list = GetForegroundDrawList(viewport); + ImTextureID tex_id = font_atlas->TexID; + draw_list->PushTextureID(tex_id); + draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); + draw_list->PopTextureID(); } } +//----------------------------------------------------------------------------- +// [SECTION] INITIALIZATION, SHUTDOWN +//----------------------------------------------------------------------------- + +// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data) +{ + GImAllocatorAllocFunc = alloc_func; + GImAllocatorFreeFunc = free_func; + GImAllocatorUserData = user_data; +} + +// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space) +void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data) +{ + *p_alloc_func = GImAllocatorAllocFunc; + *p_free_func = GImAllocatorFreeFunc; + *p_user_data = GImAllocatorUserData; +} + +ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) +{ + ImGuiContext* prev_ctx = GetCurrentContext(); + ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + SetCurrentContext(ctx); + Initialize(); + if (prev_ctx != NULL) + SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one. + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + ImGuiContext* prev_ctx = GetCurrentContext(); + if (ctx == NULL) //-V1051 + ctx = prev_ctx; + SetCurrentContext(ctx); + Shutdown(); + SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL); + IM_DELETE(ctx); +} + +// IMPORTANT: ###xxx suffixes must be same in ALL languages +static const ImGuiLocEntry GLocalizationEntriesEnUS[] = +{ + { ImGuiLocKey_TableSizeOne, "Size column to fit###SizeOne" }, + { ImGuiLocKey_TableSizeAllFit, "Size all columns to fit###SizeAll" }, + { ImGuiLocKey_TableSizeAllDefault, "Size all columns to default###SizeAll" }, + { ImGuiLocKey_TableResetOrder, "Reset order###ResetOrder" }, + { ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" }, + { ImGuiLocKey_WindowingPopup, "(Popup)" }, + { ImGuiLocKey_WindowingUntitled, "(Untitled)" }, +}; + +void ImGui::Initialize() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + + // Add .ini handle for ImGuiWindow and ImGuiTable types + { + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHashStr("Window"); + ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll; + AddSettingsHandler(&ini_handler); + } + TableSettingsAddSettingsHandler(); + + // Setup default localization table + LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS)); + + // Create default viewport + ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); + g.Viewports.push_back(viewport); + g.TempBuffer.resize(1024 * 3 + 1, 0); + +#ifdef IMGUI_HAS_DOCK +#endif + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown() +{ + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + ImGuiContext& g = *GImGui; + if (g.IO.Fonts && g.FontAtlasOwnedByContext) + { + g.IO.Fonts->Locked = false; + IM_DELETE(g.IO.Fonts); + } + g.IO.Fonts = NULL; + g.DrawListSharedData.TempBuffer.clear(); + + // Cleanup of other data are conditional on actually having initialized Dear ImGui. + if (!g.Initialized) + return; + + // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) + if (g.SettingsLoaded && g.IO.IniFilename != NULL) + SaveIniSettingsToDisk(g.IO.IniFilename); + + CallContextHooks(&g, ImGuiContextHookType_Shutdown); + + // Clear everything else + g.Windows.clear_delete(); + g.WindowsFocusOrder.clear(); + g.WindowsTempSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; + g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.MovingWindow = NULL; + + g.KeysRoutingTable.Clear(); + + g.ColorStack.clear(); + g.StyleVarStack.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.BeginPopupStack.clear(); + + g.Viewports.clear_delete(); + + g.TabBars.Clear(); + g.CurrentTabBarStack.clear(); + g.ShrinkWidthBuffer.clear(); + + g.ClipperTempData.clear_destruct(); + + g.Tables.Clear(); + g.TablesTempData.clear_destruct(); + g.DrawChannelsTempMergeBuffer.clear(); + + g.ClipboardHandlerData.clear(); + g.MenusIdSubmittedThisFrame.clear(); + g.InputTextState.ClearFreeMemory(); + + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile) + { +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + if (g.LogFile != stdout) +#endif + ImFileClose(g.LogFile); + g.LogFile = NULL; + } + g.LogBuffer.clear(); + g.DebugLogBuf.clear(); + g.DebugLogIndex.clear(); + + g.Initialized = false; +} + +// No specific ordering/dependency support, will see as needed +ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) +{ + ImGuiContext& g = *ctx; + IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_); + g.Hooks.push_back(*hook); + g.Hooks.back().HookId = ++g.HookIdNext; + return g.HookIdNext; +} + +// Deferred removal, avoiding issue with changing vector while iterating it +void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id) +{ + ImGuiContext& g = *ctx; + IM_ASSERT(hook_id != 0); + for (int n = 0; n < g.Hooks.Size; n++) + if (g.Hooks[n].HookId == hook_id) + g.Hooks[n].Type = ImGuiContextHookType_PendingRemoval_; +} + +// Call context hooks (used by e.g. test engine) +// We assume a small number of hooks so all stored in same array +void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) +{ + ImGuiContext& g = *ctx; + for (int n = 0; n < g.Hooks.Size; n++) + if (g.Hooks[n].Type == hook_type) + g.Hooks[n].Callback(&g, &g.Hooks[n]); +} + //----------------------------------------------------------------------------- // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) @@ -3293,7 +3637,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) : DrawListInst ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); AutoFitFramesX = AutoFitFramesY = -1; AutoPosLastDirection = ImGuiDir_None; - SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = 0; SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; LastTimeActive = -1.0f; @@ -3435,7 +3779,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // Clear declaration of inputs claimed by the widget // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet) g.ActiveIdUsingNavDirMask = 0x00; - g.ActiveIdUsingKeyInputMask.ClearAllBits(); + g.ActiveIdUsingAllKeyboardKeys = false; #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO g.ActiveIdUsingNavInputMask = 0x00; #endif @@ -3451,7 +3795,6 @@ void ImGui::SetHoveredID(ImGuiID id) ImGuiContext& g = *GImGui; g.HoveredId = id; g.HoveredIdAllowOverlap = false; - g.HoveredIdUsingMouseWheel = false; if (id != 0 && g.HoveredIdPreviousFrame != id) g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f; } @@ -3476,7 +3819,7 @@ void ImGui::KeepAliveID(ImGuiID id) void ImGui::MarkItemEdited(ImGuiID id) { // This marking is solely 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 need to fill the data. + // 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; IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive); IM_UNUSED(id); // Avoid unused variable warnings when asserts are compiled out. @@ -3496,11 +3839,17 @@ static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFla if (focused_root_window->WasActive && focused_root_window != window->RootWindow) { // For the purpose of those flags we differentiate "standard popup" from "modal popup" - // NB: The order of those two tests is important because Modal windows are also Popups. + // NB: The 'else' is important because Modal windows are also Popups. + bool want_inhibit = false; if (focused_root_window->Flags & ImGuiWindowFlags_Modal) - return false; - if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - return false; + want_inhibit = true; + else if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + want_inhibit = true; + + // Inhibit hover unless the window is within the stack of our modal/popup + if (want_inhibit) + if (!ImGui::IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window)) + return false; } return true; } @@ -3527,6 +3876,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return false; IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) == 0); // Flags not supported by this function + // Done with rectangle culling so we can perform heavier checks now // Test if we are hovering the right window (our window could be behind another window) // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable @@ -3543,7 +3893,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) // Test if interactions on this window are blocked by an active popup or modal. // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here. - if (!IsWindowContentHoverable(window, flags)) + if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.InFlags & ImGuiItemFlags_NoWindowHoverableCheck)) return false; // Test if the item is disabled @@ -3551,11 +3901,29 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return false; // Special handling for calling after Begin() which represent the title bar or tab. - // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. + // When the window is skipped/collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. if (g.LastItemData.ID == window->MoveId && window->WriteAccessed) return false; } + // Handle hover delay + // (some ideas: https://www.nngroup.com/articles/timing-exposing-content) + float delay; + if (flags & ImGuiHoveredFlags_DelayNormal) + delay = g.IO.HoverDelayNormal; + else if (flags & ImGuiHoveredFlags_DelayShort) + delay = g.IO.HoverDelayShort; + else + delay = 0.0f; + if (delay > 0.0f) + { + ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromRectangle(g.LastItemData.Rect); + if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverDelayIdPreviousFrame != hover_delay_id)) + g.HoverDelayTimer = 0.0f; + g.HoverDelayId = hover_delay_id; + return g.HoverDelayTimer >= delay; + } + return true; } @@ -3573,7 +3941,10 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return false; if (!IsMouseHoveringRect(bb.Min, bb.Max)) return false; - if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) + + // Done with rectangle culling so we can perform heavier checks now. + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); + if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdDisabled = true; return false; @@ -3585,7 +3956,6 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) SetHoveredID(id); // When disabled we'll return false but still set HoveredId - ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); if (item_flags & ImGuiItemFlags_Disabled) { // Release active id if turning disabled @@ -3600,8 +3970,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) // [DEBUG] Item Picker tool! // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered - // items if we perform the test in ItemAdd(), but that would incur a small runtime cost. - // #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this check to also be performed in ItemAdd(). + // items if we performed the test in ItemAdd(), but that would incur a small runtime cost. if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id) GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); if (g.DebugItemPickerBreakId == id) @@ -3614,6 +3983,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return true; } +// FIXME: This is inlined/duplicated in ItemAdd() bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) { ImGuiContext& g = *GImGui; @@ -3695,89 +4065,6 @@ const char* ImGui::GetVersion() return IMGUI_VERSION; } -// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself -// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module -ImGuiContext* ImGui::GetCurrentContext() -{ - return GImGui; -} - -void ImGui::SetCurrentContext(ImGuiContext* ctx) -{ -#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC - IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. -#else - GImGui = ctx; -#endif -} - -void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data) -{ - GImAllocatorAllocFunc = alloc_func; - GImAllocatorFreeFunc = free_func; - GImAllocatorUserData = user_data; -} - -// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space) -void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data) -{ - *p_alloc_func = GImAllocatorAllocFunc; - *p_free_func = GImAllocatorFreeFunc; - *p_user_data = GImAllocatorUserData; -} - -ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) -{ - ImGuiContext* prev_ctx = GetCurrentContext(); - ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); - SetCurrentContext(ctx); - Initialize(); - if (prev_ctx != NULL) - SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one. - return ctx; -} - -void ImGui::DestroyContext(ImGuiContext* ctx) -{ - ImGuiContext* prev_ctx = GetCurrentContext(); - if (ctx == NULL) //-V1051 - ctx = prev_ctx; - SetCurrentContext(ctx); - Shutdown(); - SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL); - IM_DELETE(ctx); -} - -// No specific ordering/dependency support, will see as needed -ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) -{ - ImGuiContext& g = *ctx; - IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_); - g.Hooks.push_back(*hook); - g.Hooks.back().HookId = ++g.HookIdNext; - return g.HookIdNext; -} - -// Deferred removal, avoiding issue with changing vector while iterating it -void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id) -{ - ImGuiContext& g = *ctx; - IM_ASSERT(hook_id != 0); - for (int n = 0; n < g.Hooks.Size; n++) - if (g.Hooks[n].HookId == hook_id) - g.Hooks[n].Type = ImGuiContextHookType_PendingRemoval_; -} - -// Call context hooks (used by e.g. test engine) -// We assume a small number of hooks so all stored in same array -void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) -{ - ImGuiContext& g = *ctx; - for (int n = 0; n < g.Hooks.Size; n++) - if (g.Hooks[n].Type == hook_type) - g.Hooks[n].Callback(&g, &g.Hooks[n]); -} - ImGuiIO& ImGui::GetIO() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); @@ -3971,273 +4258,6 @@ static bool IsWindowActiveAndVisible(ImGuiWindow* window) return (window->Active) && (!window->Hidden); } -static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value) -{ - IM_ASSERT(ImGui::IsAliasKey(key)); - ImGuiKeyData* key_data = ImGui::GetKeyData(key); - key_data->Down = v; - key_data->AnalogValue = analog_value; -} - -static void ImGui::UpdateKeyboardInputs() -{ - ImGuiContext& g = *GImGui; - ImGuiIO& io = g.IO; - - // Import legacy keys or verify they are not used -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - if (io.BackendUsingLegacyKeyArrays == 0) - { - // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally. - for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT((io.KeysDown[n] == false || IsKeyDown(n)) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - } - else - { - if (g.FrameCount == 0) - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!"); - - // Build reverse KeyMap (Named -> Legacy) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - if (io.KeyMap[n] != -1) - { - IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n])); - io.KeyMap[io.KeyMap[n]] = n; - } - - // Import legacy keys into new ones - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1) - { - const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n); - IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key)); - io.KeysData[key].Down = io.KeysDown[n]; - if (key != n) - io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends - io.BackendUsingLegacyKeyArrays = 1; - } - if (io.BackendUsingLegacyKeyArrays == 1) - { - io.KeysData[ImGuiKey_ModCtrl].Down = io.KeyCtrl; - io.KeysData[ImGuiKey_ModShift].Down = io.KeyShift; - io.KeysData[ImGuiKey_ModAlt].Down = io.KeyAlt; - io.KeysData[ImGuiKey_ModSuper].Down = io.KeySuper; - } - } - -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active) - { - #define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; } while (0) - #define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); } while (0) - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown); - #undef NAV_MAP_KEY - } -#endif - -#endif - - // Synchronize io.KeyMods with individual modifiers io.KeyXXX bools, update aliases - io.KeyMods = GetMergedModFlags(); - for (int n = 0; n < ImGuiMouseButton_COUNT; n++) - UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f); - UpdateAliasKey(ImGuiKey_MouseWheelX, io.MouseWheelH != 0.0f, io.MouseWheelH); - UpdateAliasKey(ImGuiKey_MouseWheelY, io.MouseWheel != 0.0f, io.MouseWheel); - - // Clear gamepad data if disabled - if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) - for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++) - { - io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false; - io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f; - } - - // Update keys - for (int i = 0; i < IM_ARRAYSIZE(io.KeysData); i++) - { - ImGuiKeyData* key_data = &io.KeysData[i]; - key_data->DownDurationPrev = key_data->DownDuration; - key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; - } -} - -static void ImGui::UpdateMouseInputs() -{ - ImGuiContext& g = *GImGui; - ImGuiIO& io = g.IO; - - // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) - if (IsMousePosValid(&io.MousePos)) - io.MousePos = g.MouseLastValidPos = ImFloorSigned(io.MousePos); - - // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta - if (IsMousePosValid(&io.MousePos) && IsMousePosValid(&io.MousePosPrev)) - io.MouseDelta = io.MousePos - io.MousePosPrev; - else - io.MouseDelta = ImVec2(0.0f, 0.0f); - - // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. - if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) - g.NavDisableMouseHover = false; - - io.MousePosPrev = io.MousePos; - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) - { - io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; - io.MouseClickedCount[i] = 0; // Will be filled below - io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f; - io.MouseDownDurationPrev[i] = io.MouseDownDuration[i]; - io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f; - if (io.MouseClicked[i]) - { - bool is_repeated_click = false; - if ((float)(g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime) - { - ImVec2 delta_from_click_pos = IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); - if (ImLengthSqr(delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist) - is_repeated_click = true; - } - if (is_repeated_click) - io.MouseClickedLastCount[i]++; - else - io.MouseClickedLastCount[i] = 1; - io.MouseClickedTime[i] = g.Time; - io.MouseClickedPos[i] = io.MousePos; - io.MouseClickedCount[i] = io.MouseClickedLastCount[i]; - io.MouseDragMaxDistanceSqr[i] = 0.0f; - } - else if (io.MouseDown[i]) - { - // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold - float delta_sqr_click_pos = IsMousePosValid(&io.MousePos) ? ImLengthSqr(io.MousePos - io.MouseClickedPos[i]) : 0.0f; - io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], delta_sqr_click_pos); - } - - // We provide io.MouseDoubleClicked[] as a legacy service - io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2); - - // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation - if (io.MouseClicked[i]) - g.NavDisableMouseHover = false; - } -} - -static void StartLockWheelingWindow(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (g.WheelingWindow == window) - return; - g.WheelingWindow = window; - g.WheelingWindowRefMousePos = g.IO.MousePos; - g.WheelingWindowTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER; -} - -void ImGui::UpdateMouseWheel() -{ - ImGuiContext& g = *GImGui; - - // Reset the locked window if we move the mouse or after the timer elapses - if (g.WheelingWindow != NULL) - { - g.WheelingWindowTimer -= g.IO.DeltaTime; - if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) - g.WheelingWindowTimer = 0.0f; - if (g.WheelingWindowTimer <= 0.0f) - { - g.WheelingWindow = NULL; - g.WheelingWindowTimer = 0.0f; - } - } - - const bool hovered_id_using_mouse_wheel = (g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrameUsingMouseWheel); - const bool active_id_using_mouse_wheel_x = g.ActiveIdUsingKeyInputMask.TestBit(ImGuiKey_MouseWheelX); - const bool active_id_using_mouse_wheel_y = g.ActiveIdUsingKeyInputMask.TestBit(ImGuiKey_MouseWheelY); - - float wheel_x = (!hovered_id_using_mouse_wheel && !active_id_using_mouse_wheel_x) ? g.IO.MouseWheelH : 0.0f; - float wheel_y = (!hovered_id_using_mouse_wheel && !active_id_using_mouse_wheel_y) ? g.IO.MouseWheel : 0; - if (wheel_x == 0.0f && wheel_y == 0.0f) - return; - - ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; - if (!window || window->Collapsed) - return; - - // 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) - { - StartLockWheelingWindow(window); - const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); - const float scale = new_font_scale / window->FontWindowScale; - window->FontWindowScale = new_font_scale; - if (window == window->RootWindow) - { - const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; - SetWindowPos(window, window->Pos + offset, 0); - window->Size = ImFloor(window->Size * scale); - window->SizeFull = ImFloor(window->SizeFull * scale); - } - return; - } - - // Mouse wheel scrolling - // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent - if (g.IO.KeyCtrl) - return; - - // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead - // (we avoid doing it on OSX as it the OS input layer handles this already) - const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors; - if (swap_axis) - { - wheel_x = wheel_y; - wheel_y = 0.0f; - } - - // Vertical Mouse Wheel scrolling - if (wheel_y != 0.0f) - { - StartLockWheelingWindow(window); - while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) - window = window->ParentWindow; - if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) - { - float max_step = window->InnerRect.GetHeight() * 0.67f; - float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); - SetScrollY(window, window->Scroll.y - wheel_y * scroll_step); - } - } - - // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held - if (wheel_x != 0.0f) - { - StartLockWheelingWindow(window); - while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) - window = window->ParentWindow; - if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) - { - float max_step = window->InnerRect.GetWidth() * 0.67f; - float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); - SetScrollX(window, window->Scroll.x - wheel_x * scroll_step); - } - } -} - // 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) void ImGui::UpdateHoveredWindowAndCaptureFlags() { @@ -4315,18 +4335,6 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } -// [Internal] Do not use directly (can read io.KeyMods instead) -ImGuiModFlags ImGui::GetMergedModFlags() -{ - ImGuiContext& g = *GImGui; - ImGuiModFlags key_mods = ImGuiModFlags_None; - if (g.IO.KeyCtrl) { key_mods |= ImGuiModFlags_Ctrl; } - if (g.IO.KeyShift) { key_mods |= ImGuiModFlags_Shift; } - if (g.IO.KeyAlt) { key_mods |= ImGuiModFlags_Alt; } - if (g.IO.KeySuper) { key_mods |= ImGuiModFlags_Super; } - return key_mods; -} - void ImGui::NewFrame() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); @@ -4360,6 +4368,11 @@ void ImGui::NewFrame() g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(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 + g.InputEventsTrail.resize(0); + UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue); + + // Update viewports (after processing input queue, so io.MouseHoveredViewport is set) UpdateViewportsNewFrame(); // Setup current font and draw list shared data @@ -4403,10 +4416,8 @@ void ImGui::NewFrame() if (g.HoveredId && g.ActiveId != g.HoveredId) g.HoveredIdNotActiveTimer += g.IO.DeltaTime; g.HoveredIdPreviousFrame = g.HoveredId; - g.HoveredIdPreviousFrameUsingMouseWheel = g.HoveredIdUsingMouseWheel; g.HoveredId = 0; g.HoveredIdAllowOverlap = false; - g.HoveredIdUsingMouseWheel = false; g.HoveredIdDisabled = false; // Clear ActiveID if the item is not alive anymore. @@ -4434,7 +4445,10 @@ void ImGui::NewFrame() if (g.ActiveId == 0) { g.ActiveIdUsingNavDirMask = 0x00; - g.ActiveIdUsingKeyInputMask.ClearAllBits(); + g.ActiveIdUsingAllKeyboardKeys = false; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + g.ActiveIdUsingNavInputMask = 0x00; +#endif } #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -4443,14 +4457,31 @@ void ImGui::NewFrame() else if (g.ActiveIdUsingNavInputMask != 0) { // If your custom widget code used: { g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); } - // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetActiveIdUsingKey(ImGuiKey_Escape); SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel); } + // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetKeyOwner(ImGuiKey_Escape, g.ActiveId); SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId); } if (g.ActiveIdUsingNavInputMask & (1 << ImGuiNavInput_Cancel)) - SetActiveIdUsingKey(ImGuiKey_Escape); + SetKeyOwner(ImGuiKey_Escape, g.ActiveId); if (g.ActiveIdUsingNavInputMask & ~(1 << ImGuiNavInput_Cancel)) IM_ASSERT(0); // Other values unsupported } #endif + // Update hover delay for IsItemHovered() with delays and tooltips + g.HoverDelayIdPreviousFrame = g.HoverDelayId; + if (g.HoverDelayId != 0) + { + //if (g.IO.MouseDelta.x == 0.0f && g.IO.MouseDelta.y == 0.0f) // Need design/flags + g.HoverDelayTimer += g.IO.DeltaTime; + g.HoverDelayClearTimer = 0.0f; + g.HoverDelayId = 0; + } + else if (g.HoverDelayTimer > 0.0f) + { + // This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps + g.HoverDelayClearTimer += g.IO.DeltaTime; + if (g.HoverDelayClearTimer >= ImMax(0.20f, g.IO.DeltaTime * 2.0f)) // ~6 frames at 30 Hz + allow for low framerate + g.HoverDelayTimer = g.HoverDelayClearTimer = 0.0f; // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer. + } + // Drag and drop g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; g.DragDropAcceptIdCurr = 0; @@ -4463,10 +4494,6 @@ void ImGui::NewFrame() //if (g.IO.AppFocusLost) // ClosePopupsExceptModals(); - // Process input queue (trickle as many events as possible) - g.InputEventsTrail.resize(0); - UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue); - // Update keyboard input state UpdateKeyboardInputs(); @@ -4511,9 +4538,10 @@ void ImGui::NewFrame() { ImGuiWindow* window = g.Windows[i]; window->WasActive = window->Active; - window->BeginCount = 0; window->Active = false; window->WriteAccessed = false; + window->BeginCountPreviousFrame = window->BeginCount; + window->BeginCount = 0; // Garbage collect transient buffers of recently unused windows if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) @@ -4546,10 +4574,12 @@ void ImGui::NewFrame() // [DEBUG] Update debug features UpdateDebugToolItemPicker(); UpdateDebugToolStackQueries(); + if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0) + g.DebugLocateId = 0; // Create implicit/fallback window - which we will only render it if the user has added something to it. // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. - // This fallback is particularly important as it avoid ImGui:: calls from crashing. + // This fallback is particularly important as it prevents ImGui:: calls from crashing. g.WithinFrameScopeWithImplicitWindow = true; SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); Begin("Debug##Default"); @@ -4558,110 +4588,6 @@ void ImGui::NewFrame() CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } -void ImGui::Initialize() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(!g.Initialized && !g.SettingsLoaded); - - // Add .ini handle for ImGuiWindow type - { - ImGuiSettingsHandler ini_handler; - ini_handler.TypeName = "Window"; - ini_handler.TypeHash = ImHashStr("Window"); - ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll; - ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen; - ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine; - ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll; - ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll; - AddSettingsHandler(&ini_handler); - } - - // Add .ini handle for ImGuiTable type - TableSettingsAddSettingsHandler(); - - // Create default viewport - ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); - g.Viewports.push_back(viewport); - g.TempBuffer.resize(1024 * 3 + 1, 0); - -#ifdef IMGUI_HAS_DOCK -#endif - - g.Initialized = true; -} - -// This function is merely here to free heap allocations. -void ImGui::Shutdown() -{ - // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - ImGuiContext& g = *GImGui; - if (g.IO.Fonts && g.FontAtlasOwnedByContext) - { - g.IO.Fonts->Locked = false; - IM_DELETE(g.IO.Fonts); - } - g.IO.Fonts = NULL; - - // Cleanup of other data are conditional on actually having initialized Dear ImGui. - if (!g.Initialized) - return; - - // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) - if (g.SettingsLoaded && g.IO.IniFilename != NULL) - SaveIniSettingsToDisk(g.IO.IniFilename); - - CallContextHooks(&g, ImGuiContextHookType_Shutdown); - - // Clear everything else - g.Windows.clear_delete(); - g.WindowsFocusOrder.clear(); - g.WindowsTempSortBuffer.clear(); - g.CurrentWindow = NULL; - g.CurrentWindowStack.clear(); - g.WindowsById.Clear(); - g.NavWindow = NULL; - g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; - g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; - g.MovingWindow = NULL; - g.ColorStack.clear(); - g.StyleVarStack.clear(); - g.FontStack.clear(); - g.OpenPopupStack.clear(); - g.BeginPopupStack.clear(); - - g.Viewports.clear_delete(); - - g.TabBars.Clear(); - g.CurrentTabBarStack.clear(); - g.ShrinkWidthBuffer.clear(); - - g.ClipperTempData.clear_destruct(); - - g.Tables.Clear(); - g.TablesTempData.clear_destruct(); - g.DrawChannelsTempMergeBuffer.clear(); - - g.ClipboardHandlerData.clear(); - g.MenusIdSubmittedThisFrame.clear(); - g.InputTextState.ClearFreeMemory(); - - g.SettingsWindows.clear(); - g.SettingsHandlers.clear(); - - if (g.LogFile) - { -#ifndef IMGUI_DISABLE_TTY_FUNCTIONS - if (g.LogFile != stdout) -#endif - ImFileClose(g.LogFile); - g.LogFile = NULL; - } - g.LogBuffer.clear(); - g.DebugLogBuf.clear(); - - g.Initialized = false; -} - // FIXME: Add a more explicit sort order in the window structure. static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) { @@ -4906,8 +4832,12 @@ void ImGui::EndFrame() ErrorCheckEndFrameSanityChecks(); // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) - if (g.IO.SetPlatformImeDataFn && memcmp(&g.PlatformImeData, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) - g.IO.SetPlatformImeDataFn(GetMainViewport(), &g.PlatformImeData); + ImGuiPlatformImeData* ime_data = &g.PlatformImeData; + if (g.IO.SetPlatformImeDataFn && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) + { + IMGUI_DEBUG_LOG_IO("Calling io.SetPlatformImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); + g.IO.SetPlatformImeDataFn(GetMainViewport(), ime_data); + } // Hide implicit/fallback "Debug" window if it hasn't been used g.WithinFrameScopeWithImplicitWindow = false; @@ -4963,6 +4893,7 @@ void ImGui::EndFrame() g.IO.Fonts->Locked = false; // Clear Input data for next frame + g.IO.AppFocusLost = false; g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; g.IO.InputQueueCharacters.resize(0); @@ -5201,7 +5132,7 @@ bool ImGui::IsAnyItemFocused() bool ImGui::IsItemVisible() { ImGuiContext& g = *GImGui; - return g.CurrentWindow->ClipRect.Overlaps(g.LastItemData.Rect); + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) != 0; } bool ImGui::IsItemEdited() @@ -5222,33 +5153,22 @@ void ImGui::SetItemAllowOverlap() g.ActiveIdAllowOverlap = true; } -void ImGui::SetItemUsingMouseWheel() -{ - ImGuiContext& g = *GImGui; - ImGuiID id = g.LastItemData.ID; - if (g.HoveredId == id) - g.HoveredIdUsingMouseWheel = true; - if (g.ActiveId == id) - { - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_MouseWheelX); - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_MouseWheelY); - } -} - -// FIXME: Technically this also prevents use of Gamepad D-Pad, may not be an issue. +// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version for the two users of this function. void ImGui::SetActiveIdUsingAllKeyboardKeys() { ImGuiContext& g = *GImGui; IM_ASSERT(g.ActiveId != 0); - g.ActiveIdUsingNavDirMask = ~(ImU32)0; - g.ActiveIdUsingKeyInputMask.SetBitRange(ImGuiKey_Keyboard_BEGIN, ImGuiKey_Keyboard_END); - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModCtrl); - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModShift); - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModAlt); - g.ActiveIdUsingKeyInputMask.SetBit(ImGuiKey_ModSuper); + g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_COUNT) - 1; + g.ActiveIdUsingAllKeyboardKeys = true; NavMoveRequestCancel(); } +ImGuiID ImGui::GetItemID() +{ + ImGuiContext& g = *GImGui; + return g.LastItemData.ID; +} + ImVec2 ImGui::GetItemRectMin() { ImGuiContext& g = *GImGui; @@ -5280,7 +5200,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, b ImVec2 size = ImFloor(size_arg); const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); if (size.x <= 0.0f) - size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too many issues) if (size.y <= 0.0f) size.y = ImMax(content_avail.y + size.y, 4.0f); SetNextWindowSize(size); @@ -5427,7 +5347,7 @@ static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, { ImGuiContext& g = *GImGui; - const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0; + const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0); const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild; if ((just_created || child_flag_changed) && !new_is_explicit_child) { @@ -5446,32 +5366,22 @@ static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, window->IsExplicitChild = new_is_explicit_child; } -static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) +static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { - ImGuiContext& g = *GImGui; - //IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags); - - // Create window the first time - ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); - window->Flags = flags; - g.WindowsById.SetVoidPtr(window->ID, window); - - // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + // Initial window state with e.g. default/arbitrary window position + // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); window->Pos = main_viewport->Pos + ImVec2(60, 60); + window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. - if (!(flags & ImGuiWindowFlags_NoSavedSettings)) - if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) - { - // Retrieve settings from .ini file - window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); - SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); - ApplyWindowSettings(window, settings); - } + if (settings != NULL) + { + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + ApplyWindowSettings(window, settings); + } window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values - if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) { window->AutoFitFramesX = window->AutoFitFramesY = 2; window->AutoFitOnlyGrows = false; @@ -5484,6 +5394,23 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) window->AutoFitFramesY = 2; window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); } +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) +{ + // Create window the first time + //IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + ImGuiWindowSettings* settings = NULL; + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + if ((settings = ImGui::FindWindowSettingsByWindow(window)) != 0) + window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); + + InitOrLoadWindowSettings(window, settings); if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) g.Windows.push_front(window); // Quite slow but rare and only once @@ -5521,9 +5448,9 @@ static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& s if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) { ImGuiWindow* window_for_height = window; - const float decoration_up_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight(); new_size = ImMax(new_size, g.Style.WindowMinSize); - new_size.y = ImMax(new_size.y, decoration_up_height + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + const float minimum_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f); + new_size.y = ImMax(new_size.y, minimum_height); // Reduce artifacts with very small windows } return new_size; } @@ -5552,9 +5479,10 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; - const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); + const float decoration_w_without_scrollbars = window->DecoOuterSizeX1 + window->DecoOuterSizeX2 - window->ScrollbarSizes.x; + const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y; ImVec2 size_pad = window->WindowPadding * 2.0f; - ImVec2 size_desired = size_contents + size_pad + ImVec2(0.0f, decoration_up_height); + ImVec2 size_desired = size_contents + size_pad + ImVec2(decoration_w_without_scrollbars, decoration_h_without_scrollbars); if (window->Flags & ImGuiWindowFlags_Tooltip) { // Tooltip always resize @@ -5569,15 +5497,14 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); - // FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size depending on the type of windows? - ImVec2 avail_size = ImGui::GetMainViewport()->Size; + ImVec2 avail_size = ImGui::GetMainViewport()->WorkSize; ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, avail_size - style.DisplaySafeAreaPadding * 2.0f)); // 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 - 0.0f < 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_up_height < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); + 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); if (will_have_scrollbar_x) size_auto_fit.y += style.ScrollbarSize; if (will_have_scrollbar_y) @@ -5684,7 +5611,7 @@ ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) } // Handle resize for: Resize Grips, Borders, Gamepad -// Return true when using auto-fit (double click on resize grip) +// Return true when using auto-fit (double-click on resize grip) static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect) { ImGuiContext& g = *GImGui; @@ -5692,7 +5619,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) return false; - if (window->WasActive == false) // Early out to avoid running this code for e.g. an hidden implicit/fallback Debug window. + if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window. return false; bool ret_auto_fit = false; @@ -5720,7 +5647,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x); if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y); ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID() - KeepAliveID(resize_grip_id); + ItemAdd(resize_rect, resize_grip_id, NULL, ImGuiItemFlags_NoNav); ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); if (hovered || held) @@ -5756,7 +5683,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s bool hovered, held; ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING); ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() - KeepAliveID(border_id); + ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav); ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); //GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255)); if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held) @@ -5787,9 +5714,9 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s { ImVec2 nav_resize_dir; if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift) - nav_resize_dir = GetKeyVector2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow); + nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow); if (g.NavInputSource == ImGuiInputSource_Gamepad) - nav_resize_dir = GetKeyVector2d(ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown); + nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown); if (nav_resize_dir.x != 0.0f || nav_resize_dir.y != 0.0f) { const float NAV_RESIZE_SPEED = 600.0f; @@ -5825,7 +5752,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s return ret_auto_fit; } -static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& visibility_rect) +static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_rect) { ImGuiContext& g = *GImGui; ImVec2 size_for_clamping = window->Size; @@ -5860,7 +5787,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) // Draw background and borders // Draw and handle scrollbars -void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size) +void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size) { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; @@ -5871,13 +5798,13 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar window->SkipItems = false; // Draw window + handle manual resize - // As we highlight the title bar when want_focus is set, multiple reappearing windows will have have their title bar highlighted on their reappearing frame. + // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame. const float window_rounding = window->WindowRounding; const float window_border_size = window->WindowBorderSize; if (window->Collapsed) { // Title bar only - float backup_border_size = style.FrameBorderSize; + const float backup_border_size = style.FrameBorderSize; g.Style.FrameBorderSize = window->WindowBorderSize; ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); @@ -5925,21 +5852,25 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar Scrollbar(ImGuiAxis_Y); // Render resize grips (after their input handling so we don't have a frame of latency) - if (!(flags & ImGuiWindowFlags_NoResize)) + if (handle_borders_and_resize_grips && !(flags & ImGuiWindowFlags_NoResize)) { for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) { + const ImU32 col = resize_grip_col[resize_grip_n]; + if ((col & IM_COL32_A_MASK) == 0) + continue; const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN); window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size))); window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size))); window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); - window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + window->DrawList->PathFillConvex(col); } } // Borders - RenderWindowOuterBorders(window); + if (handle_borders_and_resize_grips) + RenderWindowOuterBorders(window); } } @@ -6157,10 +6088,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_stack_data.ParentLastItemDataBackup = g.LastItemData; window_stack_data.StackSizesOnBegin.SetToCurrentState(); g.CurrentWindowStack.push_back(window_stack_data); - g.CurrentWindow = NULL; if (flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuCount++; + // Update ->RootWindow and others pointers (before any possible call to FocusWindow) + if (first_begin_of_the_frame) + { + UpdateWindowParentAndRootLinks(window, flags, parent_window); + window->ParentWindowInBeginStack = parent_window_in_stack; + } + + // Add to focus scope stack + PushFocusScope(window->ID); + window->NavRootFocusScopeId = g.CurrentFocusScopeId; + g.CurrentWindow = NULL; + + // Add to popup stack if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; @@ -6170,13 +6113,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->PopupId = popup_ref.PopupId; } - // Update ->RootWindow and others pointers (before any possible call to FocusWindow) - if (first_begin_of_the_frame) - { - UpdateWindowParentAndRootLinks(window, flags, parent_window); - window->ParentWindowInBeginStack = parent_window_in_stack; - } - // Process SetNextWindow***() calls // (FIXME: Consider splitting the HasXXX flags into X/Y components bool window_pos_set_by_api = false; @@ -6307,6 +6243,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; + bool use_current_size_for_scrollbar_x = window_just_created; + bool use_current_size_for_scrollbar_y = window_just_created; + // Collapse window by double-clicking on title bar // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) @@ -6318,6 +6257,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->WantCollapseToggle) { window->Collapsed = !window->Collapsed; + if (!window->Collapsed) + use_current_size_for_scrollbar_y = true; MarkIniSettingsDirty(window); } } @@ -6329,10 +6270,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // SIZE + // Outer Decoration Sizes + // (we need to clear ScrollbarSize immediatly as CalcWindowAutoFitSize() needs it and can be called from other locations). + const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes; + window->DecoOuterSizeX1 = 0.0f; + window->DecoOuterSizeX2 = 0.0f; + window->DecoOuterSizeY1 = window->TitleBarHeight() + window->MenuBarHeight(); + window->DecoOuterSizeY2 = 0.0f; + window->ScrollbarSizes = ImVec2(0.0f, 0.0f); + // Calculate auto-fit size, handle automatic resize const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal); - bool use_current_size_for_scrollbar_x = window_just_created; - bool use_current_size_for_scrollbar_y = window_just_created; if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) { // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. @@ -6369,9 +6317,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull); window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; - // Decoration size - const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); - // POSITION // Popup latch its initial position, will position itself when it appears next frame @@ -6411,9 +6356,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Clamp position/size so window stays visible within its viewport or monitor // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. - if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow)) if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f) - ClampWindowRect(window, visibility_rect); + ClampWindowPos(window, visibility_rect); window->Pos = ImFloor(window->Pos); // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) @@ -6455,7 +6400,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (g.TestEngineHookItems) { IM_ASSERT(window->IDStack.Size == 1); - window->IDStack.Size = 0; + window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself. IMGUI_TEST_ENGINE_ITEM_ADD(window->Rect(), window->ID); IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0); window->IDStack.Size = 1; @@ -6478,9 +6423,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (!window->Collapsed) { // When reading the current size we need to read it after size constraints have been applied. - // When we use InnerRect here we are intentionally reading last frame size, same for ScrollbarSizes values before we set them again. - ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - decoration_up_height); - ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + window->ScrollbarSizes; + // Intentionally use previous frame values for InnerRect and ScrollbarSizes. + // And when we use window->DecorationUp here it doesn't have ScrollbarSizes.y applied yet. + 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; @@ -6490,10 +6436,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->ScrollbarX && !window->ScrollbarY) window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars) && !(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. + window->DecoOuterSizeX2 += window->ScrollbarSizes.x; + window->DecoOuterSizeY2 += window->ScrollbarSizes.y; } // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING) - // Update various regions. Variables they depends on should be set above in this function. + // Update various regions. Variables they depend on should be set above in this function. // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame. // Outer rectangle @@ -6513,10 +6463,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // - ScrollToRectEx() // - NavUpdatePageUpPageDown() // - Scrollbar() - window->InnerRect.Min.x = window->Pos.x; - window->InnerRect.Min.y = window->Pos.y + decoration_up_height; - window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x; - window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y; + window->InnerRect.Min.x = window->Pos.x + window->DecoOuterSizeX1; + window->InnerRect.Min.y = window->Pos.y + window->DecoOuterSizeY1; + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->DecoOuterSizeX2; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->DecoOuterSizeY2; // Inner clipping rectangle. // Will extend a little bit outside the normal work region. @@ -6549,6 +6499,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Apply scrolling window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + window->DecoInnerSizeX1 = window->DecoInnerSizeY1 = 0.0f; // DRAWING @@ -6578,7 +6529,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Handle title bar, scrollbar, resize grips and resize borders const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); - RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, resize_grip_count, resize_grip_col, resize_grip_draw_size); + const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch. + RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size); if (render_decorations_in_parent) window->DrawList = &window->DrawListInst; @@ -6593,8 +6545,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // - BeginTabBar() for right-most edge const bool allow_scrollbar_x = !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar); const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar); - const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f, window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x)); - const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f, window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - window->ScrollbarSizes.y)); + const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f, window->Size.x - window->WindowPadding.x * 2.0f - (window->DecoOuterSizeX1 + window->DecoOuterSizeX2))); + const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f, window->Size.y - window->WindowPadding.y * 2.0f - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2))); window->WorkRect.Min.x = ImFloor(window->InnerRect.Min.x - window->Scroll.x + ImMax(window->WindowPadding.x, window->WindowBorderSize)); window->WorkRect.Min.y = ImFloor(window->InnerRect.Min.y - window->Scroll.y + ImMax(window->WindowPadding.y, window->WindowBorderSize)); window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x; @@ -6605,21 +6557,21 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it. // Used by: // - Mouse wheel scrolling + many other things - window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x; - window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + decoration_up_height; - window->ContentRegionRect.Max.x = window->ContentRegionRect.Min.x + (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : (window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x)); - window->ContentRegionRect.Max.y = window->ContentRegionRect.Min.y + (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : (window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - window->ScrollbarSizes.y)); + window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x + window->DecoOuterSizeX1; + window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->DecoOuterSizeY1; + window->ContentRegionRect.Max.x = window->ContentRegionRect.Min.x + (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : (window->Size.x - window->WindowPadding.x * 2.0f - (window->DecoOuterSizeX1 + window->DecoOuterSizeX2))); + window->ContentRegionRect.Max.y = window->ContentRegionRect.Min.y + (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : (window->Size.y - window->WindowPadding.y * 2.0f - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2))); // Setup drawing context // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) - window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.Indent.x = window->DecoOuterSizeX1 + window->WindowPadding.x - window->Scroll.x; window->DC.GroupOffset.x = 0.0f; window->DC.ColumnsOffset.x = 0.0f; // Record the loss of precision of CursorStartPos which can happen due to really large scrolling amount. // This is used by clipper to compensate and fix the most common use case of large scroll area. Easy and cheap, next best thing compared to switching everything to double or ImU64. - double start_pos_highp_x = (double)window->Pos.x + window->WindowPadding.x - (double)window->Scroll.x + window->DC.ColumnsOffset.x; - double start_pos_highp_y = (double)window->Pos.y + window->WindowPadding.y - (double)window->Scroll.y + decoration_up_height; + double start_pos_highp_x = (double)window->Pos.x + window->WindowPadding.x - (double)window->Scroll.x + window->DecoOuterSizeX1 + window->DC.ColumnsOffset.x; + double start_pos_highp_y = (double)window->Pos.y + window->WindowPadding.y - (double)window->Scroll.y + window->DecoOuterSizeY1; window->DC.CursorStartPos = ImVec2((float)start_pos_highp_x, (float)start_pos_highp_y); window->DC.CursorStartPosLossyness = ImVec2((float)(start_pos_highp_x - window->DC.CursorStartPos.x), (float)(start_pos_highp_y - window->DC.CursorStartPos.y)); window->DC.CursorPos = window->DC.CursorStartPos; @@ -6628,7 +6580,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.IdealMaxPos = window->DC.CursorStartPos; window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f); window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; - window->DC.IsSameLine = false; + window->DC.IsSameLine = window->DC.IsSetPos = false; window->DC.NavLayerCurrent = ImGuiNavLayer_Main; window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext; @@ -6684,9 +6636,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // This is useful to allow creating context menus on title bar only, etc. SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + // [DEBUG] +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.DebugLocateId != 0 && (window->ID == g.DebugLocateId || window->MoveId == g.DebugLocateId)) + DebugLocateItemResolveWithLastItem(); +#endif + // [Test Engine] Register title bar / tab +#ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID); +#endif } else { @@ -6694,9 +6654,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetCurrentWindow(window); } - // Pull/inherit current state - window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : window->GetID("#FOCUSSCOPE"); // Inherit from parent only // -V595 - PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) @@ -6774,11 +6731,15 @@ void ImGui::End() if (window->DC.CurrentColumns) EndColumns(); PopClipRect(); // Inner window clip rectangle + PopFocusScope(); // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging LogFinish(); + if (window->DC.IsSetPos) + ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + // Pop from window stack g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup; if (window->Flags & ImGuiWindowFlags_ChildMenu) @@ -6880,12 +6841,12 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.NavMousePosDirty = true; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavLayer = ImGuiNavLayer_Main; - g.NavFocusScopeId = 0; + g.NavFocusScopeId = window ? window->NavRootFocusScopeId : 0; g.NavIdIsAlive = false; - } - // Close popups if any - ClosePopupsOverWindow(window, false); + // Close popups if any + ClosePopupsOverWindow(window, false); + } // Move the root window to the top of the pile IM_ASSERT(window == NULL || window->RootWindow != NULL); @@ -7451,18 +7412,16 @@ void ImGui::ActivateItem(ImGuiID id) void ImGui::PushFocusScope(ImGuiID id) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - g.FocusScopeStack.push_back(window->DC.NavFocusScopeIdCurrent); - window->DC.NavFocusScopeIdCurrent = id; + g.FocusScopeStack.push_back(id); + g.CurrentFocusScopeId = id; } void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? - window->DC.NavFocusScopeIdCurrent = g.FocusScopeStack.back(); g.FocusScopeStack.pop_back(); + g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back() : 0; } // Note: this will likely be called ActivateItem() once we rework our Focus/Activation system! @@ -7512,7 +7471,7 @@ void ImGui::SetItemDefaultFocus() g.NavInitResultRectRel = WindowRectAbsToRel(window, g.LastItemData.Rect); NavUpdateAnyRequestFlag(); - // Scroll could be done in NavInitRequestApplyResult() via a opt-in flag (we however don't want regular init requests to scroll) + // Scroll could be done in NavInitRequestApplyResult() via an opt-in flag (we however don't want regular init requests to scroll) if (!IsItemVisible()) ScrollToRectEx(window, g.LastItemData.Rect, ImGuiScrollFlags_None); } @@ -7583,6 +7542,15 @@ ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) return id; } +ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) +{ + ImGuiID id = ImHashData(&n, sizeof(n), seed); + ImGuiContext& g = *GImGui; + if (g.DebugHookIdInfo == id) + DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); + return id; +} + void ImGui::PopID() { ImGuiWindow* window = GImGui->CurrentWindow; @@ -7624,50 +7592,91 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) //----------------------------------------------------------------------------- // [SECTION] INPUTS //----------------------------------------------------------------------------- - -// Test if mouse cursor is hovering given rectangle -// NB- Rectangle is clipped by our current clip setting -// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) -bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) -{ - ImGuiContext& g = *GImGui; - - // Clip - ImRect rect_clipped(r_min, r_max); - if (clip) - rect_clipped.ClipWith(g.CurrentWindow->ClipRect); - - // Expand for touch input - const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); - if (!rect_for_touch.Contains(g.IO.MousePos)) - return false; - return true; -} +// - GetKeyData() [Internal] +// - GetKeyIndex() [Internal] +// - GetKeyName() +// - GetKeyChordName() [Internal] +// - CalcTypematicRepeatAmount() [Internal] +// - GetTypematicRepeatRate() [Internal] +// - GetKeyPressedAmount() [Internal] +// - GetKeyMagnitude2d() [Internal] +//----------------------------------------------------------------------------- +// - UpdateKeyRoutingTable() [Internal] +// - GetRoutingIdFromOwnerId() [Internal] +// - GetShortcutRoutingData() [Internal] +// - CalcRoutingScore() [Internal] +// - SetShortcutRouting() [Internal] +// - TestShortcutRouting() [Internal] +//----------------------------------------------------------------------------- +// - IsKeyDown() +// - IsKeyPressed() +// - IsKeyReleased() +//----------------------------------------------------------------------------- +// - IsMouseDown() +// - IsMouseClicked() +// - IsMouseReleased() +// - IsMouseDoubleClicked() +// - GetMouseClickedCount() +// - IsMouseHoveringRect() [Internal] +// - IsMouseDragPastThreshold() [Internal] +// - IsMouseDragging() +// - GetMousePos() +// - GetMousePosOnOpeningCurrentPopup() +// - IsMousePosValid() +// - IsAnyMouseDown() +// - GetMouseDragDelta() +// - ResetMouseDragDelta() +// - GetMouseCursor() +// - SetMouseCursor() +//----------------------------------------------------------------------------- +// - UpdateAliasKey() +// - GetMergedModsFromKeys() +// - UpdateKeyboardInputs() +// - UpdateMouseInputs() +//----------------------------------------------------------------------------- +// - LockWheelingWindow [Internal] +// - FindBestWheelingWindow [Internal] +// - UpdateMouseWheel() [Internal] +//----------------------------------------------------------------------------- +// - SetNextFrameWantCaptureKeyboard() +// - SetNextFrameWantCaptureMouse() +//----------------------------------------------------------------------------- +// - GetInputSourceName() [Internal] +// - DebugPrintInputEvent() [Internal] +// - UpdateInputEvents() [Internal] +//----------------------------------------------------------------------------- +// - GetKeyOwner() [Internal] +// - TestKeyOwner() [Internal] +// - SetKeyOwner() [Internal] +// - SetItemKeyOwner() [Internal] +// - Shortcut() [Internal] +//----------------------------------------------------------------------------- ImGuiKeyData* ImGui::GetKeyData(ImGuiKey key) { ImGuiContext& g = *GImGui; - int index; + + // Special storage location for mods + if (key & ImGuiMod_Mask_) + key = ConvertSingleModFlagToKey(key); + #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END); - if (IsLegacyKey(key)) - index = (g.IO.KeyMap[key] != -1) ? g.IO.KeyMap[key] : key; // Remap native->imgui or imgui->native - else - index = key; + if (IsLegacyKey(key) && g.IO.KeyMap[key] != -1) + key = (ImGuiKey)g.IO.KeyMap[key]; // Remap native->imgui or imgui->native #else IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code."); - index = key - ImGuiKey_NamedKey_BEGIN; #endif - return &g.IO.KeysData[index]; + return &g.IO.KeysData[key - ImGuiKey_KeysData_OFFSET]; } #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -int ImGui::GetKeyIndex(ImGuiKey key) +ImGuiKey ImGui::GetKeyIndex(ImGuiKey key) { ImGuiContext& g = *GImGui; IM_ASSERT(IsNamedKey(key)); const ImGuiKeyData* key_data = GetKeyData(key); - return (int)(key_data - g.IO.KeysData); + return (ImGuiKey)(key_data - g.IO.KeysData); } #endif @@ -7691,8 +7700,8 @@ static const char* const GKeyNames[] = "GamepadL1", "GamepadR1", "GamepadL2", "GamepadR2", "GamepadL3", "GamepadR3", "GamepadLStickLeft", "GamepadLStickRight", "GamepadLStickUp", "GamepadLStickDown", "GamepadRStickLeft", "GamepadRStickRight", "GamepadRStickUp", "GamepadRStickDown", - "ModCtrl", "ModShift", "ModAlt", "ModSuper", "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)); @@ -7712,22 +7721,26 @@ const char* ImGui::GetKeyName(ImGuiKey key) #endif if (key == ImGuiKey_None) return "None"; + if (key & ImGuiMod_Mask_) + key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) return "Unknown"; return GKeyNames[key - ImGuiKey_NamedKey_BEGIN]; } -void ImGui::GetKeyChordName(ImGuiModFlags mods, ImGuiKey key, char* out_buf, int out_buf_size) +// ImGuiMod_Shortcut is translated to either Ctrl or Super. +void ImGui::GetKeyChordName(ImGuiKeyChord key_chord, char* out_buf, int out_buf_size) { ImGuiContext& g = *GImGui; - IM_ASSERT((mods & ~ImGuiModFlags_All) == 0 && "Passing invalid ImGuiModFlags value!"); // A frequent mistake is to pass ImGuiKey_ModXXX instead of ImGuiModFlags_XXX + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); ImFormatString(out_buf, (size_t)out_buf_size, "%s%s%s%s%s", - (mods & ImGuiModFlags_Ctrl) ? "Ctrl+" : "", - (mods & ImGuiModFlags_Shift) ? "Shift+" : "", - (mods & ImGuiModFlags_Alt) ? "Alt+" : "", - (mods & ImGuiModFlags_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "", - GetKeyName(key)); + (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", + (key_chord & ImGuiMod_Shift) ? "Shift+" : "", + (key_chord & ImGuiMod_Alt) ? "Alt+" : "", + (key_chord & ImGuiMod_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "", + GetKeyName((ImGuiKey)(key_chord & ~ImGuiMod_Mask_))); } // t0 = previous time (e.g.: g.Time - g.IO.DeltaTime) @@ -7765,41 +7778,232 @@ int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_ra { ImGuiContext& g = *GImGui; const ImGuiKeyData* key_data = GetKeyData(key); + if (!key_data->Down) // 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 0; const float t = key_data->DownDuration; return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate); } // Return 2D vector representing the combination of four cardinal direction, with analog value support (for e.g. ImGuiKey_GamepadLStick* values). -ImVec2 ImGui::GetKeyVector2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down) +ImVec2 ImGui::GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down) { return ImVec2( GetKeyData(key_right)->AnalogValue - GetKeyData(key_left)->AnalogValue, GetKeyData(key_down)->AnalogValue - GetKeyData(key_up)->AnalogValue); } +// Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data. +// Entries D,A,B,B,A,C,B --> A,A,B,B,B,C,D +// Index A:1 B:2 C:5 D:0 --> A:0 B:2 C:5 D:6 +// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation. +static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) +{ + ImGuiContext& g = *GImGui; + rt->EntriesNext.resize(0); + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + const int new_routing_start_idx = rt->EntriesNext.Size; + ImGuiKeyRoutingData* routing_entry; + for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex) + { + routing_entry = &rt->Entries[old_routing_idx]; + routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry + routing_entry->RoutingNext = ImGuiKeyOwner_None; + routing_entry->RoutingNextScore = 255; + if (routing_entry->RoutingCurr == ImGuiKeyOwner_None) + continue; + rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer + + // Apply routing to owner if there's no owner already (RoutingCurr == None at this point) + if (routing_entry->Mods == g.IO.KeyMods) + { + ImGuiKeyOwnerData* owner_data = ImGui::GetKeyOwnerData(key); + if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + owner_data->OwnerCurr = routing_entry->RoutingCurr; + } + } + + // Rewrite linked-list + rt->Index[key - ImGuiKey_NamedKey_BEGIN] = (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1); + for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++) + rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1); + } + rt->Entries.swap(rt->EntriesNext); // Swap new and old indexes +} + +// owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope(). +static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id) +{ + ImGuiContext& g = *GImGui; + return (owner_id != ImGuiKeyOwner_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId; +} + +ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) +{ + // Majority of shortcuts will be Key + any number of Mods + // We accept _Single_ mod with ImGuiKey_None. + // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl); // Legal + // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift); // Legal + // - Shortcut(ImGuiMod_Ctrl); // Legal + // - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal + ImGuiContext& g = *GImGui; + ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; + ImGuiKeyRoutingData* routing_data; + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + if (key == ImGuiKey_None) + key = ConvertSingleModFlagToKey(mods); + IM_ASSERT(IsNamedKey(key)); + + // Get (in the majority of case, the linked list will have one element so this should be 2 reads. + // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame). + for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; idx = routing_data->NextEntryIndex) + { + routing_data = &rt->Entries[idx]; + if (routing_data->Mods == mods) + return routing_data; + } + + // Add to linked-list + ImGuiKeyRoutingIndex routing_data_idx = (ImGuiKeyRoutingIndex)rt->Entries.Size; + rt->Entries.push_back(ImGuiKeyRoutingData()); + routing_data = &rt->Entries[routing_data_idx]; + routing_data->Mods = (ImU16)mods; + routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list + rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx; + return routing_data; +} + +// Current score encoding (lower is highest priority): +// - 0: ImGuiInputFlags_RouteGlobalHigh +// - 1: ImGuiInputFlags_RouteFocused (if item active) +// - 2: ImGuiInputFlags_RouteGlobal +// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack) +// - 254: ImGuiInputFlags_RouteGlobalLow +// - 255: never route +// 'flags' should include an explicit routing policy +static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputFlags flags) +{ + if (flags & ImGuiInputFlags_RouteFocused) + { + ImGuiContext& g = *GImGui; + ImGuiWindow* focused = g.NavWindow; + + // ActiveID gets top priority + // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) + if (owner_id != 0 && g.ActiveId == owner_id) + return 1; + + // Score based on distance to focused window (lower is better) + // Assuming both windows are submitting a routing request, + // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match) + // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best) + // Assuming only WindowA is submitting a routing request, + // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score. + if (focused != NULL && focused->RootWindow == location->RootWindow) + for (int next_score = 3; focused != NULL; next_score++) + { + if (focused == location) + { + IM_ASSERT(next_score < 255); + return next_score; + } + focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path + } + return 255; + } + + // ImGuiInputFlags_RouteGlobalHigh is default, so calls without flags are not conditional + if (flags & ImGuiInputFlags_RouteGlobal) + return 2; + if (flags & ImGuiInputFlags_RouteGlobalLow) + return 254; + return 0; +} + +// Request a desired route for an input chord (key + mods). +// Return true if the route is available this frame. +// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state. +// (Conceptually this does a "Submit for next frame" + "Test for current frame". +// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.) +// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) +// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut. +bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiInputFlags_RouteMask_) == 0) + flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() + else + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used + + if (flags & ImGuiInputFlags_RouteUnlessBgFocused) + if (g.NavWindow == NULL) + return false; + if (flags & ImGuiInputFlags_RouteAlways) + return true; + + const int score = CalcRoutingScore(g.CurrentWindow, owner_id, flags); + if (score == 255) + return false; + + // Submit routing for NEXT frame (assuming score is sufficient) + // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <). + ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); + const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); + if (score < routing_data->RoutingNextScore) + { + routing_data->RoutingNext = routing_id; + routing_data->RoutingNextScore = (ImU8)score; + } + + // Return routing state for CURRENT frame + return routing_data->RoutingCurr == routing_id; +} + +// Currently unused by core (but used by tests) +// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading. +bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) +{ + const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry. + return routing_data->RoutingCurr == routing_id; +} + // Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes. // Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87) bool ImGui::IsKeyDown(ImGuiKey key) +{ + return IsKeyDown(key, ImGuiKeyOwner_Any); +} + +bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); if (!key_data->Down) return false; + if (!TestKeyOwner(key, owner_id)) + return false; return true; } bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) { - return IsKeyPressedEx(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); + return IsKeyPressed(key, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); } -// Important: unlike legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. -// [Internal] 2022/07: Do not call this directly! It is a temporary entry point which we will soon replace with an overload for IsKeyPressed() when we introduce key ownership. -bool ImGui::IsKeyPressedEx(ImGuiKey key, ImGuiInputFlags flags) +// Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. +bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) { const ImGuiKeyData* key_data = GetKeyData(key); + if (!key_data->Down) // 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 = key_data->DownDuration; if (t < 0.0f) return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! bool pressed = (t == 0.0f); if (!pressed && ((flags & ImGuiInputFlags_Repeat) != 0)) @@ -7808,17 +8012,25 @@ bool ImGui::IsKeyPressedEx(ImGuiKey key, ImGuiInputFlags flags) GetTypematicRepeatRate(flags, &repeat_delay, &repeat_rate); pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0; } - if (!pressed) return false; + if (!TestKeyOwner(key, owner_id)) + return false; return true; } bool ImGui::IsKeyReleased(ImGuiKey key) +{ + return IsKeyReleased(key, ImGuiKeyOwner_Any); +} + +bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); if (key_data->DownDurationPrev < 0.0f || key_data->Down) return false; + if (!TestKeyOwner(key, owner_id)) + return false; return true; } @@ -7826,33 +8038,62 @@ bool ImGui::IsMouseDown(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseDown[button]; + 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)); + 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. } bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) +{ + return IsMouseClicked(button, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); +} + +bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(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]; - if (t == 0.0f) - return true; - if (repeat && t > g.IO.KeyRepeatDelay) - return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; - return false; + if (t < 0.0f) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! + + const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0; + const bool pressed = (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay && CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0); + if (!pressed) + return false; + + if (!TestKeyOwner(MouseButtonToKey(button), owner_id)) + return false; + + return true; } bool ImGui::IsMouseReleased(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseReleased[button]; + 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)); + return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseClickedCount[button] == 2; + return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); } int ImGui::GetMouseClickedCount(ImGuiMouseButton button) @@ -7862,6 +8103,25 @@ int ImGui::GetMouseClickedCount(ImGuiMouseButton button) return g.IO.MouseClickedCount[button]; } +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(g.CurrentWindow->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + if (!rect_for_touch.Contains(g.IO.MousePos)) + return false; + return true; +} + // Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. // [Internal] This doesn't test if the button is pressed bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold) @@ -7942,6 +8202,10 @@ void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) g.IO.MouseClickedPos[button] = g.IO.MousePos; } +// Get desired mouse cursor shape. +// Important: this is meant to be used by a platform backend, it is reset in ImGui::NewFrame(), +// updated during the frame, and locked in EndFrame()/Render(). +// If you use software rendering by setting io.MouseDrawCursor then Dear ImGui will render those for you ImGuiMouseCursor ImGui::GetMouseCursor() { ImGuiContext& g = *GImGui; @@ -7954,6 +8218,355 @@ void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) g.MouseCursor = cursor_type; } +static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value) +{ + IM_ASSERT(ImGui::IsAliasKey(key)); + ImGuiKeyData* key_data = ImGui::GetKeyData(key); + key_data->Down = v; + key_data->AnalogValue = analog_value; +} + +// [Internal] Do not use directly +static ImGuiKeyChord GetMergedModsFromKeys() +{ + 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; } + return mods; +} + +static void ImGui::UpdateKeyboardInputs() +{ + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + + // Import legacy keys or verify they are not used +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + if (io.BackendUsingLegacyKeyArrays == 0) + { + // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally. + for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++) + IM_ASSERT((io.KeysDown[n] == false || IsKeyDown((ImGuiKey)n)) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); + } + else + { + if (g.FrameCount == 0) + for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) + IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!"); + + // Build reverse KeyMap (Named -> Legacy) + for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) + if (io.KeyMap[n] != -1) + { + IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n])); + io.KeyMap[io.KeyMap[n]] = n; + } + + // Import legacy keys into new ones + for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) + if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1) + { + const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n); + IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key)); + io.KeysData[key].Down = io.KeysDown[n]; + if (key != n) + io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends + io.BackendUsingLegacyKeyArrays = 1; + } + if (io.BackendUsingLegacyKeyArrays == 1) + { + GetKeyData(ImGuiMod_Ctrl)->Down = io.KeyCtrl; + GetKeyData(ImGuiMod_Shift)->Down = io.KeyShift; + GetKeyData(ImGuiMod_Alt)->Down = io.KeyAlt; + GetKeyData(ImGuiMod_Super)->Down = io.KeySuper; + } + } + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active) + { + #define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; } while (0) + #define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); } while (0) + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown); + MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow); + MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown); + #undef NAV_MAP_KEY + } +#endif +#endif + + // Update aliases + for (int n = 0; n < ImGuiMouseButton_COUNT; n++) + UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f); + UpdateAliasKey(ImGuiKey_MouseWheelX, io.MouseWheelH != 0.0f, io.MouseWheelH); + UpdateAliasKey(ImGuiKey_MouseWheelY, io.MouseWheel != 0.0f, io.MouseWheel); + + // Synchronize io.KeyMods and io.KeyXXX values. + // - New backends (1.87+): send io.AddKeyEvent(ImGuiMod_XXX) -> -> (here) deriving io.KeyMods + io.KeyXXX from key array. + // - Legacy backends: set io.KeyXXX bools -> (above) set key array from io.KeyXXX -> (here) deriving io.KeyMods + io.KeyXXX from key array. + // So with legacy backends the 4 values will do a unnecessary back-and-forth but it makes the code simpler and future facing. + io.KeyMods = GetMergedModsFromKeys(); + io.KeyCtrl = (io.KeyMods & ImGuiMod_Ctrl) != 0; + io.KeyShift = (io.KeyMods & ImGuiMod_Shift) != 0; + io.KeyAlt = (io.KeyMods & ImGuiMod_Alt) != 0; + io.KeySuper = (io.KeyMods & ImGuiMod_Super) != 0; + + // Clear gamepad data if disabled + if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) + for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++) + { + io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false; + io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f; + } + + // Update keys + for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++) + { + ImGuiKeyData* key_data = &io.KeysData[i]; + key_data->DownDurationPrev = key_data->DownDuration; + key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; + } + + // Update keys/input owner (named keys only): one entry per key + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; + owner_data->OwnerCurr = owner_data->OwnerNext; + if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. + owner_data->OwnerNext = ImGuiKeyOwner_None; + owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore + } + + UpdateKeyRoutingTable(&g.KeysRoutingTable); +} + +static void ImGui::UpdateMouseInputs() +{ + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + + // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) + if (IsMousePosValid(&io.MousePos)) + io.MousePos = g.MouseLastValidPos = ImFloorSigned(io.MousePos); + + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta + if (IsMousePosValid(&io.MousePos) && IsMousePosValid(&io.MousePosPrev)) + io.MouseDelta = io.MousePos - io.MousePosPrev; + else + io.MouseDelta = ImVec2(0.0f, 0.0f); + + // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. + if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) + g.NavDisableMouseHover = false; + + io.MousePosPrev = io.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) + { + io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; + io.MouseClickedCount[i] = 0; // Will be filled below + io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f; + io.MouseDownDurationPrev[i] = io.MouseDownDuration[i]; + io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f; + if (io.MouseClicked[i]) + { + bool is_repeated_click = false; + if ((float)(g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime) + { + ImVec2 delta_from_click_pos = IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + if (ImLengthSqr(delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist) + is_repeated_click = true; + } + if (is_repeated_click) + io.MouseClickedLastCount[i]++; + else + io.MouseClickedLastCount[i] = 1; + io.MouseClickedTime[i] = g.Time; + io.MouseClickedPos[i] = io.MousePos; + io.MouseClickedCount[i] = io.MouseClickedLastCount[i]; + io.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (io.MouseDown[i]) + { + // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold + float delta_sqr_click_pos = IsMousePosValid(&io.MousePos) ? ImLengthSqr(io.MousePos - io.MouseClickedPos[i]) : 0.0f; + io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], delta_sqr_click_pos); + } + + // We provide io.MouseDoubleClicked[] as a legacy service + io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2); + + // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + if (io.MouseClicked[i]) + g.NavDisableMouseHover = false; + } +} + +static void LockWheelingWindow(ImGuiWindow* window, float wheel_amount) +{ + ImGuiContext& g = *GImGui; + if (window) + g.WheelingWindowReleaseTimer = ImMin(g.WheelingWindowReleaseTimer + ImAbs(wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER, WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER); + else + g.WheelingWindowReleaseTimer = 0.0f; + if (g.WheelingWindow == window) + return; + IMGUI_DEBUG_LOG_IO("LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL"); + g.WheelingWindow = window; + g.WheelingWindowRefMousePos = g.IO.MousePos; + if (window == NULL) + { + g.WheelingWindowStartFrame = -1; + g.WheelingAxisAvg = ImVec2(0.0f, 0.0f); + } +} + +static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel) +{ + // For each axis, find window in the hierarchy that may want to use scrolling + ImGuiContext& g = *GImGui; + ImGuiWindow* windows[2] = { NULL, NULL }; + for (int axis = 0; axis < 2; axis++) + if (wheel[axis] != 0.0f) + for (ImGuiWindow* window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow; window = windows[axis] = window->ParentWindow) + { + // Bubble up into parent window if: + // - a child window doesn't allow any scrolling. + // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag. + //// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in (FIXME-WIP) + const bool has_scrolling = (window->ScrollMax[axis] != 0.0f); + const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs); + //const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]); + if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits) + break; // select this window + } + if (windows[0] == NULL && windows[1] == NULL) + return NULL; + + // If there's only one window or only one axis then there's no ambiguity + if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL) + return windows[1] ? windows[1] : windows[0]; + + // If candidate are different windows we need to decide which one to prioritize + // - First frame: only find a winner if one axis is zero. + // - Subsequent frames: only find a winner when one is more than the other. + if (g.WheelingWindowStartFrame == -1) + g.WheelingWindowStartFrame = g.FrameCount; + if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) || (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y)) + { + g.WheelingWindowWheelRemainder = wheel; + return NULL; + } + return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1]; +} + +// Called by NewFrame() +void ImGui::UpdateMouseWheel() +{ + // Reset the locked window if we move the mouse or after the timer elapses. + // FIXME: Ideally we could refactor to have one timer for "changing window w/ same axis" and a shorter timer for "changing window or axis w/ other axis" (#3795) + ImGuiContext& g = *GImGui; + if (g.WheelingWindow != NULL) + { + g.WheelingWindowReleaseTimer -= g.IO.DeltaTime; + if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) + g.WheelingWindowReleaseTimer = 0.0f; + if (g.WheelingWindowReleaseTimer <= 0.0f) + LockWheelingWindow(NULL, 0.0f); + } + + ImVec2 wheel; + wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f; + wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? 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; + + // 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) + { + LockWheelingWindow(mouse_window, wheel.y); + ImGuiWindow* window = mouse_window; + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + if (window == window->RootWindow) + { + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + SetWindowPos(window, window->Pos + offset, 0); + window->Size = ImFloor(window->Size * scale); + window->SizeFull = ImFloor(window->SizeFull * scale); + } + return; + } + if (g.IO.KeyCtrl) + return; + + // Mouse wheel scrolling + // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead + // - We avoid doing it on OSX as it the OS input layer handles this already. + // - However this means when running on OSX over Emcripten, Shift+WheelY will incur two swappings (1 in OS, 1 here), cancelling the feature. + const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors; + if (swap_axis) + { + wheel.x = wheel.y; + wheel.y = 0.0f; + } + + // Maintain a rough average of moving magnitude on both axises + // FIXME: should by based on wall clock time rather than frame-counter + g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30); + g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30); + + // In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now. + wheel += g.WheelingWindowWheelRemainder; + g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f); + if (wheel.x == 0.0f && wheel.y == 0.0f) + return; + + // Mouse wheel scrolling: find target and apply + // - don't renew lock if axis doesn't apply on the window. + // - select a main axis when both axises are being moved. + if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel))) + if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) + { + bool do_scroll[2] = { wheel.x != 0.0f && window->ScrollMax.x != 0.0f, wheel.y != 0.0f && window->ScrollMax.y != 0.0f }; + if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y]) + do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false; + if (do_scroll[ImGuiAxis_X]) + { + LockWheelingWindow(window, wheel.x); + float max_step = window->InnerRect.GetWidth() * 0.67f; + float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); + SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); + } + if (do_scroll[ImGuiAxis_Y]) + { + LockWheelingWindow(window, wheel.y); + float max_step = window->InnerRect.GetHeight() * 0.67f; + float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); + SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); + } + } +} + void ImGui::SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard) { ImGuiContext& g = *GImGui; @@ -7976,9 +8589,9 @@ static const char* GetInputSourceName(ImGuiInputSource source) static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) { ImGuiContext& g = *GImGui; - if (e->Type == ImGuiInputEventType_MousePos) { IMGUI_DEBUG_LOG_IO("%s: MousePos (%.1f, %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; } + if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("%s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("%s: MousePos (%.1f, %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; } if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("%s: MouseButton %d %s\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up"); return; } - if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.1f, %.1f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; } + if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.3f, %.3f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; } if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_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("%s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; } if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("%s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; } @@ -8009,79 +8622,52 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) ImGuiInputEvent* e = &g.InputEventsQueue[event_n]; if (e->Type == ImGuiInputEventType_MousePos) { + // Trickling Rule: Stop processing queued events if we already handled a mouse button change ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY); - if (IsMousePosValid(&event_pos)) - event_pos = ImVec2(ImFloorSigned(event_pos.x), ImFloorSigned(event_pos.y)); // Apply same flooring as UpdateMouseInputs() - e->IgnoredAsSame = (io.MousePos.x == event_pos.x && io.MousePos.y == event_pos.y); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we already handled a mouse button change - if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted)) - break; - io.MousePos = event_pos; - mouse_moved = true; - } + if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted)) + break; + io.MousePos = event_pos; + mouse_moved = true; } else if (e->Type == ImGuiInputEventType_MouseButton) { + // Trickling Rule: Stop processing queued events if we got multiple action on the same button const ImGuiMouseButton button = e->MouseButton.Button; IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); - e->IgnoredAsSame = (io.MouseDown[button] == e->MouseButton.Down); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we got multiple action on the same button - if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled)) - break; - io.MouseDown[button] = e->MouseButton.Down; - mouse_button_changed |= (1 << button); - } + if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled)) + break; + io.MouseDown[button] = e->MouseButton.Down; + mouse_button_changed |= (1 << button); } else if (e->Type == ImGuiInputEventType_MouseWheel) { - e->IgnoredAsSame = (e->MouseWheel.WheelX == 0.0f && e->MouseWheel.WheelY == 0.0f); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we got multiple action on the event - if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0)) - break; - io.MouseWheelH += e->MouseWheel.WheelX; - io.MouseWheel += e->MouseWheel.WheelY; - mouse_wheeled = true; - } + // Trickling Rule: Stop processing queued events if we got multiple action on the event + if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0)) + break; + io.MouseWheelH += e->MouseWheel.WheelX; + io.MouseWheel += e->MouseWheel.WheelY; + mouse_wheeled = true; } else if (e->Type == ImGuiInputEventType_Key) { + // Trickling Rule: Stop processing queued events if we got multiple action on the same button ImGuiKey key = e->Key.Key; IM_ASSERT(key != ImGuiKey_None); - const int keydata_index = (key - ImGuiKey_KeysData_OFFSET); - ImGuiKeyData* keydata = &io.KeysData[keydata_index]; - e->IgnoredAsSame = (keydata->Down == e->Key.Down && keydata->AnalogValue == e->Key.AnalogValue); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we got multiple action on the same button - if (trickle_fast_inputs && keydata->Down != e->Key.Down && (key_changed_mask.TestBit(keydata_index) || text_inputted || mouse_button_changed != 0)) - break; - keydata->Down = e->Key.Down; - keydata->AnalogValue = e->Key.AnalogValue; - key_changed = true; - key_changed_mask.SetBit(keydata_index); + ImGuiKeyData* key_data = GetKeyData(key); + const int key_data_index = (int)(key_data - g.IO.KeysData); + if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || text_inputted || mouse_button_changed != 0)) + break; + key_data->Down = e->Key.Down; + key_data->AnalogValue = e->Key.AnalogValue; + key_changed = true; + key_changed_mask.SetBit(key_data_index); - if (key == ImGuiKey_ModCtrl || key == ImGuiKey_ModShift || key == ImGuiKey_ModAlt || key == ImGuiKey_ModSuper) - { - if (key == ImGuiKey_ModCtrl) { io.KeyCtrl = keydata->Down; } - if (key == ImGuiKey_ModShift) { io.KeyShift = keydata->Down; } - if (key == ImGuiKey_ModAlt) { io.KeyAlt = keydata->Down; } - if (key == ImGuiKey_ModSuper) { io.KeySuper = keydata->Down; } - io.KeyMods = GetMergedModFlags(); - } - - // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends + // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - io.KeysDown[key] = keydata->Down; - if (io.KeyMap[key] != -1) - io.KeysDown[io.KeyMap[key]] = keydata->Down; + io.KeysDown[key_data_index] = key_data->Down; + if (io.KeyMap[key_data_index] != -1) + io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; #endif - } } else if (e->Type == ImGuiInputEventType_Text) { @@ -8095,12 +8681,10 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) } else if (e->Type == ImGuiInputEventType_Focus) { - // We intentionally overwrite this and process lower, in order to give a chance + // We intentionally overwrite this and process in NewFrame(), in order to give a chance // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame. const bool focus_lost = !e->AppFocused.Focused; - e->IgnoredAsSame = (io.AppFocusLost == focus_lost); - if (!e->IgnoredAsSame) - io.AppFocusLost = focus_lost; + io.AppFocusLost = focus_lost; } else { @@ -8117,7 +8701,7 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (event_n != 0 && (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO)) for (int n = 0; n < g.InputEventsQueue.Size; n++) - DebugPrintInputEvent(n < event_n ? (g.InputEventsQueue[n].IgnoredAsSame ? "Processed (Same)" : "Processed") : "Remaining", &g.InputEventsQueue[n]); + DebugPrintInputEvent(n < event_n ? "Processed" : "Remaining", &g.InputEventsQueue[n]); #endif // Remaining events will be processed on the next frame @@ -8127,12 +8711,127 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) g.InputEventsQueue.erase(g.InputEventsQueue.Data, g.InputEventsQueue.Data + event_n); // Clear buttons state when focus is lost - // (this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle) + // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle. + // - we clear in EndFrame() and not now in order allow application/user code polling this flag + // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling" event). if (g.IO.AppFocusLost) - { g.IO.ClearInputKeys(); - g.IO.AppFocusLost = false; +} + +ImGuiID ImGui::GetKeyOwner(ImGuiKey key) +{ + if (!IsNamedKeyOrModKey(key)) + return ImGuiKeyOwner_None; + + ImGuiContext& g = *GImGui; + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + ImGuiID owner_id = owner_data->OwnerCurr; + + if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) + if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) + return ImGuiKeyOwner_None; + + return owner_id; +} + +// TestKeyOwner(..., ID) : (owner == None || owner == ID) +// TestKeyOwner(..., None) : (owner == None) +// TestKeyOwner(..., Any) : no owner test +// All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags. +bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) +{ + if (!IsNamedKeyOrModKey(key)) + return true; + + ImGuiContext& g = *GImGui; + if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) + if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) + return false; + + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + if (owner_id == ImGuiKeyOwner_Any) + 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. + // Setting OwnerCurr in SetKeyOwner() is more consistent than testing OwnerNext here: would be inconsistent with getter and other functions. + if (owner_data->OwnerCurr != owner_id) + { + if (owner_data->LockThisFrame) + return false; + if (owner_data->OwnerCurr != ImGuiKeyOwner_None) + return false; } + + return true; +} + +// _LockXXX flags are useful to lock keys away from code which is not input-owner aware. +// When using _LockXXX flags, you can use ImGuiKeyOwner_Any to lock keys from everyone. +// - SetKeyOwner(..., None) : clears owner +// - SetKeyOwner(..., Any, !Lock) : illegal (assert) +// - SetKeyOwner(..., Any or None, Lock) : set lock +void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) +{ + IM_ASSERT(IsNamedKeyOrModKey(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it) + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function! + + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + owner_data->OwnerCurr = owner_data->OwnerNext = owner_id; + + // We cannot lock by default as it would likely break lots of legacy code. + // In the case of using LockUntilRelease while key is not down we still lock during the frame (no key_data->Down test) + owner_data->LockUntilRelease = (flags & ImGuiInputFlags_LockUntilRelease) != 0; + owner_data->LockThisFrame = (flags & ImGuiInputFlags_LockThisFrame) != 0 || (owner_data->LockUntilRelease); +} + +// This is more or less equivalent to: +// 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) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = g.LastItemData.ID; + if (id == 0 || (g.HoveredId != id && g.ActiveId != id)) + return; + 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! + SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_); + } +} + +bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext& g = *GImGui; + + // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. + if ((flags & ImGuiInputFlags_RouteMask_) == 0) + flags |= ImGuiInputFlags_RouteFocused; + if (!SetShortcutRouting(key_chord, owner_id, flags)) + return false; + + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + if (g.IO.KeyMods != mods) + return false; + + // Special storage location for mods + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + if (key == ImGuiKey_None) + key = ConvertSingleModFlagToKey(mods); + + if (!IsKeyPressed(key, owner_id, (flags & (ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateMask_)))) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function! + + return true; } @@ -8161,6 +8860,38 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si return !error; } +// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell) +// This is causing issues and ambiguity and we need to retire that. +// See https://github.com/ocornut/imgui/issues/5548 for more details. +// [Scenario 1] +// Previously this would make the window content size ~200x200: +// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK +// Instead, please submit an item: +// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK +// Alternative: +// Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK +// [Scenario 2] +// For reference this is one of the issue what we aim to fix with this change: +// BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() +// The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller! +// While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue. +void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window->DC.IsSetPos); + window->DC.IsSetPos = false; +#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y) + return; + if (window->SkipItems) + return; + IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent."); +#else + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +#endif +} + static void ImGui::ErrorCheckNewFrameSanityChecks() { ImGuiContext& g = *GImGui; @@ -8173,6 +8904,13 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() // #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct! if (true) IM_ASSERT(1); else IM_ASSERT(0); + // Emscripten backends are often imprecise in their submission of DeltaTime. (#6114, #3644) + // Ideally the Emscripten app/backend should aim to fix or smooth this value and avoid feeding zero, but we tolerate it. +#ifdef __EMSCRIPTEN__ + if (g.IO.DeltaTime <= 0.0f && g.FrameCount > 0) + g.IO.DeltaTime = 0.00001f; +#endif + // Check user data // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument) IM_ASSERT(g.Initialized); @@ -8208,9 +8946,9 @@ static void ImGui::ErrorCheckEndFrameSanityChecks() // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs. - // We silently accommodate for this case by ignoring/ the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0), + // We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0), // while still correctly asserting on mid-frame key press events. - const ImGuiModFlags key_mods = GetMergedModFlags(); + const ImGuiKeyChord key_mods = GetMergedModsFromKeys(); IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); IM_UNUSED(key_mods); @@ -8320,7 +9058,7 @@ void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, vo if (log_callback) log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name); PopStyleVar(); } - while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack) //-V1044 + while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack + 1) //-V1044 { if (log_callback) log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name); PopFocusScope(); @@ -8422,7 +9160,7 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x; window->DC.CursorPosPrevLine.y = line_y1; window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line - window->DC.CursorPos.y = IM_FLOOR(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line + window->DC.CursorPos.y = IM_FLOOR(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] @@ -8431,7 +9169,7 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) window->DC.CurrLineSize.y = 0.0f; window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); window->DC.CurrLineTextBaseOffset = 0.0f; - window->DC.IsSameLine = false; + window->DC.IsSameLine = window->DC.IsSetPos = false; // Horizontal layout mode if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) @@ -8468,25 +9206,19 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. - window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); - if (g.NavId == id || g.NavAnyRequest) - if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) - if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) - NavProcessItem(); + if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav)) + { + window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(); + } // [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of "##something". // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something". // READ THE FAQ: https://dearimgui.org/faq IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); - - // [DEBUG] Item Picker tool, when enabling the "extended" version we perform the check in ItemAdd() -#ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX - if (id == g.DebugItemPickerBreakId) - { - IM_DEBUG_BREAK(); - g.DebugItemPickerBreakId = 0; - } -#endif } g.NextItemData.Flags = ImGuiNextItemDataFlags_None; @@ -8496,12 +9228,26 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu #endif // Clipping test - const bool is_clipped = IsClippedEx(bb, id); - if (is_clipped) - return false; + // (FIXME: This is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value) + //const bool is_clipped = IsClippedEx(bb, id); + //if (is_clipped) + // return false; + const bool is_rect_visible = bb.Overlaps(window->ClipRect); + if (!is_rect_visible) + if (id == 0 || (id != g.ActiveId && id != g.NavId)) + if (!g.LogEnabled) + return false; + + // [DEBUG] +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (id != 0 && id == g.DebugLocateId) + DebugLocateItemResolveWithLastItem(); +#endif //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (is_rect_visible) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; if (IsMouseHoveringRect(bb.Min, bb.Max)) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; return true; @@ -8551,7 +9297,8 @@ void ImGui::SetCursorScreenPos(const ImVec2& pos) { ImGuiWindow* window = GetCurrentWindow(); window->DC.CursorPos = pos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); + //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); + window->DC.IsSetPos = true; } // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. @@ -8578,21 +9325,24 @@ void ImGui::SetCursorPos(const ImVec2& local_pos) { ImGuiWindow* window = GetCurrentWindow(); window->DC.CursorPos = window->Pos - window->Scroll + local_pos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); + //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); + window->DC.IsSetPos = true; } void ImGui::SetCursorPosX(float x) { ImGuiWindow* window = GetCurrentWindow(); window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; - window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); + //window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); + window->DC.IsSetPos = true; } void ImGui::SetCursorPosY(float y) { ImGuiWindow* window = GetCurrentWindow(); window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; - window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); + //window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); + window->DC.IsSetPos = true; } ImVec2 ImGui::GetCursorStartPos() @@ -8809,6 +9559,9 @@ void ImGui::EndGroup() ImGuiGroupData& group_data = g.GroupStack.back(); IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window? + if (window->DC.IsSetPos) + ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos)); window->DC.CursorPos = group_data.BackupCursorPos; @@ -8831,7 +9584,7 @@ void ImGui::EndGroup() ItemAdd(group_bb, 0, NULL, ImGuiItemFlags_NoTabStop); // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. - // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. + // It would be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. // Also if you grep for LastItemId you'll notice it is only used in that context. // (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; @@ -8881,38 +9634,24 @@ static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, fl static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) { ImVec2 scroll = window->Scroll; - if (window->ScrollTarget.x < FLT_MAX) + ImVec2 decoration_size(window->DecoOuterSizeX1 + window->DecoInnerSizeX1 + window->DecoOuterSizeX2, window->DecoOuterSizeY1 + window->DecoInnerSizeY1 + window->DecoOuterSizeY2); + for (int axis = 0; axis < 2; axis++) { - float decoration_total_width = window->ScrollbarSizes.x; - float center_x_ratio = window->ScrollTargetCenterRatio.x; - float scroll_target_x = window->ScrollTarget.x; - if (window->ScrollTargetEdgeSnapDist.x > 0.0f) + if (window->ScrollTarget[axis] < FLT_MAX) { - float snap_x_min = 0.0f; - float snap_x_max = window->ScrollMax.x + window->SizeFull.x - decoration_total_width; - scroll_target_x = CalcScrollEdgeSnap(scroll_target_x, snap_x_min, snap_x_max, window->ScrollTargetEdgeSnapDist.x, center_x_ratio); + float center_ratio = window->ScrollTargetCenterRatio[axis]; + float scroll_target = window->ScrollTarget[axis]; + if (window->ScrollTargetEdgeSnapDist[axis] > 0.0f) + { + float snap_min = 0.0f; + float snap_max = window->ScrollMax[axis] + window->SizeFull[axis] - decoration_size[axis]; + scroll_target = CalcScrollEdgeSnap(scroll_target, snap_min, snap_max, window->ScrollTargetEdgeSnapDist[axis], center_ratio); + } + scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll.x = scroll_target_x - center_x_ratio * (window->SizeFull.x - decoration_total_width); - } - if (window->ScrollTarget.y < FLT_MAX) - { - float decoration_total_height = window->TitleBarHeight() + window->MenuBarHeight() + window->ScrollbarSizes.y; - float center_y_ratio = window->ScrollTargetCenterRatio.y; - float scroll_target_y = window->ScrollTarget.y; - if (window->ScrollTargetEdgeSnapDist.y > 0.0f) - { - float snap_y_min = 0.0f; - float snap_y_max = window->ScrollMax.y + window->SizeFull.y - decoration_total_height; - scroll_target_y = CalcScrollEdgeSnap(scroll_target_y, snap_y_min, snap_y_max, window->ScrollTargetEdgeSnapDist.y, center_y_ratio); - } - scroll.y = scroll_target_y - center_y_ratio * (window->SizeFull.y - decoration_total_height); - } - scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f)); - scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f)); - if (!window->Collapsed && !window->SkipItems) - { - scroll.x = ImMin(scroll.x, window->ScrollMax.x); - scroll.y = ImMin(scroll.y, window->ScrollMax.y); + scroll[axis] = IM_FLOOR(ImMax(scroll[axis], 0.0f)); + if (!window->Collapsed && !window->SkipItems) + scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); } return scroll; } @@ -8933,8 +9672,11 @@ void ImGui::ScrollToRect(ImGuiWindow* window, const ImRect& item_rect, ImGuiScro ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags) { ImGuiContext& g = *GImGui; - ImRect window_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)); - //GetForegroundDrawList(window)->AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] + ImRect scroll_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)); + scroll_rect.Min.x = ImMin(scroll_rect.Min.x + window->DecoInnerSizeX1, scroll_rect.Max.x); + scroll_rect.Min.y = ImMin(scroll_rect.Min.y + window->DecoInnerSizeY1, scroll_rect.Max.y); + //GetForegroundDrawList(window)->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255,0,0,255), 0.0f, 0, 5.0f); // [DEBUG] + //GetForegroundDrawList(window)->AddRect(scroll_rect.Min, scroll_rect.Max, IM_COL32_WHITE); // [DEBUG] // Check that only one behavior is selected per axis IM_ASSERT((flags & ImGuiScrollFlags_MaskX_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskX_)); @@ -8947,35 +9689,39 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui if ((flags & ImGuiScrollFlags_MaskY_) == 0) flags |= window->Appearing ? ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeY; - const bool fully_visible_x = item_rect.Min.x >= window_rect.Min.x && item_rect.Max.x <= window_rect.Max.x; - const bool fully_visible_y = item_rect.Min.y >= window_rect.Min.y && item_rect.Max.y <= window_rect.Max.y; - const bool can_be_fully_visible_x = (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= window_rect.GetWidth(); - const bool can_be_fully_visible_y = (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= window_rect.GetHeight(); + const bool fully_visible_x = item_rect.Min.x >= scroll_rect.Min.x && item_rect.Max.x <= scroll_rect.Max.x; + const bool fully_visible_y = item_rect.Min.y >= scroll_rect.Min.y && item_rect.Max.y <= scroll_rect.Max.y; + const bool can_be_fully_visible_x = (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= scroll_rect.GetWidth() || (window->AutoFitFramesX > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0; + const bool can_be_fully_visible_y = (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= scroll_rect.GetHeight() || (window->AutoFitFramesY > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0; if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x) { - if (item_rect.Min.x < window_rect.Min.x || !can_be_fully_visible_x) + if (item_rect.Min.x < scroll_rect.Min.x || !can_be_fully_visible_x) SetScrollFromPosX(window, item_rect.Min.x - g.Style.ItemSpacing.x - window->Pos.x, 0.0f); - else if (item_rect.Max.x >= window_rect.Max.x) + else if (item_rect.Max.x >= scroll_rect.Max.x) SetScrollFromPosX(window, item_rect.Max.x + g.Style.ItemSpacing.x - window->Pos.x, 1.0f); } else if (((flags & ImGuiScrollFlags_KeepVisibleCenterX) && !fully_visible_x) || (flags & ImGuiScrollFlags_AlwaysCenterX)) { - float target_x = can_be_fully_visible_x ? ImFloor((item_rect.Min.x + item_rect.Max.x - window->InnerRect.GetWidth()) * 0.5f) : item_rect.Min.x; - SetScrollFromPosX(window, target_x - window->Pos.x, 0.0f); + if (can_be_fully_visible_x) + SetScrollFromPosX(window, ImFloor((item_rect.Min.x + item_rect.Max.x) * 0.5f) - window->Pos.x, 0.5f); + else + SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x, 0.0f); } if ((flags & ImGuiScrollFlags_KeepVisibleEdgeY) && !fully_visible_y) { - if (item_rect.Min.y < window_rect.Min.y || !can_be_fully_visible_y) + if (item_rect.Min.y < scroll_rect.Min.y || !can_be_fully_visible_y) SetScrollFromPosY(window, item_rect.Min.y - g.Style.ItemSpacing.y - window->Pos.y, 0.0f); - else if (item_rect.Max.y >= window_rect.Max.y) + else if (item_rect.Max.y >= scroll_rect.Max.y) SetScrollFromPosY(window, item_rect.Max.y + g.Style.ItemSpacing.y - window->Pos.y, 1.0f); } else if (((flags & ImGuiScrollFlags_KeepVisibleCenterY) && !fully_visible_y) || (flags & ImGuiScrollFlags_AlwaysCenterY)) { - float target_y = can_be_fully_visible_y ? ImFloor((item_rect.Min.y + item_rect.Max.y - window->InnerRect.GetHeight()) * 0.5f) : item_rect.Min.y; - SetScrollFromPosY(window, target_y - window->Pos.y, 0.0f); + if (can_be_fully_visible_y) + SetScrollFromPosY(window, ImFloor((item_rect.Min.y + item_rect.Max.y) * 0.5f) - window->Pos.y, 0.5f); + else + SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y, 0.0f); } ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); @@ -9050,7 +9796,7 @@ void ImGui::SetScrollY(float scroll_y) // - local_pos = (absolution_pos - window->Pos) // - So local_x/local_y are 0.0f for a position at the upper-left corner of a window, // and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area. -// - They mostly exists because of legacy API. +// - They mostly exist because of legacy API. // Following the rules above, when trying to work with scrolling code, consider that: // - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect! // - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course writing SetScrollY(0.0f) directly then makes more sense @@ -9058,7 +9804,7 @@ void ImGui::SetScrollY(float scroll_y) void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio) { IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f); - window->ScrollTarget.x = IM_FLOOR(local_x + window->Scroll.x); // Convert local position to scroll offset + window->ScrollTarget.x = IM_FLOOR(local_x - window->DecoOuterSizeX1 - window->DecoInnerSizeX1 + window->Scroll.x); // Convert local position to scroll offset window->ScrollTargetCenterRatio.x = center_x_ratio; window->ScrollTargetEdgeSnapDist.x = 0.0f; } @@ -9066,9 +9812,7 @@ void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio) { IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); - const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect; - local_y -= decoration_up_height; - window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y); // Convert local position to scroll offset + window->ScrollTarget.y = IM_FLOOR(local_y - window->DecoOuterSizeY1 - window->DecoInnerSizeY1 + window->Scroll.y); // Convert local position to scroll offset window->ScrollTargetCenterRatio.y = center_y_ratio; window->ScrollTargetEdgeSnapDist.y = 0.0f; } @@ -9242,7 +9986,7 @@ void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiID id = g.CurrentWindow->GetID(str_id); - IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X\n", str_id, id); + IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X)\n", str_id, id); OpenPopupEx(id, popup_flags); } @@ -9262,13 +10006,13 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) const int current_stack_size = g.BeginPopupStack.Size; if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup) - if (IsPopupOpen(0u, ImGuiPopupFlags_AnyPopupId)) + if (IsPopupOpen((ImGuiID)0, ImGuiPopupFlags_AnyPopupId)) return; ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. popup_ref.PopupId = id; popup_ref.Window = NULL; - popup_ref.SourceWindow = g.NavWindow; + popup_ref.BackupNavWindow = 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(); @@ -9370,12 +10114,13 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); // Trim open popup stack - ImGuiWindow* focus_window = g.OpenPopupStack[remaining].SourceWindow; ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window; + ImGuiWindow* popup_backup_nav_window = g.OpenPopupStack[remaining].BackupNavWindow; g.OpenPopupStack.resize(remaining); if (restore_focus_to_window_under_popup) { + ImGuiWindow* focus_window = (popup_window && popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : popup_backup_nav_window; if (focus_window && !focus_window->WasActive && popup_window) { // Fallback @@ -9630,7 +10375,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); - // If there not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width) + // If there's not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width) if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right)) continue; if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down)) @@ -9753,12 +10498,12 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) if (g.NavWindow != window) SetNavWindow(window); - // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and window->DC.NavFocusScopeIdCurrent are valid. + // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and g.CurrentFocusScopeId are valid. // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; g.NavId = id; g.NavLayer = nav_layer; - g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; + g.NavFocusScopeId = g.CurrentFocusScopeId; window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); @@ -9822,7 +10567,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) } // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items) - // For example, this ensure that items in one column are not reached when moving vertically from items in another column. + // For example, this ensures that items in one column are not reached when moving vertically from items in another column. NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); // Compute distance between boxes @@ -9886,7 +10631,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) } #endif - // Is it in the quadrant we're interesting in moving to? + // Is it in the quadrant we're interested in moving to? bool new_best = false; const ImGuiDir move_dir = g.NavMoveDir; if (quadrant == move_dir) @@ -9939,7 +10684,7 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) ImGuiWindow* window = g.CurrentWindow; result->Window = window; result->ID = g.LastItemData.ID; - result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; + result->FocusScopeId = g.CurrentFocusScopeId; result->InFlags = g.LastItemData.InFlags; result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); } @@ -9982,7 +10727,7 @@ static void ImGui::NavProcessItem() if (is_tab_stop || (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi)) NavProcessItemForTabbingRequest(id); } - else if ((g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav))) + else if ((g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_Disabled)) { ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; if (!is_tabbing) @@ -10006,7 +10751,7 @@ static void ImGui::NavProcessItem() if (g.NavWindow != window) SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window. g.NavLayer = window->DC.NavLayerCurrent; - g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; + g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; window->NavRectRel[window->DC.NavLayerCurrent] = WindowRectAbsToRel(window, nav_bb); // Store item bounding box (relative to window position) } @@ -10194,7 +10939,8 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) if (window->Flags & ImGuiWindowFlags_NoNavInputs) { - g.NavId = g.NavFocusScopeId = 0; + g.NavId = 0; + g.NavFocusScopeId = window->NavRootFocusScopeId; return; } @@ -10204,7 +10950,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer); if (init_for_nav) { - SetNavID(0, g.NavLayer, 0, ImRect()); + SetNavID(0, g.NavLayer, window->NavRootFocusScopeId, ImRect()); g.NavInitRequest = true; g.NavInitRequestFromMove = false; g.NavInitResultId = 0; @@ -10214,7 +10960,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) else { g.NavId = window->NavLastIds[0]; - g.NavFocusScopeId = 0; + g.NavFocusScopeId = window->NavRootFocusScopeId; } } @@ -10291,10 +11037,6 @@ static void ImGui::NavUpdate() for (ImGuiKey key : nav_keyboard_keys_to_change_source) if (IsKeyDown(key)) g.NavInputSource = ImGuiInputSource_Keyboard; - if (!nav_gamepad_active && g.NavInputSource == ImGuiInputSource_Gamepad) - g.NavInputSource = ImGuiInputSource_None; - if (!nav_keyboard_active && g.NavInputSource == ImGuiInputSource_Keyboard) - g.NavInputSource = ImGuiInputSource_None; // Process navigation init request (select first/default focus) if (g.NavInitResultId != 0) @@ -10401,7 +11143,7 @@ static void ImGui::NavUpdate() // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. if (nav_gamepad_active) { - const ImVec2 scroll_dir = GetKeyVector2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown); + const ImVec2 scroll_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown); const float tweak_factor = IsKeyDown(ImGuiKey_NavGamepadTweakSlow) ? 1.0f / 10.0f : IsKeyDown(ImGuiKey_NavGamepadTweakFast) ? 10.0f : 1.0f; if (scroll_dir.x != 0.0f && window->ScrollbarX) SetScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed * tweak_factor)); @@ -10478,11 +11220,11 @@ void ImGui::NavUpdateCreateMoveRequest() g.NavMoveScrollFlags = ImGuiScrollFlags_None; if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs)) { - const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateNavMove; - if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadLeft, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_LeftArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadRight, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_RightArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadUp, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_UpArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressedEx(ImGuiKey_GamepadDpadDown, repeat_mode)) || (nav_keyboard_active && IsKeyPressedEx(ImGuiKey_DownArrow, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; } + const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove; + if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; } } g.NavMoveClipDir = g.NavMoveDir; g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -10515,7 +11257,7 @@ void ImGui::NavUpdateCreateMoveRequest() if (g.NavMoveDir != ImGuiDir_None) NavMoveRequestSubmit(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); - // Moving with no reference triggers a init request (will be used as a fallback if the direction fails to find a match) + // Moving with no reference triggers an init request (will be used as a fallback if the direction fails to find a match) if (g.NavMoveSubmitted && g.NavId == 0) { IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "", g.NavLayer); @@ -10525,7 +11267,7 @@ void ImGui::NavUpdateCreateMoveRequest() } // When using gamepad, we project the reference nav bounding box into window visible area. - // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with gamepad every movements are relative + // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with gamepad all movements are relative // (can't focus a visible object like we can with the mouse). if (g.NavMoveSubmitted && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main && window != NULL)// && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded)) { @@ -10542,7 +11284,7 @@ void ImGui::NavUpdateCreateMoveRequest() inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX; inner_rect_rel.Max.y = clamp_y ? (inner_rect_rel.Max.y - pad_y) : +FLT_MAX; window->NavRectRel[g.NavLayer].ClipWithFull(inner_rect_rel); - g.NavId = g.NavFocusScopeId = 0; + g.NavId = 0; } } @@ -10555,7 +11297,7 @@ void ImGui::NavUpdateCreateMoveRequest() scoring_rect.TranslateY(scoring_rect_offset_y); scoring_rect.Min.x = ImMin(scoring_rect.Min.x + 1.0f, scoring_rect.Max.x); scoring_rect.Max.x = scoring_rect.Min.x; - IM_ASSERT(!scoring_rect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). + IM_ASSERT(!scoring_rect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allow us to remove extraneous ImFabs() calls in NavScoreItem(). //GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG] //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG] } @@ -10571,7 +11313,7 @@ void ImGui::NavUpdateCreateTabbingRequest() if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs)) return; - const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, true) && !IsActiveIdUsingKey(ImGuiKey_Tab) && !g.IO.KeyCtrl && !g.IO.KeyAlt; + const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat) && !g.IO.KeyCtrl && !g.IO.KeyAlt; if (!tab_pressed) return; @@ -10604,7 +11346,7 @@ void ImGui::NavMoveRequestApplyResult() if ((g.NavTabbingCounter == 1 || g.NavTabbingDir == 0) && g.NavTabbingResultFirst.ID) result = &g.NavTabbingResultFirst; - // In a situation when there is no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result) + // In a situation when there are no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result) if (result == NULL) { if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) @@ -10689,14 +11431,13 @@ static void ImGui::NavUpdateCancelRequest() ImGuiContext& g = *GImGui; const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, false)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, false))) + if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, ImGuiKeyOwner_None)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, ImGuiKeyOwner_None))) return; IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n"); if (g.ActiveId != 0) { - if (!IsActiveIdUsingKey(ImGuiKey_Escape) && !IsActiveIdUsingKey(ImGuiKey_NavGamepadCancel)) - ClearActiveID(); + ClearActiveID(); } else if (g.NavLayer != ImGuiNavLayer_Main) { @@ -10715,7 +11456,7 @@ static void ImGui::NavUpdateCancelRequest() SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); NavRestoreHighlightAfterMove(); } - else if (g.OpenPopupStack.Size > 0 && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) { // Close open popup/menu ClosePopupToLevel(g.OpenPopupStack.Size - 1, true); @@ -10725,7 +11466,7 @@ static void ImGui::NavUpdateCancelRequest() // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) g.NavWindow->NavLastIds[0] = 0; - g.NavId = g.NavFocusScopeId = 0; + g.NavId = 0; } } @@ -10740,10 +11481,10 @@ static float ImGui::NavUpdatePageUpPageDown() if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL) return 0.0f; - const bool page_up_held = IsKeyDown(ImGuiKey_PageUp) && !IsActiveIdUsingKey(ImGuiKey_PageUp); - const bool page_down_held = IsKeyDown(ImGuiKey_PageDown) && !IsActiveIdUsingKey(ImGuiKey_PageDown); - const bool home_pressed = IsKeyPressed(ImGuiKey_Home) && !IsActiveIdUsingKey(ImGuiKey_Home); - const bool end_pressed = IsKeyPressed(ImGuiKey_End) && !IsActiveIdUsingKey(ImGuiKey_End); + const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_None); + const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_None); + const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); + const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out return 0.0f; @@ -10753,9 +11494,9 @@ static float ImGui::NavUpdatePageUpPageDown() if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll) { // Fallback manual-scroll when window has no navigable item - if (IsKeyPressed(ImGuiKey_PageUp, true)) + if (IsKeyPressed(ImGuiKey_PageUp, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); - else if (IsKeyPressed(ImGuiKey_PageDown, true)) + else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); else if (home_pressed) SetScrollY(window, 0.0f); @@ -10943,8 +11684,10 @@ static void ImGui::NavUpdateWindowing() // Start CTRL+Tab or Square+L/R window selection const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, false); - const bool start_windowing_with_keyboard = allow_windowing && nav_keyboard_active && !g.NavWindowingTarget && io.KeyCtrl && IsKeyPressed(ImGuiKey_Tab, false); + const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); + const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); + const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, 0, ImGuiInputFlags_None); + const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! if (start_windowing_with_gamepad || start_windowing_with_keyboard) if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) { @@ -10986,17 +11729,19 @@ static void ImGui::NavUpdateWindowing() if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard) { // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_; + IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev actions would keep cycling between two windows. g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f - if (IsKeyPressed(ImGuiKey_Tab, true)) - NavUpdateWindowingHighlightWindow(io.KeyShift ? +1 : -1); - if (!io.KeyCtrl) + if (keyboard_next_window || keyboard_prev_window) + NavUpdateWindowingHighlightWindow(keyboard_next_window ? -1 : +1); + else if ((io.KeyMods & shared_mods) != shared_mods) apply_focus_window = g.NavWindowingTarget; } // Keyboard: Press and Release ALT to toggle menu layer // - Testing that only Alt is tested prevents Alt+Shift or AltGR from toggling menu layer. // - AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl). But even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway. - if (nav_keyboard_active && IsKeyPressed(ImGuiKey_ModAlt)) + if (nav_keyboard_active && IsKeyPressed(ImGuiMod_Alt, ImGuiKeyOwner_None)) { g.NavWindowingToggleLayer = true; g.NavInputSource = ImGuiInputSource_Keyboard; @@ -11005,16 +11750,17 @@ static void ImGui::NavUpdateWindowing() { // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370) // We cancel toggling nav layer when other modifiers are pressed. (See #4439) - if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper) + // We cancel toggling nav layer if an owner has claimed the key. + if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false) g.NavWindowingToggleLayer = false; // Apply layer toggle on release // Important: as before version <18314 we lacked an explicit IO event for focus gain/loss, we also compare mouse validity to detect old backends clearing mouse pos on focus loss. - if (IsKeyReleased(ImGuiKey_ModAlt) && g.NavWindowingToggleLayer) + if (IsKeyReleased(ImGuiMod_Alt) && g.NavWindowingToggleLayer) if (g.ActiveId == 0 || g.ActiveIdAllowOverlap) if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev)) apply_toggle_layer = true; - if (!IsKeyDown(ImGuiKey_ModAlt)) + if (!IsKeyDown(ImGuiMod_Alt)) g.NavWindowingToggleLayer = false; } @@ -11023,9 +11769,9 @@ static void ImGui::NavUpdateWindowing() { ImVec2 nav_move_dir; if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift) - nav_move_dir = GetKeyVector2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow); + nav_move_dir = GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow); if (g.NavInputSource == ImGuiInputSource_Gamepad) - nav_move_dir = GetKeyVector2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown); + nav_move_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown); if (nav_move_dir.x != 0.0f || nav_move_dir.y != 0.0f) { const float NAV_MOVE_SPEED = 800.0f; @@ -11102,10 +11848,10 @@ static void ImGui::NavUpdateWindowing() static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) { if (window->Flags & ImGuiWindowFlags_Popup) - return "(Popup)"; + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup); if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) - return "(Main menu bar)"; - return "(Untitled)"; + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar); + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled); } // Overlay displayed when using CTRL+TAB. Called by EndFrame(). @@ -11367,7 +12113,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) } // We don't use BeginDragDropTargetCustom() and duplicate its code because: -// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 1) we use LastItemRectHoveredRect which handles items that push a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. // Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) bool ImGui::BeginDragDropTarget() @@ -11421,41 +12167,51 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); ImRect r = g.DragDropTargetRect; float r_surface = r.GetWidth() * r.GetHeight(); - if (r_surface <= g.DragDropAcceptIdCurrRectSurface) - { - g.DragDropAcceptFlags = flags; - g.DragDropAcceptIdCurr = g.DragDropTargetId; - g.DragDropAcceptIdCurrRectSurface = r_surface; - } + if (r_surface > g.DragDropAcceptIdCurrRectSurface) + return NULL; + + g.DragDropAcceptFlags = flags; + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: accept\n", g.DragDropTargetId); // Render default drop visuals - // FIXME-DRAGDROP: Settle on a proper default visuals for drop target. payload.Preview = was_accepted_previously; - flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame) if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) window->DrawList->AddRect(r.Min - ImVec2(3.5f,3.5f), r.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); g.DragDropAcceptFrameCount = g.FrameCount; - payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) return NULL; + //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: return payload\n", g.DragDropTargetId); return &payload; } +// FIXME-DRAGDROP: Settle on a proper default visuals for drop target. +void ImGui::RenderDragDropTargetRect(const ImRect& bb) +{ + GetWindowDrawList()->AddRect(bb.Min - ImVec2(3.5f, 3.5f), bb.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); +} + const ImGuiPayload* ImGui::GetDragDropPayload() { ImGuiContext& g = *GImGui; - return g.DragDropActive ? &g.DragDropPayload : NULL; + return (g.DragDropActive && g.DragDropPayload.DataFrameCount != -1) ? &g.DragDropPayload : NULL; } -// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. void ImGui::EndDragDropTarget() { ImGuiContext& g = *GImGui; IM_ASSERT(g.DragDropActive); IM_ASSERT(g.DragDropWithinTarget); g.DragDropWithinTarget = false; + + // Clear drag and drop state payload right after delivery + if (g.DragDropPayload.Delivery) + ClearDragDrop(); } //----------------------------------------------------------------------------- @@ -11710,15 +12466,17 @@ void ImGui::LogButtons() //----------------------------------------------------------------------------- // - UpdateSettings() [Internal] // - MarkIniSettingsDirty() [Internal] -// - CreateNewWindowSettings() [Internal] -// - FindWindowSettings() [Internal] -// - FindOrCreateWindowSettings() [Internal] // - FindSettingsHandler() [Internal] // - ClearIniSettings() [Internal] // - LoadIniSettingsFromDisk() // - LoadIniSettingsFromMemory() // - SaveIniSettingsToDisk() // - SaveIniSettingsToMemory() +//----------------------------------------------------------------------------- +// - CreateNewWindowSettings() [Internal] +// - FindWindowSettingsByID() [Internal] +// - FindWindowSettingsByWindow() [Internal] +// - ClearWindowSettings() [Internal] // - WindowSettingsHandler_***() [Internal] //----------------------------------------------------------------------------- @@ -11765,44 +12523,6 @@ void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) g.SettingsDirtyTimer = g.IO.IniSavingRate; } -ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) -{ - ImGuiContext& g = *GImGui; - -#if !IMGUI_DEBUG_INI_SETTINGS - // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() - // Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to make .ini inspection easier. - if (const char* p = strstr(name, "###")) - name = p; -#endif - const size_t name_len = strlen(name); - - // Allocate chunk - const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1; - ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size); - IM_PLACEMENT_NEW(settings) ImGuiWindowSettings(); - settings->ID = ImHashStr(name, name_len); - memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator - - return settings; -} - -ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) - if (settings->ID == id) - return settings; - return NULL; -} - -ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name) -{ - if (ImGuiWindowSettings* settings = FindWindowSettings(ImHashStr(name))) - return settings; - return CreateNewWindowSettings(name); -} - void ImGui::AddSettingsHandler(const ImGuiSettingsHandler* handler) { ImGuiContext& g = *GImGui; @@ -11827,6 +12547,7 @@ ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) return NULL; } +// Clear all settings (windows, tables, docking etc.) void ImGui::ClearIniSettings() { ImGuiContext& g = *GImGui; @@ -11951,6 +12672,62 @@ const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) return g.SettingsIniData.c_str(); } +ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + +#if !IMGUI_DEBUG_INI_SETTINGS + // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + // Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to make .ini inspection easier. + if (const char* p = strstr(name, "###")) + name = p; +#endif + const size_t name_len = strlen(name); + + // Allocate chunk + const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1; + ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size); + IM_PLACEMENT_NEW(settings) ImGuiWindowSettings(); + settings->ID = ImHashStr(name, name_len); + memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator + + return settings; +} + +// We don't provide a FindWindowSettingsByName() because Docking system doesn't always hold on names. +// This is called once per window .ini entry + once per newly instantiated window. +ImGuiWindowSettings* ImGui::FindWindowSettingsByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->ID == id) + return settings; + return NULL; +} + +// This is faster if you are holding on a Window already as we don't need to perform a search. +ImGuiWindowSettings* ImGui::FindWindowSettingsByWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (window->SettingsOffset != -1) + return g.SettingsWindows.ptr_from_offset(window->SettingsOffset); + return FindWindowSettingsByID(window->ID); +} + +// This will revert window to its initial state, including enabling the ImGuiCond_FirstUseEver/ImGuiCond_Once conditions once more. +void ImGui::ClearWindowSettings(const char* name) +{ + //IMGUI_DEBUG_LOG("ClearWindowSettings('%s')\n", name); + ImGuiWindow* window = FindWindowByName(name); + if (window != NULL) + { + window->Flags |= ImGuiWindowFlags_NoSavedSettings; + InitOrLoadWindowSettings(window, NULL); + } + if (ImGuiWindowSettings* settings = window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name))) + settings->WantDelete = true; +} + static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { ImGuiContext& g = *ctx; @@ -11961,9 +12738,12 @@ static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandl static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { - ImGuiWindowSettings* settings = ImGui::FindOrCreateWindowSettings(name); - ImGuiID id = settings->ID; - *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry + ImGuiID id = ImHashStr(name); + ImGuiWindowSettings* settings = ImGui::FindWindowSettingsByID(id); + if (settings) + *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry + else + settings = ImGui::CreateNewWindowSettings(name); settings->ID = id; settings->WantApply = true; return (void*)settings; @@ -12003,7 +12783,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl if (window->Flags & ImGuiWindowFlags_NoSavedSettings) continue; - ImGuiWindowSettings* settings = (window->SettingsOffset != -1) ? g.SettingsWindows.ptr_from_offset(window->SettingsOffset) : ImGui::FindWindowSettings(window->ID); + ImGuiWindowSettings* settings = ImGui::FindWindowSettingsByWindow(window); if (!settings) { settings = ImGui::CreateNewWindowSettings(window->Name); @@ -12014,12 +12794,15 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl settings->Size = ImVec2ih(window->SizeFull); settings->Collapsed = window->Collapsed; + settings->WantDelete = false; } // Write to text buffer buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) { + if (settings->WantDelete) + continue; const char* settings_name = settings->GetName(); buf->appendf("[%s][%s]\n", handler->TypeName, settings_name); buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y); @@ -12030,6 +12813,18 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl } +//----------------------------------------------------------------------------- +// [SECTION] LOCALIZATION +//----------------------------------------------------------------------------- + +void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < count; n++) + g.LocalizationTable[entries[n].Key] = entries[n].Text; +} + + //----------------------------------------------------------------------------- // [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- @@ -12330,6 +13125,55 @@ static void RenderViewportsThumbnails() ImGui::Dummy(bb_full.GetSize() * SCALE); } +// Draw an arbitrary US keyboard layout to visualize translated keys +void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) +{ + const ImVec2 key_size = ImVec2(35.0f, 35.0f); + const float key_rounding = 3.0f; + const ImVec2 key_face_size = ImVec2(25.0f, 25.0f); + const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f); + const float key_face_rounding = 2.0f; + const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f); + const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f); + const float key_row_offset = 9.0f; + + ImVec2 board_min = GetCursorScreenPos(); + ImVec2 board_max = ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f); + ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y); + + struct KeyLayoutData { int Row, Col; const char* Label; ImGuiKey Key; }; + const KeyLayoutData keys_to_display[] = + { + { 0, 0, "", ImGuiKey_Tab }, { 0, 1, "Q", ImGuiKey_Q }, { 0, 2, "W", ImGuiKey_W }, { 0, 3, "E", ImGuiKey_E }, { 0, 4, "R", ImGuiKey_R }, + { 1, 0, "", ImGuiKey_CapsLock }, { 1, 1, "A", ImGuiKey_A }, { 1, 2, "S", ImGuiKey_S }, { 1, 3, "D", ImGuiKey_D }, { 1, 4, "F", ImGuiKey_F }, + { 2, 0, "", ImGuiKey_LeftShift },{ 2, 1, "Z", ImGuiKey_Z }, { 2, 2, "X", ImGuiKey_X }, { 2, 3, "C", ImGuiKey_C }, { 2, 4, "V", ImGuiKey_V } + }; + + // Elements rendered manually via ImDrawList API are not clipped automatically. + // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of view. + Dummy(board_max - board_min); + if (!IsItemVisible()) + return; + draw_list->PushClipRect(board_min, board_max, true); + for (int n = 0; n < IM_ARRAYSIZE(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); + ImVec2 key_max = key_min + key_size; + draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding); + 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->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); + if (ImGui::IsKeyDown(key_data->Key)) + draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding); + } + draw_list->PopClipRect(); +} + // Helper tool to diagnose between text encoding issues and font loading issues. Pass your UTF-8 string and verify that there are correct. void ImGui::DebugTextEncoding(const char* str) { @@ -12370,7 +13214,7 @@ void ImGui::DebugTextEncoding(const char* str) static void MetricsHelpMarker(const char* desc) { ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); @@ -12390,10 +13234,13 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) DebugNodeFont(font); PopID(); } - if (TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) { - ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); + ImGuiContext& g = *GImGui; + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + Checkbox("Tint with Text Color", &cfg->ShowAtlasTintedWithTextColor); // Using text color ensure visibility of core atlas data, but will alter custom colored icons + ImVec4 tint_col = cfg->ShowAtlasTintedWithTextColor ? GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 border_col = GetStyleColorVec4(ImGuiCol_Border); Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), tint_col, border_col); TreePop(); } @@ -12450,8 +13297,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } else if (rect_type == TRT_ColumnsContentHeadersUsed){ ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed, table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight); } // Note: y1/y2 not always accurate else if (rect_type == TRT_ColumnsContentHeadersIdeal){ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal, table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight); } - else if (rect_type == TRT_ColumnsContentFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight); } - else if (rect_type == TRT_ColumnsContentUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); } + else if (rect_type == TRT_ColumnsContentFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight); } + else if (rect_type == TRT_ColumnsContentUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); } IM_ASSERT(0); return ImRect(); } @@ -12625,8 +13472,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) { for (int i = 0; i < g.OpenPopupStack.Size; i++) { - ImGuiWindow* window = g.OpenPopupStack[i].Window; - BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + // As it's difficult to interact with tree nodes while popups are open, we display everything inline. + const ImGuiPopupData* popup_data = &g.OpenPopupStack[i]; + ImGuiWindow* window = popup_data->Window; + BulletText("PopupID: %08x, Window: '%s' (%s%s), BackupNavWindow '%s', ParentWindow '%s'", + popup_data->PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? "Child;" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? "Menu;" : "", + popup_data->BackupNavWindow ? popup_data->BackupNavWindow->Name : "NULL", window && window->ParentWindow ? window->ParentWindow->Name : "NULL"); } TreePop(); } @@ -12724,7 +13575,98 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } - // Misc Details + if (TreeNode("Inputs")) + { + Text("KEYBOARD/GAMEPAD/MOUSE KEYS"); + { + // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. + // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. + Indent(); +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } }; +#else + struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key < 512 && GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array + //Text("Legacy raw:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { SameLine(); Text("\"%s\" %d", GetKeyName(key), key); } } +#endif + Text("Keys down:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } + Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys released:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; SameLine(); Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. + DebugRenderKeyboardPreview(GetWindowDrawList()); + Unindent(); + } + + Text("MOUSE STATE"); + { + Indent(); + if (IsMousePosValid()) + Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + Text("Mouse pos: "); + Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); + int count = IM_ARRAYSIZE(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); } + Text("Mouse wheel: %.1f", io.MouseWheel); + Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused + Unindent(); + } + + Text("MOUSE WHEELING"); + { + Indent(); + Text("WheelingWindow: '%s'", g.WheelingWindow ? g.WheelingWindow->Name : "NULL"); + Text("WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer); + Text("WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y, (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? "X" : (g.WheelingAxisAvg.x < g.WheelingAxisAvg.y) ? "Y" : ""); + Unindent(); + } + + Text("KEY OWNERS"); + { + Indent(); + if (BeginListBox("##owners", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 6))) + { + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key); + if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + continue; + Text("%s: 0x%08X%s", GetKeyName(key), owner_data->OwnerCurr, + owner_data->LockUntilRelease ? " LockUntilRelease" : owner_data->LockThisFrame ? " LockThisFrame" : ""); + DebugLocateItemOnHover(owner_data->OwnerCurr); + } + EndListBox(); + } + Unindent(); + } + Text("SHORTCUT ROUTING"); + { + Indent(); + if (BeginListBox("##routes", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 6))) + { + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; + for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; ) + { + char key_chord_name[64]; + ImGuiKeyRoutingData* routing_data = &rt->Entries[idx]; + GetKeyChordName(key | routing_data->Mods, key_chord_name, IM_ARRAYSIZE(key_chord_name)); + Text("%s: 0x%08X", key_chord_name, routing_data->RoutingCurr); + DebugLocateItemOnHover(routing_data->RoutingCurr); + idx = routing_data->NextEntryIndex; + } + } + EndListBox(); + } + Text("(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask); + Unindent(); + } + TreePop(); + } + if (TreeNode("Internal state")) { Text("WINDOWING"); @@ -12738,20 +13680,20 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("ITEMS"); Indent(); Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, GetInputSourceName(g.ActiveIdSource)); + DebugLocateItemOnHover(g.ActiveId); Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); - - int active_id_using_key_input_count = 0; - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - active_id_using_key_input_count += g.ActiveIdUsingKeyInputMask[n] ? 1 : 0; - Text("ActiveIdUsing: NavDirMask: %X, KeyInputMask: %d key(s)", g.ActiveIdUsingNavDirMask, active_id_using_key_input_count); + Text("ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: %X", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask); Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame + Text("HoverDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.HoverDelayId, g.HoverDelayTimer, g.HoverDelayClearTimer); Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + DebugLocateItemOnHover(g.DragDropPayload.SourceId); Unindent(); Text("NAV,FOCUS"); Indent(); Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + DebugLocateItemOnHover(g.NavId); Text("NavInputSource: %s", GetInputSourceName(g.NavInputSource)); Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); Text("NavActivateId/DownId/PressedId/InputId: %08X/%08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId, g.NavActivateInputId); @@ -13090,7 +14032,7 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; p += ImFormatString(p, buf_end - p, "%s'%s'", - tab_n > 0 ? ", " : "", (tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "???"); + tab_n > 0 ? ", " : "", TabBarGetTabName(tab_bar, tab)); } p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } "); if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } @@ -13107,12 +14049,12 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) { for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { - const ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; PushID(tab); if (SmallButton("<")) { TabBarQueueReorder(tab_bar, tab, -1); } SameLine(0, 2); if (SmallButton(">")) { TabBarQueueReorder(tab_bar, tab, +1); } SameLine(); - Text("%02d%c Tab 0x%08X '%s' Offset: %.1f, Width: %.1f/%.1f", - tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, (tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "???", tab->Offset, tab->Width, tab->ContentWidth); + Text("%02d%c Tab 0x%08X '%s' Offset: %.2f, Width: %.2f/%.2f", + tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, TabBarGetTabName(tab_bar, tab), tab->Offset, tab->Width, tab->ContentWidth); PopID(); } TreePop(); @@ -13175,13 +14117,10 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) { ImRect r = window->NavRectRel[layer]; if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) - { BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); - continue; - } - BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); - if (IsItemHovered()) - GetForegroundDrawList(window)->AddRect(r.Min + window->Pos, r.Max + window->Pos, IM_COL32(255, 255, 0, 255)); + else + BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); + DebugLocateItemOnHover(window->NavLastIds[layer]); } BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } @@ -13199,8 +14138,12 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings) { + if (settings->WantDelete) + BeginDisabled(); Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed); + if (settings->WantDelete) + EndDisabled(); } void ImGui::DebugNodeWindowsList(ImVector* windows, const char* label) @@ -13235,7 +14178,7 @@ void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int wi } //----------------------------------------------------------------------------- -// [SECTION] DEBUG LOG +// [SECTION] DEBUG LOG WINDOW //----------------------------------------------------------------------------- void ImGui::DebugLog(const char* fmt, ...) @@ -13254,6 +14197,7 @@ void ImGui::DebugLogV(const char* fmt, va_list args) g.DebugLogBuf.appendfv(fmt, args); if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY) IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size); + g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size()); } void ImGui::ShowDebugLogWindow(bool* p_open) @@ -13274,15 +14218,42 @@ void ImGui::ShowDebugLogWindow(bool* p_open) SameLine(); CheckboxFlags("Focus", &g.DebugLogFlags, ImGuiDebugLogFlags_EventFocus); SameLine(); CheckboxFlags("Popup", &g.DebugLogFlags, ImGuiDebugLogFlags_EventPopup); SameLine(); CheckboxFlags("Nav", &g.DebugLogFlags, ImGuiDebugLogFlags_EventNav); + SameLine(); CheckboxFlags("Clipper", &g.DebugLogFlags, ImGuiDebugLogFlags_EventClipper); SameLine(); CheckboxFlags("IO", &g.DebugLogFlags, ImGuiDebugLogFlags_EventIO); if (SmallButton("Clear")) + { g.DebugLogBuf.clear(); + g.DebugLogIndex.clear(); + } SameLine(); if (SmallButton("Copy")) SetClipboardText(g.DebugLogBuf.c_str()); BeginChild("##log", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); - TextUnformatted(g.DebugLogBuf.begin(), g.DebugLogBuf.end()); // FIXME-OPT: Could use a line index, but TextUnformatted() has a semi-decent fast path for large text. + + ImGuiListClipper clipper; + clipper.Begin(g.DebugLogIndex.size()); + while (clipper.Step()) + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_begin = g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no); + const char* line_end = g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no); + TextUnformatted(line_begin, line_end); + ImRect text_rect = g.LastItemData.Rect; + if (IsItemHovered()) + for (const char* p = line_begin; p < line_end - 10; p++) + { + ImGuiID id = 0; + if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1) + continue; + ImVec2 p0 = CalcTextSize(line_begin, p); + ImVec2 p1 = CalcTextSize(p, p + 10); + g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y)); + if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true)) + DebugLocateItemOnHover(id); + p += 10; + } + } if (GetScrollY() >= GetScrollMaxY()) SetScrollHereY(1.0f); EndChild(); @@ -13294,6 +14265,38 @@ void ImGui::ShowDebugLogWindow(bool* p_open) // [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL) //----------------------------------------------------------------------------- +static const ImU32 DEBUG_LOCATE_ITEM_COLOR = IM_COL32(0, 255, 0, 255); // Green + +void ImGui::DebugLocateItem(ImGuiID target_id) +{ + ImGuiContext& g = *GImGui; + g.DebugLocateId = target_id; + g.DebugLocateFrames = 2; +} + +void ImGui::DebugLocateItemOnHover(ImGuiID target_id) +{ + if (target_id == 0 || !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return; + ImGuiContext& g = *GImGui; + DebugLocateItem(target_id); + GetForegroundDrawList(g.CurrentWindow)->AddRect(g.LastItemData.Rect.Min - ImVec2(3.0f, 3.0f), g.LastItemData.Rect.Max + ImVec2(3.0f, 3.0f), DEBUG_LOCATE_ITEM_COLOR); +} + +void ImGui::DebugLocateItemResolveWithLastItem() +{ + ImGuiContext& g = *GImGui; + ImGuiLastItemData item_data = g.LastItemData; + g.DebugLocateId = 0; + ImDrawList* draw_list = GetForegroundDrawList(g.CurrentWindow); + ImRect r = item_data.Rect; + r.Expand(3.0f); + ImVec2 p1 = g.IO.MousePos; + ImVec2 p2 = ImVec2((p1.x < r.Min.x) ? r.Min.x : (p1.x > r.Max.x) ? r.Max.x : p1.x, (p1.y < r.Min.y) ? r.Min.y : (p1.y > r.Max.y) ? r.Max.y : p1.y); + draw_list->AddRect(r.Min, r.Max, DEBUG_LOCATE_ITEM_COLOR); + draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR); +} + // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. void ImGui::UpdateDebugToolItemPicker() { @@ -13306,7 +14309,7 @@ void ImGui::UpdateDebugToolItemPicker() SetMouseCursor(ImGuiMouseCursor_Hand); if (IsKeyPressed(ImGuiKey_Escape)) g.DebugItemPickerActive = false; - const bool change_mapping = g.IO.KeyMods == (ImGuiModFlags_Ctrl | ImGuiModFlags_Shift); + const bool change_mapping = g.IO.KeyMods == (ImGuiMod_Ctrl | ImGuiMod_Shift); if (!change_mapping && IsMouseClicked(g.DebugItemPickerMouseButton) && hovered_id) { g.DebugItemPickerBreakId = hovered_id; @@ -13375,7 +14378,7 @@ void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* dat ImGuiStackTool* tool = &g.DebugStackTool; // Step 0: stack query - // This assume that the ID was computed with the current ID stack, which tends to be the case for our widget. + // This assumes that the ID was computed with the current ID stack, which tends to be the case for our widget. if (tool->StackLevel == -1) { tool->StackLevel++; @@ -13461,7 +14464,7 @@ void ImGui::ShowStackToolWindow(bool* p_open) Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC); SameLine(); TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*"); - if (tool->CopyToClipboardOnCtrlC && IsKeyDown(ImGuiKey_ModCtrl) && IsKeyPressed(ImGuiKey_C)) + if (tool->CopyToClipboardOnCtrlC && IsKeyDown(ImGuiMod_Ctrl) && IsKeyPressed(ImGuiKey_C)) { tool->CopyToClipboardLastTime = (float)g.Time; char* p = g.TempBuffer.Data; diff --git a/external/imgui/imgui/imgui.h b/external/imgui/imgui/imgui.h index d09f09fb..1106ef35 100644 --- a/external/imgui/imgui/imgui.h +++ b/external/imgui/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (headers) // Help: @@ -11,7 +11,7 @@ // - FAQ http://dearimgui.org/faq // - Homepage & latest https://github.com/ocornut/imgui // - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/5243 (please post your screenshots/video there!) +// - Gallery https://github.com/ocornut/imgui/issues/5886 (please post your screenshots/video there!) // - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Glossary https://github.com/ocornut/imgui/wiki/Glossary // - Issues & support https://github.com/ocornut/imgui/issues @@ -20,6 +20,12 @@ // - For first-time users having issues compiling/linking/running or issues loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. +// Library Version +// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') +#define IMGUI_VERSION "1.89.4 WIP" +#define IMGUI_VERSION_NUM 18933 +#define IMGUI_HAS_TABLE + /* Index of this file: @@ -31,7 +37,7 @@ Index of this file: // [SECTION] ImGuiStyle // [SECTION] ImGuiIO // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) -// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, ImColor) +// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) @@ -42,13 +48,12 @@ Index of this file: #pragma once -// Configuration file with compile-time options (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system') +// Configuration file with compile-time options +// (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system') #ifdef IMGUI_USER_CONFIG #include IMGUI_USER_CONFIG #endif -#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) #include "imconfig.h" -#endif #ifndef IMGUI_DISABLE @@ -62,13 +67,6 @@ Index of this file: #include // ptrdiff_t, NULL #include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp -// Version -// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) -#define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18808 -#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) -#define IMGUI_HAS_TABLE - // Define attributes of all API symbols declarations (e.g. for DLL under Windows) // IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default backends files (imgui_impl_xxx.h) // Using dear imgui via a shared library is not recommended, because we don't guarantee backward nor forward ABI compatibility (also function call overhead, as dear imgui is a call-heavy API) @@ -87,6 +85,7 @@ Index of this file: #define IM_ARRAYSIZE(_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_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in C++11 +#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. #if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) && !defined(__clang__) @@ -162,20 +161,26 @@ struct ImGuiTextBuffer; // Helper to hold and append into a text buf struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") struct ImGuiViewport; // A Platform Window (always only one in 'master' branch), in the future may represent Platform Monitor -// Enums/Flags (declared as int for compatibility with old C++, to allow using as flags without overhead, and to not pollute the top of this file) +// Enumerations +// - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! // In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. // With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +enum ImGuiKey : int; // -> enum ImGuiKey // Enum: A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value) typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A condition for many Set*() functions typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction -typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A key identifier typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) -typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier +typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor shape typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() + +// Flags (declared as int for compatibility with old C++, to allow using as flags without overhead, and to not pollute the top of this file) +// - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! +// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build @@ -188,7 +193,7 @@ typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: f typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline() -typedef int ImGuiModFlags; // -> enum ImGuiModFlags_ // Flags: for io.KeyMods (Ctrl/Shift/Alt/Super) +typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for storage only for now: an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values. typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. @@ -250,8 +255,8 @@ struct ImVec2 float x, y; constexpr ImVec2() : x(0.0f), y(0.0f) { } constexpr ImVec2(float _x, float _y) : x(_x), y(_y) { } - float operator[] (size_t idx) const { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. - float& operator[] (size_t idx) { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. + float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. + float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. #ifdef IM_VEC2_CLASS_EXTRA IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. #endif @@ -302,7 +307,7 @@ namespace ImGui IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) IMGUI_API bool ShowStyleSelector(const char* label); // add style selector block (not a window), essentially a combo listing the default styles. IMGUI_API void ShowFontSelector(const char* label); // add font selector block (not a window), essentially a combo listing the loaded fonts. - IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as an end-user (mouse/keyboard controls). IMGUI_API const char* GetVersion(); // get the compiled version string e.g. "1.80 WIP" (essentially the value for IMGUI_VERSION from the compiled version of imgui.cpp) // Styles @@ -357,6 +362,7 @@ namespace ImGui IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ scrollable client area, which enforce the range of scrollbars). Not including window decorations (title bar, menu bar, etc.) nor WindowPadding. set an axis to 0.0f to leave it automatic. call before Begin() IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() IMGUI_API void SetNextWindowFocus(); // set next window to be focused / top-most. call before Begin() + IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // set next window scrolling value (use < 0.0f to not affect a given axis). IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily override the Alpha component of ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground. IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. @@ -374,9 +380,11 @@ namespace ImGui IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min for the full window (roughly (0,0)-Scroll), in window coordinates - IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max for the full window (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max for the full window (roughly (0,0)+Size-Scroll) where Size can be overridden with SetNextWindowContentSize(), in window coordinates // Windows Scrolling + // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). + // - You may instead use SetNextWindowScroll() prior to calling Begin() to avoid this delay, as an alternative to using SetScrollX()/SetScrollY(). IMGUI_API float GetScrollX(); // get scrolling amount [0 .. GetScrollMaxX()] IMGUI_API float GetScrollY(); // get scrolling amount [0 .. GetScrollMaxY()] IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0 .. GetScrollMaxX()] @@ -411,7 +419,7 @@ namespace ImGui IMGUI_API void PopTextWrapPos(); // Style read access - // - Use the style editor (ShowStyleEditor() function) to interactively see what the colors are) + // - Use the ShowStyleEditor() function to interactively see/edit the colors. IMGUI_API ImFont* GetFont(); // get current font IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API @@ -429,7 +437,7 @@ namespace ImGui // Absolute coordinate: GetCursorScreenPos(), SetCursorScreenPos(), all ImDrawList:: functions. IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. IMGUI_API void SameLine(float offset_from_start_x=0.0f, float spacing=-1.0f); // call between widgets or groups to layout them horizontally. X position given in window coordinates. - IMGUI_API void NewLine(); // undo a SameLine() or force a new line when in an horizontal-layout context. + IMGUI_API void NewLine(); // undo a SameLine() or force a new line when in a horizontal-layout context. IMGUI_API void Spacing(); // add vertical spacing. IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size. unlike InvisibleButton(), Dummy() won't take the mouse click or be navigable into. IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by indent_w, or style.IndentSpacing if indent_w <= 0 @@ -485,6 +493,7 @@ namespace ImGui IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void SeparatorText(const char* label); // currently: formatted text with an horizontal line // Widgets: Main // - Most widgets return true when the value has been changed or when pressed/selected @@ -506,7 +515,7 @@ namespace ImGui IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)); IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); - // Widgets: Combo Box + // Widgets: Combo Box (Dropdown) // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. // - The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. This is analogous to how ListBox are created. IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); @@ -517,7 +526,7 @@ namespace ImGui // Widgets: Drag Sliders // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. - // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', + // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every function, note that a 'float v[X]' function argument is the same as 'float* v', // the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Format string may also be set to NULL or use the default format ("%f" or "%d"). @@ -525,7 +534,7 @@ namespace ImGui // - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits if ImGuiSliderFlags_AlwaysClamp is not used. // - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum. // - We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. - // - Legacy: Pre-1.78 there are DragXXX() function signatures that takes a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. + // - Legacy: Pre-1.78 there are DragXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. // If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0); // If v_min >= v_max we have no bound IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0); @@ -544,7 +553,7 @@ namespace ImGui // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Format string may also be set to NULL or use the default format ("%f" or "%d"). - // - Legacy: Pre-1.78 there are SliderXXX() function signatures that takes a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. + // - Legacy: Pre-1.78 there are SliderXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. // If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); @@ -602,7 +611,7 @@ namespace ImGui IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired. - IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePush(const void* ptr_id); // " IMGUI_API void TreePop(); // ~ Unindent()+PopId() IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). @@ -672,7 +681,7 @@ namespace ImGui // Popups: begin/end functions // - BeginPopup(): query popup state, if open start appending into the window. Call EndPopup() afterwards. ImGuiWindowFlags are forwarded to the window. - // - BeginPopupModal(): block every interactions behind the window, cannot be closed by user, add a dimming background, has a title bar. + // - BeginPopupModal(): block every interaction behind the window, cannot be closed by user, add a dimming background, has a title bar. IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // return true if the modal is open, and you can start outputting to it. IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! @@ -716,7 +725,7 @@ namespace ImGui // - 4. Optionally call TableHeadersRow() to submit a header row. Names are pulled from TableSetupColumn() data. // - 5. Populate contents: // - In most situations you can use TableNextRow() + TableSetColumnIndex(N) to start appending into a column. - // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, + // - If you are using tables as a sort of grid, where every column is holding the same type of contents, // you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex(). // TableNextColumn() will automatically wrap-around into the next row if needed. // - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column! @@ -774,6 +783,7 @@ namespace ImGui IMGUI_API int GetColumnsCount(); // Tab Bars, Tabs + // - Note: Tabs are automatically created by the docking system (when in 'docking' branch). Use this to create tab bars/tabs yourself. IMGUI_API bool BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0); // create and append into a TabBar IMGUI_API void EndTabBar(); // only call EndTabBar() if BeginTabBar() returns true! IMGUI_API bool BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0); // create a Tab. Returns true if the Tab is selected. @@ -827,16 +837,17 @@ namespace ImGui IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false) IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? - IMGUI_API bool IsItemClicked(ImGuiMouseButton mouse_button = 0); // is the last item hovered and mouse clicked on? (**) == IsMouseClicked(mouse_button) && IsItemHovered()Important. (**) this it NOT equivalent to the behavior of e.g. Button(). Read comments in function definition. + IMGUI_API bool IsItemClicked(ImGuiMouseButton mouse_button = 0); // is the last item hovered and mouse clicked on? (**) == IsMouseClicked(mouse_button) && IsItemHovered()Important. (**) this is NOT equivalent to the behavior of e.g. Button(). Read comments in function definition. IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) IMGUI_API bool IsItemEdited(); // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return value of many widgets. IMGUI_API bool IsItemActivated(); // was the last item just made active (item was previously inactive). - IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that requires continuous editing. - IMGUI_API bool IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that requires continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). + IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that require continuous editing. + IMGUI_API bool IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that require continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). IMGUI_API bool IsItemToggledOpen(); // was the last item open state toggled? set by TreeNode(). IMGUI_API bool IsAnyItemHovered(); // is any item hovered? IMGUI_API bool IsAnyItemActive(); // is any item active? IMGUI_API bool IsAnyItemFocused(); // is any item focused? + IMGUI_API ImGuiID GetItemID(); // get ID of last item (~~ often same ImGui::GetID(label) beforehand) 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 @@ -873,12 +884,11 @@ 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 - // Without IMGUI_DISABLE_OBSOLETE_KEYIO: (legacy support) - // - For 'ImGuiKey key' you can still use your legacy native/user indices according to how your backend/engine stored them in io.KeysDown[]. - // With IMGUI_DISABLE_OBSOLETE_KEYIO: (this is the way forward) - // - Any use of 'ImGuiKey' will assert when key < 512 will be passed, previously reserved as native/user keys indices - // - GetKeyIndex() is pass-through and therefore deprecated (gone if IMGUI_DISABLE_OBSOLETE_KEYIO is defined) + // Inputs Utilities: Keyboard/Mouse/Gamepad + // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...). + // - before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. About use of those legacy ImGuiKey values: + // - without IMGUI_DISABLE_OBSOLETE_KEYIO (legacy support): you can still use your legacy native/user indices (< 512) according to how your backend/engine stored them in io.KeysDown[], but need to cast them to ImGuiKey. + // - with IMGUI_DISABLE_OBSOLETE_KEYIO (this is the way forward): any use of ImGuiKey will assert with key < 512. GetKeyIndex() is pass-through and therefore deprecated (gone if IMGUI_DISABLE_OBSOLETE_KEYIO is defined). 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 IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)? @@ -886,7 +896,7 @@ namespace ImGui IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names a provided for debugging purpose and are not meant to be saved persistently not 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: Mouse + // Inputs Utilities: Mouse specific // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle. // - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and 'io.MouseDraggingThreshold') @@ -903,8 +913,8 @@ namespace ImGui IMGUI_API bool IsMouseDragging(ImGuiMouseButton button, float lock_threshold = -1.0f); // is mouse dragging? (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) IMGUI_API ImVec2 GetMouseDragDelta(ImGuiMouseButton button = 0, float lock_threshold = -1.0f); // return the delta from the initial clicking position while the mouse button is pressed or was just released. This is locked and return 0.0f until the mouse moves past a distance threshold at least once (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) IMGUI_API void ResetMouseDragDelta(ImGuiMouseButton button = 0); // - IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you - IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired cursor type + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired mouse cursor shape. Important: reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired mouse cursor shape IMGUI_API void SetNextFrameWantCaptureMouse(bool want_capture_mouse); // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instucts your app to ignore inputs). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse;" after the next NewFrame() call. // Clipboard Utilities @@ -941,6 +951,7 @@ namespace ImGui //----------------------------------------------------------------------------- // Flags for ImGui::Begin() +// (Those are per-window flags. There are shared flags in ImGuiIO: io.ConfigWindowsResizeFromEdges and io.ConfigWindowsMoveFromTitleBarOnly) enum ImGuiWindowFlags_ { ImGuiWindowFlags_None = 0, @@ -978,6 +989,7 @@ enum ImGuiWindowFlags_ }; // Flags for ImGui::InputText() +// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive) enum ImGuiInputTextFlags_ { ImGuiInputTextFlags_None = 0, @@ -1001,6 +1013,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) ImGuiInputTextFlags_CallbackResize = 1 << 18, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) ImGuiInputTextFlags_CallbackEdit = 1 << 19, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + ImGuiInputTextFlags_EscapeClearsAll = 1 << 20, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) // Obsolete names (will be removed soon) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1036,7 +1049,7 @@ enum ImGuiTreeNodeFlags_ // 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 another another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag. +// and want to use another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag explicitly. // - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later). enum ImGuiPopupFlags_ { @@ -1057,7 +1070,7 @@ enum ImGuiPopupFlags_ enum ImGuiSelectableFlags_ { ImGuiSelectableFlags_None = 0, - ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this doesn't close parent popup window ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (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 @@ -1125,7 +1138,7 @@ enum ImGuiTabItemFlags_ // - When ScrollX is on: // - Table defaults to ImGuiTableFlags_SizingFixedFit -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed // - Columns sizing policy allowed: Fixed/Auto mostly. -// - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. +// - Fixed Columns can be enlarged as needed. Table will show a horizontal scrollbar if needed. // - When using auto-resizing (non-resizable) fixed columns, querying the content width to use item right-alignment e.g. SetNextItemWidth(-FLT_MIN) doesn't make sense, would create a feedback loop. // - Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable(). // If you specify a value for 'inner_width' then effectively the scrolling space is known and Stretch or mixed Fixed/Stretch columns become meaningful again. @@ -1151,8 +1164,8 @@ enum ImGuiTableFlags_ ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. - ImGuiTableFlags_NoBordersInBody = 1 << 11, // [ALPHA] Disable vertical borders in columns Body (borders will always appears in Headers). -> May move to style - ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 12, // [ALPHA] Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers). -> May move to style + ImGuiTableFlags_NoBordersInBody = 1 << 11, // [ALPHA] Disable vertical borders in columns Body (borders will always appear in Headers). -> May move to style + ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 12, // [ALPHA] Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers). -> May move to style // Sizing Policy (read above for defaults) ImGuiTableFlags_SizingFixedFit = 1 << 13, // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching contents width. ImGuiTableFlags_SizingFixedSame = 2 << 13, // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching the maximum contents width of all columns. Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible. @@ -1166,11 +1179,11 @@ enum ImGuiTableFlags_ // Clipping ImGuiTableFlags_NoClip = 1 << 20, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze(). // Padding - ImGuiTableFlags_PadOuterX = 1 << 21, // Default if BordersOuterV is on. Enable outer-most padding. Generally desirable if you have headers. - ImGuiTableFlags_NoPadOuterX = 1 << 22, // Default if BordersOuterV is off. Disable outer-most padding. + ImGuiTableFlags_PadOuterX = 1 << 21, // Default if BordersOuterV is on. Enable outermost padding. Generally desirable if you have headers. + ImGuiTableFlags_NoPadOuterX = 1 << 22, // Default if BordersOuterV is off. Disable outermost padding. ImGuiTableFlags_NoPadInnerX = 1 << 23, // Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off). // Scrolling - ImGuiTableFlags_ScrollX = 1 << 24, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollX = 1 << 24, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this creates a child window, ScrollY is currently generally recommended when using ScrollX. ImGuiTableFlags_ScrollY = 1 << 25, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. // Sorting ImGuiTableFlags_SortMulti = 1 << 26, // Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1). @@ -1178,12 +1191,6 @@ enum ImGuiTableFlags_ // [Internal] Combinations and masks ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame, - - // Obsolete names (will be removed soon) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - //, ImGuiTableFlags_ColumnsWidthFixed = ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_ColumnsWidthStretch = ImGuiTableFlags_SizingStretchSame // WIP Tables 2020/12 - //, ImGuiTableFlags_SizingPolicyFixed = ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_SizingPolicyStretch = ImGuiTableFlags_SizingStretchSame // WIP Tables 2021/01 -#endif }; // Flags for ImGui::TableSetupColumn() @@ -1221,11 +1228,6 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_IndentMask_ = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable, ImGuiTableColumnFlags_StatusMask_ = ImGuiTableColumnFlags_IsEnabled | ImGuiTableColumnFlags_IsVisible | ImGuiTableColumnFlags_IsSorted | ImGuiTableColumnFlags_IsHovered, ImGuiTableColumnFlags_NoDirectResize_ = 1 << 30, // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) - - // Obsolete names (will be removed soon) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - //ImGuiTableColumnFlags_WidthAuto = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, // Column will not stretch and keep resizing based on submitted contents. -#endif }; // Flags for ImGui::TableNextRow() @@ -1240,7 +1242,7 @@ enum ImGuiTableRowFlags_ // - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. // - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. // - Layer 2: draw with CellBg color if set. -// The purpose of the two row/columns layers is to let you decide if a background color changes should override or blend with the existing color. +// The purpose of the two row/columns layers is to let you decide if a background color change should override or blend with the existing color. // When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows. // If you set the color of RowBg0 target, your color will override the existing RowBg0 color. // If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. @@ -1283,6 +1285,11 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_NoNavOverride = 1 << 10, // Disable using gamepad/keyboard navigation state when active, always query mouse. ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows, + + // Hovering delays (for tooltips) + ImGuiHoveredFlags_DelayNormal = 1 << 11, // Return true after io.HoverDelayNormal elapsed (~0.30 sec) + ImGuiHoveredFlags_DelayShort = 1 << 12, // Return true after io.HoverDelayShort elapsed (~0.10 sec) + ImGuiHoveredFlags_NoSharedDelay = 1 << 13, // Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) }; // Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() @@ -1290,8 +1297,8 @@ enum ImGuiDragDropFlags_ { ImGuiDragDropFlags_None = 0, // BeginDragDropSource() flags - ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. - ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // Disable preview tooltip. By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disables this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This flag disables this behavior so you can still call IsItemHovered() on the source item. ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of dear imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. @@ -1342,9 +1349,12 @@ enum ImGuiSortDirection_ ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. }; -// Keys value 0 to 511 are left unused as legacy native/opaque key values (< 1.87) -// Keys value >= 512 are named keys (>= 1.87) -enum ImGuiKey_ +// A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value): can represent Keyboard, Mouse and Gamepad values. +// All our named keys are >= 512. Keys value 0 to 511 are left unused as legacy native/opaque key values (< 1.87). +// Since >= 1.89 we increased typing (went from int to enum), some legacy code may need a cast to ImGuiKey. +// Read details about the 1.87 and 1.89 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(). +enum ImGuiKey : int { // Keyboard ImGuiKey_None = 0, @@ -1398,7 +1408,7 @@ enum ImGuiKey_ ImGuiKey_KeypadEnter, ImGuiKey_KeypadEqual, - // Gamepad (some of those are analog values, 0.0f to 1.0f) // GAME NAVIGATION ACTION + // Gamepad (some of those are analog values, 0.0f to 1.0f) // NAVIGATION ACTION // (download controller mapping PNG/PSD at http://dearimgui.org/controls_sheets) ImGuiKey_GamepadStart, // Menu (Xbox) + (Switch) Start/Options (PS) ImGuiKey_GamepadBack, // View (Xbox) - (Switch) Share (PS) @@ -1425,52 +1435,50 @@ enum ImGuiKey_ ImGuiKey_GamepadRStickUp, // [Analog] ImGuiKey_GamepadRStickDown, // [Analog] - // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) - // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing - // them to be accessed via standard key API, allowing calls such as IsKeyPressed(), IsKeyReleased(), querying duration etc. - // - Code polling every keys (e.g. an interface to detect a key press for input mapping) might want to ignore those - // and prefer using the real keys (e.g. ImGuiKey_LeftCtrl, ImGuiKey_RightCtrl instead of ImGuiKey_ModCtrl). - // - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys. - // In practice: it's complicated; mods are often provided from different sources. Keyboard layout, IME, sticky keys and - // backends tend to interfere and break that equivalence. The safer decision is to relay that ambiguity down to the end-user... - ImGuiKey_ModCtrl, ImGuiKey_ModShift, ImGuiKey_ModAlt, ImGuiKey_ModSuper, - - // Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls) + // Aliases: Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls) // - This is mirroring the data also written to io.MouseDown[], io.MouseWheel, in a format allowing them to be accessed via standard key API. ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle, ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, ImGuiKey_MouseWheelY, - // End of list - ImGuiKey_COUNT, // No valid ImGuiKey is ever greater than this value + // [Internal] Reserved for mod storage + ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper, + ImGuiKey_COUNT, - // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + a io.KeyMap[] array. + // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) + // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing + // them to be accessed via standard key API, allowing calls such as IsKeyPressed(), IsKeyReleased(), querying duration etc. + // - Code polling every key (e.g. an interface to detect a key press for input mapping) might want to ignore those + // and prefer using the real keys (e.g. ImGuiKey_LeftCtrl, ImGuiKey_RightCtrl instead of ImGuiMod_Ctrl). + // - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys. + // In practice: it's complicated; mods are often provided from different sources. Keyboard layout, IME, sticky keys and + // backends tend to interfere and break that equivalence. The safer decision is to relay that ambiguity down to the end-user... + ImGuiMod_None = 0, + ImGuiMod_Ctrl = 1 << 12, // Ctrl + ImGuiMod_Shift = 1 << 13, // Shift + ImGuiMod_Alt = 1 << 14, // Option/Menu + ImGuiMod_Super = 1 << 15, // Cmd/Super/Windows + ImGuiMod_Shortcut = 1 << 11, // Alias for Ctrl (non-macOS) _or_ Super (macOS). + ImGuiMod_Mask_ = 0xF800, // 5-bits + + // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array. // We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE) + // If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. ImGuiKey_NamedKey_BEGIN = 512, ImGuiKey_NamedKey_END = ImGuiKey_COUNT, ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, #ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // First key stored in io.KeysData[0]. Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET). + ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys + ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. #else - ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys - ImGuiKey_KeysData_OFFSET = 0, // First key stored in io.KeysData[0]. Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET). + ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys + ImGuiKey_KeysData_OFFSET = 0, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. #endif #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 #endif }; -// Helper "flags" version of key-mods to store and compare multiple key-mods easily. Sometimes used for storage (e.g. io.KeyMods) but otherwise not much used in public API. -enum ImGuiModFlags_ -{ - ImGuiModFlags_None = 0, - ImGuiModFlags_Ctrl = 1 << 0, - ImGuiModFlags_Shift = 1 << 1, - ImGuiModFlags_Alt = 1 << 2, // Option/Menu key - ImGuiModFlags_Super = 1 << 3, // Cmd/Super/Windows key - ImGuiModFlags_All = 0x0F -}; - #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO // OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[]. // Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set. @@ -1603,6 +1611,9 @@ enum ImGuiStyleVar_ ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_SeparatorTextBorderSize,// float SeparatorTextBorderSize + ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign + ImGuiStyleVar_SeparatorTextPadding,// ImVec2 SeparatorTextPadding ImGuiStyleVar_COUNT }; @@ -1665,6 +1676,7 @@ enum ImGuiColorEditFlags_ // Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. // We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. +// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigDragClickToInputText) enum ImGuiSliderFlags_ { ImGuiSliderFlags_None = 0, @@ -1698,7 +1710,7 @@ enum ImGuiMouseCursor_ ImGuiMouseCursor_Arrow = 0, ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. ImGuiMouseCursor_ResizeAll, // (Unused by Dear ImGui functions) - ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeNS, // When hovering over a horizontal border ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window @@ -1713,7 +1725,7 @@ enum ImGuiMouseCursor_ enum ImGuiCond_ { ImGuiCond_None = 0, // No condition (always set the variable), same as _Always - ImGuiCond_Always = 1 << 0, // No condition (always set the variable) + ImGuiCond_Always = 1 << 0, // No condition (always set the variable), same as _None ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call will succeed) ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the object/window has no persistently saved data (no entry in .ini file) ImGuiCond_Appearing = 1 << 3, // Set the variable if the object/window is appearing after being hidden/inactive (or the first time) @@ -1765,7 +1777,7 @@ struct ImVector // Constructors, destructor inline ImVector() { Size = Capacity = 0; Data = NULL; } inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } - inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } + inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); if (src.Data) memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } inline ~ImVector() { if (Data) IM_FREE(Data); } // Important: does not destruct anything inline void clear() { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } } // Important: does not destruct anything @@ -1829,7 +1841,7 @@ struct ImGuiStyle ImVec2 WindowPadding; // Padding within a window. float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). - ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constrain individual windows, use SetNextWindowSizeConstraints(). ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. ImGuiDir WindowMenuButtonPosition; // Side of the collapsing/docking button in the title bar (None/Left/Right). Defaults to ImGuiDir_Left. float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. @@ -1852,10 +1864,13 @@ struct ImGuiStyle float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. 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 TabMinWidthForCloseButton; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + float TabMinWidthForCloseButton; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. 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 SeparatorTextBorderSize; // Thickkness 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. ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. @@ -1878,7 +1893,7 @@ struct ImGuiStyle //----------------------------------------------------------------------------- // [Internal] Storage used by IsKeyDown(), IsKeyPressed() etc functions. -// If prior to 1.87 you used io.KeysDownDuration[] (which was marked as internal), you should use GetKeyData(key)->DownDuration and not io.KeysData[key]->DownDuration. +// If prior to 1.87 you used io.KeysDownDuration[] (which was marked as internal), you should use GetKeyData(key)->DownDuration and *NOT* io.KeysData[key]->DownDuration. struct ImGuiKeyData { bool Down; // True for if key is down @@ -1905,7 +1920,9 @@ struct ImGuiIO float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. float KeyRepeatDelay; // = 0.275f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. - void* UserData; // = NULL // Store your own data for retrieval by callbacks. + float HoverDelayNormal; // = 0.30 sec // Delay on hovering before IsItemHovered(ImGuiHoveredFlags_DelayNormal) returns true. + float HoverDelayShort; // = 0.10 sec // Delay on hovering before IsItemHovered(ImGuiHoveredFlags_DelayShort) returns true. + void* UserData; // = NULL // Store your own data. ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. float FontGlobalScale; // = 1.0f // Global scale all fonts @@ -1960,11 +1977,11 @@ struct ImGuiIO IMGUI_API void AddKeyAnalogEvent(ImGuiKey key, bool down, float v); // Queue a new key down/up event for analog values (e.g. ImGuiKey_Gamepad_ values). Dead-zones should be handled by the backend. IMGUI_API void AddMousePosEvent(float x, float y); // Queue a mouse position update. Use -FLT_MAX,-FLT_MAX to signify no mouse (e.g. app not focused and not hovered) IMGUI_API void AddMouseButtonEvent(int button, bool down); // Queue a mouse button change - IMGUI_API void AddMouseWheelEvent(float wh_x, float wh_y); // Queue a mouse wheel update + IMGUI_API void AddMouseWheelEvent(float wheel_x, float wheel_y); // Queue a mouse wheel update. wheel_y<0: scroll down, wheel_y>0: scroll up, wheel_x<0: scroll right, wheel_x>0: scroll left. IMGUI_API void AddFocusEvent(bool focused); // Queue a gain/loss of focus for the application (generally based on OS/platform focus of your window) IMGUI_API void AddInputCharacter(unsigned int c); // Queue a new character input - IMGUI_API void AddInputCharacterUTF16(ImWchar16 c); // Queue a new character input from an UTF-16 character, it can be a surrogate - IMGUI_API void AddInputCharactersUTF8(const char* str); // Queue a new characters input from an UTF-8 string + IMGUI_API void AddInputCharacterUTF16(ImWchar16 c); // Queue a new character input from a UTF-16 character, it can be a surrogate + IMGUI_API void AddInputCharactersUTF8(const char* str); // Queue a new characters input from a UTF-8 string IMGUI_API void SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode. IMGUI_API void SetAppAcceptingEvents(bool accepting_events); // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. @@ -1994,6 +2011,7 @@ struct ImGuiIO // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). + // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. @@ -2008,17 +2026,17 @@ struct ImGuiIO // (this block used to be written by backend, since 1.87 it is best to NOT write to those directly, call the AddXXX functions above instead) // (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere) ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX, -FLT_MAX) if mouse is unavailable (on another screen, etc.) - bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton_COUNT == 5). Dear ImGui mostly uses left and right buttons. Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. - float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. - float MouseWheelH; // Mouse wheel Horizontal. Most users don't have a mouse with an horizontal wheel, may not be filled by all backends. + bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton_COUNT == 5). Dear ImGui mostly uses left and right buttons. Other buttons allow us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into horizontal scroll. + float MouseWheelH; // Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. bool KeyCtrl; // Keyboard modifier down: Control bool KeyShift; // Keyboard modifier down: Shift bool KeyAlt; // Keyboard modifier down: Alt bool KeySuper; // Keyboard modifier down: Cmd/Super/Windows // Other state maintained from data above + IO function calls - ImGuiModFlags KeyMods; // Key mods flags (same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags), updated by NewFrame() - ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE]; // Key state for all known keys. Use IsKeyXXX() functions to access this. + ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. DOES NOT CONTAINS ImGuiMod_Shortcut which is pretranslated). Read-only, updated by NewFrame() + ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE]; // Key state for all known keys. Use IsKeyXXX() functions to access this. bool WantCaptureMouseUnlessPopupClose; // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup. ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) ImVec2 MouseClickedPos[5]; // Position at time of clicking @@ -2090,7 +2108,7 @@ struct ImGuiInputTextCallbackData // NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. struct ImGuiSizeCallbackData { - void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints(). Generally store an integer or float in here (need reinterpret_cast<>). ImVec2 Pos; // Read-only. Window position, for reference. ImVec2 CurrentSize; // Read-only. Current window size. ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. @@ -2143,7 +2161,7 @@ struct ImGuiTableSortSpecs }; //----------------------------------------------------------------------------- -// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, ImColor) +// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) //----------------------------------------------------------------------------- // Helper: Unicode defines @@ -2154,7 +2172,7 @@ struct ImGuiTableSortSpecs #define IM_UNICODE_CODEPOINT_MAX 0xFFFF // Maximum Unicode code point supported by this build. #endif -// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create a UI within deep-nested code that runs multiple times every frame. // Usage: static ImGuiOnceUponAFrame oaf; if (oaf) ImGui::Text("This will be called only once per frame"); struct ImGuiOnceUponAFrame { @@ -2262,7 +2280,7 @@ struct ImGuiStorage }; // Helper: Manually clip large list of items. -// If you have lots evenly spaced items and you have a random access to the list, you can perform coarse +// If you have lots evenly spaced items and you have random access to the list, you can perform coarse // clipping based on visibility to only submit items that are in view. // The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. // (Dear ImGui already clip items based on their bounds but: it needs to first layout the item to do so, and generally @@ -2306,6 +2324,32 @@ struct ImGuiListClipper #endif }; +// Helpers: ImVec2/ImVec4 operators +// - 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) +// - You can use '#define IMGUI_DEFINE_MATH_OPERATORS' to import our operators, provided as a courtesy. +// - We unfortunately don't have a unary- operator for ImVec2 because this would needs to be defined inside the class itself. +#ifdef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED +IM_MSVC_RUNTIME_CHECKS_OFF +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +static 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); } +static 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); } +static 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); } +IM_MSVC_RUNTIME_CHECKS_RESTORE +#endif + // Helpers macros to generate 32-bit encoded colors // User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. #ifndef IM_COL32_R_SHIFT @@ -2408,7 +2452,7 @@ struct ImDrawVert #else // You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h // The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. -// The type has to be described within the macro (you can either declare the struct or use a typedef). This is because ImVec2/ImU32 are likely not declared a the time you'd want to set your type up. +// The type has to be described within the macro (you can either declare the struct or use a typedef). This is because ImVec2/ImU32 are likely not declared at the time you'd want to set your type up. // NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; #endif @@ -2496,7 +2540,7 @@ struct ImDrawList // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. - const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) const char* _OwnerName; // Pointer to owner window's name for debugging ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) @@ -2508,7 +2552,7 @@ struct ImDrawList float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) - ImDrawList(const ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } + ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } ~ImDrawList() { _ClearFreeMemory(); } IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) @@ -2525,7 +2569,7 @@ struct ImDrawList // - For circle primitives, use "num_segments == 0" to automatically calculate tessellation (preferred). // In older versions (until Dear ImGui 1.77) the AddCircle functions defaulted to num_segments == 12. // In future versions we will use textures to provide cheaper and higher-quality circles. - // Use AddNgon() and AddNgonFilled() functions if you need to guaranteed a specific number of sides. + // 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 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) @@ -2648,7 +2692,7 @@ struct ImFontConfig bool PixelSnapH; // false // Align every glyph to pixel boundary. 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. ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. - const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + const ImWchar* GlyphRanges; // NULL // 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). float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs 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. @@ -2729,7 +2773,7 @@ enum ImFontAtlasFlags_ // - 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, // - Even though many functions are suffixed with "TTF", OTF data is supported just as well. -// - This is an old API and it is currently awkward for those and and various other reasons! We will address them in the future! +// - This is an old API and it is currently awkward for those and various other reasons! We will address them in the future! struct ImFontAtlas { IMGUI_API ImFontAtlas(); @@ -2753,7 +2797,7 @@ struct ImFontAtlas IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't built texture but effectively we should check TexID != 0 except that would be backend dependent... + bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... void SetTexID(ImTextureID id) { TexID = id; } //------------------------------------------- @@ -2764,6 +2808,7 @@ struct ImFontAtlas // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesGreek(); // Default + Greek and Coptic IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs IMGUI_API const ImWchar* GetGlyphRangesChineseFull(); // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs @@ -2800,6 +2845,7 @@ struct ImFontAtlas int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). // [Internal] // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. @@ -2848,8 +2894,10 @@ struct ImFont const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. ImWchar FallbackChar; // 2 // out // = FFFD/'?' // Character used if a glyph isn't found. - ImWchar EllipsisChar; // 2 // out // = '...' // Character used for ellipsis rendering. - ImWchar DotChar; // 2 // out // = '.' // Character used for ellipsis rendering (if a single '...' character isn't found) + ImWchar EllipsisChar; // 2 // out // = '...'/'.'// Character used for ellipsis rendering. + short EllipsisCharCount; // 1 // out // 1 or 3 + float EllipsisWidth; // 4 // out // Width + float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 bool DirtyLookupTables; // 1 // out // float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] @@ -2943,9 +2991,9 @@ struct ImGuiPlatformImeData namespace ImGui { #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IMGUI_API int GetKeyIndex(ImGuiKey key); // map ImGuiKey_* values into legacy native key index. == io.KeyMap[key] + IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // map ImGuiKey_* values into legacy native key index. == io.KeyMap[key] #else - static inline int GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "ImGuiKey and native_index was merged together and native_index is disabled by IMGUI_DISABLE_OBSOLETE_KEYIO. Please switch to ImGuiKey."); return key; } + static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "ImGuiKey and native_index was merged together and native_index is disabled by IMGUI_DISABLE_OBSOLETE_KEYIO. Please switch to ImGuiKey."); return key; } #endif } @@ -2955,43 +3003,42 @@ namespace ImGui // OBSOLETED in 1.89 (from August 2022) IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // Use new ImageButton() signature (explicit item id, regular FramePadding) // OBSOLETED in 1.88 (from May 2022) - static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. - static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. + static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. // OBSOLETED in 1.86 (from November 2021) IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // Calculate coarse clipping for large list of evenly sized items. Prefer using ImGuiListClipper. // OBSOLETED in 1.85 (from August 2021) - static inline float GetWindowContentRegionWidth() { return GetWindowContentRegionMax().x - GetWindowContentRegionMin().x; } + static inline float GetWindowContentRegionWidth() { return GetWindowContentRegionMax().x - GetWindowContentRegionMin().x; } // OBSOLETED in 1.81 (from February 2021) IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // Helper to calculate size from items_count and height_in_items - static inline bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0)) { return BeginListBox(label, size); } - static inline void ListBoxFooter() { EndListBox(); } - // OBSOLETED in 1.79 (from August 2020) - static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! - // OBSOLETED in 1.78 (from June 2020) - // Old drag/sliders functions that took a 'float power = 1.0' argument instead of flags. - // For shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. - IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power); - IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power); - static inline bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); } - static inline bool DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } - static inline bool DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); } - static inline bool DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } - IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power); - IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format, float power); - static inline bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); } - static inline bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); } - static inline bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } - static inline bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); } - // OBSOLETED in 1.77 (from June 2020) - static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } + static inline bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0)) { return BeginListBox(label, size); } + static inline void ListBoxFooter() { EndListBox(); } // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + //-- OBSOLETED in 1.79 (from August 2020) + //static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! + //-- OBSOLETED in 1.78 (from June 2020): Old drag/sliders functions that took a 'float power > 1.0f' argument instead of ImGuiSliderFlags_Logarithmic. See github.com/ocornut/imgui/issues/3361 for details. + //IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power = 1.0f) // OBSOLETED in 1.78 (from June 2020) + //IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power = 1.0f); // OBSOLETED in 1.78 (from June 2020) + //IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power = 1.0f); // OBSOLETED in 1.78 (from June 2020) + //IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format, float power = 1.0f); // OBSOLETED in 1.78 (from June 2020) + //static inline bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power = 1.0f) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power = 1.0f) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power = 1.0f) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //static inline bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power = 1.0f) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) + //-- OBSOLETED in 1.77 and before + //static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } // OBSOLETED in 1.77 (from June 2020) //static inline void TreeAdvanceToLabelPos() { SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); } // OBSOLETED in 1.72 (from July 2019) //static inline void SetNextTreeNodeOpen(bool open, ImGuiCond cond = 0) { SetNextItemOpen(open, cond); } // OBSOLETED in 1.71 (from June 2019) //static inline float GetContentRegionAvailWidth() { return GetContentRegionAvail().x; } // OBSOLETED in 1.70 (from May 2019) //static inline ImDrawList* GetOverlayDrawList() { return GetForegroundDrawList(); } // OBSOLETED in 1.69 (from Mar 2019) //static inline void SetScrollHere(float ratio = 0.5f) { SetScrollHereY(ratio); } // OBSOLETED in 1.66 (from Nov 2018) //static inline bool IsItemDeactivatedAfterChange() { return IsItemDeactivatedAfterEdit(); } // OBSOLETED in 1.63 (from Aug 2018) + //-- OBSOLETED in 1.60 and before //static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } // OBSOLETED in 1.60 (from Apr 2018) //static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } // OBSOLETED in 1.60 (between Dec 2017 and Apr 2018) //static inline void ShowTestWindow() { return ShowDemoWindow(); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) @@ -2999,6 +3046,19 @@ namespace ImGui //static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) //static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) //static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + //IMGUI_API bool Begin(char* name, bool* p_open, ImVec2 size_first_use, float bg_alpha = -1.0f, ImGuiWindowFlags flags=0); // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017): Equivalent of using SetNextWindowSize(size, ImGuiCond_FirstUseEver) and SetNextWindowBgAlpha(). + //static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + //static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + //static inline void SetNextWindowPosCenter(ImGuiCond c=0) { SetNextWindowPos(GetMainViewport()->GetCenter(), c, ImVec2(0.5f,0.5f)); } // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + //static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + //static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017): This was misleading and partly broken. You probably want to use the io.WantCaptureMouse flag instead. + //static inline bool IsMouseHoveringAnyWindow() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + //static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + //-- OBSOLETED in 1.50 and before + //static inline bool CollapsingHeader(char* label, const char* str_id, bool framed = true, bool default_open = false) { return CollapsingHeader(label, (default_open ? (1 << 5) : 0)); } // OBSOLETED in 1.49 + //static inline ImFont*GetWindowFont() { return GetFont(); } // OBSOLETED in 1.48 + //static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETED in 1.48 + //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } // OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() @@ -3017,9 +3077,12 @@ enum ImDrawCornerFlags_ ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, }; -// RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022) -typedef int ImGuiKeyModFlags; -enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = ImGuiModFlags_None, ImGuiKeyModFlags_Ctrl = ImGuiModFlags_Ctrl, ImGuiKeyModFlags_Shift = ImGuiModFlags_Shift, ImGuiKeyModFlags_Alt = ImGuiModFlags_Alt, ImGuiKeyModFlags_Super = ImGuiModFlags_Super }; +// RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) +// RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obscolescence schedule to reduce confusion and because they were not meant to be used in the first place. +typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", but you may store only mods in there. +enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; +//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 }; #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/external/imgui/imgui/imgui_demo.cpp b/external/imgui/imgui/imgui_demo.cpp index 1e77e31e..82a80114 100644 --- a/external/imgui/imgui/imgui_demo.cpp +++ b/external/imgui/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (demo code) // Help: @@ -8,11 +8,15 @@ // Read imgui.cpp for more details, documentation and comments. // Get the latest version at https://github.com/ocornut/imgui +// ------------------------------------------------- +// PLEASE DO NOT REMOVE THIS FILE FROM YOUR PROJECT! +// ------------------------------------------------- // Message to the person tempted to delete this file when integrating Dear ImGui into their codebase: -// Do NOT remove this file from your project! Think again! It is the most useful reference code that you and other -// coders will want to refer to and call. Have the ImGui::ShowDemoWindow() function wired in an always-available -// debug menu of your game/app! Removing this file from your project is hindering access to documentation for everyone -// in your team, likely leading you to poorer usage of the library. +// Think again! It is the most useful reference code that you and other coders will want to refer to and call. +// Have the ImGui::ShowDemoWindow() function wired in an always-available debug menu of your game/app! +// Also include Metrics! ItemPicker! DebugLog! and other debug features. +// Removing this file from your project is hindering access to documentation for everyone in your team, +// likely leading you to poorer usage of the library. // Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). // If you want to link core Dear ImGui in your shipped builds but want a thorough guarantee that the demo will not be // linked, you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty. @@ -34,7 +38,7 @@ // - We try to declare static variables in the local scope, as close as possible to the code using them. // - We never use any of the helpers/facilities used internally by Dear ImGui, unless available in the public API. // - We never use maths operators on ImVec2/ImVec4. For our other sources files we use them, and they are provided -// by imgui_internal.h using the IMGUI_DEFINE_MATH_OPERATORS define. For your own sources file they are optional +// by imgui.h using the IMGUI_DEFINE_MATH_OPERATORS define. For your own sources file they are optional // and require you either enable those, either provide your own via IM_VEC2_CLASS_EXTRA in imconfig.h. // Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp. @@ -46,15 +50,18 @@ Index of this file: -// [SECTION] Forward Declarations, Helpers +// [SECTION] Forward Declarations +// [SECTION] Helpers // [SECTION] Demo Window / ShowDemoWindow() +// - ShowDemoWindow() // - sub section: ShowDemoWindowWidgets() // - sub section: ShowDemoWindowLayout() // - sub section: ShowDemoWindowPopups() // - sub section: ShowDemoWindowTables() -// - sub section: ShowDemoWindowMisc() +// - sub section: ShowDemoWindowInputs() // [SECTION] About Window / ShowAboutWindow() // [SECTION] Style Editor / ShowStyleEditor() +// [SECTION] User Guide / ShowUserGuide() // [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() // [SECTION] Example App: Debug Console / ShowExampleAppConsole() // [SECTION] Example App: Debug Log / ShowExampleAppLog() @@ -94,7 +101,7 @@ Index of this file: #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #endif // Clang/GCC warnings with -Weverything @@ -186,12 +193,25 @@ static void ShowExampleAppWindowTitles(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); static void ShowExampleMenuFile(); +// We split the contents of the big ShowDemoWindow() function into smaller functions +// (because the link time of very large functions grow non-linearly) +static void ShowDemoWindowWidgets(); +static void ShowDemoWindowLayout(); +static void ShowDemoWindowPopups(); +static void ShowDemoWindowTables(); +static void ShowDemoWindowColumns(); +static void ShowDemoWindowInputs(); + +//----------------------------------------------------------------------------- +// [SECTION] Helpers +//----------------------------------------------------------------------------- + // Helper to display a little (?) mark which shows a tooltip when hovered. // In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md) static void HelpMarker(const char* desc) { ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); @@ -201,78 +221,38 @@ static void HelpMarker(const char* desc) } } -// Helper to wire demo markers located in code to a interactive browser +// 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; +extern ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback; +extern void* GImGuiDemoMarkerCallbackUserData; +ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback = NULL; +void* GImGuiDemoMarkerCallbackUserData = NULL; #define IMGUI_DEMO_MARKER(section) do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0) -// Helper to display basic user controls. -void ImGui::ShowUserGuide() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui::BulletText("Double-click on title bar to collapse window."); - ImGui::BulletText( - "Click and drag on lower corner to resize window\n" - "(double-click to auto fit window to its contents)."); - ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); - ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); - ImGui::BulletText("CTRL+Tab to select a window."); - if (io.FontAllowUserScaling) - ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); - ImGui::BulletText("While inputing text:\n"); - ImGui::Indent(); - ImGui::BulletText("CTRL+Left/Right to word jump."); - ImGui::BulletText("CTRL+A or double-click to select all."); - ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); - ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); - ImGui::BulletText("ESCAPE to revert."); - ImGui::Unindent(); - ImGui::BulletText("With keyboard navigation enabled:"); - ImGui::Indent(); - ImGui::BulletText("Arrow keys to navigate."); - ImGui::BulletText("Space to activate a widget."); - ImGui::BulletText("Return to input text into a widget."); - ImGui::BulletText("Escape to deactivate a widget, close popup, exit child window."); - ImGui::BulletText("Alt to jump to the menu layer of a window."); - ImGui::Unindent(); -} - //----------------------------------------------------------------------------- // [SECTION] Demo Window / ShowDemoWindow() //----------------------------------------------------------------------------- +// - ShowDemoWindow() // - ShowDemoWindowWidgets() // - ShowDemoWindowLayout() // - ShowDemoWindowPopups() // - ShowDemoWindowTables() // - ShowDemoWindowColumns() -// - ShowDemoWindowMisc() +// - ShowDemoWindowInputs() //----------------------------------------------------------------------------- -// We split the contents of the big ShowDemoWindow() function into smaller functions -// (because the link time of very large functions grow non-linearly) -static void ShowDemoWindowWidgets(); -static void ShowDemoWindowLayout(); -static void ShowDemoWindowPopups(); -static void ShowDemoWindowTables(); -static void ShowDemoWindowColumns(); -static void ShowDemoWindowMisc(); - // Demonstrate most Dear ImGui features (this is big function!) // You may execute this function to experiment with the UI and understand what it does. // You may then search for keywords in the code when you are interested by a specific feature. void ImGui::ShowDemoWindow(bool* p_open) { // Exceptionally add an extra assert here for people confused about initial Dear ImGui setup - // Most ImGui functions would normally just crash if the context is missing. + // Most functions would normally just crash if the context is missing. IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing dear imgui context. Refer to examples app!"); // Examples Apps (accessible from the "Examples" menu) static bool show_app_main_menu_bar = false; static bool show_app_documents = false; - static bool show_app_console = false; static bool show_app_log = false; static bool show_app_layout = false; @@ -287,7 +267,6 @@ void ImGui::ShowDemoWindow(bool* p_open) if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); - if (show_app_console) ShowExampleAppConsole(&show_app_console); if (show_app_log) ShowExampleAppLog(&show_app_log); if (show_app_layout) ShowExampleAppLayout(&show_app_layout); @@ -300,7 +279,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); - // Dear ImGui Apps (accessible from the "Tools" menu) + // Dear ImGui Tools/Apps (accessible from the "Tools" menu) static bool show_app_metrics = false; static bool show_app_debug_log = false; static bool show_app_stack_tool = false; @@ -363,10 +342,8 @@ void ImGui::ShowDemoWindow(bool* p_open) } // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. - // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); - // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. ImGui::PushItemWidth(ImGui::GetFontSize() * -12); @@ -449,6 +426,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (ImGui::TreeNode("Configuration##2")) { + ImGui::SeparatorText("General"); ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); ImGui::SameLine(); HelpMarker("Enable keyboard controls."); ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); @@ -471,6 +449,10 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); ImGui::Checkbox("io.ConfigInputTrickleEventQueue", &io.ConfigInputTrickleEventQueue); ImGui::SameLine(); HelpMarker("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."); + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + + ImGui::SeparatorText("Widgets"); 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); @@ -480,11 +462,10 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); - ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); - ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors); ImGui::Text("Also see Style->Rendering for rendering options."); ImGui::TreePop(); - ImGui::Separator(); + ImGui::Spacing(); } IMGUI_DEMO_MARKER("Configuration/Backend Flags"); @@ -502,7 +483,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &backend_flags, ImGuiBackendFlags_HasSetMousePos); ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &backend_flags, ImGuiBackendFlags_RendererHasVtxOffset); ImGui::TreePop(); - ImGui::Separator(); + ImGui::Spacing(); } IMGUI_DEMO_MARKER("Configuration/Style"); @@ -511,7 +492,7 @@ void ImGui::ShowDemoWindow(bool* p_open) HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); ImGui::ShowStyleEditor(); ImGui::TreePop(); - ImGui::Separator(); + ImGui::Spacing(); } IMGUI_DEMO_MARKER("Configuration/Capture, Logging"); @@ -559,7 +540,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ShowDemoWindowLayout(); ShowDemoWindowPopups(); ShowDemoWindowTables(); - ShowDemoWindowMisc(); + ShowDemoWindowInputs(); // End of ShowDemoWindow() ImGui::PopItemWidth(); @@ -579,6 +560,8 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Basic"); if (ImGui::TreeNode("Basic")) { + ImGui::SeparatorText("General"); + IMGUI_DEMO_MARKER("Widgets/Basic/Button"); static int clicked = 0; if (ImGui::Button("Button")) @@ -633,35 +616,42 @@ static void ShowDemoWindowWidgets() ImGui::SameLine(); ImGui::Text("%d", counter); - IMGUI_DEMO_MARKER("Widgets/Basic/Tooltips"); - ImGui::Text("Hover over me"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("I am a tooltip"); - - ImGui::SameLine(); - ImGui::Text("- or me"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - 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::EndTooltip(); + // Tooltips + IMGUI_DEMO_MARKER("Widgets/Basic/Tooltips"); + //ImGui::AlignTextToFramePadding(); + ImGui::Text("Tooltips:"); + + ImGui::SameLine(); + ImGui::SmallButton("Button"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::SmallButton("Fancy"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + 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::Text("Sin(time) = %f", sinf((float)ImGui::GetTime())); + ImGui::EndTooltip(); + } + + ImGui::SameLine(); + ImGui::SmallButton("Delayed"); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) // With a delay + ImGui::SetTooltip("I am a tooltip with a delay."); + + ImGui::SameLine(); + HelpMarker( + "Tooltip are created by using the IsItemHovered() function over any kind of item."); } - ImGui::Separator(); ImGui::LabelText("label", "Value"); - { - // Using the _simplified_ one-liner Combo() api here - // See "Combo" section for examples of how to use the more flexible BeginCombo()/EndCombo() api. - 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::SameLine(); HelpMarker( - "Using the simplified one-liner Combo API here.\nRefer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API."); - } + ImGui::SeparatorText("Inputs"); { // To wire InputText() with std::string or any other custom string type, @@ -673,7 +663,7 @@ static void ShowDemoWindowWidgets() "USER:\n" "Hold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" - "CTRL+A or double-click to select all.\n" + "CTRL+A or Double-Click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n\n" @@ -705,6 +695,8 @@ static void ShowDemoWindowWidgets() ImGui::InputFloat3("input float3", vec4a); } + ImGui::SeparatorText("Drags"); + { IMGUI_DEMO_MARKER("Widgets/Basic/DragInt, DragFloat"); static int i1 = 50, i2 = 42; @@ -721,6 +713,8 @@ static void ShowDemoWindowWidgets() ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); } + ImGui::SeparatorText("Sliders"); + { IMGUI_DEMO_MARKER("Widgets/Basic/SliderInt, SliderFloat"); static int i1 = 0; @@ -747,6 +741,8 @@ static void ShowDemoWindowWidgets() ImGui::SameLine(); HelpMarker("Using the format string parameter to display a name instead of the underlying integer."); } + ImGui::SeparatorText("Selectors/Pickers"); + { IMGUI_DEMO_MARKER("Widgets/Basic/ColorEdit3, ColorEdit4"); static float col1[3] = { 1.0f, 0.0f, 0.2f }; @@ -761,6 +757,17 @@ static void ShowDemoWindowWidgets() ImGui::ColorEdit4("color 2", col2); } + { + // Using the _simplified_ one-liner Combo() api here + // See "Combo" section for examples of how to use the more flexible BeginCombo()/EndCombo() api. + 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::SameLine(); HelpMarker( + "Using the simplified one-liner Combo API here.\nRefer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API."); + } + { // Using the _simplified_ one-liner ListBox() api here // See "List boxes" section for examples of how to use the more flexible BeginListBox()/EndListBox() api. @@ -990,7 +997,7 @@ static void ShowDemoWindowWidgets() // Note that characters values are preserved even by InputText() if the font cannot be displayed, // so you can safely copy & paste garbled characters into another application. ImGui::TextWrapped( - "CJK text will only appears if the font was loaded with the appropriate CJK character ranges. " + "CJK text will only appear if the font was loaded with the appropriate CJK character ranges. " "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. " "Read docs/FONTS.md for details."); ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string. @@ -1031,12 +1038,14 @@ static void ShowDemoWindowWidgets() float my_tex_w = (float)io.Fonts->TexWidth; float my_tex_h = (float)io.Fonts->TexHeight; { + static bool use_text_color_for_tint = false; + ImGui::Checkbox("Use Text Color for Tint", &use_text_color_for_tint); 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 - ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint - ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white + ImVec4 tint_col = use_text_color_for_tint ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint + ImVec4 border_col = ImGui::GetStyleColorVec4(ImGuiCol_Border); ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, tint_col, border_col); if (ImGui::IsItemHovered()) { @@ -1089,6 +1098,7 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Combo"); if (ImGui::TreeNode("Combo")) { + // Combo Boxes are also called "Dropdown" in other systems // Expose flags as checkbox for the demo static ImGuiComboFlags flags = 0; ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", &flags, ImGuiComboFlags_PopupAlignLeft); @@ -1453,7 +1463,7 @@ static void ShowDemoWindowWidgets() static char buf3[64]; static int edit_count = 0; ImGui::InputText("Edit", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); - ImGui::SameLine(); HelpMarker("Here we toggle the casing of the first character on every edits + count edits."); + ImGui::SameLine(); HelpMarker("Here we toggle the casing of the first character on every edit + count edits."); ImGui::SameLine(); ImGui::Text("(%d)", edit_count); ImGui::TreePop(); @@ -1693,7 +1703,7 @@ static void ShowDemoWindowWidgets() } // Use functions to generate output - // FIXME: This is rather awkward because current plot API only pass in indices. + // FIXME: This is actually VERY awkward because current plot API only pass in indices. // We probably want an API passing floats and user provide sample rate/count. struct Funcs { @@ -1701,7 +1711,7 @@ static void ShowDemoWindowWidgets() static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } }; static int func_type = 0, display_count = 70; - ImGui::Separator(); + ImGui::SeparatorText("Functions"); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::SameLine(); @@ -1744,6 +1754,7 @@ static void ShowDemoWindowWidgets() static bool drag_and_drop = true; static bool options_menu = true; static bool hdr = false; + ImGui::SeparatorText("Options"); ImGui::Checkbox("With Alpha Preview", &alpha_preview); ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); ImGui::Checkbox("With Drag and Drop", &drag_and_drop); @@ -1752,6 +1763,7 @@ static void ShowDemoWindowWidgets() ImGuiColorEditFlags misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit"); + ImGui::SeparatorText("Inline color editor"); ImGui::Text("Color widget:"); ImGui::SameLine(); HelpMarker( "Click on the color square to open a color picker.\n" @@ -1849,7 +1861,7 @@ static void ShowDemoWindowWidgets() ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80)); IMGUI_DEMO_MARKER("Widgets/Color/ColorPicker"); - ImGui::Text("Color picker:"); + ImGui::SeparatorText("Color picker"); static bool alpha = true; static bool alpha_bar = true; static bool side_preview = true; @@ -1978,7 +1990,7 @@ static void ShowDemoWindowWidgets() // - integer/float/double // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum // to pass the type, and passing all arguments by pointer. - // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each types. + // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each type. // In practice, if you frequently use a given type that is not covered by the normal API entry points, // you can wrap it yourself inside a 1 line function which can take typed argument as value instead of void*, // and then pass their address to the generic function. For example: @@ -2020,10 +2032,10 @@ static void ShowDemoWindowWidgets() const float drag_speed = 0.2f; static bool drag_clamp = false; IMGUI_DEMO_MARKER("Widgets/Data Types/Drags"); - ImGui::Text("Drags:"); + ImGui::SeparatorText("Drags"); ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); ImGui::SameLine(); HelpMarker( - "As with every widgets in dear imgui, we never modify values unless there is a user interaction.\n" + "As with every widget in dear imgui, we never modify values unless there is a user interaction.\n" "You can override the clamping limits by using CTRL+Click to input a value."); ImGui::DragScalar("drag s8", ImGuiDataType_S8, &s8_v, drag_speed, drag_clamp ? &s8_zero : NULL, drag_clamp ? &s8_fifty : NULL); ImGui::DragScalar("drag u8", ImGuiDataType_U8, &u8_v, drag_speed, drag_clamp ? &u8_zero : NULL, drag_clamp ? &u8_fifty : NULL, "%u ms"); @@ -2040,7 +2052,7 @@ static void ShowDemoWindowWidgets() ImGui::DragScalar("drag double log",ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", ImGuiSliderFlags_Logarithmic); IMGUI_DEMO_MARKER("Widgets/Data Types/Sliders"); - ImGui::Text("Sliders"); + ImGui::SeparatorText("Sliders"); ImGui::SliderScalar("slider s8 full", ImGuiDataType_S8, &s8_v, &s8_min, &s8_max, "%d"); ImGui::SliderScalar("slider u8 full", ImGuiDataType_U8, &u8_v, &u8_min, &u8_max, "%u"); ImGui::SliderScalar("slider s16 full", ImGuiDataType_S16, &s16_v, &s16_min, &s16_max, "%d"); @@ -2065,7 +2077,7 @@ static void ShowDemoWindowWidgets() ImGui::SliderScalar("slider double low log",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f", ImGuiSliderFlags_Logarithmic); ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams"); - ImGui::Text("Sliders (reverse)"); + ImGui::SeparatorText("Sliders (reverse)"); ImGui::SliderScalar("slider s8 reverse", ImGuiDataType_S8, &s8_v, &s8_max, &s8_min, "%d"); ImGui::SliderScalar("slider u8 reverse", ImGuiDataType_U8, &u8_v, &u8_max, &u8_min, "%u"); ImGui::SliderScalar("slider s32 reverse", ImGuiDataType_S32, &s32_v, &s32_fifty, &s32_zero, "%d"); @@ -2075,7 +2087,7 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Data Types/Inputs"); static bool inputs_step = true; - ImGui::Text("Inputs"); + ImGui::SeparatorText("Inputs"); ImGui::Checkbox("Show step buttons", &inputs_step); ImGui::InputScalar("input s8", ImGuiDataType_S8, &s8_v, inputs_step ? &s8_one : NULL, NULL, "%d"); ImGui::InputScalar("input u8", ImGuiDataType_U8, &u8_v, inputs_step ? &u8_one : NULL, NULL, "%u"); @@ -2099,22 +2111,23 @@ static void ShowDemoWindowWidgets() static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; static int vec4i[4] = { 1, 5, 100, 255 }; + 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::Spacing(); + 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::Spacing(); + 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); @@ -2322,7 +2335,7 @@ static void ShowDemoWindowWidgets() 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); - // Submit selected item item so we can query their status in the code following it. + // Submit selected items so we can query their status in the code following it. bool ret = false; static bool b = false; static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f }; @@ -2346,6 +2359,10 @@ static void ShowDemoWindowWidgets() 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)); } + bool hovered_delay_none = ImGui::IsItemHovered(); + bool hovered_delay_short = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort); + bool hovered_delay_normal = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal); + // Display the values of IsItemHovered() and other common item state functions. // Note that the ImGuiHoveredFlags_XXX flags can be combined. // Because BulletText is an item itself and that would affect the output of IsItemXXX functions, @@ -2390,6 +2407,8 @@ static void ShowDemoWindowWidgets() ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y, ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y ); + ImGui::BulletText( + "w/ Hovering Delay: None = %d, Fast %d, Normal = %d", hovered_delay_none, hovered_delay_short, hovered_delay_normal); if (item_disabled) ImGui::EndDisabled(); @@ -2494,6 +2513,26 @@ static void ShowDemoWindowWidgets() ImGui::SameLine(); HelpMarker("Demonstrate using BeginDisabled()/EndDisabled() across this section."); ImGui::TreePop(); } + + IMGUI_DEMO_MARKER("Widgets/Text Filter"); + if (ImGui::TreeNode("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."); + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-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++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + ImGui::TreePop(); + } } static void ShowDemoWindowLayout() @@ -2505,6 +2544,8 @@ static void ShowDemoWindowLayout() IMGUI_DEMO_MARKER("Layout/Child windows"); if (ImGui::TreeNode("Child windows")) { + ImGui::SeparatorText("Child windows"); + HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); static bool disable_mouse_wheel = false; static bool disable_menu = false; @@ -2557,7 +2598,7 @@ static void ShowDemoWindowLayout() ImGui::PopStyleVar(); } - ImGui::Separator(); + ImGui::SeparatorText("Misc/Advanced"); // Demonstrate a few extra things // - Changing ImGuiCol_ChildBg (which is transparent black in default styles) @@ -3321,8 +3362,7 @@ static void ShowDemoWindowPopups() ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); if (ImGui::BeginPopup("my_select_popup")) { - ImGui::Text("Aquarium"); - ImGui::Separator(); + ImGui::SeparatorText("Aquarium"); for (int i = 0; i < IM_ARRAYSIZE(names); i++) if (ImGui::Selectable(names[i])) selected_fish = i; @@ -3408,7 +3448,7 @@ static void ShowDemoWindowPopups() // if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) // OpenPopup(id); // return BeginPopup(id); - // For advanced advanced uses you may want to replicate and customize this code. + // For advanced uses you may want to replicate and customize this code. // See more details in BeginPopupContextItem(). // Example 1 @@ -3416,11 +3456,14 @@ static void ShowDemoWindowPopups() // and BeginPopupContextItem() will use the last item ID as the popup ID. { const char* names[5] = { "Label1", "Label2", "Label3", "Label4", "Label5" }; + static int selected = -1; for (int n = 0; n < 5; n++) { - ImGui::Selectable(names[n]); + if (ImGui::Selectable(names[n], selected == n)) + selected = n; if (ImGui::BeginPopupContextItem()) // <-- use last item id as popup id { + selected = n; ImGui::Text("This a popup for \"%s\"!", names[n]); if (ImGui::Button("Close")) ImGui::CloseCurrentPopup(); @@ -3496,7 +3539,7 @@ static void ShowDemoWindowPopups() if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!"); ImGui::Separator(); //static int unused_i = 0; @@ -3576,7 +3619,7 @@ static void ShowDemoWindowPopups() } // Dummy data structure that we use for the Table demo. -// (pre-C++11 doesn't allow us to instantiate ImVector template if this structure if defined inside the demo function) +// (pre-C++11 doesn't allow us to instantiate ImVector template if this structure is defined inside the demo function) namespace { // We are passing our own identifier to TableSetupColumn() to facilitate identifying columns in the sorting code. @@ -3795,7 +3838,7 @@ static void ShowDemoWindowTables() } // [Method 2] Using TableNextColumn() called multiple times, instead of using a for loop + TableSetColumnIndex(). - // This is generally more convenient when you have code manually submitting the contents of each columns. + // This is generally more convenient when you have code manually submitting the contents of each column. HelpMarker("Using TableNextRow() + calling TableNextColumn() _before_ each cell, manually."); if (ImGui::BeginTable("table2", 3)) { @@ -3813,10 +3856,10 @@ static void ShowDemoWindowTables() } // [Method 3] We call TableNextColumn() _before_ each cell. We never call TableNextRow(), - // as TableNextColumn() will automatically wrap around and create new roes as needed. + // as TableNextColumn() will automatically wrap around and create new rows as needed. // This is generally more convenient when your cells all contains the same type of data. HelpMarker( - "Only using TableNextColumn(), which tends to be convenient for tables where every cells contains the same type of contents.\n" + "Only using TableNextColumn(), which tends to be convenient for tables where every cell contains the same type of contents.\n" "This is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); if (ImGui::BeginTable("table3", 3)) { @@ -3868,7 +3911,7 @@ static void ShowDemoWindowTables() ImGui::SameLine(); ImGui::RadioButton("Text", &contents_type, CT_Text); ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); ImGui::Checkbox("Display headers", &display_headers); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appear in Headers"); PopStyleCompact(); if (ImGui::BeginTable("table1", 3, flags)) @@ -3907,8 +3950,8 @@ static void ShowDemoWindowTables() IMGUI_DEMO_MARKER("Tables/Resizable, stretch"); if (ImGui::TreeNode("Resizable, stretch")) { - // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" - // Each columns maintain a sizing weight, and they will occupy all available width. + // 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; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); @@ -4030,7 +4073,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", &flags, ImGuiTableFlags_Reorderable); ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", &flags, ImGuiTableFlags_Hideable); ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)"); PopStyleCompact(); if (ImGui::BeginTable("table1", 3, flags)) @@ -4088,7 +4131,7 @@ static void ShowDemoWindowTables() "- any form of row selection\n" "Because of this, activating BorderOuterV sets the default to PadOuterX. Using PadOuterX or NoPadOuterX you can override the default.\n\n" "Actual padding values are using style.CellPadding.\n\n" - "In this demo we don't show horizontal borders to emphasis how they don't affect default horizontal padding."); + "In this demo we don't show horizontal borders to emphasize how they don't affect default horizontal padding."); static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV; PushStyleCompact(); @@ -4557,7 +4600,7 @@ static void ShowDemoWindowTables() IMGUI_DEMO_MARKER("Tables/Nested tables"); if (ImGui::TreeNode("Nested tables")) { - HelpMarker("This demonstrate embedding a table into another table cell."); + HelpMarker("This demonstrates embedding a table into another table cell."); if (ImGui::BeginTable("table_nested1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { @@ -4602,7 +4645,7 @@ static void ShowDemoWindowTables() IMGUI_DEMO_MARKER("Tables/Row height"); if (ImGui::TreeNode("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\nWe cannot honor a _maximum_ row height as that would requires a unique clipping rectangle per row."); + 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\nWe cannot honor a _maximum_ row height as that would require a unique clipping rectangle per row."); if (ImGui::BeginTable("table_row_height", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerV)) { for (int row = 0; row < 10; row++) @@ -5021,18 +5064,23 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("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; + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("ImGuiTableFlags_SizingFixedFit", &flags, ImGuiTableFlags_SizingFixedFit); for (int n = 0; n < 3; n++) { char buf[32]; sprintf(buf, "Synced Table %d", n); bool open = ImGui::CollapsingHeader(buf, ImGuiTreeNodeFlags_DefaultOpen); - if (open && ImGui::BeginTable("Table", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + if (open && ImGui::BeginTable("Table", 3, flags, ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 5))) { ImGui::TableSetupColumn("One"); ImGui::TableSetupColumn("Two"); ImGui::TableSetupColumn("Three"); ImGui::TableHeadersRow(); - for (int cell = 0; cell < 9; cell++) + const int cell_count = (n == 1) ? 27 : 9; // Make second table have a scrollbar to verify that additional decoration is not affecting column positions. + for (int cell = 0; cell < cell_count; cell++) { ImGui::TableNextColumn(); ImGui::Text("this cell %d", cell); @@ -5164,7 +5212,7 @@ static void ShowDemoWindowTables() static bool show_headers = true; static bool show_wrapped_text = false; //static ImGuiTextFilter filter; - //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which tend to affects column sizing + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which tend to affect column sizing if (ImGui::TreeNode("Options")) { // Make the UI compact because there are so many fields @@ -5191,8 +5239,8 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", &flags, ImGuiTableFlags_BordersOuterH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", &flags, ImGuiTableFlags_BordersInnerH); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appear in Headers"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)"); ImGui::TreePop(); } @@ -5255,7 +5303,7 @@ static void ShowDemoWindowTables() HelpMarker("If scrolling is disabled (ScrollX and ScrollY not set):\n" "- The table is output directly in the parent window.\n" "- OuterSize.x < 0.0f will right-align the table.\n" - "- OuterSize.x = 0.0f will narrow fit the table unless there are any Stretch column.\n" + "- OuterSize.x = 0.0f will narrow fit the table unless there are any Stretch columns.\n" "- OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendY is set)."); // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. @@ -5662,137 +5710,51 @@ static void ShowDemoWindowColumns() namespace ImGui { extern ImGuiKeyData* GetKeyData(ImGuiKey key); } -static void ShowDemoWindowMisc() +static void ShowDemoWindowInputs() { - IMGUI_DEMO_MARKER("Filtering"); - if (ImGui::CollapsingHeader("Filtering")) - { - // Helper class to easy setup a text filter. - // You may want to implement a more feature-full filtering scheme in your own application. - static ImGuiTextFilter filter; - ImGui::Text("Filter usage:\n" - " \"\" display all lines\n" - " \"xxx\" display lines containing \"xxx\"\n" - " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" - " \"-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++) - if (filter.PassFilter(lines[i])) - ImGui::BulletText("%s", lines[i]); - } - - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus"); - if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) + IMGUI_DEMO_MARKER("Inputs & Focus"); + if (ImGui::CollapsingHeader("Inputs & Focus")) { ImGuiIO& io = ImGui::GetIO(); - // Display ImGuiIO output flags - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Output"); + // Display inputs submitted to ImGuiIO + IMGUI_DEMO_MARKER("Inputs & Focus/Inputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::TreeNode("Output")) - { - ImGui::Text("io.WantCaptureMouse: %d", io.WantCaptureMouse); - ImGui::Text("io.WantCaptureMouseUnlessPopupClose: %d", io.WantCaptureMouseUnlessPopupClose); - ImGui::Text("io.WantCaptureKeyboard: %d", io.WantCaptureKeyboard); - ImGui::Text("io.WantTextInput: %d", io.WantTextInput); - ImGui::Text("io.WantSetMousePos: %d", io.WantSetMousePos); - ImGui::Text("io.NavActive: %d, io.NavVisible: %d", io.NavActive, io.NavVisible); - ImGui::TreePop(); - } - - // Display Mouse state - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Mouse State"); - if (ImGui::TreeNode("Mouse State")) + if (ImGui::TreeNode("Inputs")) { + HelpMarker( + "This is a simplified view. See more detailed input state:\n" + "- in 'Tools->Metrics/Debugger->Inputs'.\n" + "- in 'Tools->Debug Log->IO'."); 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); - - int count = IM_ARRAYSIZE(io.MouseDown); - ImGui::Text("Mouse down:"); for (int i = 0; i < count; i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } - ImGui::Text("Mouse clicked:"); for (int i = 0; i < count; i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d (%d)", i, ImGui::GetMouseClickedCount(i)); } - ImGui::Text("Mouse released:"); for (int i = 0; i < count; i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + 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]); } ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); - ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused - ImGui::TreePop(); - } - // Display Keyboard/Mouse state - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Keyboard, Gamepad & Navigation State"); - if (ImGui::TreeNode("Keyboard, Gamepad & Navigation State")) - { - // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allow displaying the data for old/new backends. - // User code should never have to go through such hoops: old code may use native keycodes, new code may use ImGuiKey codes. + // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. + // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. #ifdef IMGUI_DISABLE_OBSOLETE_KEYIO struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } }; - const ImGuiKey key_first = ImGuiKey_NamedKey_BEGIN; + ImGuiKey start_key = ImGuiKey_NamedKey_BEGIN; #else struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key < 512 && ImGui::GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array - const ImGuiKey key_first = 0; - //ImGui::Text("Legacy raw:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { ImGui::SameLine(); ImGui::Text("\"%s\" %d", ImGui::GetKeyName(key), key); } } + ImGuiKey start_key = (ImGuiKey)0; #endif - ImGui::Text("Keys down:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key++) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyDown(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d (%.02f secs)", ImGui::GetKeyName(key), key, ImGui::GetKeyData(key)->DownDuration); } } - ImGui::Text("Keys pressed:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key++) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyPressed(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d", ImGui::GetKeyName(key), key); } } - ImGui::Text("Keys released:"); for (ImGuiKey key = key_first; key < ImGuiKey_COUNT; key++) { if (funcs::IsLegacyNativeDupe(key)) continue; if (ImGui::IsKeyReleased(key)) { ImGui::SameLine(); ImGui::Text("\"%s\" %d", ImGui::GetKeyName(key), key); } } + ImGui::Text("Keys down:"); for (ImGuiKey key = start_key; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !ImGui::IsKeyDown(key)) continue; ImGui::SameLine(); ImGui::Text((key < ImGuiKey_NamedKey_BEGIN) ? "\"%s\"" : "\"%s\" %d", ImGui::GetKeyName(key), key); ImGui::SameLine(); ImGui::Text("(%.02f)", ImGui::GetKeyData(key)->DownDuration); } ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); - ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. + ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. - // Draw an arbitrary US keyboard layout to visualize translated keys - { - const ImVec2 key_size = ImVec2(35.0f, 35.0f); - const float key_rounding = 3.0f; - const ImVec2 key_face_size = ImVec2(25.0f, 25.0f); - const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f); - const float key_face_rounding = 2.0f; - const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f); - const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f); - const float key_row_offset = 9.0f; - - ImVec2 board_min = ImGui::GetCursorScreenPos(); - ImVec2 board_max = ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f); - ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y); - - struct KeyLayoutData { int Row, Col; const char* Label; ImGuiKey Key; }; - const KeyLayoutData keys_to_display[] = - { - { 0, 0, "", ImGuiKey_Tab }, { 0, 1, "Q", ImGuiKey_Q }, { 0, 2, "W", ImGuiKey_W }, { 0, 3, "E", ImGuiKey_E }, { 0, 4, "R", ImGuiKey_R }, - { 1, 0, "", ImGuiKey_CapsLock }, { 1, 1, "A", ImGuiKey_A }, { 1, 2, "S", ImGuiKey_S }, { 1, 3, "D", ImGuiKey_D }, { 1, 4, "F", ImGuiKey_F }, - { 2, 0, "", ImGuiKey_LeftShift },{ 2, 1, "Z", ImGuiKey_Z }, { 2, 2, "X", ImGuiKey_X }, { 2, 3, "C", ImGuiKey_C }, { 2, 4, "V", ImGuiKey_V } - }; - - // Elements rendered manually via ImDrawList API are not clipped automatically. - // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of view. - ImGui::Dummy(ImVec2(board_max.x - board_min.x, board_max.y - board_min.y)); - if (ImGui::IsItemVisible()) - { - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->PushClipRect(board_min, board_max, true); - for (int n = 0; n < IM_ARRAYSIZE(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); - ImVec2 key_max = ImVec2(key_min.x + key_size.x, key_min.y + key_size.y); - draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding); - 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->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); - if (ImGui::IsKeyDown(key_data->Key)) - draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding); - } - draw_list->PopClipRect(); - } - } ImGui::TreePop(); } - if (ImGui::TreeNode("Capture override")) + // Display ImGuiIO output flags + IMGUI_DEMO_MARKER("Inputs & Focus/Outputs"); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::TreeNode("Outputs")) { HelpMarker( "The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui " @@ -5801,32 +5763,68 @@ static void ShowDemoWindowMisc() "The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, " "and underlying application should ignore mouse inputs (in practice there are many and more subtle " "rules leading to how those flags are set)."); - ImGui::Text("io.WantCaptureMouse: %d", io.WantCaptureMouse); ImGui::Text("io.WantCaptureMouseUnlessPopupClose: %d", io.WantCaptureMouseUnlessPopupClose); ImGui::Text("io.WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("io.WantTextInput: %d", io.WantTextInput); + ImGui::Text("io.WantSetMousePos: %d", io.WantSetMousePos); + ImGui::Text("io.NavActive: %d, io.NavVisible: %d", io.NavActive, io.NavVisible); - HelpMarker( - "Hovering the colored canvas will override io.WantCaptureXXX fields.\n" - "Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering and true when clicking."); - static int capture_override_mouse = -1; - static int capture_override_keyboard = -1; - const char* capture_override_desc[] = { "None", "Set to false", "Set to true" }; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderInt("SetNextFrameWantCaptureMouse()", &capture_override_mouse, -1, +1, capture_override_desc[capture_override_mouse + 1], ImGuiSliderFlags_AlwaysClamp); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderInt("SetNextFrameWantCaptureKeyboard()", &capture_override_keyboard, -1, +1, capture_override_desc[capture_override_keyboard + 1], ImGuiSliderFlags_AlwaysClamp); + IMGUI_DEMO_MARKER("Inputs & Focus/Outputs/WantCapture override"); + if (ImGui::TreeNode("WantCapture override")) + { + HelpMarker( + "Hovering the colored canvas will override io.WantCaptureXXX fields.\n" + "Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering and true when clicking."); + static int capture_override_mouse = -1; + static int capture_override_keyboard = -1; + const char* capture_override_desc[] = { "None", "Set to false", "Set to true" }; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); + ImGui::SliderInt("SetNextFrameWantCaptureMouse() on hover", &capture_override_mouse, -1, +1, capture_override_desc[capture_override_mouse + 1], ImGuiSliderFlags_AlwaysClamp); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); + ImGui::SliderInt("SetNextFrameWantCaptureKeyboard() on hover", &capture_override_keyboard, -1, +1, capture_override_desc[capture_override_keyboard + 1], ImGuiSliderFlags_AlwaysClamp); - ImGui::ColorButton("##panel", ImVec4(0.7f, 0.1f, 0.7f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, ImVec2(256.0f, 192.0f)); // Dummy item - if (ImGui::IsItemHovered() && capture_override_mouse != -1) - ImGui::SetNextFrameWantCaptureMouse(capture_override_mouse == 1); - if (ImGui::IsItemHovered() && capture_override_keyboard != -1) - ImGui::SetNextFrameWantCaptureKeyboard(capture_override_keyboard == 1); + ImGui::ColorButton("##panel", ImVec4(0.7f, 0.1f, 0.7f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, ImVec2(128.0f, 96.0f)); // Dummy item + if (ImGui::IsItemHovered() && capture_override_mouse != -1) + ImGui::SetNextFrameWantCaptureMouse(capture_override_mouse == 1); + if (ImGui::IsItemHovered() && capture_override_keyboard != -1) + ImGui::SetNextFrameWantCaptureKeyboard(capture_override_keyboard == 1); + ImGui::TreePop(); + } ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Tabbing"); + // Display mouse cursors + IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors"); + if (ImGui::TreeNode("Mouse Cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "NotAllowed" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); + + ImGuiMouseCursor current = ImGui::GetMouseCursor(); + ImGui::Text("Current mouse cursor = %d: %s", current, mouse_cursors_names[current]); + ImGui::BeginDisabled(true); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); + ImGui::EndDisabled(); + + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); HelpMarker( + "Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. " + "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " + "otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Inputs & Focus/Tabbing"); if (ImGui::TreeNode("Tabbing")) { ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); @@ -5842,7 +5840,7 @@ static void ShowDemoWindowMisc() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Focus from code"); + IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); if (ImGui::TreeNode("Focus from code")) { bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); @@ -5884,7 +5882,7 @@ static void ShowDemoWindowMisc() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Dragging"); + IMGUI_DEMO_MARKER("Inputs & Focus/Dragging"); if (ImGui::TreeNode("Dragging")) { ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); @@ -5912,30 +5910,6 @@ static void ShowDemoWindowMisc() ImGui::Text("io.MouseDelta: (%.1f, %.1f)", mouse_delta.x, mouse_delta.y); ImGui::TreePop(); } - - IMGUI_DEMO_MARKER("Inputs, Navigation & Focus/Mouse cursors"); - if (ImGui::TreeNode("Mouse cursors")) - { - const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "NotAllowed" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); - - ImGuiMouseCursor current = ImGui::GetMouseCursor(); - ImGui::Text("Current mouse cursor = %d: %s", current, mouse_cursors_names[current]); - ImGui::Text("Hover to see mouse cursors:"); - ImGui::SameLine(); HelpMarker( - "Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. " - "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " - "otherwise your backend needs to handle it."); - for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) - { - char label[32]; - sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); - ImGui::Bullet(); ImGui::Selectable(label, false); - if (ImGui::IsItemHovered()) - ImGui::SetMouseCursor(i); - } - ImGui::TreePop(); - } } } @@ -6039,6 +6013,9 @@ void ImGui::ShowAboutWindow(bool* p_open) #endif #ifdef __clang_version__ ImGui::Text("define: __clang_version__=%s", __clang_version__); +#endif +#ifdef __EMSCRIPTEN__ + ImGui::Text("define: __EMSCRIPTEN__"); #endif ImGui::Separator(); ImGui::Text("io.BackendPlatformName: %s", io.BackendPlatformName ? io.BackendPlatformName : "NULL"); @@ -6188,7 +6165,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { if (ImGui::BeginTabItem("Sizes")) { - ImGui::Text("Main"); + ImGui::SeparatorText("Main"); ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); @@ -6198,22 +6175,24 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - ImGui::Text("Borders"); + + ImGui::SeparatorText("Borders"); ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::Text("Rounding"); + + ImGui::SeparatorText("Rounding"); ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::Text("Alignment"); + + ImGui::SeparatorText("Widgets"); ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); int window_menu_button_position = style.WindowMenuButtonPosition + 1; if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) @@ -6223,9 +6202,13 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); ImGui::SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); - ImGui::Text("Safe Area Padding"); - ImGui::SameLine(); HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%0.f"); + ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + + ImGui::SeparatorText("Misc"); + ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); ImGui::EndTabItem(); } @@ -6388,6 +6371,40 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::PopItemWidth(); } +//----------------------------------------------------------------------------- +// [SECTION] User Guide / ShowUserGuide() +//----------------------------------------------------------------------------- + +void ImGui::ShowUserGuide() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText( + "Click and drag on lower corner to resize window\n" + "(double-click to auto fit window to its contents)."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("CTRL+Tab to select a window."); + if (io.FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("While inputing text:\n"); + ImGui::Indent(); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::Unindent(); + ImGui::BulletText("With keyboard navigation enabled:"); + ImGui::Indent(); + ImGui::BulletText("Arrow keys to navigate."); + ImGui::BulletText("Space to activate a widget."); + ImGui::BulletText("Return to input text into a widget."); + ImGui::BulletText("Escape to deactivate a widget, close popup, exit child window."); + ImGui::BulletText("Alt to jump to the menu layer of a window."); + ImGui::Unindent(); +} + //----------------------------------------------------------------------------- // [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() //----------------------------------------------------------------------------- @@ -6501,6 +6518,7 @@ static void ShowExampleMenuFile() IM_ASSERT(0); } if (ImGui::MenuItem("Checked", NULL, true)) {} + ImGui::Separator(); if (ImGui::MenuItem("Quit", "Alt+F4")) {} } @@ -6622,72 +6640,76 @@ struct ExampleAppConsole // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); - if (ImGui::BeginPopupContextWindow()) + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar)) { - if (ImGui::Selectable("Clear")) ClearLog(); - ImGui::EndPopup(); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. + // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping + // to only process visible items. The clipper will automatically measure the height of your first item and then + // "seek" to display only items in the visible area. + // To use the clipper we can replace your standard loop: + // for (int i = 0; i < Items.Size; i++) + // With: + // ImGuiListClipper clipper; + // clipper.Begin(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // - That your items are evenly spaced (same height) + // - That you have cheap random access to your elements (you can access them given their index, + // without processing all the ones before) + // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. + // We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices + // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage + // to improve this example code! + // If your items are of variable height: + // - Split them into same height items would be simpler and facilitate random-seeking into your list. + // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!Filter.PassFilter(item)) + continue; + + // Normally you would store more information in your item than just a string. + // (e.g. make Items[] an array of structure, store color/type etc.) + ImVec4 color; + bool has_color = false; + if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } + else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } + if (has_color) + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(item); + if (has_color) + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. + // Using a scrollbar or mouse-wheel will take away from the bottom edge. + if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) + ImGui::SetScrollHereY(1.0f); + ScrollToBottom = false; + + ImGui::PopStyleVar(); } - - // Display every line as a separate entry so we can change their color or add custom widgets. - // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); - // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping - // to only process visible items. The clipper will automatically measure the height of your first item and then - // "seek" to display only items in the visible area. - // To use the clipper we can replace your standard loop: - // for (int i = 0; i < Items.Size; i++) - // With: - // ImGuiListClipper clipper; - // clipper.Begin(Items.Size); - // while (clipper.Step()) - // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - // - That your items are evenly spaced (same height) - // - That you have cheap random access to your elements (you can access them given their index, - // without processing all the ones before) - // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. - // We would need random-access on the post-filtered list. - // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices - // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, - // and appending newly elements as they are inserted. This is left as a task to the user until we can manage - // to improve this example code! - // If your items are of variable height: - // - Split them into same height items would be simpler and facilitate random-seeking into your list. - // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing - if (copy_to_clipboard) - ImGui::LogToClipboard(); - for (int i = 0; i < Items.Size; i++) - { - const char* item = Items[i]; - if (!Filter.PassFilter(item)) - continue; - - // Normally you would store more information in your item than just a string. - // (e.g. make Items[] an array of structure, store color/type etc.) - ImVec4 color; - bool has_color = false; - if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } - else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } - if (has_color) - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(item); - if (has_color) - ImGui::PopStyleColor(); - } - if (copy_to_clipboard) - ImGui::LogFinish(); - - if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) - ImGui::SetScrollHereY(1.0f); - ScrollToBottom = false; - - ImGui::PopStyleVar(); ImGui::EndChild(); ImGui::Separator(); // Command-line bool reclaim_focus = false; - ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; + 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)) { char* s = InputBuf; @@ -6929,63 +6951,66 @@ struct ExampleAppLog Filter.Draw("Filter", -100.0f); ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - if (clear) - Clear(); - if (copy) - ImGui::LogToClipboard(); + if (ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) + { + if (clear) + Clear(); + if (copy) + ImGui::LogToClipboard(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - const char* buf = Buf.begin(); - const char* buf_end = Buf.end(); - if (Filter.IsActive()) - { - // In this example we don't use the clipper when Filter is enabled. - // This is because we don't have a random access on the result on our filter. - // A real application processing logs with ten of thousands of entries may want to store the result of - // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). - for (int line_no = 0; line_no < LineOffsets.Size; line_no++) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + if (Filter.IsActive()) { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - if (Filter.PassFilter(line_start, line_end)) - ImGui::TextUnformatted(line_start, line_end); - } - } - else - { - // The simplest and easy way to display the entire buffer: - // ImGui::TextUnformatted(buf_begin, buf_end); - // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward - // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are - // within the visible area. - // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them - // on your side is recommended. Using ImGuiListClipper requires - // - A) random access into your data - // - B) items all being the same height, - // both of which we can handle since we an array pointing to the beginning of each line of text. - // When using the filter (in the block of code above) we don't have random access into the data to display - // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make - // it possible (and would be recommended if you want to search through tens of thousands of entries). - ImGuiListClipper clipper; - clipper.Begin(LineOffsets.Size); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + // In this example we don't use the clipper when Filter is enabled. + // This is because we don't have random access to the result of our filter. + // A real application processing logs with ten of thousands of entries may want to store the result of + // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). + for (int line_no = 0; line_no < LineOffsets.Size; line_no++) { const char* line_start = buf + LineOffsets[line_no]; const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - ImGui::TextUnformatted(line_start, line_end); + if (Filter.PassFilter(line_start, line_end)) + ImGui::TextUnformatted(line_start, line_end); } } - clipper.End(); + else + { + // The simplest and easy way to display the entire buffer: + // ImGui::TextUnformatted(buf_begin, buf_end); + // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward + // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are + // within the visible area. + // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them + // on your side is recommended. Using ImGuiListClipper requires + // - A) random access into your data + // - B) items all being the same height, + // both of which we can handle since we have an array pointing to the beginning of each line of text. + // When using the filter (in the block of code above) we don't have random access into the data to display + // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make + // it possible (and would be recommended if you want to search through tens of thousands of entries). + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + while (clipper.Step()) + { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + } + ImGui::PopStyleVar(); + + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. + // Using a scrollbar or mouse-wheel will take away from the bottom edge. + if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); } - ImGui::PopStyleVar(); - - if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); - ImGui::EndChild(); ImGui::End(); } @@ -7037,7 +7062,7 @@ static void ShowExampleAppLayout(bool* p_open) { if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("Close")) *p_open = false; + if (ImGui::MenuItem("Close", "Ctrl+W")) { *p_open = false; } ImGui::EndMenu(); } ImGui::EndMenuBar(); @@ -7265,51 +7290,82 @@ static void ShowExampleAppAutoResize(bool* p_open) //----------------------------------------------------------------------------- // Demonstrate creating a window with custom resize constraints. +// Note that size constraints currently don't work on a docked window (when in 'docking' branch) static void ShowExampleAppConstrainedResize(bool* p_open) { struct CustomConstraints { // Helper functions to demonstrate programmatic constraints - static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize.x = data->DesiredSize.y = IM_MAX(data->DesiredSize.x, data->DesiredSize.y); } - static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + // FIXME: This doesn't take account of decoration size (e.g. title bar), library should make this easier. + static void AspectRatio(ImGuiSizeCallbackData* data) { float aspect_ratio = *(float*)data->UserData; data->DesiredSize.x = IM_MAX(data->CurrentSize.x, data->CurrentSize.y); data->DesiredSize.y = (float)(int)(data->DesiredSize.x / aspect_ratio); } + static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize.x = data->DesiredSize.y = IM_MAX(data->CurrentSize.x, data->CurrentSize.y); } + static void Step(ImGuiSizeCallbackData* data) { float step = *(float*)data->UserData; data->DesiredSize = ImVec2((int)(data->CurrentSize.x / step + 0.5f) * step, (int)(data->CurrentSize.y / step + 0.5f) * step); } }; const char* test_desc[] = { + "Between 100x100 and 500x500", + "At least 100x100", "Resize vertical only", "Resize horizontal only", - "Width > 100, Height > 100", - "Width 400-500", - "Height 400-500", + "Width Between 400 and 500", + "Custom: Aspect Ratio 16:9", "Custom: Always Square", "Custom: Fixed Steps (100)", }; + // Options static bool auto_resize = false; - static int type = 0; + static bool window_padding = true; + static int type = 5; // Aspect Ratio static int display_lines = 10; - if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only - if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only - if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 - if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 - if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 - if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square - if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)(intptr_t)100); // Fixed Step - ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; - if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + // Submit constraint + float aspect_ratio = 16.0f / 9.0f; + float fixed_step = 100.0f; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(500, 500)); // Between 100x100 and 500x500 + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width Between and 400 and 500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, (void*)&aspect_ratio); // Aspect ratio + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 7) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)&fixed_step); // Fixed Step + + // Submit window + if (!window_padding) + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + const ImGuiWindowFlags window_flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + const bool window_open = ImGui::Begin("Example: Constrained Resize", p_open, window_flags); + if (!window_padding) + ImGui::PopStyleVar(); + if (window_open) { IMGUI_DEMO_MARKER("Examples/Constrained Resizing window"); - if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); - if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); - if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } - ImGui::SetNextItemWidth(200); - ImGui::Combo("Constraint", &type, test_desc, IM_ARRAYSIZE(test_desc)); - ImGui::SetNextItemWidth(200); - ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); - ImGui::Checkbox("Auto-resize", &auto_resize); - for (int i = 0; i < display_lines; i++) - ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + if (ImGui::GetIO().KeyShift) + { + // Display a dummy viewport (in your real app you would likely use ImageButton() to display a texture. + ImVec2 avail_size = ImGui::GetContentRegionAvail(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::ColorButton("viewport", ImVec4(0.5f, 0.2f, 0.5f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, avail_size); + ImGui::SetCursorScreenPos(ImVec2(pos.x + 10, pos.y + 10)); + ImGui::Text("%.2f x %.2f", avail_size.x, avail_size.y); + } + else + { + ImGui::Text("(Hold SHIFT to display a dummy viewport)"); + if (ImGui::Button("Set 200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + 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::SetNextItemWidth(ImGui::GetFontSize() * 20); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::Checkbox("Auto-resize", &auto_resize); + ImGui::Checkbox("Window padding", &window_padding); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } } ImGui::End(); } @@ -7322,28 +7378,34 @@ static void ShowExampleAppConstrainedResize(bool* p_open) // + a context-menu to choose which corner of the screen to use. static void ShowExampleAppSimpleOverlay(bool* p_open) { - static int corner = 0; + static int location = 0; ImGuiIO& io = ImGui::GetIO(); ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; - if (corner != -1) + if (location >= 0) { const float PAD = 10.0f; const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any! ImVec2 work_size = viewport->WorkSize; ImVec2 window_pos, window_pos_pivot; - window_pos.x = (corner & 1) ? (work_pos.x + work_size.x - PAD) : (work_pos.x + PAD); - window_pos.y = (corner & 2) ? (work_pos.y + work_size.y - PAD) : (work_pos.y + PAD); - window_pos_pivot.x = (corner & 1) ? 1.0f : 0.0f; - window_pos_pivot.y = (corner & 2) ? 1.0f : 0.0f; + window_pos.x = (location & 1) ? (work_pos.x + work_size.x - PAD) : (work_pos.x + PAD); + window_pos.y = (location & 2) ? (work_pos.y + work_size.y - PAD) : (work_pos.y + PAD); + window_pos_pivot.x = (location & 1) ? 1.0f : 0.0f; + window_pos_pivot.y = (location & 2) ? 1.0f : 0.0f; ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); window_flags |= ImGuiWindowFlags_NoMove; } + else if (location == -2) + { + // Center window + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + window_flags |= ImGuiWindowFlags_NoMove; + } ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background if (ImGui::Begin("Example: Simple overlay", p_open, window_flags)) { IMGUI_DEMO_MARKER("Examples/Simple Overlay"); - ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); + ImGui::Text("Simple overlay\n" "(right-click to change position)"); ImGui::Separator(); if (ImGui::IsMousePosValid()) ImGui::Text("Mouse Position: (%.1f,%.1f)", io.MousePos.x, io.MousePos.y); @@ -7351,11 +7413,12 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) ImGui::Text("Mouse Position: "); if (ImGui::BeginPopupContextWindow()) { - if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1; - if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; - if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; - if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; - if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + if (ImGui::MenuItem("Custom", NULL, location == -1)) location = -1; + if (ImGui::MenuItem("Center", NULL, location == -2)) location = -2; + if (ImGui::MenuItem("Top-left", NULL, location == 0)) location = 0; + if (ImGui::MenuItem("Top-right", NULL, location == 1)) location = 1; + if (ImGui::MenuItem("Bottom-left", NULL, location == 2)) location = 2; + if (ImGui::MenuItem("Bottom-right", NULL, location == 3)) location = 3; if (p_open && ImGui::MenuItem("Close")) *p_open = false; ImGui::EndPopup(); } @@ -7403,8 +7466,8 @@ static void ShowExampleAppFullscreen(bool* p_open) // [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() //----------------------------------------------------------------------------- -// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. -// This apply to all regular items as well. +// Demonstrate the use of "##" and "###" in identifiers to manipulate ID generation. +// This applies to all regular items as well. // Read FAQ section "How can I have multiple widgets with the same label?" for details. static void ShowExampleAppWindowTitles(bool*) { diff --git a/external/imgui/imgui/imgui_draw.cpp b/external/imgui/imgui/imgui_draw.cpp index 256dd6f9..be2058ee 100644 --- a/external/imgui/imgui/imgui_draw.cpp +++ b/external/imgui/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (drawing and font code) /* @@ -26,38 +26,24 @@ Index of this file: #define _CRT_SECURE_NO_WARNINGS #endif -#include "imgui.h" -#ifndef IMGUI_DISABLE - #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif +#include "imgui.h" +#ifndef IMGUI_DISABLE #include "imgui_internal.h" #ifdef IMGUI_ENABLE_FREETYPE #include "misc/freetype/imgui_freetype.h" #endif #include // vsnprintf, sscanf, printf -#if !defined(alloca) -#if defined(__GLIBC__) || defined(__sun) || defined(__APPLE__) || defined(__NEWLIB__) -#include // alloca (glibc uses . Note that Cygwin may have _WIN32 defined, so the order matters here) -#elif defined(_WIN32) -#include // alloca -#if !defined(alloca) -#define alloca _alloca // for clang with MS Codegen -#endif -#else -#include // alloca -#endif -#endif // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#pragma warning (disable: 6255) // [Static Analyzer] _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead. #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) #endif @@ -67,9 +53,6 @@ Index of this file: #if __has_warning("-Wunknown-warning-option") #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! #endif -#if __has_warning("-Walloca") -#pragma clang diagnostic ignored "-Walloca" // warning: use of function '__builtin_alloca' is discouraged -#endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok. @@ -405,6 +388,8 @@ void ImDrawList::_ResetForNewFrame() IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, ClipRect) == 0); IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, TextureId) == sizeof(ImVec4)); IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + if (_Splitter._Count > 1) + _Splitter.Merge(this); CmdBuffer.resize(0); IdxBuffer.resize(0); @@ -463,11 +448,13 @@ void ImDrawList::AddDrawCmd() // Note that this leaves the ImDrawList in a state unfit for further commands, as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL void ImDrawList::_PopUnusedDrawCmd() { - if (CmdBuffer.Size == 0) - return; - ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - if (curr_cmd->ElemCount == 0 && curr_cmd->UserCallback == NULL) + while (CmdBuffer.Size > 0) + { + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount != 0 || curr_cmd->UserCallback != NULL) + return;// break; CmdBuffer.pop_back(); + } } void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) @@ -720,7 +707,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c // 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) { - if (points_count < 2) + if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; const bool closed = (flags & ImDrawFlags_Closed) != 0; @@ -753,7 +740,8 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // Temporary buffer // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((use_texture || !thick_line) ? 3 : 5) * sizeof(ImVec2)); //-V630 + _Data->TempBuffer.reserve_discard(points_count * ((use_texture || !thick_line) ? 3 : 5)); + ImVec2* temp_normals = _Data->TempBuffer.Data; ImVec2* temp_points = temp_normals + points_count; // Calculate normals (tangents) for each line segment @@ -977,7 +965,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) { - if (points_count < 3) + if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; const ImVec2 uv = _Data->TexUvWhitePixel; @@ -1001,7 +989,8 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun } // Compute normals - ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); //-V630 + _Data->TempBuffer.reserve_discard(points_count); + ImVec2* temp_normals = _Data->TempBuffer.Data; for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { const ImVec2& p0 = points[i0]; @@ -1295,6 +1284,7 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons ImVec2 p1 = _Path.back(); if (num_segments == 0) { + IM_ASSERT(_Data->CurveTessellationTol > 0.0f); PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated } else @@ -1310,6 +1300,7 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, ImVec2 p1 = _Path.back(); if (num_segments == 0) { + IM_ASSERT(_Data->CurveTessellationTol > 0.0f); PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated } else @@ -1324,6 +1315,7 @@ IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) { #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Obsoleted in 1.82 (from February 2021) // Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) // ~0 --> ImDrawFlags_RoundCornersAll or 0 if (flags == ~0) @@ -2298,10 +2290,11 @@ void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], fl void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) { + IM_ASSERT_PARANOID(w <= stride); unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride) - for (int i = 0; i < w; i++) - data[i] = table[data[i]]; + for (int j = h; j > 0; j--, data += stride - w) + for (int i = w; i > 0; i--, data++) + *data = table[*data]; } #ifdef IMGUI_ENABLE_STB_TRUETYPE @@ -2318,7 +2311,7 @@ struct ImFontBuildSrcData int GlyphsHighest; // Highest requested codepoint int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsMap) + ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet) }; // Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) @@ -2390,7 +2383,12 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault(); for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) + { + // Check for valid range. This may also help detect *some* dangling pointers, because a common + // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent. + IM_ASSERT(src_range[0] <= src_range[1]); src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); + } dst_tmp.SrcCount++; dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); } @@ -2818,6 +2816,17 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault() return &ranges[0]; } +const ImWchar* ImFontAtlas::GetGlyphRangesGreek() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0370, 0x03FF, // Greek and Coptic + 0, + }; + return &ranges[0]; +} + const ImWchar* ImFontAtlas::GetGlyphRangesKorean() { static const ImWchar ranges[] = @@ -2934,19 +2943,19 @@ const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() // 2999 ideograms code points for Japanese // - 2136 Joyo (meaning "for regular use" or "for common use") Kanji code points // - 863 Jinmeiyo (meaning "for personal name") Kanji code points - // - Sourced from the character information database of the Information-technology Promotion Agency, Japan - // - https://mojikiban.ipa.go.jp/mji/ - // - Available under the terms of the Creative Commons Attribution-ShareAlike 2.1 Japan (CC BY-SA 2.1 JP). - // - https://creativecommons.org/licenses/by-sa/2.1/jp/deed.en - // - https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode - // - You can generate this code by the script at: - // - https://github.com/vaiorabbit/everyday_use_kanji + // - Sourced from official information provided by the government agencies of Japan: + // - List of Joyo Kanji by the Agency for Cultural Affairs + // - https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kijun/naikaku/kanji/ + // - List of Jinmeiyo Kanji by the Ministry of Justice + // - http://www.moj.go.jp/MINJI/minji86.html + // - Available under the terms of the Creative Commons Attribution 4.0 International (CC BY 4.0). + // - https://creativecommons.org/licenses/by/4.0/legalcode + // - You can generate this code by the script at: + // - https://github.com/vaiorabbit/everyday_use_kanji // - References: // - List of Joyo Kanji - // - (Official list by the Agency for Cultural Affairs) https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/14/tosin02/index.html // - (Wikipedia) https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji // - List of Jinmeiyo Kanji - // - (Official list by the Ministry of Justice) http://www.moj.go.jp/MINJI/minji86.html // - (Wikipedia) https://en.wikipedia.org/wiki/Jinmeiy%C5%8D_kanji // - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi), see https://github.com/ocornut/imgui/pull/3627 for details. // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. @@ -3109,7 +3118,8 @@ ImFont::ImFont() FallbackAdvanceX = 0.0f; FallbackChar = (ImWchar)-1; EllipsisChar = (ImWchar)-1; - DotChar = (ImWchar)-1; + EllipsisWidth = EllipsisCharStep = 0.0f; + EllipsisCharCount = 0; FallbackGlyph = NULL; ContainerAtlas = NULL; ConfigData = NULL; @@ -3197,8 +3207,20 @@ void ImFont::BuildLookupTable() const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; if (EllipsisChar == (ImWchar)-1) EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); - if (DotChar == (ImWchar)-1) - DotChar = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); + const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); + if (EllipsisChar != (ImWchar)-1) + { + EllipsisCharCount = 1; + EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; + } + else if (dot_char != (ImWchar)-1) + { + const ImFontGlyph* glyph = FindGlyph(dot_char); + EllipsisChar = dot_char; + EllipsisCharCount = 3; + EllipsisCharStep = (glyph->X1 - glyph->X0) + 1.0f; + EllipsisWidth = EllipsisCharStep * 3.0f - 1.0f; + } // Setup fallback character const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; @@ -3330,11 +3352,21 @@ const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const return &Glyphs.Data[i]; } +// Wrapping skips upcoming blanks +static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) +{ + while (text < text_end && ImCharIsBlankA(*text)) + text++; + if (*text == '\n') + text++; + return text; +} + +// 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.) const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const { - // Simple word-wrapping for English, not full-featured. Please submit failing cases! - // 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.) - // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" // ^ ^ ^ ^ ^__ ^ ^ @@ -3346,7 +3378,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // Cut words that cannot possibly fit within one line. // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" - float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; @@ -3357,6 +3388,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c bool inside_word = true; const char* s = text; + IM_ASSERT(text_end != NULL); while (s < text_end) { unsigned int c = (unsigned int)*s; @@ -3365,8 +3397,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c next_s = s + 1; else next_s = s + ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) - break; if (c < 32) { @@ -3426,6 +3456,10 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c s = next_s; } + // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol). + if (s == text && text < text_end) + return s + 1; return s; } @@ -3450,11 +3484,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - { word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } if (s >= word_wrap_eol) { @@ -3463,13 +3493,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons text_size.y += line_height; line_width = 0.0f; word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } + s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; } } @@ -3478,15 +3502,9 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons const char* prev_s = s; unsigned int c = (unsigned int)*s; if (c < 0x80) - { s += 1; - } else - { s += ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) // Malformed UTF-8? - break; - } if (c < 32) { @@ -3554,15 +3572,25 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im const float scale = size / FontSize; const float line_height = FontSize * scale; const bool word_wrap_enabled = (wrap_width > 0.0f); - const char* word_wrap_eol = NULL; // Fast-forward to first visible line const char* s = text_begin; - if (y + line_height < clip_rect.y && !word_wrap_enabled) + if (y + line_height < clip_rect.y) while (y + line_height < clip_rect.y && s < text_end) { - s = (const char*)memchr(s, '\n', text_end - s); - s = s ? s + 1 : text_end; + const char* line_end = (const char*)memchr(s, '\n', text_end - s); + if (word_wrap_enabled) + { + // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPositionA(). + // If the specs for CalcWordWrapPositionA() were reworked to optionally return on \n we could combine both. + // However it is still better than nothing performing the fast-forward! + s = CalcWordWrapPositionA(scale, s, line_end ? line_end : text_end, wrap_width); + s = CalcWordWrapNextLineStartA(s, text_end); + } + else + { + s = line_end ? line_end + 1 : text_end; + } y += line_height; } @@ -3588,12 +3616,12 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im const int idx_count_max = (int)(text_end - s) * 6; const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; draw_list->PrimReserve(idx_count_max, vtx_count_max); - - ImDrawVert* vtx_write = draw_list->_VtxWritePtr; - ImDrawIdx* idx_write = draw_list->_IdxWritePtr; - unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_index = draw_list->_VtxCurrentIdx; const ImU32 col_untinted = col | ~IM_COL32_A_MASK; + const char* word_wrap_eol = NULL; while (s < text_end) { @@ -3601,24 +3629,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - { word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x)); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } if (s >= word_wrap_eol) { x = start_x; y += line_height; word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } + s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; } } @@ -3626,15 +3644,9 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im // Decode and advance source unsigned int c = (unsigned int)*s; if (c < 0x80) - { s += 1; - } else - { s += ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) // Malformed UTF-8? - break; - } if (c < 32) { @@ -3705,14 +3717,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: { - idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); - idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = glyph_col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = glyph_col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = glyph_col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + idx_write[0] = (ImDrawIdx)(vtx_index); idx_write[1] = (ImDrawIdx)(vtx_index + 1); idx_write[2] = (ImDrawIdx)(vtx_index + 2); + idx_write[3] = (ImDrawIdx)(vtx_index); idx_write[4] = (ImDrawIdx)(vtx_index + 2); idx_write[5] = (ImDrawIdx)(vtx_index + 3); vtx_write += 4; - vtx_current_idx += 4; + vtx_index += 4; idx_write += 6; } } @@ -3726,7 +3738,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); draw_list->_VtxWritePtr = vtx_write; draw_list->_IdxWritePtr = idx_write; - draw_list->_VtxCurrentIdx = vtx_current_idx; + draw_list->_VtxCurrentIdx = vtx_index; } //----------------------------------------------------------------------------- diff --git a/external/imgui/imgui/imgui_internal.h b/external/imgui/imgui/imgui_internal.h index ab76fb00..176e63d3 100644 --- a/external/imgui/imgui/imgui_internal.h +++ b/external/imgui/imgui/imgui_internal.h @@ -1,10 +1,12 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (internal structures/api) -// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! -// Set: -// #define IMGUI_DEFINE_MATH_OPERATORS -// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) +// You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. +// To implement maths operators for ImVec2 (disabled by default to not conflict with using IM_VEC2_CLASS_EXTRA with your own math types+operators), use: +/* +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" +*/ /* @@ -26,6 +28,7 @@ Index of this file: // [SECTION] Docking support // [SECTION] Viewport support // [SECTION] Settings support +// [SECTION] Localization support // [SECTION] Metrics, Debug tools // [SECTION] Generic context hooks // [SECTION] ImGuiContext (main imgui context) @@ -55,7 +58,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(IMGUI_DISABLE_SSE) +#if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) #define IMGUI_ENABLE_SSE #include #endif @@ -92,6 +95,12 @@ Index of this file: #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 #endif +// In 1.89.4, we moved the implementation of "courtesy maths operators" from imgui_internal.h in imgui.h +// As they are frequently requested, we do not want to encourage to many people using imgui_internal.h +#if defined(IMGUI_DEFINE_MATH_OPERATORS) && !defined(IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED) +#error Please '#define IMGUI_DEFINE_MATH_OPERATORS' _BEFORE_ including imgui.h! +#endif + // Legacy defines #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Renamed in 1.74 #error Use IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS @@ -121,6 +130,7 @@ struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDat struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box struct ImGuiLastItemData; // Status storage for last submitted items +struct ImGuiLocEntry; // A localization entry. struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions @@ -144,13 +154,17 @@ struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) +// Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. +enum ImGuiLocKey : int; // -> enum ImGuiLocKey // Enum: a localization entry for translation. typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical + +// Flags typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags -typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressedEx() -typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() -typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags +typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressed(), IsMouseClicked(), SetKeyOwner(), SetItemKeyOwner() etc. +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags +typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests @@ -200,16 +214,21 @@ namespace ImStb #ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS #define IMGUI_DEBUG_PRINTF(_FMT,...) printf(_FMT, __VA_ARGS__) #else -#define IMGUI_DEBUG_PRINTF(_FMT,...) +#define IMGUI_DEBUG_PRINTF(_FMT,...) ((void)0) #endif #endif // Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam. -#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) +#else +#define IMGUI_DEBUG_LOG(...) ((void)0) +#endif #define IMGUI_DEBUG_LOG_ACTIVEID(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_FOCUS(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_POPUP(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_NAV(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventNav) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts @@ -237,7 +256,9 @@ namespace ImStb #else #define IM_NEWLINE "\n" #endif +#ifndef IM_TABSIZE // Until we move this to runtime and/or add proper tab support, at least allow users to compile-time override #define IM_TABSIZE (4) +#endif #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 @@ -299,11 +320,12 @@ namespace ImStb // - Helper: ImSpan<>, ImSpanAllocator<> // - Helper: ImPool<> // - Helper: ImChunkStream<> +// - Helper: ImGuiTextIndex //----------------------------------------------------------------------------- // Helpers: Hashing -IMGUI_API ImGuiID ImHashData(const void* data, size_t data_size, ImU32 seed = 0); -IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, ImU32 seed = 0); +IMGUI_API ImGuiID ImHashData(const void* data, size_t data_size, ImGuiID seed = 0); +IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, ImGuiID seed = 0); // Helpers: Sorting #ifndef ImQsort @@ -331,8 +353,11 @@ IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* bu IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); IMGUI_API void ImStrTrimBlanks(char* str); IMGUI_API const char* ImStrSkipBlank(const char* str); +IM_MSVC_RUNTIME_CHECKS_OFF +static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Formatting IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); @@ -355,29 +380,6 @@ IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 -// Helpers: ImVec2/ImVec4 operators -// We are keeping those disabled by default so they don't leak in user space, to allow user enabling implicit cast operators between ImVec2 and their own types (using IM_VEC2_CLASS_EXTRA etc.) -// We unfortunately don't have a unary- operator for ImVec2 because this would needs to be defined inside the class itself. -#ifdef IMGUI_DEFINE_MATH_OPERATORS -IM_MSVC_RUNTIME_CHECKS_OFF -static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } -static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } -static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } -static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } -static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } -static 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); } -static 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); } -static 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); } -IM_MSVC_RUNTIME_CHECKS_RESTORE -#endif - // Helpers: File System #ifdef IMGUI_DISABLE_FILE_FUNCTIONS #define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS @@ -460,6 +462,7 @@ static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } +static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry @@ -530,9 +533,12 @@ struct IMGUI_API ImRect 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); } }; -IM_MSVC_RUNTIME_CHECKS_RESTORE // Helper: ImBitArray +#define IM_BITARRAY_TESTBIT(_ARRAY, _N) ((_ARRAY[(_N) >> 5] & ((ImU32)1 << ((_N) & 31))) != 0) // Macro version of ImBitArrayTestBit(): ensure args have side-effect or are costly! +#define IM_BITARRAY_CLEARBIT(_ARRAY, _N) ((_ARRAY[(_N) >> 5] &= ~((ImU32)1 << ((_N) & 31)))) // Macro version of ImBitArrayClearBit(): ensure args have side-effect or are costly! +inline size_t ImBitArrayGetStorageSizeInBytes(int bitcount) { return (size_t)((bitcount + 31) >> 5) << 2; } +inline void ImBitArrayClearAllBits(ImU32* arr, int bitcount){ memset(arr, 0, ImBitArrayGetStorageSizeInBytes(bitcount)); } inline bool ImBitArrayTestBit(const ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); return (arr[n >> 5] & mask) != 0; } inline void ImBitArrayClearBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] &= ~mask; } inline void ImBitArraySetBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] |= mask; } @@ -549,6 +555,8 @@ inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) // Works on ran } } +typedef ImU32* ImBitArrayPtr; // Name for use in structs + // Helper: ImBitArray class (wrapper over ImBitArray functions) // Store 1-bit per value. template @@ -558,11 +566,11 @@ struct ImBitArray 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 ImBitArrayTestBit(Storage, n); } + 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 ImBitArrayTestBit(Storage, n); } + bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); } }; // Helper: ImBitVector @@ -572,10 +580,11 @@ struct IMGUI_API ImBitVector ImVector Storage; void Create(int sz) { Storage.resize((sz + 31) >> 5); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); } void Clear() { Storage.clear(); } - bool TestBit(int n) const { IM_ASSERT(n < (Storage.Size << 5)); return ImBitArrayTestBit(Storage.Data, n); } + bool TestBit(int n) const { IM_ASSERT(n < (Storage.Size << 5)); return IM_BITARRAY_TESTBIT(Storage.Data, n); } void SetBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArraySetBit(Storage.Data, n); } void ClearBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); } }; +IM_MSVC_RUNTIME_CHECKS_RESTORE // Helper: ImSpan<> // Pointing to a span of data we don't own. @@ -688,6 +697,20 @@ struct ImChunkStream }; +// Helper: ImGuiTextIndex<> +// Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API. +struct ImGuiTextIndex +{ + ImVector LineOffsets; + int EndOffset = 0; // Because we don't own text buffer we need to maintain EndOffset (may bake in LineOffsets?) + + void clear() { LineOffsets.clear(); EndOffset = 0; } + int size() { return LineOffsets.Size; } + const char* get_line_begin(const char* base, int n) { return base + LineOffsets[n]; } + const char* get_line_end(const char* base, int n) { return base + (n + 1 < LineOffsets.Size ? (LineOffsets[n + 1] - 1) : EndOffset); } + void append(const char* base, int old_size, int new_size); +}; + //----------------------------------------------------------------------------- // [SECTION] ImDrawList support //----------------------------------------------------------------------------- @@ -729,6 +752,9 @@ struct IMGUI_API ImDrawListSharedData ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + // [Internal] Temp write buffer + ImVector TempBuffer; + // [Internal] Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() @@ -753,7 +779,10 @@ struct ImDrawDataBuilder // [SECTION] Widgets support: flags, enums, data structures //----------------------------------------------------------------------------- -// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// Flags used by upcoming items +// - input: PushItemFlag() manipulates g.CurrentItemFlags, ItemAdd() calls may add extra flags. +// - output: stored in g.LastItemData.InFlags +// Current window shared by all windows. // This is going to be exposed in imgui.h when stabilized enough. enum ImGuiItemFlags_ { @@ -767,12 +796,14 @@ enum ImGuiItemFlags_ ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window ImGuiItemFlags_MixedValue = 1 << 6, // 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_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. + ImGuiItemFlags_NoWindowHoverableCheck = 1 << 8, // false // Disable hoverable check in ItemHoverable() // Controlled by widget code - ImGuiItemFlags_Inputable = 1 << 8, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. + ImGuiItemFlags_Inputable = 1 << 10, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. }; -// Storage for LastItem data +// Status flags for an already submitted item +// - output: stored in g.LastItemData.StatusFlags enum ImGuiItemStatusFlags_ { ImGuiItemStatusFlags_None = 0, @@ -785,12 +816,15 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_Deactivated = 1 << 6, // Only valid if ImGuiItemStatusFlags_HasDeactivated is set. ImGuiItemStatusFlags_HoveredWindow = 1 << 7, // Override the HoveredWindow test to allow cross-window hover testing. ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Set when the Focusable item just got focused by Tabbing (FIXME: to be removed soon) + ImGuiItemStatusFlags_Visible = 1 << 9, // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()). + // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE ImGuiItemStatusFlags_Openable = 1 << 20, // Item is an openable (e.g. TreeNode) - ImGuiItemStatusFlags_Opened = 1 << 21, // + ImGuiItemStatusFlags_Opened = 1 << 21, // Opened status ImGuiItemStatusFlags_Checkable = 1 << 22, // Item is a checkable (e.g. CheckBox, MenuItem) - ImGuiItemStatusFlags_Checked = 1 << 23, // + ImGuiItemStatusFlags_Checked = 1 << 23, // Checked status + ImGuiItemStatusFlags_Inputable = 1 << 24, // Item is a text-inputable (e.g. InputText, SliderXXX, DragXXX) #endif }; @@ -820,8 +854,10 @@ enum ImGuiButtonFlagsPrivate_ 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 ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) - ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated + ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used everytime an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item + ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) + ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, }; @@ -848,9 +884,9 @@ enum ImGuiSelectableFlagsPrivate_ ImGuiSelectableFlags_SelectOnClick = 1 << 22, // Override button behavior to react on Click (default is Click+Release) ImGuiSelectableFlags_SelectOnRelease = 1 << 23, // Override button behavior to react on Release (default is Click+Release) ImGuiSelectableFlags_SpanAvailWidth = 1 << 24, // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) - ImGuiSelectableFlags_DrawHoveredWhenHeld = 1 << 25, // Always show active when held, even is not hovered. This concept could probably be renamed/formalized somehow. - ImGuiSelectableFlags_SetNavIdOnHover = 1 << 26, // Set Nav/Focus ID on mouse hover (used by MenuItem) - ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 27, // Disable padding each side with ItemSpacing * 0.5f + ImGuiSelectableFlags_SetNavIdOnHover = 1 << 25, // Set Nav/Focus ID on mouse hover (used by MenuItem) + ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 26, // Disable padding each side with ItemSpacing * 0.5f + ImGuiSelectableFlags_NoSetKeyOwner = 1 << 27, // Don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) }; // Extend ImGuiTreeNodeFlags_ @@ -1007,6 +1043,7 @@ struct IMGUI_API ImGuiMenuColumns // For a given item ID, access with ImGui::GetInputTextState() struct IMGUI_API ImGuiInputTextState { + ImGuiContext* Ctx; // parent dear imgui context ImGuiID ID; // widget id owning the text state int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. @@ -1020,9 +1057,9 @@ struct IMGUI_API ImGuiInputTextState bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool Edited; // edited this frame - ImGuiInputTextFlags Flags; // copy of InputText() flags + ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. - ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } + ImGuiInputTextState(ImGuiContext* ctx) { memset(this, 0, sizeof(*this)); Ctx = ctx;} void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } void ClearFreeMemory() { TextW.clear(); TextA.clear(); InitialTextA.clear(); } int GetUndoAvailCount() const { return Stb.undostate.undo_point; } @@ -1045,7 +1082,7 @@ struct ImGuiPopupData { ImGuiID PopupId; // Set on OpenPopup() ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() - ImGuiWindow* SourceWindow; // Set on OpenPopup() copy of NavWindow at the time of opening the popup + ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value int OpenFrameCount; // Set on OpenPopup() ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) @@ -1168,30 +1205,30 @@ struct ImGuiPtrOrIndex // [SECTION] Inputs support //----------------------------------------------------------------------------- +// Bit array for named keys typedef ImBitArray ImBitArrayForNamedKeys; -// Extend ImGuiKey_ -enum ImGuiKeyPrivate_ -{ - ImGuiKey_LegacyNativeKey_BEGIN = 0, - ImGuiKey_LegacyNativeKey_END = 512, - ImGuiKey_Keyboard_BEGIN = ImGuiKey_NamedKey_BEGIN, - ImGuiKey_Keyboard_END = ImGuiKey_GamepadStart, - ImGuiKey_Gamepad_BEGIN = ImGuiKey_GamepadStart, - ImGuiKey_Gamepad_END = ImGuiKey_GamepadRStickDown + 1, - ImGuiKey_Aliases_BEGIN = ImGuiKey_MouseLeft, - ImGuiKey_Aliases_END = ImGuiKey_COUNT, +// [Internal] Key ranges +#define ImGuiKey_LegacyNativeKey_BEGIN 0 +#define ImGuiKey_LegacyNativeKey_END 512 +#define ImGuiKey_Keyboard_BEGIN (ImGuiKey_NamedKey_BEGIN) +#define ImGuiKey_Keyboard_END (ImGuiKey_GamepadStart) +#define ImGuiKey_Gamepad_BEGIN (ImGuiKey_GamepadStart) +#define ImGuiKey_Gamepad_END (ImGuiKey_GamepadRStickDown + 1) +#define ImGuiKey_Mouse_BEGIN (ImGuiKey_MouseLeft) +#define ImGuiKey_Mouse_END (ImGuiKey_MouseWheelY + 1) +#define ImGuiKey_Aliases_BEGIN (ImGuiKey_Mouse_BEGIN) +#define ImGuiKey_Aliases_END (ImGuiKey_Mouse_END) - // [Internal] Named shortcuts for Navigation - ImGuiKey_NavKeyboardTweakSlow = ImGuiKey_ModCtrl, - ImGuiKey_NavKeyboardTweakFast = ImGuiKey_ModShift, - ImGuiKey_NavGamepadTweakSlow = ImGuiKey_GamepadL1, - ImGuiKey_NavGamepadTweakFast = ImGuiKey_GamepadR1, - ImGuiKey_NavGamepadActivate = ImGuiKey_GamepadFaceDown, - ImGuiKey_NavGamepadCancel = ImGuiKey_GamepadFaceRight, - ImGuiKey_NavGamepadMenu = ImGuiKey_GamepadFaceLeft, - ImGuiKey_NavGamepadInput = ImGuiKey_GamepadFaceUp, -}; +// [Internal] Named shortcuts for Navigation +#define ImGuiKey_NavKeyboardTweakSlow ImGuiMod_Ctrl +#define ImGuiKey_NavKeyboardTweakFast ImGuiMod_Shift +#define ImGuiKey_NavGamepadTweakSlow ImGuiKey_GamepadL1 +#define ImGuiKey_NavGamepadTweakFast ImGuiKey_GamepadR1 +#define ImGuiKey_NavGamepadActivate ImGuiKey_GamepadFaceDown +#define ImGuiKey_NavGamepadCancel ImGuiKey_GamepadFaceRight +#define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft +#define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp enum ImGuiInputEventType { @@ -1238,23 +1275,99 @@ struct ImGuiInputEvent ImGuiInputEventText Text; // if Type == ImGuiInputEventType_Text ImGuiInputEventAppFocused AppFocused; // if Type == ImGuiInputEventType_Focus }; - bool IgnoredAsSame; bool AddedByTestEngine; ImGuiInputEvent() { memset(this, 0, sizeof(*this)); } }; -// Flags for IsKeyPressedEx(). In upcoming feature this will be used more (and IsKeyPressedEx() renamed) +// Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior. +#define ImGuiKeyOwner_Any ((ImGuiID)0) // Accept key that have an owner, UNLESS a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. +#define ImGuiKeyOwner_None ((ImGuiID)-1) // Require key to have no owner. + +typedef ImS16 ImGuiKeyRoutingIndex; + +// Routing table entry (sizeof() == 16 bytes) +struct ImGuiKeyRoutingData +{ + ImGuiKeyRoutingIndex NextEntryIndex; + ImU16 Mods; // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits. ImGuiMod_Shortcut is already translated to Ctrl/Super. + ImU8 RoutingNextScore; // Lower is better (0: perfect score) + ImGuiID RoutingCurr; + ImGuiID RoutingNext; + + ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_None; } +}; + +// Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching. +// Stored in main context (1 instance) +struct ImGuiKeyRoutingTable +{ + ImGuiKeyRoutingIndex Index[ImGuiKey_NamedKey_COUNT]; // Index of first entry in Entries[] + ImVector Entries; + 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(); } +}; + +// This extends ImGuiKeyData but only for named keys (legacy keys don't support the new features) +// Stored in main context (1 per named key). In the future it might be merged into ImGuiKeyData. +struct ImGuiKeyOwnerData +{ + ImGuiID OwnerCurr; + ImGuiID OwnerNext; + bool LockThisFrame; // Reading this key requires explicit owner id (until end of frame). Set by ImGuiInputFlags_LockThisFrame. + bool LockUntilRelease; // Reading this key requires explicit owner id (until key is released). Set by ImGuiInputFlags_LockUntilRelease. When this is true LockThisFrame is always true as well. + + ImGuiKeyOwnerData() { OwnerCurr = OwnerNext = ImGuiKeyOwner_None; LockThisFrame = LockUntilRelease = false; } +}; + +// Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() // Don't mistake with ImGuiInputTextFlags! (for ImGui::InputText() function) enum ImGuiInputFlags_ { - // Flags for IsKeyPressedEx() + // Flags for IsKeyPressed(), IsMouseClicked(), Shortcut() ImGuiInputFlags_None = 0, ImGuiInputFlags_Repeat = 1 << 0, // Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, + + // Flags for SetItemKeyOwner() + ImGuiInputFlags_CondHovered = 1 << 4, // Only set if item is hovered (default to both) + ImGuiInputFlags_CondActive = 1 << 5, // Only set if item is active (default to both) + ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + + // Flags for SetKeyOwner(), SetItemKeyOwner() + ImGuiInputFlags_LockThisFrame = 1 << 6, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. + ImGuiInputFlags_LockUntilRelease = 1 << 7, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. + + // Routing policies for Shortcut() + low-level SetShortcutRouting() + // - The general idea is that several callers register interest in a shortcut, and only one owner gets it. + // - When a policy (other than _RouteAlways) is set, Shortcut() will register itself with SetShortcutRouting(), + // allowing the system to decide where to route the input among other route-aware calls. + // - Shortcut() uses ImGuiInputFlags_RouteFocused by default: meaning that a simple Shortcut() poll + // will register a route and only succeed when parent window is in the focus stack and if no-one + // with a higher priority is claiming the shortcut. + // - Using ImGuiInputFlags_RouteAlways is roughly equivalent to doing e.g. IsKeyPressed(key) + testing mods. + // - Priorities: GlobalHigh > Focused (when owner is active item) > Global > Focused (when focused window) > GlobalLow. + // - Can select only 1 policy among all available. + ImGuiInputFlags_RouteFocused = 1 << 8, // (Default) Register focused route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. + ImGuiInputFlags_RouteGlobalLow = 1 << 9, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority. + ImGuiInputFlags_RouteGlobal = 1 << 10, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText). + ImGuiInputFlags_RouteGlobalHigh = 1 << 11, // Register route globally (highest priority: unlikely you need to use that: will interfere with every active items) + ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! + ImGuiInputFlags_RouteAlways = 1 << 12, // Do not register route, poll keys directly. + ImGuiInputFlags_RouteUnlessBgFocused= 1 << 13, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. + ImGuiInputFlags_RouteExtraMask_ = ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, + + // [Internal] Mask of which function support which flags + ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_, + ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteExtraMask_, + ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, + ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, }; //----------------------------------------------------------------------------- @@ -1480,6 +1593,7 @@ struct ImGuiWindowSettings ImVec2ih Size; bool Collapsed; 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)); } char* GetName() { return (char*)(this + 1); } @@ -1500,6 +1614,30 @@ struct ImGuiSettingsHandler ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } }; +//----------------------------------------------------------------------------- +// [SECTION] Localization support +//----------------------------------------------------------------------------- + +// This is experimental and not officially supported, it'll probably fall short of features, if/when it does we may backtrack. +enum ImGuiLocKey : int +{ + ImGuiLocKey_TableSizeOne, + ImGuiLocKey_TableSizeAllFit, + ImGuiLocKey_TableSizeAllDefault, + ImGuiLocKey_TableResetOrder, + ImGuiLocKey_WindowingMainMenuBar, + ImGuiLocKey_WindowingPopup, + ImGuiLocKey_WindowingUntitled, + ImGuiLocKey_COUNT +}; + +struct ImGuiLocEntry +{ + ImGuiLocKey Key; + const char* Text; +}; + + //----------------------------------------------------------------------------- // [SECTION] Metrics, Debug Tools //----------------------------------------------------------------------------- @@ -1512,30 +1650,24 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventFocus = 1 << 1, ImGuiDebugLogFlags_EventPopup = 1 << 2, ImGuiDebugLogFlags_EventNav = 1 << 3, - ImGuiDebugLogFlags_EventIO = 1 << 4, - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventIO, + ImGuiDebugLogFlags_EventClipper = 1 << 4, + ImGuiDebugLogFlags_EventIO = 1 << 5, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventIO, ImGuiDebugLogFlags_OutputToTTY = 1 << 10, // Also send output to TTY }; struct ImGuiMetricsConfig { - bool ShowDebugLog; - bool ShowStackTool; - bool ShowWindowsRects; - bool ShowWindowsBeginOrder; - bool ShowTablesRects; - bool ShowDrawCmdMesh; - bool ShowDrawCmdBoundingBoxes; - int ShowWindowsRectsType; - int ShowTablesRectsType; - - ImGuiMetricsConfig() - { - ShowDebugLog = ShowStackTool = ShowWindowsRects = ShowWindowsBeginOrder = ShowTablesRects = false; - ShowDrawCmdMesh = true; - ShowDrawCmdBoundingBoxes = true; - ShowWindowsRectsType = ShowTablesRectsType = -1; - } + bool ShowDebugLog = false; + bool ShowStackTool = false; + bool ShowWindowsRects = false; + bool ShowWindowsBeginOrder = false; + bool ShowTablesRects = false; + bool ShowDrawCmdMesh = true; + bool ShowDrawCmdBoundingBoxes = true; + bool ShowAtlasTintedWithTextColor = false; + int ShowWindowsRectsType = -1; + int ShowTablesRectsType = -1; }; struct ImGuiStackLevelInfo @@ -1621,15 +1753,16 @@ struct ImGuiContext ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow. ImGuiWindow* WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window. ImVec2 WheelingWindowRefMousePos; - float WheelingWindowTimer; + int WheelingWindowStartFrame; // This may be set one frame before WheelingWindow is != NULL + float WheelingWindowReleaseTimer; + ImVec2 WheelingWindowWheelRemainder; + ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; bool HoveredIdAllowOverlap; - bool HoveredIdUsingMouseWheel; // Hovered widget will use mouse wheel. Blocks scrolling the underlying window. - bool HoveredIdPreviousFrameUsingMouseWheel; bool HoveredIdDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0. float HoveredIdTimer; // Measure contiguous hovering time float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active @@ -1653,15 +1786,22 @@ struct ImGuiContext ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. - // Input Ownership + // [EXPERIMENTAL] Key/Input Ownership + Shortcut Routing system + // - The idea is that instead of "eating" a given key, we can link to an owner. + // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. + // - Routing is requested ahead of time for a given chord (Key + Mods) and granted in NewFrame(). + ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT]; + ImGuiKeyRoutingTable KeysRoutingTable; ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) - ImBitArrayForNamedKeys ActiveIdUsingKeyInputMask; // Active widget will want to read those key inputs. When we grow the ImGuiKey enum we'll need to either to order the enum to make useful keys come first, either redesign this into e.g. a small array. + bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (FIXME: This is a shortcut for not taking ownership of 100+ keys but perhaps best to not have the inconsistency) #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes 'SetActiveIdUsingKey(ImGuiKey_Escape); SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel);' + ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' #endif // Next window/item data + ImGuiID CurrentFocusScopeId; // == g.FocusScopeStack.back() ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() + ImGuiID DebugLocateId; // Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions @@ -1670,7 +1810,7 @@ struct ImGuiContext ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() - ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - not inherited by Begin(), unless child window + ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVectorItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVectorGroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVectorOpenPopupStack; // Which popups are open (persistent) @@ -1691,7 +1831,7 @@ struct ImGuiContext ImGuiActivateFlags NavActivateFlags; ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). - ImGuiModFlags NavJustMovedToKeyMods; + ImGuiKeyChord NavJustMovedToKeyMods; ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS WILL ONLY BE None or NavGamepad or NavKeyboard. @@ -1712,7 +1852,7 @@ struct ImGuiContext bool NavMoveForwardToNextFrame; ImGuiNavMoveFlags NavMoveFlags; ImGuiScrollFlags NavMoveScrollFlags; - ImGuiModFlags NavMoveKeyMods; + ImGuiKeyChord NavMoveKeyMods; ImGuiDir NavMoveDir; // Direction of the move request (left/right/up/down) ImGuiDir NavMoveDirForDebug; ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? @@ -1727,6 +1867,8 @@ struct ImGuiContext ImGuiNavItemData NavTabbingResultFirst; // First tabbing request candidate within NavWindow and flattened hierarchy // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) + ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab, for reconfiguration (see #4828) + ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab 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 @@ -1777,15 +1919,23 @@ struct ImGuiContext ImVector CurrentTabBarStack; ImVector ShrinkWidthBuffer; + // Hover Delay system + ImGuiID HoverDelayId; + ImGuiID HoverDelayIdPreviousFrame; + float HoverDelayTimer; // Currently used IsItemHovered(), generally inferred from g.HoveredIdTimer but kept uncleared until clear timer elapse. + float HoverDelayClearTimer; // Currently used IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared. + // Widget state ImVec2 MouseLastValidPos; ImGuiInputTextState InputTextState; ImFont InputTextPasswordFont; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets - float ColorEditLastHue; // Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips - float ColorEditLastSat; // Backup of last Saturation associated to LastColor, so we can restore Saturation in lossy RGB<>HSV round trips - ImU32 ColorEditLastColor; // RGB value with alpha set to 0. + ImGuiID ColorEditCurrentID; // Set temporarily while inside of the parent-most ColorEdit4/ColorPicker4 (because they call each others). + ImGuiID ColorEditSavedID; // ID we are saving/restoring HS for + float ColorEditSavedHue; // Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips + float ColorEditSavedSat; // Backup of last Saturation associated to LastColor, so we can restore Saturation in lossy RGB<>HSV round trips + ImU32 ColorEditSavedColor; // RGB value with alpha set to 0. ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the color picker. ImGuiComboPreviewData ComboPreviewData; float SliderGrabClickOffset; @@ -1798,7 +1948,6 @@ struct ImGuiContext float DisabledAlphaBackup; // Backup for style.Alpha for BeginDisabled() short DisabledStackSize; short TooltipOverrideCount; - float TooltipSlowDelay; // Time before slow tooltips appears (FIXME: This is temporary until we merge in tooltip timer+priority work) ImVector ClipboardHandlerData; // If no custom clipboard handler is defined ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once @@ -1817,6 +1966,9 @@ struct ImGuiContext ImVector Hooks; // Hooks for extensions (e.g. test engine) ImGuiID HookIdNext; // Next available HookId + // Localization + const char* LocalizationTable[ImGuiLocKey_COUNT]; + // Capture/Logging bool LogEnabled; // Currently capturing ImGuiLogType LogType; // Capture target @@ -1833,6 +1985,8 @@ struct ImGuiContext // Debug Tools ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; + ImGuiTextIndex DebugLogIndex; + ImU8 DebugLocateFrames; // For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above. bool DebugItemPickerActive; // Item picker is active (started with DebugStartItemPicker()) ImU8 DebugItemPickerMouseButton; ImGuiID DebugItemPickerBreakId; // Will call IM_DEBUG_BREAK() when encountering this ID @@ -1850,6 +2004,7 @@ struct ImGuiContext ImVector TempBuffer; // Temporary text buffer ImGuiContext(ImFontAtlas* shared_font_atlas) + : InputTextState(this) { Initialized = false; FontAtlasOwnedByContext = shared_font_atlas ? false : true; @@ -1870,12 +2025,12 @@ struct ImGuiContext HoveredWindowUnderMovingWindow = NULL; MovingWindow = NULL; WheelingWindow = NULL; - WheelingWindowTimer = 0.0f; + WheelingWindowStartFrame = -1; + WheelingWindowReleaseTimer = 0.0f; DebugHookIdInfo = 0; HoveredId = HoveredIdPreviousFrame = 0; HoveredIdAllowOverlap = false; - HoveredIdUsingMouseWheel = HoveredIdPreviousFrameUsingMouseWheel = false; HoveredIdDisabled = false; HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; ActiveId = 0; @@ -1899,11 +2054,12 @@ struct ImGuiContext LastActiveIdTimer = 0.0f; ActiveIdUsingNavDirMask = 0x00; - ActiveIdUsingKeyInputMask.ClearAllBits(); + ActiveIdUsingAllKeyboardKeys = false; #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO ActiveIdUsingNavInputMask = 0x00; #endif + CurrentFocusScopeId = 0; CurrentItemFlags = ImGuiItemFlags_None; BeginMenuCount = 0; @@ -1911,7 +2067,7 @@ struct ImGuiContext NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavActivateInputId = 0; NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; - NavJustMovedToKeyMods = ImGuiModFlags_None; + NavJustMovedToKeyMods = ImGuiMod_None; NavInputSource = ImGuiInputSource_None; NavLayer = ImGuiNavLayer_Main; NavIdIsAlive = false; @@ -1927,12 +2083,14 @@ struct ImGuiContext NavMoveForwardToNextFrame = false; NavMoveFlags = ImGuiNavMoveFlags_None; NavMoveScrollFlags = ImGuiScrollFlags_None; - NavMoveKeyMods = ImGuiModFlags_None; + NavMoveKeyMods = ImGuiMod_None; NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None; NavScoringDebugCount = 0; NavTabbingDir = 0; NavTabbingCounter = 0; + ConfigNavWindowingKeyNext = ImGuiMod_Ctrl | ImGuiKey_Tab; + ConfigNavWindowingKeyPrev = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab; NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; @@ -1958,21 +2116,24 @@ struct ImGuiContext TablesTempDataStacked = 0; CurrentTabBar = NULL; + HoverDelayId = HoverDelayIdPreviousFrame = 0; + HoverDelayTimer = HoverDelayClearTimer = 0.0f; + TempInputId = 0; ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; - ColorEditLastHue = ColorEditLastSat = 0.0f; - ColorEditLastColor = 0; + ColorEditCurrentID = ColorEditSavedID = 0; + ColorEditSavedHue = ColorEditSavedSat = 0.0f; + ColorEditSavedColor = 0; SliderGrabClickOffset = 0.0f; SliderCurrentAccum = 0.0f; SliderCurrentAccumDirty = false; DragCurrentAccumDirty = false; DragCurrentAccum = 0.0f; DragSpeedDefaultRatio = 1.0f / 100.0f; + ScrollbarClickDeltaToGrabCenter = 0.0f; DisabledAlphaBackup = 0.0f; DisabledStackSize = 0; - ScrollbarClickDeltaToGrabCenter = 0.0f; TooltipOverrideCount = 0; - TooltipSlowDelay = 0.50f; PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission @@ -1982,6 +2143,8 @@ struct ImGuiContext SettingsDirtyTimer = 0.0f; HookIdNext = 0; + memset(LocalizationTable, 0, sizeof(LocalizationTable)); + LogEnabled = false; LogType = ImGuiLogType_None; LogNextPrefix = LogNextSuffix = NULL; @@ -1992,6 +2155,8 @@ struct ImGuiContext LogDepthToExpand = LogDepthToExpandDefault = 2; DebugLogFlags = ImGuiDebugLogFlags_OutputToTTY; + DebugLocateId = 0; + DebugLocateFrames = 0; DebugItemPickerActive = false; DebugItemPickerMouseButton = ImGuiMouseButton_Left; DebugItemPickerBreakId = 0; @@ -2023,6 +2188,7 @@ struct IMGUI_API ImGuiWindowTempData float CurrLineTextBaseOffset; // Baseline offset (0.0f by default on a new line, generally == style.FramePadding.y when a framed item has been added). float PrevLineTextBaseOffset; bool IsSameLine; + bool IsSetPos; ImVec1 Indent; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) ImVec1 ColumnsOffset; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. ImVec1 GroupOffset; @@ -2032,7 +2198,6 @@ struct IMGUI_API ImGuiWindowTempData ImGuiNavLayer NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) short NavLayersActiveMask; // Which layers have been written to (result from previous frame) short NavLayersActiveMaskNext;// Which layers have been written to (accumulator for current frame) - ImGuiID NavFocusScopeIdCurrent; // Current focus scope ID while appending bool NavHideHighlightOneFrame; bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) @@ -2073,6 +2238,9 @@ struct IMGUI_API ImGuiWindow ImVec2 WindowPadding; // Window padding at the time of Begin(). float WindowRounding; // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc. float WindowBorderSize; // Window border size at the time of Begin(). + float DecoOuterSizeX1, DecoOuterSizeY1; // Left/Up offsets. Sum of non-scrolling outer decorations (X1 generally == 0.0f. Y1 generally = TitleBarHeight + MenuBarHeight). Locked during Begin(). + float DecoOuterSizeX2, DecoOuterSizeY2; // Right/Down offsets (X2 generally == ScrollbarSize.x, Y2 == ScrollbarSizes.y). + float DecoInnerSizeX1, DecoInnerSizeY1; // Applied AFTER/OVER InnerRect. Specialized for Tables as they use specialized form of clipping and frozen rows/columns are inside InnerRect (and not part of regular decoration sizes). int NameBufLen; // Size of buffer storing Name. May be larger than strlen(Name)! ImGuiID MoveId; // == window->GetID("#MOVE") ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) @@ -2096,6 +2264,7 @@ struct IMGUI_API ImGuiWindow bool HasCloseButton; // Set when the window has a close button (p_open != NULL) signed char ResizeBorderHeld; // Current border being held for resize (-1: none, otherwise 0-3) short BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + short BeginCountPreviousFrame; // Number of Begin() during the previous frame short BeginOrderWithinParent; // Begin() order within immediate parent window, if we are a child window. Otherwise 0. short BeginOrderWithinContext; // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues. short FocusOrder; // Order within WindowsFocusOrder[], altered when windows are focused. @@ -2149,6 +2318,7 @@ struct IMGUI_API ImGuiWindow ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) ImRect NavRectRel[ImGuiNavLayer_COUNT]; // Reference rectangle, in window relative space + ImGuiID NavRootFocusScopeId; // Focus Scope ID at the time of Begin() int MemoryDrawListIdxCapacity; // Backup of last idx/vtx count, so when waking up the window we can preallocate and avoid iterative alloc/copy int MemoryDrawListVtxCapacity; @@ -2163,7 +2333,7 @@ public: ImGuiID GetID(int n); ImGuiID GetIDFromRectangle(const ImRect& r_abs); - // We don't use g.FontSize because the window may be != g.CurrentWidow. + // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } float CalcFontSize() const { ImGuiContext& g = *GImGui; float scale = g.FontBaseSize * FontWindowScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } float TitleBarHeight() const { ImGuiContext& g = *GImGui; return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + g.Style.FramePadding.y * 2.0f; } @@ -2205,10 +2375,10 @@ struct ImGuiTabItem float RequestedWidth; // Width optionally requested by caller, -1.0f is unused ImS32 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS16 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable - ImS16 IndexDuringLayout; // Index only used during TabBarLayout() + 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; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } + ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } }; // Storage for a tab bar (sizeof() 152 bytes) @@ -2247,12 +2417,6 @@ struct IMGUI_API ImGuiTabBar ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer. ImGuiTabBar(); - int GetTabOrder(const ImGuiTabItem* tab) const { return Tabs.index_from_ptr(tab); } - const char* GetTabName(const ImGuiTabItem* tab) const - { - IM_ASSERT(tab->NameOffset != -1 && tab->NameOffset < TabsNames.Buf.Size); - return TabsNames.Buf.Data + tab->NameOffset; - } }; //----------------------------------------------------------------------------- @@ -2260,12 +2424,11 @@ struct IMGUI_API ImGuiTabBar //----------------------------------------------------------------------------- #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. -#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. -#define IMGUI_TABLE_MAX_DRAW_CHANNELS (4 + 64 * 2) // See TableSetupDrawChannels() +#define IMGUI_TABLE_MAX_COLUMNS 512 // May be further lifted // Our current column maximum is 64 but we may raise that in the future. -typedef ImS8 ImGuiTableColumnIdx; -typedef ImU8 ImGuiTableDrawChannelIdx; +typedef ImS16 ImGuiTableColumnIdx; +typedef ImU16 ImGuiTableDrawChannelIdx; // [Internal] sizeof() ~ 104 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. @@ -2336,16 +2499,18 @@ struct ImGuiTableCellData ImGuiTableColumnIdx Column; // Column number }; -// Per-instance data that needs preserving across frames (seemingly most others do not need to be preserved aside from debug needs, does that needs they could be moved to ImGuiTableTempData ?) +// Per-instance data that needs preserving across frames (seemingly most others do not need to be preserved aside from debug needs. Does that means they could be moved to ImGuiTableTempData?) struct ImGuiTableInstanceData { - float LastOuterHeight; // Outer height from last frame // FIXME: multi-instance issue (#3955) - float LastFirstRowHeight; // Height of first row from last frame // FIXME: possible multi-instance issue? + ImGuiID TableInstanceID; + float LastOuterHeight; // Outer height from last frame + float LastFirstRowHeight; // Height of first row from last frame (FIXME: this is used as "header height" and may be reworked) + float LastFrozenHeight; // Height of frozen section from last frame - ImGuiTableInstanceData() { LastOuterHeight = LastFirstRowHeight = 0.0f; } + ImGuiTableInstanceData() { TableInstanceID = 0; LastOuterHeight = LastFirstRowHeight = LastFrozenHeight = 0.0f; } }; -// FIXME-TABLE: more transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData +// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs, incoming RowData struct IMGUI_API ImGuiTable { ImGuiID ID; @@ -2355,10 +2520,9 @@ struct IMGUI_API ImGuiTable ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. - ImU64 EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map - ImU64 EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data - ImU64 VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect) - ImU64 RequestOutputMaskByIndex; // Column Index -> IsVisible || AutoFit (== expect user to submit items) + ImBitArrayPtr EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map + ImBitArrayPtr EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data + ImBitArrayPtr VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect) ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; @@ -2450,6 +2614,8 @@ struct IMGUI_API ImGuiTable bool IsResetDisplayOrderRequest; bool IsUnfrozenRows; // Set when we got past the frozen row. bool IsDefaultSizingPolicy; // Set if user didn't explicitly set a sizing policy in BeginTable() + bool HasScrollbarYCurr; // Whether ANY instance of this table had a vertical scrollbar during the current frame. + bool HasScrollbarYPrev; // Whether ANY instance of this table had a vertical scrollbar during the previous. bool MemoryCompacted; bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis @@ -2587,15 +2753,21 @@ namespace ImGui IMGUI_API void MarkIniSettingsDirty(); IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); IMGUI_API void ClearIniSettings(); - IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); - IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); - IMGUI_API ImGuiWindowSettings* FindOrCreateWindowSettings(const char* name); IMGUI_API void AddSettingsHandler(const ImGuiSettingsHandler* handler); IMGUI_API void RemoveSettingsHandler(const char* type_name); IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + // Settings - Windows + IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); + IMGUI_API ImGuiWindowSettings* FindWindowSettingsByID(ImGuiID id); + IMGUI_API ImGuiWindowSettings* FindWindowSettingsByWindow(ImGuiWindow* window); + IMGUI_API void ClearWindowSettings(const char* name); + + // Localization + IMGUI_API void LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count); + inline const char* LocalizeGetMsg(ImGuiLocKey key) { ImGuiContext& g = *GImGui; const char* msg = g.LocalizationTable[key]; return msg ? msg : "*Missing Text*"; } + // Scrolling - IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // Use -1.0f on one axis to leave as-is IMGUI_API void SetScrollX(ImGuiWindow* window, float scroll_x); IMGUI_API void SetScrollY(ImGuiWindow* window, float scroll_y); IMGUI_API void SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio); @@ -2610,7 +2782,6 @@ namespace ImGui //#endif // Basic Accessors - inline ImGuiID GetItemID() { ImGuiContext& g = *GImGui; return g.LastItemData.ID; } // Get ID of last item (~~ often same ImGui::GetID(label) beforehand) inline ImGuiItemStatusFlags GetItemStatusFlags(){ ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.InFlags; } inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } @@ -2624,6 +2795,7 @@ namespace ImGui IMGUI_API void MarkItemEdited(ImGuiID id); // Mark data associated to given item as "edited", used by IsItemDeactivatedAfterEdit() function. IMGUI_API void PushOverrideID(ImGuiID id); // Push given value as-is at the top of the ID stack (whereas PushID combines old and new hashes) IMGUI_API ImGuiID GetIDWithSeed(const char* str_id_begin, const char* str_id_end, ImGuiID seed); + IMGUI_API ImGuiID GetIDWithSeed(int n, ImGuiID seed); // Basic Helpers for widget code IMGUI_API void ItemSize(const ImVec2& size, float text_baseline_y = -1.0f); @@ -2639,7 +2811,7 @@ namespace ImGui IMGUI_API ImVec2 GetContentRegionMaxAbs(); IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); - // Parameter stacks + // Parameter stacks (shared) IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); IMGUI_API void PopItemFlag(); @@ -2688,44 +2860,101 @@ namespace ImGui IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); - // Focus Scope (WIP) - // This is generally used to identify a selection set (multiple of which may be in the same window), as selection - // patterns generally need to react (e.g. clear selection) when landing on an item of the set. - IMGUI_API void PushFocusScope(ImGuiID id); - IMGUI_API void PopFocusScope(); - inline ImGuiID GetFocusedFocusScope() { ImGuiContext& g = *GImGui; return g.NavFocusScopeId; } // Focus scope which is actually active - inline ImGuiID GetFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.NavFocusScopeIdCurrent; } // Focus scope we are outputting into, set by PushFocusScope() - // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. inline bool IsNamedKey(ImGuiKey key) { return key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END; } + inline bool IsNamedKeyOrModKey(ImGuiKey key) { return (key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super || key == ImGuiMod_Shortcut; } inline bool IsLegacyKey(ImGuiKey key) { return key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END; } + inline bool IsKeyboardKey(ImGuiKey key) { return key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END; } inline bool IsGamepadKey(ImGuiKey key) { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; } + inline bool IsMouseKey(ImGuiKey key) { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; } inline bool IsAliasKey(ImGuiKey key) { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; } + inline ImGuiKeyChord ConvertShortcutMod(ImGuiKeyChord key_chord) { ImGuiContext& g = *GImGui; IM_ASSERT_PARANOID(key_chord & ImGuiMod_Shortcut); return (key_chord & ~ImGuiMod_Shortcut) | (g.IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); } + inline ImGuiKey ConvertSingleModFlagToKey(ImGuiKey key) + { + ImGuiContext& g = *GImGui; + if (key == ImGuiMod_Ctrl) return ImGuiKey_ReservedForModCtrl; + if (key == ImGuiMod_Shift) return ImGuiKey_ReservedForModShift; + if (key == ImGuiMod_Alt) return ImGuiKey_ReservedForModAlt; + if (key == ImGuiMod_Super) return ImGuiKey_ReservedForModSuper; + if (key == ImGuiMod_Shortcut) return (g.IO.ConfigMacOSXBehaviors ? ImGuiKey_ReservedForModSuper : ImGuiKey_ReservedForModCtrl); + return key; + } + IMGUI_API ImGuiKeyData* GetKeyData(ImGuiKey key); - IMGUI_API void GetKeyChordName(ImGuiModFlags mods, ImGuiKey key, char* out_buf, int out_buf_size); - IMGUI_API void SetItemUsingMouseWheel(); - IMGUI_API void SetActiveIdUsingAllKeyboardKeys(); - inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } - inline bool IsActiveIdUsingKey(ImGuiKey key) { ImGuiContext& g = *GImGui; return g.ActiveIdUsingKeyInputMask[key]; } - inline void SetActiveIdUsingKey(ImGuiKey key) { ImGuiContext& g = *GImGui; g.ActiveIdUsingKeyInputMask.SetBit(key); } - inline ImGuiKey MouseButtonToKey(ImGuiMouseButton button) { IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); return ImGuiKey_MouseLeft + button; } + IMGUI_API void GetKeyChordName(ImGuiKeyChord key_chord, char* out_buf, int out_buf_size); + inline ImGuiKey MouseButtonToKey(ImGuiMouseButton button) { IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); return (ImGuiKey)(ImGuiKey_MouseLeft + button); } IMGUI_API bool IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold = -1.0f); - IMGUI_API ImGuiModFlags GetMergedModFlags(); - IMGUI_API ImVec2 GetKeyVector2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down); + IMGUI_API ImVec2 GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down); IMGUI_API float GetNavTweakPressedAmount(ImGuiAxis axis); IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate); IMGUI_API void GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, float* repeat_rate); - IMGUI_API bool IsKeyPressedEx(ImGuiKey key, ImGuiInputFlags flags = 0); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // [removed in 1.87] -#endif + IMGUI_API void SetActiveIdUsingAllKeyboardKeys(); + inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } + + // [EXPERIMENTAL] Low-Level: Key/Input Ownership + // - The idea is that instead of "eating" a given input, we can link to an owner id. + // - Ownership is most often claimed as a result of reacting to a press/down event (but occasionally may be claimed ahead). + // - Input queries can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. + // - Legacy input queries (without specifying an owner or _Any or _None) are equivalent to using ImGuiKeyOwner_Any (== 0). + // - Input ownership is automatically released on the frame after a key is released. Therefore: + // - for ownership registration happening as a result of a down/press event, the SetKeyOwner() call may be done once (common case). + // - for ownership registration happening ahead of a down/press event, the SetKeyOwner() call needs to be made every frame (happens if e.g. claiming ownership on hover). + // - SetItemKeyOwner() is a shortcut for common simple case. A custom widget will probably want to call SetKeyOwner() multiple times directly based on its interaction state. + // - This is marked experimental because not all widgets are fully honoring the Set/Test idioms. We will need to move forward step by step. + // Please open a GitHub Issue to submit your usage scenario or if there's a use case you need solved. + IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); + IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags = 0); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' + inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &GImGui->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } + + // [EXPERIMENTAL] High-Level: Input Access functions w/ support for Key/Input Ownership + // - Important: legacy IsKeyPressed(ImGuiKey, bool repeat=true) _DEFAULTS_ to repeat, new IsKeyPressed() requires _EXPLICIT_ ImGuiInputFlags_Repeat flag. + // - Expected to be later promoted to public API, the prototypes are designed to replace existing ones (since owner_id can default to Any == 0) + // - Specifying a value for 'ImGuiID owner' will test that EITHER the key is NOT owned (UNLESS locked), EITHER the key is owned by 'owner'. + // Legacy functions use ImGuiKeyOwner_Any meaning that they typically ignore ownership, unless a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. + // - Binding generators may want to ignore those for now, or suffix them with Ex() until we decide if this gets moved into public API. + IMGUI_API bool IsKeyDown(ImGuiKey key, ImGuiID owner_id); + IMGUI_API bool IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat. + IMGUI_API bool IsKeyReleased(ImGuiKey key, ImGuiID owner_id); + IMGUI_API bool IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id); + IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API bool IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id); + + // [EXPERIMENTAL] Shortcut Routing + // - ImGuiKeyChord = a ImGuiKey optionally OR-red with ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. + // ImGuiKey_C (accepted by functions taking ImGuiKey or ImGuiKeyChord) + // ImGuiKey_C | ImGuiMod_Ctrl (accepted by functions taking ImGuiKeyChord) + // ONLY ImGuiMod_XXX values are legal to 'OR' with an ImGuiKey. You CANNOT 'OR' two ImGuiKey values. + // - When using one of the routing flags (e.g. ImGuiInputFlags_RouteFocused): routes requested ahead of time given a chord (key + modifiers) and a routing policy. + // - Routes are resolved during NewFrame(): if keyboard modifiers are matching current ones: SetKeyOwner() is called + route is granted for the frame. + // - Route is granted to a single owner. When multiple requests are made we have policies to select the winning route. + // - Multiple read sites may use the same owner id and will all get the granted route. + // - For routing: when owner_id is 0 we use the current Focus Scope ID as a default owner in order to identify our location. + IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); + IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); + + // [EXPERIMENTAL] Focus Scope + // This is generally used to identify a unique input location (for e.g. a selection set) + // There is one per window (automatically set in Begin), but: + // - Selection patterns generally need to react (e.g. clear a selection) when landing on one item of the set. + // So in order to identify a set multiple lists in same window may each need a focus scope. + // If you imagine an hypothetical BeginSelectionGroup()/EndSelectionGroup() api, it would likely call PushFocusScope()/EndFocusScope() + // - Shortcut routing also use focus scope as a default location identifier if an owner is not provided. + // 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(); + inline ImGuiID GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; } // Focus scope we are outputting into, set by PushFocusScope() // Drag and Drop IMGUI_API bool IsDragDropActive(); IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); + IMGUI_API void RenderDragDropTargetRect(const ImRect& bb); // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); @@ -2762,7 +2991,8 @@ namespace ImGui IMGUI_API void TableDrawContextMenu(ImGuiTable* table); 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 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 TableSortSpecsSanitize(ImGuiTable* table); IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column); @@ -2774,7 +3004,7 @@ namespace ImGui IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); - IMGUI_API ImGuiID TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no = 0); + IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); IMGUI_API float TableGetMaxColumnWidth(const ImGuiTable* table, int column_n); IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); @@ -2793,15 +3023,22 @@ namespace ImGui IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); // Tab Bars + inline ImGuiTabBar* GetCurrentTabBar() { ImGuiContext& g = *GImGui; return g.CurrentTabBar; } IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); + IMGUI_API ImGuiTabItem* TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order); + IMGUI_API ImGuiTabItem* TabBarGetCurrentTab(ImGuiTabBar* tab_bar); + inline int TabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { return tab_bar->Tabs.index_from_ptr(tab); } + IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); - IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset); - IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, ImVec2 mouse_pos); + IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset); + IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); - IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags); - IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button); + IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window); + IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker); + IMGUI_API ImVec2 TabItemCalcSize(ImGuiWindow* window); IMGUI_API void TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col); IMGUI_API void 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); @@ -2831,25 +3068,28 @@ namespace ImGui // Widgets IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); + IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags); + IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); + IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); + IMGUI_API bool CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value); + + // Widgets: Window Decorations IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos); IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); - IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); IMGUI_API void Scrollbar(ImGuiAxis axis); IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags flags); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col); IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis); 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 SeparatorEx(ImGuiSeparatorFlags flags); - IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); - IMGUI_API bool CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value); // Widgets low-level behaviors IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags); IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); - IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f); + IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0); IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API void TreePushOverrideID(ImGuiID id); IMGUI_API void TreeNodeSetOpen(ImGuiID id, bool open); @@ -2886,7 +3126,7 @@ namespace ImGui IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags); // 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, ImVec2 frame_size); + 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); // Shade functions (write over already created vertices) IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); @@ -2904,6 +3144,10 @@ namespace ImGui // Debug Tools IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); + IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + IMGUI_API void DebugLocateItem(ImGuiID target_id); // Call sparingly: only 1 at the same time! + IMGUI_API void DebugLocateItemOnHover(ImGuiID target_id); // Only call on reaction to a mouse Hover: because only 1 at the same time! + IMGUI_API void DebugLocateItemResolveWithLastItem(); inline void DebugDrawItemRect(ImU32 col = IM_COL32(255,0,0,255)) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, col); } inline void DebugStartItemPicker() { ImGuiContext& g = *GImGui; g.DebugItemPickerActive = true; } IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); @@ -2923,10 +3167,12 @@ namespace ImGui IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); IMGUI_API void DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int windows_size, ImGuiWindow* parent_in_begin_stack); IMGUI_API void DebugNodeViewport(ImGuiViewportP* viewport); + IMGUI_API void DebugRenderKeyboardPreview(ImDrawList* draw_list); IMGUI_API void DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb); // Obsolete functions #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets that used FocusableItemRegister(): @@ -2937,6 +3183,9 @@ namespace ImGui inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() inline void FocusableItemUnregister(ImGuiWindow* window) { IM_ASSERT(0); IM_UNUSED(window); } // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem #endif +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! +#endif } // namespace ImGui diff --git a/external/imgui/imgui/imgui_memory_editor.h b/external/imgui/imgui/imgui_memory_editor.h deleted file mode 100644 index ec2beef7..00000000 --- a/external/imgui/imgui/imgui_memory_editor.h +++ /dev/null @@ -1,742 +0,0 @@ -// Mini memory editor for Dear ImGui (to embed in your game/tools) -// Get latest version at http://www.github.com/ocornut/imgui_club -// -// Right-click anywhere to access the Options menu! -// You can adjust the keyboard repeat delay/rate in ImGuiIO. -// The code assume a mono-space font for simplicity! -// If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before calling this. -// -// Usage: -// // Create a window and draw memory editor inside it: -// static MemoryEditor mem_edit_1; -// static char data[0x10000]; -// size_t data_size = 0x10000; -// mem_edit_1.DrawWindow("Memory Editor", data, data_size); -// -// Usage: -// // If you already have a window, use DrawContents() instead: -// static MemoryEditor mem_edit_2; -// ImGui::Begin("MyWindow") -// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this); -// ImGui::End(); -// -// Changelog: -// - v0.10: initial version -// - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write. -// - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61). -// - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns". -// - v0.26 (2018/08/02): fixed clicking on hex region -// - v0.30 (2018/08/02): added data preview for common data types -// - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display [@samhocevar] -// - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char* -// - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting. -// - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble] -// - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69. -// - v0.36 (2020/05/05): minor tweaks, minor refactor. -// - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor position appears on left-side of edit box. option popup appears on mouse release. fix MSVC warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions. -// - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled. -// - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out. -// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel] -// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width. -// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size. -// -// Todo/Bugs: -// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day. -// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own... -// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. -// - Using InputText() is awkward and maybe overkill here, consider implementing something custom. - -#pragma once - -#include // sprintf, scanf -#include // uint8_t, etc. - -#ifdef _MSC_VER -#define _PRISizeT "I" -#define ImSnprintf _snprintf -#else -#define _PRISizeT "z" -#define ImSnprintf snprintf -#endif - -#ifdef _MSC_VER -#pragma warning (push) -#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe. -#endif - -struct MemoryEditor -{ - enum DataFormat - { - DataFormat_Bin = 0, - DataFormat_Dec = 1, - DataFormat_Hex = 2, - DataFormat_COUNT - }; - - // Settings - bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow(). - bool ReadOnly; // = false // disable any editing. - int Cols; // = 16 // number of columns to display. - bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them. - bool OptShowDataPreview; // = false // display a footer previewing the decimal/binary/hex/float representation of the currently selected bytes. - bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X". - bool OptShowAscii; // = true // display ASCII representation on the right side. - bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color. - bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff". - int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols. - int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). - float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets - ImU32 HighlightColor; // // background color of highlighted bytes. - ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes. - void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. - bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). - - // [Internal State] - bool ContentsWidthChanged; - size_t DataPreviewAddr; - size_t DataEditingAddr; - bool DataEditingTakeFocus; - char DataInputBuf[32]; - char AddrInputBuf[32]; - size_t GotoAddr; - size_t HighlightMin, HighlightMax; - int PreviewEndianess; - ImGuiDataType PreviewDataType; - - MemoryEditor() - { - // Settings - Open = true; - ReadOnly = false; - Cols = 16; - OptShowOptions = true; - OptShowDataPreview = false; - OptShowHexII = false; - OptShowAscii = true; - OptGreyOutZeroes = true; - OptUpperCaseHex = true; - OptMidColsCount = 8; - OptAddrDigitsCount = 0; - OptFooterExtraHeight = 0.0f; - HighlightColor = IM_COL32(255, 255, 255, 50); - ReadFn = NULL; - WriteFn = NULL; - HighlightFn = NULL; - - // State/Internals - ContentsWidthChanged = false; - DataPreviewAddr = DataEditingAddr = (size_t)-1; - DataEditingTakeFocus = false; - memset(DataInputBuf, 0, sizeof(DataInputBuf)); - memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); - GotoAddr = (size_t)-1; - HighlightMin = HighlightMax = (size_t)-1; - PreviewEndianess = 0; - PreviewDataType = ImGuiDataType_S32; - } - - void GotoAddrAndHighlight(size_t addr_min, size_t addr_max) - { - GotoAddr = addr_min; - HighlightMin = addr_min; - HighlightMax = addr_max; - } - - struct Sizes - { - int AddrDigitsCount; - float LineHeight; - float GlyphWidth; - float HexCellWidth; - float SpacingBetweenMidCols; - float PosHexStart; - float PosHexEnd; - float PosAsciiStart; - float PosAsciiEnd; - float WindowWidth; - - Sizes() { memset(this, 0, sizeof(*this)); } - }; - - void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr) - { - ImGuiStyle& style = ImGui::GetStyle(); - s.AddrDigitsCount = OptAddrDigitsCount; - if (s.AddrDigitsCount == 0) - for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4) - s.AddrDigitsCount++; - s.LineHeight = ImGui::GetTextLineHeight(); - s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space - s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere - s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing - s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; - s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); - s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; - if (OptShowAscii) - { - s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; - if (OptMidColsCount > 0) - s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; - s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; - } - s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; - } - - // Standalone Memory Editor window - void DrawWindow(const char* title, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) - { - Sizes s; - CalcSizes(s, mem_size, base_display_addr); - ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX)); - - Open = true; - if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) - { - if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) - ImGui::OpenPopup("context"); - DrawContents(mem_data, mem_size, base_display_addr); - if (ContentsWidthChanged) - { - CalcSizes(s, mem_size, base_display_addr); - ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y)); - } - } - ImGui::End(); - } - - // Memory Editor contents only - void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000) - { - if (Cols < 1) - Cols = 1; - - ImU8* mem_data = (ImU8*)mem_data_void; - Sizes s; - CalcSizes(s, mem_size, base_display_addr); - ImGuiStyle& style = ImGui::GetStyle(); - - // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. - // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move. - const float height_separator = style.ItemSpacing.y; - float footer_height = OptFooterExtraHeight; - if (OptShowOptions) - footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; - if (OptShowDataPreview) - footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; - ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - - // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function. - const int line_total_count = (int)((mem_size + Cols - 1) / Cols); - ImGuiListClipper clipper; - clipper.Begin(line_total_count, s.LineHeight); - - bool data_next = false; - - if (ReadOnly || DataEditingAddr >= mem_size) - DataEditingAddr = (size_t)-1; - if (DataPreviewAddr >= mem_size) - DataPreviewAddr = (size_t)-1; - - size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0; - - size_t data_editing_addr_next = (size_t)-1; - if (DataEditingAddr != (size_t)-1) - { - // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered) - if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; } - } - - // Draw vertical separator - ImVec2 window_pos = ImGui::GetWindowPos(); - if (OptShowAscii) - draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border)); - - const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); - const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; - - const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: "; - const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x"; - const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; - const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; - - while (clipper.Step()) - for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines - { - size_t addr = (size_t)(line_i * Cols); - ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); - - // Draw Hexadecimal - for (int n = 0; n < Cols && addr < mem_size; n++, addr++) - { - float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; - if (OptMidColsCount > 0) - byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; - ImGui::SameLine(byte_pos_x); - - // Draw highlight - bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); - bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); - bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); - if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) - { - ImVec2 pos = ImGui::GetCursorScreenPos(); - float highlight_width = s.GlyphWidth * 2; - bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); - if (is_next_byte_highlighted || (n + 1 == Cols)) - { - highlight_width = s.HexCellWidth; - if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) - highlight_width += s.SpacingBetweenMidCols; - } - draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); - } - - if (DataEditingAddr == addr) - { - // Display text input on current byte - bool data_write = false; - ImGui::PushID((void*)addr); - if (DataEditingTakeFocus) - { - ImGui::SetKeyboardFocusHere(0); - sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); - sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); - } - struct UserData - { - // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. - static int Callback(ImGuiInputTextCallbackData* data) - { - UserData* user_data = (UserData*)data->UserData; - if (!data->HasSelection()) - user_data->CursorPos = data->CursorPos; - if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) - { - // When not editing a byte, always refresh its InputText content pulled from underlying memory data - // (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there) - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, user_data->CurrentBufOverwrite); - data->SelectionStart = 0; - data->SelectionEnd = 2; - data->CursorPos = 0; - } - return 0; - } - char CurrentBufOverwrite[3]; // Input - int CursorPos; // Output - }; - UserData user_data; - user_data.CursorPos = -1; - sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways; -#if IMGUI_VERSION_NUM >= 18104 - flags |= ImGuiInputTextFlags_AlwaysOverwrite; -#else - flags |= ImGuiInputTextFlags_AlwaysInsertMode; -#endif - ImGui::SetNextItemWidth(s.GlyphWidth * 2); - if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data)) - data_write = data_next = true; - else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) - DataEditingAddr = data_editing_addr_next = (size_t)-1; - DataEditingTakeFocus = false; - if (user_data.CursorPos >= 2) - data_write = data_next = true; - if (data_editing_addr_next != (size_t)-1) - data_write = data_next = false; - unsigned int data_input_value = 0; - if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) - { - if (WriteFn) - WriteFn(mem_data, addr, (ImU8)data_input_value); - else - mem_data[addr] = (ImU8)data_input_value; - } - ImGui::PopID(); - } - else - { - // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. - ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; - - if (OptShowHexII) - { - if ((b >= 32 && b < 128)) - ImGui::Text(".%c ", b); - else if (b == 0xFF && OptGreyOutZeroes) - ImGui::TextDisabled("## "); - else if (b == 0x00) - ImGui::Text(" "); - else - ImGui::Text(format_byte_space, b); - } - else - { - if (b == 0 && OptGreyOutZeroes) - ImGui::TextDisabled("00 "); - else - ImGui::Text(format_byte_space, b); - } - if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) - { - DataEditingTakeFocus = true; - data_editing_addr_next = addr; - } - } - } - - if (OptShowAscii) - { - // Draw ASCII values - ImGui::SameLine(s.PosAsciiStart); - ImVec2 pos = ImGui::GetCursorScreenPos(); - addr = line_i * Cols; - ImGui::PushID(line_i); - if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) - { - DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); - DataEditingTakeFocus = true; - } - ImGui::PopID(); - for (int n = 0; n < Cols && addr < mem_size; n++, addr++) - { - if (addr == DataEditingAddr) - { - draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); - draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); - } - unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; - char display_c = (c < 32 || c >= 128) ? '.' : c; - draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); - pos.x += s.GlyphWidth; - } - } - } - ImGui::PopStyleVar(2); - ImGui::EndChild(); - - // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) - ImGui::SetCursorPosX(s.WindowWidth); - - if (data_next && DataEditingAddr + 1 < mem_size) - { - DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1; - DataEditingTakeFocus = true; - } - else if (data_editing_addr_next != (size_t)-1) - { - DataEditingAddr = DataPreviewAddr = data_editing_addr_next; - DataEditingTakeFocus = true; - } - - const bool lock_show_data_preview = OptShowDataPreview; - if (OptShowOptions) - { - ImGui::Separator(); - DrawOptionsLine(s, mem_data, mem_size, base_display_addr); - } - - if (lock_show_data_preview) - { - ImGui::Separator(); - DrawPreviewLine(s, mem_data, mem_size, base_display_addr); - } - } - - void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) - { - IM_UNUSED(mem_data); - ImGuiStyle& style = ImGui::GetStyle(); - const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; - - // Options menu - if (ImGui::Button("Options")) - ImGui::OpenPopup("context"); - if (ImGui::BeginPopup("context")) - { - ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f); - if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; } - ImGui::Checkbox("Show Data Preview", &OptShowDataPreview); - ImGui::Checkbox("Show HexII", &OptShowHexII); - if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; } - ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes); - ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex); - - ImGui::EndPopup(); - } - - ImGui::SameLine(); - ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); - ImGui::SameLine(); - ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f); - if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) - { - size_t goto_addr; - if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1) - { - GotoAddr = goto_addr - base_display_addr; - HighlightMin = HighlightMax = (size_t)-1; - } - } - - if (GotoAddr != (size_t)-1) - { - if (GotoAddr < mem_size) - { - ImGui::BeginChild("##scrolling"); - ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight()); - ImGui::EndChild(); - DataEditingAddr = DataPreviewAddr = GotoAddr; - DataEditingTakeFocus = true; - } - GotoAddr = (size_t)-1; - } - } - - void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr) - { - IM_UNUSED(base_display_addr); - ImU8* mem_data = (ImU8*)mem_data_void; - ImGuiStyle& style = ImGui::GetStyle(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Preview as:"); - ImGui::SameLine(); - ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); - if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) - { - for (int n = 0; n < ImGuiDataType_COUNT; n++) - if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n)) - PreviewDataType = (ImGuiDataType)n; - ImGui::EndCombo(); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); - ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); - - char buf[128] = ""; - float x = s.GlyphWidth * 6.0f; - bool has_value = DataPreviewAddr != (size_t)-1; - if (has_value) - DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf)); - ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); - if (has_value) - DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf)); - ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); - if (has_value) - DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf)); - buf[IM_ARRAYSIZE(buf) - 1] = 0; - ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); - } - - // Utilities for Data Preview - const char* DataTypeGetDesc(ImGuiDataType data_type) const - { - const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); - return descs[data_type]; - } - - size_t DataTypeGetSize(ImGuiDataType data_type) const - { - const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) }; - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); - return sizes[data_type]; - } - - const char* DataFormatGetDesc(DataFormat data_format) const - { - const char* descs[] = { "Bin", "Dec", "Hex" }; - IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT); - return descs[data_format]; - } - - bool IsBigEndian() const - { - uint16_t x = 1; - char c[2]; - memcpy(c, &x, 2); - return c[0] != 0; - } - - static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) - { - if (is_little_endian) - { - uint8_t* dst = (uint8_t*)_dst; - uint8_t* src = (uint8_t*)_src + s - 1; - for (int i = 0, n = (int)s; i < n; ++i) - memcpy(dst++, src--, 1); - return _dst; - } - else - { - return memcpy(_dst, _src, s); - } - } - - static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) - { - if (is_little_endian) - { - return memcpy(_dst, _src, s); - } - else - { - uint8_t* dst = (uint8_t*)_dst; - uint8_t* src = (uint8_t*)_src + s - 1; - for (int i = 0, n = (int)s; i < n; ++i) - memcpy(dst++, src--, 1); - return _dst; - } - } - - void* EndianessCopy(void* dst, void* src, size_t size) const - { - static void* (*fp)(void*, void*, size_t, int) = NULL; - if (fp == NULL) - fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; - return fp(dst, src, size, PreviewEndianess); - } - - const char* FormatBinary(const uint8_t* buf, int width) const - { - IM_ASSERT(width <= 64); - size_t out_n = 0; - static char out_buf[64 + 8 + 1]; - int n = width / 8; - for (int j = n - 1; j >= 0; --j) - { - for (int i = 0; i < 8; ++i) - out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0'; - out_buf[out_n++] = ' '; - } - IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf)); - out_buf[out_n] = 0; - return out_buf; - } - - // [Internal] - void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const - { - uint8_t buf[8]; - size_t elem_size = DataTypeGetSize(data_type); - size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; - if (ReadFn) - for (int i = 0, n = (int)size; i < n; ++i) - buf[i] = ReadFn(mem_data, addr + i); - else - memcpy(buf, mem_data + addr, size); - - if (data_format == DataFormat_Bin) - { - uint8_t binbuf[8]; - EndianessCopy(binbuf, buf, size); - ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); - return; - } - - out_buf[0] = 0; - switch (data_type) - { - case ImGuiDataType_S8: - { - int8_t int8 = 0; - EndianessCopy(&int8, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } - break; - } - case ImGuiDataType_U8: - { - uint8_t uint8 = 0; - EndianessCopy(&uint8, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } - break; - } - case ImGuiDataType_S16: - { - int16_t int16 = 0; - EndianessCopy(&int16, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } - break; - } - case ImGuiDataType_U16: - { - uint16_t uint16 = 0; - EndianessCopy(&uint16, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } - break; - } - case ImGuiDataType_S32: - { - int32_t int32 = 0; - EndianessCopy(&int32, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; } - break; - } - case ImGuiDataType_U32: - { - uint32_t uint32 = 0; - EndianessCopy(&uint32, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } - break; - } - case ImGuiDataType_S64: - { - int64_t int64 = 0; - EndianessCopy(&int64, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } - break; - } - case ImGuiDataType_U64: - { - uint64_t uint64 = 0; - EndianessCopy(&uint64, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } - break; - } - case ImGuiDataType_Float: - { - float float32 = 0.0f; - EndianessCopy(&float32, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; } - break; - } - case ImGuiDataType_Double: - { - double float64 = 0.0; - EndianessCopy(&float64, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; } - break; - } - case ImGuiDataType_COUNT: - break; - } // Switch - IM_ASSERT(0); // Shouldn't reach - } -}; - -#undef _PRISizeT -#undef ImSnprintf - -#ifdef _MSC_VER -#pragma warning (pop) -#endif diff --git a/external/imgui/imgui/imgui_tables.cpp b/external/imgui/imgui/imgui_tables.cpp index 80ae6137..7b2671c0 100644 --- a/external/imgui/imgui/imgui_tables.cpp +++ b/external/imgui/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (tables and columns code) /* @@ -188,12 +188,12 @@ Index of this file: #define _CRT_SECURE_NO_WARNINGS #endif -#include "imgui.h" -#ifndef IMGUI_DISABLE - #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE #include "imgui_internal.h" // System includes @@ -315,7 +315,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG return false; // Sanity checks - IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!"); + IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS); if (flags & ImGuiTableFlags_ScrollX) IM_ASSERT(inner_width >= 0.0f); @@ -332,11 +332,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Acquire storage for the table ImGuiTable* table = g.Tables.GetOrAddByKey(id); - const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; - const ImGuiID instance_id = id + instance_no; const ImGuiTableFlags table_last_flags = table->Flags; - if (instance_no > 0) - IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); // Acquire temporary buffers const int table_idx = g.Tables.GetIndex(table); @@ -352,17 +348,32 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG flags = TableFixFlags(flags, outer_window); // Initialize + const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; table->ID = id; table->Flags = flags; - table->InstanceCurrent = (ImS16)instance_no; table->LastFrameActive = g.FrameCount; table->OuterWindow = table->InnerWindow = outer_window; table->ColumnsCount = columns_count; table->IsLayoutLocked = false; table->InnerWidth = inner_width; temp_data->UserOuterSize = outer_size; - if (instance_no > 0 && table->InstanceDataExtra.Size < instance_no) - table->InstanceDataExtra.push_back(ImGuiTableInstanceData()); + + // Instance data (for instance 0, TableID == TableInstanceID) + ImGuiID instance_id; + table->InstanceCurrent = (ImS16)instance_no; + if (instance_no > 0) + { + IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); + if (table->InstanceDataExtra.Size < instance_no) + table->InstanceDataExtra.push_back(ImGuiTableInstanceData()); + instance_id = GetIDWithSeed(instance_no, GetIDWithSeed("##Instances", NULL, id)); // Push "##Instance" followed by (int)instance_no in ID stack. + } + else + { + instance_id = id; + } + ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); + table_instance->TableInstanceID = instance_id; // When not using a child window, WorkRect.Max will grow as we append contents. if (use_child_window) @@ -395,6 +406,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->OuterRect = table->InnerWindow->Rect(); table->InnerRect = table->InnerWindow->InnerRect; IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f); + + // When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned) + if (instance_no == 0) + { + table->HasScrollbarYPrev = table->HasScrollbarYCurr; + table->HasScrollbarYCurr = false; + } + table->HasScrollbarYCurr |= (table->InnerWindow->ScrollMax.y > 0.0f); } else { @@ -404,7 +423,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } // Push a standardized ID for both child-using and not-child-using tables - PushOverrideID(instance_id); + PushOverrideID(id); + if (instance_no > 0) + PushOverrideID(instance_id); // FIXME: Somehow this is not resolved by stack-tool, even tho GetIDWithSeed() submitted the symbol. // Backup a copy of host window members we will modify ImGuiWindow* inner_window = table->InnerWindow; @@ -573,16 +594,22 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count) { // Allocate single buffer for our arrays - ImSpanAllocator<3> span_allocator; + const int columns_bit_array_size = (int)ImBitArrayGetStorageSizeInBytes(columns_count); + ImSpanAllocator<6> span_allocator; span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn)); span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx)); span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4); + for (int n = 3; n < 6; n++) + span_allocator.Reserve(n, columns_bit_array_size); table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes()); memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes()); span_allocator.SetArenaBasePtr(table->RawData); span_allocator.GetSpan(0, &table->Columns); span_allocator.GetSpan(1, &table->DisplayOrderToIndex); span_allocator.GetSpan(2, &table->RowCellData); + table->EnabledMaskByDisplayOrder = (ImU32*)span_allocator.GetSpanPtrBegin(3); + table->EnabledMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(4); + table->VisibleMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(5); } // Apply queued resizing/reordering/hiding requests @@ -721,8 +748,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); table->IsDefaultDisplayOrder = true; table->ColumnsEnabledCount = 0; - table->EnabledMaskByIndex = 0x00; - table->EnabledMaskByDisplayOrder = 0x00; + ImBitArrayClearAllBits(table->EnabledMaskByIndex, table->ColumnsCount); + ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, table->ColumnsCount); table->LeftMostEnabledColumn = -1; table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE @@ -787,8 +814,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n; column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; - table->EnabledMaskByIndex |= (ImU64)1 << column_n; - table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder; + ImBitArraySetBit(table->EnabledMaskByIndex, column_n); + ImBitArraySetBit(table->EnabledMaskByDisplayOrder, column->DisplayOrder); prev_visible_column_idx = column_n; IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); @@ -836,7 +863,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -852,7 +879,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) if (column->AutoFitQueue != 0x00) column->WidthRequest = width_auto; - else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n))) + else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && column->IsRequestOutput) column->WidthRequest = width_auto; // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets @@ -893,13 +920,14 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 4] Apply final widths based on requested widths const ImRect work_rect = table->WorkRect; const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); - const float width_avail = ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth(); + const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synched tables with mismatching scrollbar state (#5920) + const float width_avail = ImMax(1.0f, (((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth()) - width_removed); const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests; float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -926,7 +954,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths)) for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) { - if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) continue; ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) @@ -936,11 +964,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) width_remaining_for_stretched_columns -= 1.0f; } + // Determine if table is hovered which will be used to flag columns as hovered. + // - In principle we'd like to use the equivalent of IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + // but because our item is partially submitted at this point we use ItemHoverable() and a workaround (temporarily + // clear ActiveId, which is equivalent to the change provided by _AllowWhenBLockedByActiveItem). + // - This allows columns to be marked as hovered when e.g. clicking a button inside the column, or using drag and drop. ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); table->HoveredColumnBody = -1; table->HoveredColumnBorder = -1; const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table_instance->LastOuterHeight)); + const ImGuiID backup_active_id = g.ActiveId; + g.ActiveId = 0; const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0); + g.ActiveId = backup_active_id; // [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. @@ -949,14 +985,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; ImRect host_clip_rect = table->InnerClipRect; //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2; - table->VisibleMaskByIndex = 0x00; - table->RequestOutputMaskByIndex = 0x00; + ImBitArrayClearAllBits(table->VisibleMaskByIndex, table->ColumnsCount); for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; - column->NavLayerCurrent = (ImS8)((table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); + column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); // Use Count NOT request so Header line changes layer when frozen if (offset_x_frozen && table->FreezeColumnsCount == visible_n) { @@ -967,7 +1002,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Clear status flags column->Flags &= ~ImGuiTableColumnFlags_StatusMask_; - if ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) { // Hidden column: clear a few fields and we are done with it for the remainder of the function. // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. @@ -1020,12 +1055,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y); const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY; if (is_visible) - table->VisibleMaskByIndex |= ((ImU64)1 << column_n); + ImBitArraySetBit(table->VisibleMaskByIndex, column_n); // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; - if (column->IsRequestOutput) - table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n); // Mark column as SkipItems (ignoring all items/layout) column->IsSkipItems = !column->IsEnabled || table->HostSkipItems; @@ -1111,11 +1144,18 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) EndPopup(); } - // [Part 13] Sanitize and build sort specs before we have a change to use them for display. + // [Part 12] Sanitize and build sort specs before we have a change to use them for display. // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change) if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable)) TableSortSpecsBuild(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; + if (table->FreezeRowsRequest > 0) + table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight; + table_instance->LastFrozenHeight = 0.0f; + // Initial state ImGuiWindow* inner_window = table->InnerWindow; if (table->Flags & ImGuiTableFlags_NoClip) @@ -1145,7 +1185,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) continue; const int column_n = table->DisplayOrderToIndex[order_n]; @@ -1163,8 +1203,8 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); + ItemAdd(hit_rect, column_id, NULL, ImGuiItemFlags_NoNav); //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); - KeepAliveID(column_id); bool hovered = false, held = false; bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_NoNavFocus); @@ -1281,7 +1321,7 @@ void ImGui::EndTable() float auto_fit_width_for_stretched = 0.0f; float auto_fit_width_for_stretched_min = 0.0f; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - if (table->EnabledMaskByIndex & ((ImU64)1 << column_n)) + if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) { ImGuiTableColumn* column = &table->Columns[column_n]; float column_width_request = ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) ? column->WidthRequest : TableGetColumnWidthAuto(table, column); @@ -1321,8 +1361,10 @@ void ImGui::EndTable() } // Pop from id stack - IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); + IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table_instance->TableInstanceID, "Mismatching PushID/PopID!"); IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!"); + if (table->InstanceCurrent > 0) + PopID(); PopID(); // Restore window data that we modified @@ -1592,11 +1634,11 @@ ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n) } // Return the resizing ID for the right-side of the given column. -ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no) +ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no) { IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); - ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n; - return id; + ImGuiID instance_id = TableGetInstanceID(table, instance_no); + return instance_id + 1 + column_n; // FIXME: #6140: still not ideal } // Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. @@ -1627,7 +1669,7 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n return; if (column_n == -1) column_n = table->CurrentColumn; - if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) + if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n)) return; if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) table->RowCellDataCurrent++; @@ -1718,7 +1760,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent window->DC.PrevLineTextBaseOffset = 0.0f; window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); - window->DC.IsSameLine = false; + window->DC.IsSameLine = window->DC.IsSetPos = false; window->DC.CursorMaxPos.y = next_y1; // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. @@ -1831,17 +1873,15 @@ void ImGui::TableEndRow(ImGuiTable* table) // get the new cursor position. if (unfreeze_rows_request) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - { - ImGuiTableColumn* column = &table->Columns[column_n]; - column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); - } + table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main; if (unfreeze_rows_actual) { IM_ASSERT(table->IsUnfrozenRows == false); + const float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); table->IsUnfrozenRows = true; + TableGetInstanceData(table, table->InstanceCurrent)->LastFrozenHeight = y0 - table->OuterRect.Min.y; // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect - float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; @@ -1904,7 +1944,7 @@ bool ImGui::TableSetColumnIndex(int column_n) // Return whether the column is visible. User may choose to skip submitting items based on this return value, // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. - return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; + return table->Columns[column_n].IsRequestOutput; } // [Public] Append into the next column, wrap and create a new row when already on last column @@ -1929,8 +1969,7 @@ bool ImGui::TableNextColumn() // Return whether the column is visible. User may choose to skip submitting items based on this return value, // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. - int column_n = table->CurrentColumn; - return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; + return table->Columns[table->CurrentColumn].IsRequestOutput; } @@ -2000,6 +2039,9 @@ void ImGui::TableEndCell(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; ImGuiWindow* window = table->InnerWindow; + if (window->DC.IsSetPos) + ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + // Report maximum position so we can infer content size per column. float* p_max_pos_x; if (table->RowFlags & ImGuiTableRowFlags_Headers) @@ -2267,7 +2309,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount; const int channels_for_bg = 1 + 1 * freeze_row_multiplier; - const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0; + const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || (memcmp(table->VisibleMaskByIndex, table->EnabledMaskByIndex, ImBitArrayGetStorageSizeInBytes(table->ColumnsCount)) != 0)) ? +1 : 0; const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total); table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1); @@ -2341,19 +2383,26 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) // Track which groups we are going to attempt to merge, and which channels goes into each group. struct MergeGroup { - ImRect ClipRect; - int ChannelsCount; - ImBitArray ChannelsMask; - - MergeGroup() { ChannelsCount = 0; } + ImRect ClipRect; + int ChannelsCount; + ImBitArrayPtr ChannelsMask; }; int merge_group_mask = 0x00; - MergeGroup merge_groups[4]; + MergeGroup merge_groups[4] = {}; + + // Use a reusable temp buffer for the merge masks as they are dynamically sized. + const int max_draw_channels = (4 + table->ColumnsCount * 2); + 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++) + 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)); // 1. Scan channels and take note of those which can be merged for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) + if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -2385,11 +2434,11 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) } const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2); - IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); + IM_ASSERT(channel_no < max_draw_channels); MergeGroup* merge_group = &merge_groups[merge_group_n]; if (merge_group->ChannelsCount == 0) merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); - merge_group->ChannelsMask.SetBit(channel_no); + ImBitArraySetBit(merge_group->ChannelsMask, channel_no); merge_group->ChannelsCount++; merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); merge_group_mask |= (1 << merge_group_n); @@ -2425,9 +2474,8 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) const int LEADING_DRAW_CHANNELS = 2; g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; - ImBitArray remaining_mask; // We need 132-bit of storage - remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count); - remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen); + ImBitArraySetBitRange(remaining_mask, LEADING_DRAW_CHANNELS, splitter->_Count); + ImBitArrayClearBit(remaining_mask, table->Bg2DrawChannelUnfrozen); IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN); 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; @@ -2460,14 +2508,14 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); #endif remaining_count -= merge_group->ChannelsCount; - for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) - remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; + for (int n = 0; n < (size_for_masks_bitarrays_one >> 2); n++) + remaining_mask[n] &= ~merge_group->ChannelsMask[n]; for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) { // Copy + overwrite new clip rect - if (!merge_group->ChannelsMask.TestBit(n)) + if (!IM_BITARRAY_TESTBIT(merge_group->ChannelsMask, n)) continue; - merge_group->ChannelsMask.ClearBit(n); + IM_BITARRAY_CLEARBIT(merge_group->ChannelsMask, n); merge_channels_count--; ImDrawChannel* channel = &splitter->_Channels[n]; @@ -2485,7 +2533,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) // Append unmergeable channels that we didn't reorder at the end of the list for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) { - if (!remaining_mask.TestBit(n)) + if (!IM_BITARRAY_TESTBIT(remaining_mask, n)) continue; ImDrawChannel* channel = &splitter->_Channels[n]; memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); @@ -2517,7 +2565,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) continue; const int column_n = table->DisplayOrderToIndex[order_n]; @@ -2845,10 +2893,9 @@ void ImGui::TableHeadersRow() continue; // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) - // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide - // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. + // In your own code you may omit the PushID/PopID all-together, provided you know they won't collide. const char* name = (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) ? "" : TableGetColumnName(column_n); - PushID(table->InstanceCurrent * table->ColumnsCount + column_n); + PushID(column_n); TableHeader(name); PopID(); } @@ -2994,7 +3041,7 @@ void ImGui::TableHeader(const char* label) RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); - if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay) + if (text_clipped && hovered && g.ActiveId == 0 && IsItemHovered(ImGuiHoveredFlags_DelayNormal)) SetTooltip("%.*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 @@ -3059,15 +3106,15 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) if (column != NULL) { const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled; - if (MenuItem("Size column to fit###SizeOne", NULL, false, can_resize)) + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableSizeOne), NULL, false, can_resize)) // "###SizeOne" TableSetColumnWidthAutoSingle(table, column_n); } const char* size_all_desc; if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame) - size_all_desc = "Size all columns to fit###SizeAll"; // All fixed + size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllFit); // "###SizeAll" All fixed else - size_all_desc = "Size all columns to default###SizeAll"; // All stretch or mixed + size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllDefault); // "###SizeAll" All stretch or mixed if (MenuItem(size_all_desc, NULL)) TableSetColumnWidthAutoAll(table); want_separator = true; @@ -3076,7 +3123,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) // Ordering if (table->Flags & ImGuiTableFlags_Reorderable) { - if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder)) + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder)) table->IsResetDisplayOrderRequest = true; want_separator = true; } @@ -4011,8 +4058,7 @@ void ImGui::EndColumns() const ImGuiID column_id = columns->ID + ImGuiID(n); const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH; const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); - KeepAliveID(column_id); - if (IsClippedEx(column_hit_rect, column_id)) // FIXME: Can be removed or replaced with a lower-level test + if (!ItemAdd(column_hit_rect, column_id, NULL, ImGuiItemFlags_NoNav)) continue; bool hovered = false, held = false; diff --git a/external/imgui/imgui/imgui_widgets.cpp b/external/imgui/imgui/imgui_widgets.cpp index 1f9fb883..6f7d6572 100644 --- a/external/imgui/imgui/imgui_widgets.cpp +++ b/external/imgui/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.89 WIP +// dear imgui, v1.89.4 WIP // (widgets code) /* @@ -32,16 +32,15 @@ Index of this file: #define _CRT_SECURE_NO_WARNINGS #endif -#include "imgui.h" -#ifndef IMGUI_DISABLE - #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE #include "imgui_internal.h" // System includes -#include // toupper #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier #include // intptr_t #else @@ -127,7 +126,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); // For InputTextEx() static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source); static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); -static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); +static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. @@ -275,7 +274,6 @@ void ImGui::TextV(const char* fmt, va_list args) if (window->SkipItems) return; - // FIXME-OPT: Handle the %s shortcut? const char* text, *text_end; ImFormatStringToTempBufferV(&text, &text_end, fmt, args); TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); @@ -292,10 +290,7 @@ void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) { PushStyleColor(ImGuiCol_Text, col); - if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) - TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting - else - TextV(fmt, args); + TextV(fmt, args); PopStyleColor(); } @@ -311,10 +306,7 @@ void ImGui::TextDisabledV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) - TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting - else - TextV(fmt, args); + TextV(fmt, args); PopStyleColor(); } @@ -329,13 +321,10 @@ void ImGui::TextWrapped(const char* fmt, ...) void ImGui::TextWrappedV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; - bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set + const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set if (need_backup) PushTextWrapPos(0.0f); - if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) - TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting - else - TextV(fmt, args); + TextV(fmt, args); if (need_backup) PopTextWrapPos(); } @@ -542,18 +531,28 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool hovered = false; // Mouse handling + const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; if (hovered) { + // Poll mouse buttons + // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. + // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. + int mouse_button_clicked = -1; + int mouse_button_released = -1; + for (int button = 0; button < 3; button++) + if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here. + { + if (IsMouseClicked(button, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } + if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; } + } + + // Process initial action if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) { - // Poll buttons - int mouse_button_clicked = -1; - if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { mouse_button_clicked = 0; } - else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1]) { mouse_button_clicked = 1; } - else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2]) { mouse_button_clicked = 2; } - if (mouse_button_clicked != -1 && g.ActiveId != id) { + if (!(flags & ImGuiButtonFlags_NoSetKeyOwner)) + SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id); if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) { SetActiveID(id, window); @@ -577,10 +576,6 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } if (flags & ImGuiButtonFlags_PressedOnRelease) { - int mouse_button_released = -1; - if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; } - else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; } - else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; } if (mouse_button_released != -1) { const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior @@ -595,7 +590,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat)) - if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true)) + if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, test_owner_id, ImGuiInputFlags_Repeat)) pressed = true; } @@ -641,8 +636,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; const int mouse_button = g.ActiveIdMouseButton; - IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); - if (g.IO.MouseDown[mouse_button]) + if (IsMouseDown(mouse_button, test_owner_id)) { held = true; } @@ -655,7 +649,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // Report as pressed when releasing the mouse (this is the most common path) bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2; bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps - if (!is_double_click_release && !is_repeating_already) + bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id); + if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned) pressed = true; } ClearActiveID(); @@ -926,8 +921,6 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 if (window->SkipItems) return false; - KeepAliveID(id); - const float bb_frame_width = bb_frame.GetWidth(); const float bb_frame_height = bb_frame.GetHeight(); if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f) @@ -959,6 +952,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // 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); const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_avail_v); @@ -1038,7 +1032,7 @@ void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1052,7 +1046,7 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size return false; bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held); + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); @@ -1390,6 +1384,7 @@ void ImGui::AlignTextToFramePadding() } // Horizontal/vertical separating line +// FIXME: Surprisingly, this seemingly simple widget is adjacent to MANY different legacy/tricky layout issues. void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) { ImGuiWindow* window = GetCurrentWindow(); @@ -1399,20 +1394,19 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) ImGuiContext& g = *GImGui; IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected - float thickness_draw = 1.0f; - float thickness_layout = 0.0f; + const float thickness = 1.0f; // Cannot use g.Style.SeparatorTextSize yet for various reasons. if (flags & ImGuiSeparatorFlags_Vertical) { - // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout. + // Vertical separator, for menu bars (use current line height). float y1 = window->DC.CursorPos.y; float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y; - const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2)); - ItemSize(ImVec2(thickness_layout, 0.0f)); + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2)); + ItemSize(ImVec2(thickness, 0.0f)); if (!ItemAdd(bb, 0)) return; // Draw - window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); if (g.LogEnabled) LogText(" |"); } @@ -1440,13 +1434,14 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) // 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 ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw)); - ItemSize(ImVec2(0.0f, thickness_layout)); + 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. + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness)); + ItemSize(ImVec2(0.0f, thickness_for_layout)); const bool item_visible = ItemAdd(bb, 0); if (item_visible) { // Draw - window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator)); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); if (g.LogEnabled) LogRenderedText(&bb.Min, "--------------------------------\n"); @@ -1472,17 +1467,78 @@ void ImGui::Separator() SeparatorEx(flags); } +void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, label_end, false); + const ImVec2 pos = window->DC.CursorPos; + const ImVec2 padding = style.SeparatorTextPadding; + + const float separator_thickness = style.SeparatorTextBorderSize; + const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness)); + const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); + const float text_baseline_y = ImFloor((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f)); + ItemSize(min_size, text_baseline_y); + if (!ItemAdd(bb, id)) + return; + + const float sep1_x1 = pos.x; + const float sep2_x2 = bb.Max.x; + const float seps_y = ImFloor((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f); + + const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); + const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN + + // This allows using SameLine() to position something in the 'extra_w' + window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x; + + const ImU32 separator_col = GetColorU32(ImGuiCol_Separator); + if (label_size.x > 0.0f) + { + 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); + 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); + if (g.LogEnabled) + LogSetNextTextDecoration("---", NULL); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size); + } + else + { + 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); + } +} + +void ImGui::SeparatorText(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want: + // - allow headers to be draggable items (would require a stable ID + a noticeable highlight) + // - this high-level entry point to allow formatting? (may require ID separate from formatted string) + // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...' + // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item, + // and then we can turn this into a format function. + SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f); +} + // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. -bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay) +bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; - g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; - bool item_add = ItemAdd(bb, id); - g.CurrentItemFlags = item_flags_backup; - if (!item_add) + if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav)) return false; bool hovered, held; @@ -1525,7 +1581,9 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float } } - // Render + // Render at new position + if (bg_col & IM_COL32_A_MASK) + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f); const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f); @@ -1576,12 +1634,12 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc items[n].Width = width_rounded; } while (width_excess > 0.0f) - for (int n = 0; n < count; n++) - if (items[n].Width + 1.0f <= items[n].InitialWidth) - { - items[n].Width += 1.0f; - width_excess -= 1.0f; - } + for (int n = 0; n < count && width_excess > 0.0f; n++) + { + float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f); + items[n].Width += width_to_add; + width_excess -= width_to_add; + } } //------------------------------------------------------------------------- @@ -1703,7 +1761,12 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; - SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX); + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size + constraint_min.x = w; + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) + constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(constraint_min, constraint_max); } // This is essentially a specialized version of BeginPopupEx() @@ -1751,7 +1814,7 @@ bool ImGui::BeginComboPreview() ImGuiWindow* window = g.CurrentWindow; ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; - if (window->SkipItems || !window->ClipRect.Overlaps(g.LastItemData.Rect)) // FIXME: Because we don't have a ImGuiItemStatusFlags_Visible flag to test last ItemAdd() result + if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) return false; IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? if (!window->ClipRect.Contains(preview_data->PreviewRect)) // Narrower test (optional) @@ -1895,7 +1958,6 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa //------------------------------------------------------------------------- // [SECTION] Data Type and Data Formatting Helpers [Internal] //------------------------------------------------------------------------- -// - PatchFormatStringFloatToInt() // - DataTypeGetInfo() // - DataTypeFormatString() // - DataTypeApplyOp() @@ -1926,30 +1988,6 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); -// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f". -// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls. -// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?! -static const char* PatchFormatStringFloatToInt(const char* fmt) -{ - if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case. - return "%d"; - const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%) - const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user). - if (fmt_end > fmt_start && fmt_end[-1] == 'f') - { -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (fmt_start == fmt && fmt_end[0] == 0) - return "%d"; - const char* tmp_format; - ImFormatStringToTempBuffer(&tmp_format, NULL, "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision. - return tmp_format; -#else - IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d" -#endif - } - return fmt; -} - const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) { IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); @@ -2363,8 +2401,6 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, // Default format string when passing NULL if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) - format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); @@ -2372,9 +2408,11 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, { // Tabbing or CTRL-clicking on Drag turns it into an InputText const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; - const bool clicked = (hovered && g.IO.MouseClicked[0]); - const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2); + const bool clicked = hovered && IsMouseClicked(0, id); + const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id)); const bool make_active = (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id); + if (make_active && (clicked || double_clicked)) + SetKeyOwner(ImGuiKey_MouseLeft, id); if (make_active && temp_input_allowed) if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavActivateInputId == id) temp_input_is_active = true; @@ -2424,7 +2462,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; } @@ -2570,35 +2608,6 @@ bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_ return value_changed; } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - -// Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details. -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, float power) -{ - ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; - if (power != 1.0f) - { - IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); - IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds - drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths - } - return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags); -} - -bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power) -{ - ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; - if (power != 1.0f) - { - IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); - IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds - drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths - } - return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags); -} - -#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS - //------------------------------------------------------------------------- // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. //------------------------------------------------------------------------- @@ -2985,8 +2994,6 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat // Default format string when passing NULL if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) - format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); @@ -2994,8 +3001,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat { // Tabbing or CTRL-clicking on Slider turns it into an input box const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; - const bool clicked = (hovered && g.IO.MouseClicked[0]); + const bool clicked = hovered && IsMouseClicked(0, id); const bool make_active = (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id); + if (make_active && clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); if (make_active && temp_input_allowed) if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id) temp_input_is_active = true; @@ -3041,7 +3050,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; } @@ -3152,12 +3161,13 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Default format string when passing NULL if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) - format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); - if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id) + const bool clicked = hovered && IsMouseClicked(0, id); + if (clicked || g.NavActivateId == id || g.NavActivateInputId == id) { + if (clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); @@ -3200,33 +3210,6 @@ bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - -// Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details. -bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power) -{ - ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; - if (power != 1.0f) - { - IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); - slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths - } - return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags); -} - -bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; - if (power != 1.0f) - { - IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); - slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths - } - return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags); -} - -#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS - //------------------------------------------------------------------------- // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. //------------------------------------------------------------------------- @@ -3474,6 +3457,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data 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); + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); // Step buttons const ImVec2 backup_frame_padding = style.FramePadding; @@ -3646,9 +3630,9 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** return line_count; } -static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) { - ImGuiContext& g = *GImGui; + ImGuiContext& g = *ctx; ImFont* font = g.Font; const float line_height = g.FontSize; const float scale = line_height / font->FontSize; @@ -3697,14 +3681,14 @@ namespace ImStb static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { return obj->TextW[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { const ImWchar* text = obj->TextW.Data; const ImWchar* text_remaining = NULL; - const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + const ImVec2 size = InputTextCalcTextSizeW(obj->Ctx, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -3720,7 +3704,7 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { if (ImGui::GetIO().ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL @@ -3800,11 +3784,12 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st { stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len); ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW); + state->cursor = state->select_start = state->select_end = 0; if (text_len <= 0) return; if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) { - state->cursor = text_len; + state->cursor = state->select_start = state->select_end = text_len; state->has_preferred_x = 0; return; } @@ -4172,30 +4157,33 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863) } + const bool is_osx = io.ConfigMacOSXBehaviors; if (g.ActiveId != id && init_make_active) { IM_ASSERT(state && state->ID == id); SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); - - // Declare our inputs + } + if (g.ActiveId == id) + { + // Declare some inputs, the other are registered and polled via Shortcut() routing system. + if (user_clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - SetActiveIdUsingKey(ImGuiKey_Escape); - SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel); - SetActiveIdUsingKey(ImGuiKey_Home); - SetActiveIdUsingKey(ImGuiKey_End); + SetKeyOwner(ImGuiKey_Home, id); + SetKeyOwner(ImGuiKey_End, id); if (is_multiline) { - SetActiveIdUsingKey(ImGuiKey_PageUp); - SetActiveIdUsingKey(ImGuiKey_PageDown); + SetKeyOwner(ImGuiKey_PageUp, id); + SetKeyOwner(ImGuiKey_PageDown, id); } + if (is_osx) + SetKeyOwner(ImGuiMod_Alt, id); if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. - { - SetActiveIdUsingKey(ImGuiKey_Tab); - } + SetKeyOwner(ImGuiKey_Tab, id); } // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) @@ -4207,7 +4195,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ clear_active_id = true; // Lock the decision of whether we are going to take the path displaying the cursor or selection - const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active); + bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active); bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); bool value_changed = false; bool validated = false; @@ -4263,7 +4251,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX; const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); - const bool is_osx = io.ConfigMacOSXBehaviors; if (select_all) { state->SelectAll(); @@ -4306,10 +4293,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) { - // FIXME: unselect on late click could be done release? if (hovered) { - stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + if (io.KeyShift) + stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + else + stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); } } @@ -4353,7 +4342,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Process other shortcuts/key-presses - bool cancel_edit = false; + bool revert_edit = false; if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) { IM_ASSERT(state != NULL); @@ -4362,27 +4351,26 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->Stb.row_count_per_page = row_count_per_page; const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); - const bool is_osx = io.ConfigMacOSXBehaviors; - const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiModFlags_Super | ImGuiModFlags_Shift)); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End - const bool is_ctrl_key_only = (io.KeyMods == ImGuiModFlags_Ctrl); - const bool is_shift_key_only = (io.KeyMods == ImGuiModFlags_Shift); - const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiModFlags_Super) : (io.KeyMods == ImGuiModFlags_Ctrl); - const bool is_cut = ((is_shortcut_key && IsKeyPressed(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); - const bool is_copy = ((is_shortcut_key && IsKeyPressed(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); - const bool is_paste = ((is_shortcut_key && IsKeyPressed(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_readonly; - const bool is_undo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Z)) && !is_readonly && is_undoable); - const bool is_redo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressed(ImGuiKey_Z))) && !is_readonly && is_undoable; - const bool is_select_all = is_shortcut_key && IsKeyPressed(ImGuiKey_A); + // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) + // Otherwise we could simply assume that we own the keys as we are active. + const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat; + const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, f_repeat)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_C, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, id)) && !is_password && (!is_multiline || state->HasSelection()); + const bool is_paste = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_V, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, id, f_repeat)) && !is_readonly; + const bool is_undo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Z, id, f_repeat)) && !is_readonly && is_undoable; + const bool is_redo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Y, id, f_repeat) || (is_osx && Shortcut(ImGuiMod_Shortcut | ImGuiMod_Shift | ImGuiKey_Z, id, f_repeat))) && !is_readonly && is_undoable; + const bool is_select_all = Shortcut(ImGuiMod_Shortcut | ImGuiKey_A, id); // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true); const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); - const bool is_cancel = IsKeyPressed(ImGuiKey_Escape, false) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, false)); + const bool is_cancel = Shortcut(ImGuiKey_Escape, id, f_repeat) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, id, f_repeat)); + // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. if (IsKeyPressed(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } else if (IsKeyPressed(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } else if (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } @@ -4391,7 +4379,16 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; } else if (IsKeyPressed(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressed(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } - else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut) + { + if (!state->HasSelection()) + { + // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace) + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); + } else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly) { if (!state->HasSelection()) @@ -4424,7 +4421,23 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else if (is_cancel) { - clear_active_id = cancel_edit = true; + if (flags & ImGuiInputTextFlags_EscapeClearsAll) + { + if (state->CurLenA > 0) + { + revert_edit = true; + } + else + { + render_cursor = render_selection = false; + clear_active_id = true; + } + } + else + { + clear_active_id = revert_edit = true; + render_cursor = render_selection = false; + } } else if (is_undo || is_redo) { @@ -4465,12 +4478,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const int clipboard_len = (int)strlen(clipboard); ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar)); int clipboard_filtered_len = 0; - for (const char* s = clipboard; *s; ) + for (const char* s = clipboard; *s != 0; ) { unsigned int c; s += ImTextCharFromUtf8(&c, s, NULL); - if (c == 0) - break; if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard)) continue; clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; @@ -4495,11 +4506,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { IM_ASSERT(state != NULL); - if (cancel_edit) + if (revert_edit && !is_readonly) { - // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. - if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0) + if (flags & ImGuiInputTextFlags_EscapeClearsAll) { + // Clear input + apply_new_text = ""; + apply_new_text_length = 0; + STB_TEXTEDIT_CHARTYPE empty_string; + stb_textedit_replace(state, &state->Stb, &empty_string, 0); + } + else if (strcmp(buf, state->InitialTextA.Data) != 0) + { + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. // Push records into the undo stack so we can CTRL+Z the revert operation itself apply_new_text = state->InitialTextA.Data; apply_new_text_length = state->InitialTextA.Size - 1; @@ -4524,7 +4543,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue 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 = !cancel_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); if (apply_edit_back_to_user_buffer) { // Apply new value immediately - copy modified buffer back @@ -4599,6 +4618,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } if (buf_dirty) { + IM_ASSERT((flags & ImGuiInputTextFlags_ReadOnly) == 0); IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ? if (callback_data.BufTextLen > backup_current_text_length && is_resizable) @@ -4617,9 +4637,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ apply_new_text_length = state->CurLenA; } } - - // Clear temporary user storage - state->Flags = ImGuiInputTextFlags_None; } // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) @@ -4733,11 +4750,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ searches_result_line_no[1] = line_count; // Calculate 2d position by finding the beginning of the line and measuring distance - cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; cursor_offset.y = searches_result_line_no[0] * g.FontSize; if (searches_result_line_no[1] >= 0) { - select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; select_start_offset.y = searches_result_line_no[1] * g.FontSize; } @@ -4806,7 +4823,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else { - ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + ImVec2 rect_size = InputTextCalcTextSizeW(&g, p, text_selected_end, &p, NULL, true); if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); @@ -4898,7 +4915,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) MarkItemEdited(id); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) return validated; else @@ -4912,7 +4929,9 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) ImStb::STB_TexteditState* stb_state = &state->Stb; ImStb::StbUndoState* undo_state = &stb_state->undostate; Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); - Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, state->CurLenW, stb_state->cursor, stb_state->select_start, stb_state->select_end); + DebugLocateItemOnHover(state->ID); + Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenW, state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 15), true)) // Visualize undo state { @@ -4959,28 +4978,32 @@ bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flag return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); } +static void ColorEditRestoreH(const float* col, float* H) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ColorEditCurrentID != 0); + if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) + return; + *H = g.ColorEditSavedHue; +} + // ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. // Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V) { - // This check is optional. Suppose we have two color widgets side by side, both widgets display different colors, but both colors have hue and/or saturation undefined. - // With color check: hue/saturation is preserved in one widget. Editing color in one widget would reset hue/saturation in another one. - // Without color check: common hue/saturation would be displayed in all widgets that have hue/saturation undefined. - // g.ColorEditLastColor is stored as ImU32 RGB value: this essentially gives us color equality check with reduced precision. - // Tiny external color changes would not be detected and this check would still pass. This is OK, since we only restore hue/saturation _only_ if they are undefined, - // therefore this change flipping hue/saturation from undefined to a very tiny value would still be represented in color picker. ImGuiContext& g = *GImGui; - if (g.ColorEditLastColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) + IM_ASSERT(g.ColorEditCurrentID != 0); + if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) return; // When S == 0, H is undefined. // When H == 1 it wraps around to 0. - if (*S == 0.0f || (*H == 0.0f && g.ColorEditLastHue == 1)) - *H = g.ColorEditLastHue; + if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1)) + *H = g.ColorEditSavedHue; // When V == 0, S is undefined. if (*V == 0.0f) - *S = g.ColorEditLastSat; + *S = g.ColorEditSavedSat; } // Edit colors components (each component in 0.0f..1.0f range). @@ -5003,6 +5026,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag BeginGroup(); PushID(label); + const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); + if (set_current_color_edit_id) + g.ColorEditCurrentID = window->IDStack.back(); // If we're not showing any slider there's no point in doing any HSV conversions const ImGuiColorEditFlags flags_untouched = flags; @@ -5036,7 +5062,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV)) { - // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. + // Hue is lost when converting from grayscale rgb (saturation=0). Restore it. ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); ColorEditRestoreHS(col, &f[0], &f[1], &f[2]); } @@ -5141,23 +5167,29 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (BeginPopup("picker")) { - picker_active_window = g.CurrentWindow; - if (label != label_display_end) + if (g.CurrentWindow->BeginCount == 1) { - TextEx(label, label_display_end); - Spacing(); + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextEx(label, label_display_end); + Spacing(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); } - ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; - ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; - SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? - value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); EndPopup(); } } if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) { + // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), + // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this. SameLine(0.0f, style.ItemInnerSpacing.x); + window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x); TextEx(label, label_display_end); } @@ -5169,10 +5201,11 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag f[n] = i[n] / 255.0f; if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB)) { - g.ColorEditLastHue = f[0]; - g.ColorEditLastSat = f[1]; + g.ColorEditSavedHue = f[0]; + g.ColorEditSavedSat = f[1]; ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); - g.ColorEditLastColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0)); + g.ColorEditSavedID = g.ColorEditCurrentID; + g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0)); } if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV)) ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); @@ -5184,6 +5217,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag col[3] = f[3]; } + if (set_current_color_edit_id) + g.ColorEditCurrentID = 0; PopID(); EndGroup(); @@ -5194,7 +5229,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag bool accepted_drag_drop = false; if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) { - memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 + memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086 value_changed = accepted_drag_drop = true; } if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) @@ -5213,7 +5248,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) g.LastItemData.ID = g.ActiveId; - if (value_changed) + if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId MarkItemEdited(g.LastItemData.ID); return value_changed; @@ -5257,6 +5292,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl g.NextItemData.ClearFlags(); PushID(label); + const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); + if (set_current_color_edit_id) + g.ColorEditCurrentID = window->IDStack.back(); BeginGroup(); if (!(flags & ImGuiColorEditFlags_NoSidePreview)) @@ -5305,7 +5343,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl float R = col[0], G = col[1], B = col[2]; if (flags & ImGuiColorEditFlags_InputRGB) { - // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. + // Hue is lost when converting from grayscale rgb (saturation=0). Restore it. ColorConvertRGBtoHSV(R, G, B, H, S, V); ColorEditRestoreHS(col, &H, &S, &V); } @@ -5360,10 +5398,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl { S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1)); V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); - - // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square. - if (g.ColorEditLastColor == ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) - H = g.ColorEditLastHue; + ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square. value_changed = value_changed_sv = true; } if (!(flags & ImGuiColorEditFlags_NoOptions)) @@ -5438,9 +5473,10 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (flags & ImGuiColorEditFlags_InputRGB) { ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]); - g.ColorEditLastHue = H; - g.ColorEditLastSat = S; - g.ColorEditLastColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)); + g.ColorEditSavedHue = H; + g.ColorEditSavedSat = S; + g.ColorEditSavedID = g.ColorEditCurrentID; + g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)); } else if (flags & ImGuiColorEditFlags_InputHSV) { @@ -5601,9 +5637,11 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) value_changed = false; - if (value_changed) + if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId MarkItemEdited(g.LastItemData.ID); + if (set_current_color_edit_id) + g.ColorEditCurrentID = 0; PopID(); return value_changed; @@ -6377,6 +6415,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries ImGuiButtonFlags button_flags = 0; if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } + if (flags & ImGuiSelectableFlags_NoSetKeyOwner) { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; } if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } @@ -6393,7 +6432,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) // - (2) usage will fail with clipped items // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent) + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) if (g.NavJustMovedToId == id) selected = pressed = true; @@ -6402,7 +6441,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { - SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, WindowRectAbsToRel(window, bb)); // (bb == NavRect) + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) g.NavDisableHighlight = true; } } @@ -6417,8 +6456,6 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render - if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) - hovered = true; if (hovered || selected) { const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); @@ -6593,7 +6630,7 @@ bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(v // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions //------------------------------------------------------------------------- -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, ImVec2 frame_size) +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) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -6604,10 +6641,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); - if (frame_size.x == 0.0f) - frame_size.x = CalcItemWidth(); - if (frame_size.y == 0.0f) - frame_size.y = label_size.y + (style.FramePadding.y * 2); + 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); const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); @@ -6893,7 +6927,7 @@ void ImGui::EndMenuBar() // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth bothering) const ImGuiNavLayer layer = ImGuiNavLayer_Menu; - IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check + IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary) FocusWindow(window); SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. @@ -7023,9 +7057,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation. - ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; if (window->Flags & ImGuiWindowFlags_ChildMenu) - flags |= ImGuiWindowFlags_ChildWindow; + window_flags |= ImGuiWindowFlags_ChildWindow; // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. @@ -7033,7 +7067,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (g.MenusIdSubmittedThisFrame.contains(id)) { if (menu_is_open) - menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) else g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values return menu_is_open; @@ -7045,10 +7079,10 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImVec2 label_size = CalcTextSize(label, NULL, true); // 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. const bool menuset_is_open = IsRootOfOpenMenuSet(); - ImGuiWindow* backed_nav_window = g.NavWindow; if (menuset_is_open) - g.NavWindow = window; + PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); // 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(). @@ -7059,7 +7093,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) BeginDisabled(); const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; bool pressed; - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups; + + // 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_DontClosePopups; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Menu inside an horizontal menu bar @@ -7070,7 +7106,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); 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, 0.0f)); + pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); RenderText(text_pos, label); PopStyleVar(); window->DC.CursorPos.x += IM_FLOOR(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(). @@ -7086,7 +7122,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float min_w = window->DC.MenuColumns.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); - pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); RenderText(text_pos, label); if (icon_w > 0.0f) RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); @@ -7097,7 +7133,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover; if (menuset_is_open) - g.NavWindow = backed_nav_window; + PopItemFlag(); bool want_open = false; bool want_close = false; @@ -7106,26 +7142,30 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. bool moving_toward_child_menu = false; - ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL; - if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) + ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below) + ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL; + if (g.HoveredWindow == window && child_menu_window != NULL) { float ref_unit = g.FontSize; // FIXME-DPI + float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f; ImRect next_window_rect = child_menu_window->Rect(); ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta); - ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); - ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR(); float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // add a bit of extra slack. - ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues (FIXME: ??) + ta.x += child_dir * -0.5f; + tb.x += child_dir * ref_unit; + tc.x += child_dir * ref_unit; tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle has maximum height to limit the slope and the bias toward large sub-menus tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +ref_unit * 8.0f); moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); - //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_other_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] } // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not) // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon. // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.) - if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu) + if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavDisableMouseHover) want_close = true; // Open @@ -7166,23 +7206,32 @@ 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 (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) + if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { - // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + // Don't reopen/recycle same menu level in the same frame, first close the other menu and yield for a frame. OpenPopup(label); - return false; } - - menu_is_open |= want_open; - if (want_open) + else if (want_open) + { + menu_is_open = true; OpenPopup(label); + } if (menu_is_open) { - SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + ImGuiLastItemData last_item_in_parent = g.LastItemData; + SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos. PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding - menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) PopStyleVar(); + if (menu_is_open) + { + // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu() + // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck) + g.LastItemData = last_item_in_parent; + if (g.HoveredWindow == window) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + } } else { @@ -7199,17 +7248,18 @@ bool ImGui::BeginMenu(const char* label, bool enabled) void ImGui::EndMenu() { - // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu). - // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. - // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. + // Nav: When a left move request our menu failed, close ourselves. ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) - if (g.NavWindow && (g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow == window) - { - ClosePopupToLevel(g.BeginPopupStack.Size, true); - NavMoveRequestCancel(); - } + IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls + ImGuiWindow* parent_window = window->ParentWindow; // Should always be != NULL is we passed assert. + if (window->BeginCount == window->BeginCountPreviousFrame) + if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet()) + if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.BeginPopupStack.Size - 1, true); + NavMoveRequestCancel(); + } EndPopup(); } @@ -7225,10 +7275,10 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut ImVec2 pos = window->DC.CursorPos; ImVec2 label_size = CalcTextSize(label, NULL, true); + // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); - ImGuiWindow* backed_nav_window = g.NavWindow; if (menuset_is_open) - g.NavWindow = window; + PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. @@ -7237,7 +7287,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (!enabled) BeginDisabled(); - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover; + // 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; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { @@ -7249,7 +7300,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f)); PopStyleVar(); - RenderText(text_pos, label); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) + RenderText(text_pos, label); window->DC.CursorPos.x += IM_FLOOR(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 @@ -7262,25 +7314,28 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut float checkmark_w = IM_FLOOR(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 stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); - pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); - RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); - if (icon_w > 0.0f) - RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); - if (shortcut_w > 0.0f) + pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) { - PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); - RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); - PopStyleColor(); + RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + if (icon_w > 0.0f) + RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + if (shortcut_w > 0.0f) + { + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + RenderText(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); } - 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); } IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); if (!enabled) EndDisabled(); PopID(); if (menuset_is_open) - g.NavWindow = backed_nav_window; + PopItemFlag(); return pressed; } @@ -7311,11 +7366,17 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - TabBarCalcTabID() [Internal] // - TabBarCalcMaxTabWidth() [Internal] // - TabBarFindTabById() [Internal] +// - TabBarFindTabByOrder() [Internal] +// - TabBarGetCurrentTab() [Internal] +// - TabBarGetTabName() [Internal] // - TabBarRemoveTab() [Internal] // - TabBarCloseTab() [Internal] // - TabBarScrollClamp() [Internal] // - TabBarScrollToTab() [Internal] -// - TabBarQueueChangeTabOrder() [Internal] +// - TabBarQueueFocus() [Internal] +// - TabBarQueueReorder() [Internal] +// - TabBarProcessReorderFromMousePos() [Internal] +// - TabBarProcessReorder() [Internal] // - TabBarScrollingButtons() [Internal] // - TabBarTabListPopupButton() [Internal] //------------------------------------------------------------------------- @@ -7332,7 +7393,7 @@ struct ImGuiTabBarSection namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); - static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); + static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections); @@ -7440,6 +7501,7 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG tab_bar->ItemSpacingY = g.Style.ItemSpacing.y; tab_bar->FramePadding = g.Style.FramePadding; tab_bar->TabsActiveCount = 0; + tab_bar->LastTabItemIdx = -1; tab_bar->BeginCount = 1; // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap @@ -7488,6 +7550,7 @@ void ImGui::EndTabBar() if (tab_bar->BeginCount > 1) window->DC.CursorPos = tab_bar->BackupCursorPos; + tab_bar->LastTabItemIdx = -1; if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) PopID(); @@ -7597,9 +7660,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar. // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window. - const char* tab_name = tab_bar->GetTabName(tab); - const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; - tab->ContentWidth = (tab->RequestedWidth > 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button).x; + const char* tab_name = TabBarGetTabName(tab_bar, tab); + const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument); + tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x; int section_n = TabItemGetSectionIdx(tab); ImGuiTabBarSection* section = §ions[section_n]; @@ -7611,9 +7674,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++]; shrink_width_item->Index = tab_n; shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth; - - IM_ASSERT(tab->ContentWidth > 0.0f); - tab->Width = tab->ContentWidth; + tab->Width = ImMax(tab->ContentWidth, 1.0f); } // Compute total ideal width (used for e.g. auto-resizing a window) @@ -7643,7 +7704,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore - if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) + if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) { int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); @@ -7657,6 +7718,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (shrinked_width < 0.0f) continue; + shrinked_width = ImMax(1.0f, shrinked_width); int section_n = TabItemGetSectionIdx(tab); sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; @@ -7677,6 +7739,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; tab->Offset = tab_offset; + tab->NameOffset = -1; tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); @@ -7684,6 +7747,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) section_tab_index += section->TabCount; } + // Clear name buffers + tab_bar->TabsNames.Buf.resize(0); + // If we have lost the selected tab, select the next most recently active one if (found_selected_tab_id == false) tab_bar->SelectedTabId = 0; @@ -7715,10 +7781,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing; tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing; - // Clear name buffers - if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) - tab_bar->TabsNames.Buf.resize(0); - // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame) ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; @@ -7726,9 +7788,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal); } -// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. -static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label) +// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack. +static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window) { + IM_ASSERT(docked_window == NULL); // master branch only + IM_UNUSED(docked_window); if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) { ImGuiID id = ImHashStr(label); @@ -7757,7 +7821,30 @@ ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) return NULL; } -// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. +// Order = visible order, not submission order! (which is tab->BeginOrder) +ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order) +{ + if (order < 0 || order >= tab_bar->Tabs.Size) + return NULL; + return &tab_bar->Tabs[order]; +} + +ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar) +{ + if (tab_bar->LastTabItemIdx <= 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size) + return NULL; + return &tab_bar->Tabs[tab_bar->LastTabItemIdx]; +} + +const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + if (tab->NameOffset == -1) + return "N/A"; + IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size); + return tab_bar->TabsNames.Buf.Data + tab->NameOffset; +} + +// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) @@ -7788,7 +7875,7 @@ void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup) if (tab_bar->VisibleTabId != tab->ID) - tab_bar->NextSelectedTabId = tab->ID; + TabBarQueueFocus(tab_bar, tab); } } @@ -7809,7 +7896,7 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui ImGuiContext& g = *GImGui; float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) - int order = tab_bar->GetTabOrder(tab); + int order = TabBarGetTabOrder(tab_bar, tab); // Scrolling happens only in the central section (leading/trailing sections are not scrolling) // FIXME: This is all confusing. @@ -7833,7 +7920,12 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui } } -void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset) +void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + tab_bar->NextSelectedTabId = tab->ID; +} + +void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset) { IM_ASSERT(offset != 0); IM_ASSERT(tab_bar->ReorderRequestTabId == 0); @@ -7841,7 +7933,7 @@ void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, in tab_bar->ReorderRequestOffset = (ImS16)offset; } -void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos) +void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos) { ImGuiContext& g = *GImGui; IM_ASSERT(tab_bar->ReorderRequestTabId == 0); @@ -7884,7 +7976,7 @@ bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) return false; //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools - int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset; + int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset; if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) return false; @@ -7944,7 +8036,7 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) if (select_dir != 0) if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) { - int selected_order = tab_bar->GetTabOrder(tab_item); + int selected_order = TabBarGetTabOrder(tab_bar, tab_item); int target_order = selected_order + select_dir; // Skip tab item buttons until another tab item is found or end is reached @@ -7996,7 +8088,7 @@ static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) if (tab->Flags & ImGuiTabItemFlags_Button) continue; - const char* tab_name = tab_bar->GetTabName(tab); + const char* tab_name = TabBarGetTabName(tab_bar, tab); if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID)) tab_to_select = tab; } @@ -8035,7 +8127,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f } IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! - bool ret = TabItemEx(tab_bar, label, p_open, flags); + bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; @@ -8076,10 +8168,10 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } - return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder); + return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL); } -bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags) +bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window) { // Layout whole tab bar if not already done ImGuiContext& g = *GImGui; @@ -8094,14 +8186,14 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, return false; const ImGuiStyle& style = g.Style; - const ImGuiID id = TabBarCalcTabID(tab_bar, label); + const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window); // If the user called us with *p_open == false, we early out and don't render. // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); if (p_open && !*p_open) { - ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus); + ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav); return false; } @@ -8127,34 +8219,42 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab); // Calculate tab contents size - ImVec2 size = TabItemCalcSize(label, p_open != NULL); + ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument)); tab->RequestedWidth = -1.0f; if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) size.x = tab->RequestedWidth = g.NextItemData.Width; if (tab_is_new) - tab->Width = size.x; + tab->Width = ImMax(1.0f, size.x); tab->ContentWidth = size.x; tab->BeginOrder = tab_bar->TabsActiveCount++; const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); + const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument); const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; tab->LastFrameVisible = g.FrameCount; tab->Flags = flags; - // Append name with zero-terminator - tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); - tab_bar->TabsNames.append(label, label + strlen(label) + 1); + // Append name _WITH_ the zero-terminator + if (docked_window != NULL) + { + IM_ASSERT(docked_window == NULL); // master branch only + } + else + { + tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); + tab_bar->TabsNames.append(label, label + strlen(label) + 1); + } // Update selected tab if (!is_tab_button) { if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) - tab_bar->NextSelectedTabId = id; // New tabs gets activated + TabBarQueueFocus(tab_bar, tab); // New tabs gets activated if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar - tab_bar->NextSelectedTabId = id; + TabBarQueueFocus(tab_bar, tab); } // Lock visibility @@ -8172,7 +8272,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'. if (tab_appearing && (!tab_bar_appearing || tab_is_new)) { - ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus); + ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav); if (is_tab_button) return false; return tab_contents_visible; @@ -8218,7 +8318,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) - tab_bar->NextSelectedTabId = id; + TabBarQueueFocus(tab_bar, tab); // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered) if (g.ActiveId != id) @@ -8259,9 +8359,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1))) - if (!is_tab_button) - tab_bar->NextSelectedTabId = id; + if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) + TabBarQueueFocus(tab_bar, tab); if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; @@ -8270,7 +8369,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; bool just_closed; bool text_clipped; - TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); + TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); if (just_closed && p_open != NULL) { *p_open = false; @@ -8287,9 +8386,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) // FIXME: This is a mess. // FIXME: We may want disabled tab to still display the tooltip? - if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered()) + if (text_clipped && g.HoveredId == id && !held) if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) - SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); + if (IsItemHovered(ImGuiHoveredFlags_DelayNormal)) + SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected if (is_tab_button) @@ -8307,24 +8407,30 @@ void ImGui::SetTabItemClosed(const char* label) if (is_within_manual_tab_bar) { ImGuiTabBar* tab_bar = g.CurrentTabBar; - ImGuiID tab_id = TabBarCalcTabID(tab_bar, label); + ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL); if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) tab->WantClose = true; // Will be processed by next call to TabBarLayout() } } -ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button) +ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker) { ImGuiContext& g = *GImGui; ImVec2 label_size = CalcTextSize(label, NULL, true); ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); - if (has_close_button) + if (has_close_button_or_unsaved_marker) size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. else size.x += g.Style.FramePadding.x + 1.0f; return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); } +ImVec2 ImGui::TabItemCalcSize(ImGuiWindow*) +{ + IM_ASSERT(0); // This function exists to facilitate merge with 'docking' branch. + return ImVec2(0.0f, 0.0f); +} + void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) { // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it. diff --git a/external/imgui/imgui/imstb_textedit.h b/external/imgui/imgui/imstb_textedit.h index 75a159da..a8a82311 100644 --- a/external/imgui/imgui/imstb_textedit.h +++ b/external/imgui/imgui/imstb_textedit.h @@ -2,6 +2,7 @@ // This is a slightly modified version of stb_textedit.h 1.14. // Those changes would need to be pushed into nothings/stb: // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) +// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000) // Grep for [DEAR IMGUI] to find the changes. // stb_textedit.h - v1.14 - public domain - Sean Barrett @@ -524,29 +525,14 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s int z = STB_TEXTEDIT_STRINGLEN(str); int i=0, first; - if (n == z) { - // if it's at the end, then find the last line -- simpler than trying to - // explicitly handle this case in the regular code - if (single_line) { - STB_TEXTEDIT_LAYOUTROW(&r, str, 0); - find->y = 0; - find->first_char = 0; - find->length = z; - find->height = r.ymax - r.ymin; - find->x = r.x1; - } else { - find->y = 0; - find->x = 0; - find->height = 1; - while (i < z) { - STB_TEXTEDIT_LAYOUTROW(&r, str, i); - prev_start = i; - i += r.num_chars; - } - find->first_char = i; - find->length = 0; - find->prev_first = prev_start; - } + if (n == z && single_line) { + // special case if it's at the end (may not be needed?) + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; return; } @@ -557,9 +543,13 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s STB_TEXTEDIT_LAYOUTROW(&r, str, i); if (n < i + r.num_chars) break; + if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line + break; // [DEAR IMGUI] prev_start = i; i += r.num_chars; find->y += r.baseline_y_delta; + if (i == z) // [DEAR IMGUI] + break; // [DEAR IMGUI] } find->first_char = first = i; diff --git a/external/imgui/imgui/imstb_truetype.h b/external/imgui/imgui/imstb_truetype.h index 643d3789..35c827e6 100644 --- a/external/imgui/imgui/imstb_truetype.h +++ b/external/imgui/imgui/imstb_truetype.h @@ -2008,7 +2008,7 @@ static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int gly start = end; } } - if (fdselector == -1) stbtt__new_buf(NULL, 0); + if (fdselector == -1) return stbtt__new_buf(NULL, 0); // [DEAR IMGUI] fixed, see #6007 and nothings/stb#1422 return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); } diff --git a/resources/db.json b/resources/db.json deleted file mode 100644 index 757d1ad9..00000000 --- a/resources/db.json +++ /dev/null @@ -1,10471 +0,0 @@ -{ - "items": [ - { - "name": "007 - The World Is Not Enough (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "002C3B2A" - }, - { - "name": "007 - The World Is Not Enough (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "7F030D3A" - }, - { - "name": "007 - The World Is Not Enough (USA)", - "region": "USA", - "crc": "26360987" - }, - { - "name": "007 - The World Is Not Enough (USA)", - "region": "USA", - "crc": "34F0EE5B" - }, - { - "name": "007 - The World Is Not Enough (USA) (v2) (Beta)", - "region": "USA", - "crc": "6C180FEF" - }, - { - "name": "007 - The World Is Not Enough (USA) (v2) (Beta)", - "region": "USA", - "crc": "6416D1BF" - }, - { - "name": "007 - The World Is Not Enough (USA) (v21) (Beta)", - "region": "USA", - "crc": "F0B6BF59" - }, - { - "name": "007 - The World Is Not Enough (USA) (v21) (Beta)", - "region": "USA", - "crc": "EBF883ED" - }, - { - "name": "1080 Snowboarding (Europe) (En,Ja,Fr,De)", - "region": "Europe", - "crc": "75A21679" - }, - { - "name": "1080 Snowboarding (Europe) (En,Ja,Fr,De)", - "region": "Europe", - "crc": "B0256101" - }, - { - "name": "1080 Snowboarding (Japan, USA) (En,Ja)", - "crc": "08FE81C7" - }, - { - "name": "1080 Snowboarding (Japan, USA) (En,Ja)", - "crc": "D1744951" - }, - { - "name": "40 Winks (Europe) (En,Es,It) (Proto) (1999-10-07)", - "region": "Europe", - "crc": "7E718143" - }, - { - "name": "40 Winks (Europe) (En,Es,It) (Proto) (1999-10-07)", - "region": "Europe", - "crc": "366ED851" - }, - { - "name": "40 Winks (USA) (Aftermarket) (Unl)", - "region": "USA", - "crc": "31C19633" - }, - { - "name": "40 Winks (USA) (Aftermarket) (Unl)", - "region": "USA", - "crc": "15A0118F" - }, - { - "name": "40 Winks (USA) (Proto) (2000-01-10)", - "region": "USA", - "crc": "3A674026" - }, - { - "name": "40 Winks (USA) (Proto) (2000-01-10)", - "region": "USA", - "crc": "266A9E05" - }, - { - "name": "64 Hanafuda - Tenshi no Yakusoku (Japan)", - "region": "Japan", - "crc": "60A680E7" - }, - { - "name": "64 Hanafuda - Tenshi no Yakusoku (Japan)", - "region": "Japan", - "crc": "2B43006C" - }, - { - "name": "64 Oozumou (Japan)", - "region": "Japan", - "crc": "742E31FB" - }, - { - "name": "64 Oozumou (Japan)", - "region": "Japan", - "crc": "A6DBF8AE" - }, - { - "name": "64 Oozumou 2 (Japan)", - "region": "Japan", - "crc": "C1BC6FD8" - }, - { - "name": "64 Oozumou 2 (Japan)", - "region": "Japan", - "crc": "3CD5E271" - }, - { - "name": "64 Trump Collection - Alice no Wakuwaku Trump World (Japan)", - "region": "Japan", - "crc": "DCA7F4EB" - }, - { - "name": "64 Trump Collection - Alice no Wakuwaku Trump World (Japan)", - "region": "Japan", - "crc": "34BBB95D" - }, - { - "name": "64 de Hakken!! Tamagotchi - Minna de Tamagotchi World (Japan)", - "region": "Japan", - "crc": "67A789E5" - }, - { - "name": "64 de Hakken!! Tamagotchi - Minna de Tamagotchi World (Japan)", - "region": "Japan", - "crc": "C6400938" - }, - { - "name": "AI Shougi 3 (Japan)", - "region": "Japan", - "crc": "86DF90E6" - }, - { - "name": "AI Shougi 3 (Japan)", - "region": "Japan", - "crc": "43022243" - }, - { - "name": "Action Replay Pro 64 (Europe) (v3.0) (Unl)", - "region": "Europe", - "crc": "C992DFB4" - }, - { - "name": "Action Replay Pro 64 (Europe) (v3.0) (Unl)", - "region": "Europe", - "crc": "C992DFB4" - }, - { - "name": "Action Replay Pro 64 (Europe) (v3.3) (Unl)", - "region": "Europe", - "crc": "9FBABFDA" - }, - { - "name": "Action Replay Pro 64 (Europe) (v3.3) (Unl)", - "region": "Europe", - "crc": "9FBABFDA" - }, - { - "name": "AeroFighters Assault (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "6A2B08DA" - }, - { - "name": "AeroFighters Assault (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "E493E46C" - }, - { - "name": "AeroFighters Assault (USA)", - "region": "USA", - "crc": "4370D7E3" - }, - { - "name": "AeroFighters Assault (USA)", - "region": "USA", - "crc": "B08EF0B9" - }, - { - "name": "AeroGauge (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "040B0046" - }, - { - "name": "AeroGauge (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "D8CA6C8E" - }, - { - "name": "AeroGauge (Japan) (Demo) (Kiosk)", - "region": "Japan", - "crc": "6EE3B932" - }, - { - "name": "AeroGauge (Japan) (Demo) (Kiosk)", - "region": "Japan", - "crc": "4BA2A495" - }, - { - "name": "AeroGauge (Japan) (Rev 1)", - "region": "Japan", - "crc": "F322B641" - }, - { - "name": "AeroGauge (Japan) (Rev 1)", - "region": "Japan", - "crc": "1A5F6C19" - }, - { - "name": "AeroGauge (USA)", - "region": "USA", - "crc": "198B9E0E" - }, - { - "name": "AeroGauge (USA)", - "region": "USA", - "crc": "04038CB8" - }, - { - "name": "Aidyn Chronicles - The First Mage (Europe)", - "region": "Europe", - "crc": "BE7E230D" - }, - { - "name": "Aidyn Chronicles - The First Mage (Europe)", - "region": "Europe", - "crc": "9DC07FC0" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA)", - "region": "USA", - "crc": "B1F18186" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA)", - "region": "USA", - "crc": "8DA62304" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA) (Beta) (2000-02-10)", - "region": "USA", - "crc": "C46D84A0" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA) (Beta) (2000-02-10)", - "region": "USA", - "crc": "6BAEE952" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA) (Beta) (2000-05-09)", - "region": "USA", - "crc": "ABD43F3F" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA) (Beta) (2000-05-09)", - "region": "USA", - "crc": "58DCDCCE" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA) (Rev 1)", - "region": "USA", - "crc": "D5051096" - }, - { - "name": "Aidyn Chronicles - The First Mage (USA) (Rev 1)", - "region": "USA", - "crc": "276178DF" - }, - { - "name": "Air Boarder 64 (Japan)", - "region": "Japan", - "crc": "58FCB771" - }, - { - "name": "Air Boarder 64 (Japan)", - "region": "Japan", - "crc": "79F2DF14" - }, - { - "name": "Airboarder 64 (Europe)", - "region": "Europe", - "crc": "C14D45AC" - }, - { - "name": "Airboarder 64 (Europe)", - "region": "Europe", - "crc": "92659837" - }, - { - "name": "Akumajou Dracula Mokushiroku - Real Action Adventure (Japan)", - "region": "Japan", - "crc": "E349CFEC" - }, - { - "name": "Akumajou Dracula Mokushiroku - Real Action Adventure (Japan)", - "region": "Japan", - "crc": "CA79D5A0" - }, - { - "name": "Akumajou Dracula Mokushiroku Gaiden - Legend of Cornell (Japan)", - "region": "Japan", - "crc": "FF009C21" - }, - { - "name": "Akumajou Dracula Mokushiroku Gaiden - Legend of Cornell (Japan)", - "region": "Japan", - "crc": "420CE37B" - }, - { - "name": "All Star Tennis '99 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "996E845F" - }, - { - "name": "All Star Tennis '99 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "E718347D" - }, - { - "name": "All Star Tennis 99 (USA)", - "region": "USA", - "crc": "A7DCF638" - }, - { - "name": "All Star Tennis 99 (USA)", - "region": "USA", - "crc": "7326E6C9" - }, - { - "name": "All-Star Baseball 2000 (Europe)", - "region": "Europe", - "crc": "D3C29AA4" - }, - { - "name": "All-Star Baseball 2000 (Europe)", - "region": "Europe", - "crc": "15F18CAE" - }, - { - "name": "All-Star Baseball 2000 (USA)", - "region": "USA", - "crc": "69E88471" - }, - { - "name": "All-Star Baseball 2000 (USA)", - "region": "USA", - "crc": "15063EAC" - }, - { - "name": "All-Star Baseball 2001 (USA)", - "region": "USA", - "crc": "4D659E85" - }, - { - "name": "All-Star Baseball 2001 (USA)", - "region": "USA", - "crc": "3E8BE0E0" - }, - { - "name": "All-Star Baseball 99 (Europe)", - "region": "Europe", - "crc": "D0DE3584" - }, - { - "name": "All-Star Baseball 99 (Europe)", - "region": "Europe", - "crc": "CECD29A6" - }, - { - "name": "All-Star Baseball 99 (USA)", - "region": "USA", - "crc": "6D25B36F" - }, - { - "name": "All-Star Baseball 99 (USA)", - "region": "USA", - "crc": "82B7E47D" - }, - { - "name": "Armorines - Project S.W.A.R.M. (Europe)", - "region": "Europe", - "crc": "600BC49E" - }, - { - "name": "Armorines - Project S.W.A.R.M. (Europe)", - "region": "Europe", - "crc": "33332C57" - }, - { - "name": "Armorines - Project S.W.A.R.M. (Germany)", - "region": "Germany", - "crc": "5BAB9100" - }, - { - "name": "Armorines - Project S.W.A.R.M. (Germany)", - "region": "Germany", - "crc": "43A6829D" - }, - { - "name": "Armorines - Project S.W.A.R.M. (USA)", - "region": "USA", - "crc": "630A19E2" - }, - { - "name": "Armorines - Project S.W.A.R.M. (USA)", - "region": "USA", - "crc": "950985F6" - }, - { - "name": "Army Men - Air Combat (USA)", - "region": "USA", - "crc": "1952CC87" - }, - { - "name": "Army Men - Air Combat (USA)", - "region": "USA", - "crc": "2355E4F3" - }, - { - "name": "Army Men - Sarge's Heroes (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "E79048E2" - }, - { - "name": "Army Men - Sarge's Heroes (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "B2B54CC1" - }, - { - "name": "Army Men - Sarge's Heroes (USA)", - "region": "USA", - "crc": "2FE786F6" - }, - { - "name": "Army Men - Sarge's Heroes (USA)", - "region": "USA", - "crc": "97BF6DB2" - }, - { - "name": "Army Men - Sarge's Heroes 2 (USA)", - "region": "USA", - "crc": "79A71608" - }, - { - "name": "Army Men - Sarge's Heroes 2 (USA)", - "region": "USA", - "crc": "3E9D1C51" - }, - { - "name": "Asteroids Hyper 64 (USA)", - "region": "USA", - "crc": "F5CE3D91" - }, - { - "name": "Asteroids Hyper 64 (USA)", - "region": "USA", - "crc": "FB982B44" - }, - { - "name": "Automobili Lamborghini (Europe)", - "region": "Europe", - "crc": "3BAF58D5" - }, - { - "name": "Automobili Lamborghini (Europe)", - "region": "Europe", - "crc": "210F594E" - }, - { - "name": "Automobili Lamborghini (USA)", - "region": "USA", - "crc": "A4374EAC" - }, - { - "name": "Automobili Lamborghini (USA)", - "region": "USA", - "crc": "17932C62" - }, - { - "name": "Baku Bomberman (Japan)", - "region": "Japan", - "crc": "22F54A52" - }, - { - "name": "Baku Bomberman (Japan)", - "region": "Japan", - "crc": "B9FAA3E4" - }, - { - "name": "Baku Bomberman 2 (Japan)", - "region": "Japan", - "crc": "86BBC278" - }, - { - "name": "Baku Bomberman 2 (Japan)", - "region": "Japan", - "crc": "850675CC" - }, - { - "name": "Bakuretsu Muteki Bangaioh (Japan)", - "region": "Japan", - "crc": "6AB7FEC6" - }, - { - "name": "Bakuretsu Muteki Bangaioh (Japan)", - "region": "Japan", - "crc": "2A8CE2E1" - }, - { - "name": "Bakushou Jinsei 64 - Mezase! Resort Ou (Japan)", - "region": "Japan", - "crc": "EF8C2F34" - }, - { - "name": "Bakushou Jinsei 64 - Mezase! Resort Ou (Japan)", - "region": "Japan", - "crc": "5FD42464" - }, - { - "name": "Banjo to Kazooie no Daibouken (Japan)", - "region": "Japan", - "crc": "8F7C9324" - }, - { - "name": "Banjo to Kazooie no Daibouken (Japan)", - "region": "Japan", - "crc": "11AD432C" - }, - { - "name": "Banjo to Kazooie no Daibouken 2 (Japan)", - "region": "Japan", - "crc": "258C58D0" - }, - { - "name": "Banjo to Kazooie no Daibouken 2 (Japan)", - "region": "Japan", - "crc": "8A3D8792" - }, - { - "name": "Banjo-Kazooie (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "525899C9" - }, - { - "name": "Banjo-Kazooie (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "4AF7C16B" - }, - { - "name": "Banjo-Kazooie (USA)", - "region": "USA", - "crc": "AD429961" - }, - { - "name": "Banjo-Kazooie (USA)", - "region": "USA", - "crc": "4FD43199" - }, - { - "name": "Banjo-Kazooie (USA) (Rev 1)", - "region": "USA", - "crc": "FB7FFB10" - }, - { - "name": "Banjo-Kazooie (USA) (Rev 1)", - "region": "USA", - "crc": "90B38651" - }, - { - "name": "Banjo-Tooie (Australia)", - "region": "Australia", - "crc": "2736266A" - }, - { - "name": "Banjo-Tooie (Australia)", - "region": "Australia", - "crc": "38EC4A6E" - }, - { - "name": "Banjo-Tooie (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "1EC12F5A" - }, - { - "name": "Banjo-Tooie (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "5EE620FB" - }, - { - "name": "Banjo-Tooie (USA)", - "region": "USA", - "crc": "BAB803EF" - }, - { - "name": "Banjo-Tooie (USA)", - "region": "USA", - "crc": "0E0DC5A5" - }, - { - "name": "Bass Rush - ECOGEAR PowerWorm Championship (Japan)", - "region": "Japan", - "crc": "383B86EF" - }, - { - "name": "Bass Rush - ECOGEAR PowerWorm Championship (Japan)", - "region": "Japan", - "crc": "0E388FAE" - }, - { - "name": "Bassmasters 2000 (USA)", - "region": "USA", - "crc": "6B09092E" - }, - { - "name": "Bassmasters 2000 (USA)", - "region": "USA", - "crc": "8B7BD07E" - }, - { - "name": "Bassmasters 2000 (USA) (Beta)", - "region": "USA", - "crc": "86F8E439" - }, - { - "name": "Bassmasters 2000 (USA) (Beta)", - "region": "USA", - "crc": "A9DD59B1" - }, - { - "name": "Batman Beyond - Return of the Joker (USA)", - "region": "USA", - "crc": "35299F9C" - }, - { - "name": "Batman Beyond - Return of the Joker (USA)", - "region": "USA", - "crc": "D64E0A7B" - }, - { - "name": "Batman of the Future - Return of the Joker (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "82A4BB8A" - }, - { - "name": "Batman of the Future - Return of the Joker (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "86AA921C" - }, - { - "name": "BattleTanx (USA)", - "region": "USA", - "crc": "6C230765" - }, - { - "name": "BattleTanx (USA)", - "region": "USA", - "crc": "66123C8B" - }, - { - "name": "BattleTanx - Global Assault (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "C99C6030" - }, - { - "name": "BattleTanx - Global Assault (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "EC02C5AD" - }, - { - "name": "BattleTanx - Global Assault (USA)", - "region": "USA", - "crc": "31BEB053" - }, - { - "name": "BattleTanx - Global Assault (USA)", - "region": "USA", - "crc": "5819D423" - }, - { - "name": "Battlezone - Rise of the Black Dogs (USA)", - "region": "USA", - "crc": "736F9D5C" - }, - { - "name": "Battlezone - Rise of the Black Dogs (USA)", - "region": "USA", - "crc": "18CCCCFD" - }, - { - "name": "Beetle Adventure Racing! (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "5B6C6E4C" - }, - { - "name": "Beetle Adventure Racing! (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "E6F43F48" - }, - { - "name": "Beetle Adventure Racing! (Japan)", - "region": "Japan", - "crc": "49E75825" - }, - { - "name": "Beetle Adventure Racing! (Japan)", - "region": "Japan", - "crc": "4246ACD8" - }, - { - "name": "Beetle Adventure Racing! (USA) (En,Fr,De)", - "region": "USA", - "crc": "F4A97C73" - }, - { - "name": "Beetle Adventure Racing! (USA) (En,Fr,De)", - "region": "USA", - "crc": "E3517FD8" - }, - { - "name": "Big Mountain 2000 (USA)", - "region": "USA", - "crc": "3AC924BC" - }, - { - "name": "Big Mountain 2000 (USA)", - "region": "USA", - "crc": "FF44020C" - }, - { - "name": "Bio F.R.E.A.K.S. (Europe)", - "region": "Europe", - "crc": "2C4EB906" - }, - { - "name": "Bio F.R.E.A.K.S. (Europe)", - "region": "Europe", - "crc": "3EC67AD8" - }, - { - "name": "Bio F.R.E.A.K.S. (USA)", - "region": "USA", - "crc": "DFBF448C" - }, - { - "name": "Bio F.R.E.A.K.S. (USA)", - "region": "USA", - "crc": "52283865" - }, - { - "name": "Biohazard 2 (Japan)", - "region": "Japan", - "crc": "4F9D569F" - }, - { - "name": "Biohazard 2 (Japan)", - "region": "Japan", - "crc": "31A3B913" - }, - { - "name": "Blast Corps (Europe) (En,De)", - "region": "Europe", - "crc": "4C820695" - }, - { - "name": "Blast Corps (Europe) (En,De)", - "region": "Europe", - "crc": "2FF810CF" - }, - { - "name": "Blast Corps (USA)", - "region": "USA", - "crc": "767A95E7" - }, - { - "name": "Blast Corps (USA)", - "region": "USA", - "crc": "22CA221D" - }, - { - "name": "Blast Corps (USA) (Rev 1)", - "region": "USA", - "crc": "9CBBCCF1" - }, - { - "name": "Blast Corps (USA) (Rev 1)", - "region": "USA", - "crc": "23D415F8" - }, - { - "name": "Blastdozer (Japan)", - "region": "Japan", - "crc": "081A3641" - }, - { - "name": "Blastdozer (Japan)", - "region": "Japan", - "crc": "4605759E" - }, - { - "name": "Blues Brothers 2000 (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "8FB41658" - }, - { - "name": "Blues Brothers 2000 (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "56507793" - }, - { - "name": "Blues Brothers 2000 (Europe) (En,Fr,De,Es,It,Nl) (Beta) (2000-01-15) (#20)", - "region": "Europe", - "crc": "1AC60460" - }, - { - "name": "Blues Brothers 2000 (Europe) (En,Fr,De,Es,It,Nl) (Beta) (2000-01-15) (#20)", - "region": "Europe", - "crc": "93FB213B" - }, - { - "name": "Blues Brothers 2000 (Europe) (En,Fr,De,Es,It,Nl) (Beta) (2000-01-15) (#53)", - "region": "Europe", - "crc": "BB1CA04D" - }, - { - "name": "Blues Brothers 2000 (Europe) (En,Fr,De,Es,It,Nl) (Beta) (2000-01-15) (#53)", - "region": "Europe", - "crc": "9A25D5E4" - }, - { - "name": "Blues Brothers 2000 (USA)", - "region": "USA", - "crc": "C6F49764" - }, - { - "name": "Blues Brothers 2000 (USA)", - "region": "USA", - "crc": "109907B5" - }, - { - "name": "Body Harvest (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "6A04CDAE" - }, - { - "name": "Body Harvest (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "2761BDAE" - }, - { - "name": "Body Harvest (USA)", - "region": "USA", - "crc": "FABBDF02" - }, - { - "name": "Body Harvest (USA)", - "region": "USA", - "crc": "7B878C56" - }, - { - "name": "Bokujou Monogatari 2 (Japan)", - "region": "Japan", - "crc": "F97237C7" - }, - { - "name": "Bokujou Monogatari 2 (Japan)", - "region": "Japan", - "crc": "0837B349" - }, - { - "name": "Bokujou Monogatari 2 (Japan) (Rev 1)", - "region": "Japan", - "crc": "20FD2939" - }, - { - "name": "Bokujou Monogatari 2 (Japan) (Rev 1)", - "region": "Japan", - "crc": "BCFC0724" - }, - { - "name": "Bokujou Monogatari 2 (Japan) (Rev 2)", - "region": "Japan", - "crc": "9181C1B7" - }, - { - "name": "Bokujou Monogatari 2 (Japan) (Rev 2)", - "region": "Japan", - "crc": "36A97D01" - }, - { - "name": "Bomberman 64 (Europe)", - "region": "Europe", - "crc": "525339C5" - }, - { - "name": "Bomberman 64 (Europe)", - "region": "Europe", - "crc": "D3657C19" - }, - { - "name": "Bomberman 64 (Japan)", - "region": "Japan", - "crc": "CB9BD3F5" - }, - { - "name": "Bomberman 64 (Japan)", - "region": "Japan", - "crc": "1457008A" - }, - { - "name": "Bomberman 64 (USA)", - "region": "USA", - "crc": "3ED0E0DC" - }, - { - "name": "Bomberman 64 (USA)", - "region": "USA", - "crc": "50A98BDF" - }, - { - "name": "Bomberman 64 - The Second Attack! (USA)", - "region": "USA", - "crc": "57550007" - }, - { - "name": "Bomberman 64 - The Second Attack! (USA)", - "region": "USA", - "crc": "302B3135" - }, - { - "name": "Bomberman Hero (Europe)", - "region": "Europe", - "crc": "59E39947" - }, - { - "name": "Bomberman Hero (Europe)", - "region": "Europe", - "crc": "28DAD01B" - }, - { - "name": "Bomberman Hero (USA)", - "region": "USA", - "crc": "2CC2E634" - }, - { - "name": "Bomberman Hero (USA)", - "region": "USA", - "crc": "3E73AF1A" - }, - { - "name": "Bomberman Hero - Mirian Oujo o Sukue! (Japan)", - "region": "Japan", - "crc": "69CEABCC" - }, - { - "name": "Bomberman Hero - Mirian Oujo o Sukue! (Japan)", - "region": "Japan", - "crc": "F3A50116" - }, - { - "name": "Bottom of the 9th (USA)", - "region": "USA", - "crc": "1844C8CA" - }, - { - "name": "Bottom of the 9th (USA)", - "region": "USA", - "crc": "0F016E17" - }, - { - "name": "Brunswick Circuit Pro Bowling (USA)", - "region": "USA", - "crc": "80D70173" - }, - { - "name": "Brunswick Circuit Pro Bowling (USA)", - "region": "USA", - "crc": "3AC1BBAE" - }, - { - "name": "Buck Bumble (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "E26192AB" - }, - { - "name": "Buck Bumble (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "08433E0D" - }, - { - "name": "Buck Bumble (Japan)", - "region": "Japan", - "crc": "2ED81A65" - }, - { - "name": "Buck Bumble (Japan)", - "region": "Japan", - "crc": "24D1A591" - }, - { - "name": "Buck Bumble (USA)", - "region": "USA", - "crc": "8EC937DB" - }, - { - "name": "Buck Bumble (USA)", - "region": "USA", - "crc": "DA2ACD5E" - }, - { - "name": "Bug's Life, A (Europe)", - "region": "Europe", - "crc": "791881D4" - }, - { - "name": "Bug's Life, A (Europe)", - "region": "Europe", - "crc": "0D101756" - }, - { - "name": "Bug's Life, A (France)", - "region": "France", - "crc": "E5429094" - }, - { - "name": "Bug's Life, A (France)", - "region": "France", - "crc": "2420C6FD" - }, - { - "name": "Bug's Life, A (Germany)", - "region": "Germany", - "crc": "15A32836" - }, - { - "name": "Bug's Life, A (Germany)", - "region": "Germany", - "crc": "DE6CE3EF" - }, - { - "name": "Bug's Life, A (Italy)", - "region": "Italy", - "crc": "2D118764" - }, - { - "name": "Bug's Life, A (Italy)", - "region": "Italy", - "crc": "04244AAC" - }, - { - "name": "Bug's Life, A (USA)", - "region": "USA", - "crc": "CF2EA0B6" - }, - { - "name": "Bug's Life, A (USA)", - "region": "USA", - "crc": "7C2F610D" - }, - { - "name": "Bust-A-Move '99 (USA)", - "region": "USA", - "crc": "C285FC69" - }, - { - "name": "Bust-A-Move '99 (USA)", - "region": "USA", - "crc": "2A95290F" - }, - { - "name": "Bust-A-Move 2 - Arcade Edition (Europe)", - "region": "Europe", - "crc": "04731BAB" - }, - { - "name": "Bust-A-Move 2 - Arcade Edition (Europe)", - "region": "Europe", - "crc": "14F2CBE9" - }, - { - "name": "Bust-A-Move 2 - Arcade Edition (USA)", - "region": "USA", - "crc": "9F54CD2D" - }, - { - "name": "Bust-A-Move 2 - Arcade Edition (USA)", - "region": "USA", - "crc": "47511877" - }, - { - "name": "Bust-A-Move 3 DX (Europe)", - "region": "Europe", - "crc": "95595889" - }, - { - "name": "Bust-A-Move 3 DX (Europe)", - "region": "Europe", - "crc": "9F3F484B" - }, - { - "name": "California Speed (Europe) (Proto)", - "region": "Europe", - "crc": "D913B95F" - }, - { - "name": "California Speed (Europe) (Proto)", - "region": "Europe", - "crc": "8EF396FB" - }, - { - "name": "California Speed (USA)", - "region": "USA", - "crc": "6F6262CB" - }, - { - "name": "California Speed (USA)", - "region": "USA", - "crc": "387B0C4C" - }, - { - "name": "Carmageddon 64 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "8569F1A0" - }, - { - "name": "Carmageddon 64 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "D8D87DCA" - }, - { - "name": "Carmageddon 64 (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "8036F999" - }, - { - "name": "Carmageddon 64 (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "17B07602" - }, - { - "name": "Carmageddon 64 (USA)", - "region": "USA", - "crc": "10C6A0A1" - }, - { - "name": "Carmageddon 64 (USA)", - "region": "USA", - "crc": "E78DEBB6" - }, - { - "name": "Carnivale - Cenzo's Adventure (USA) (Proto)", - "region": "USA", - "crc": "3D9F8D32" - }, - { - "name": "Carnivale - Cenzo's Adventure (USA) (Proto)", - "region": "USA", - "crc": "ABB60A59" - }, - { - "name": "Castlevania (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "D9D76235" - }, - { - "name": "Castlevania (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "D1D4D306" - }, - { - "name": "Castlevania (USA)", - "region": "USA", - "crc": "8B0D3C00" - }, - { - "name": "Castlevania (USA)", - "region": "USA", - "crc": "84E815F3" - }, - { - "name": "Castlevania (USA) (Rev 1)", - "region": "USA", - "crc": "274D3493" - }, - { - "name": "Castlevania (USA) (Rev 1)", - "region": "USA", - "crc": "BF911B10" - }, - { - "name": "Castlevania (USA) (Rev 2)", - "region": "USA", - "crc": "83032D97" - }, - { - "name": "Castlevania (USA) (Rev 2)", - "region": "USA", - "crc": "7F50E637" - }, - { - "name": "Castlevania - Legacy of Darkness (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "12AB9B45" - }, - { - "name": "Castlevania - Legacy of Darkness (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "A12BF3DE" - }, - { - "name": "Castlevania - Legacy of Darkness (USA)", - "region": "USA", - "crc": "AB13028C" - }, - { - "name": "Castlevania - Legacy of Darkness (USA)", - "region": "USA", - "crc": "1F147376" - }, - { - "name": "Centre Court Tennis (Europe)", - "region": "Europe", - "crc": "B1D26F39" - }, - { - "name": "Centre Court Tennis (Europe)", - "region": "Europe", - "crc": "2573171A" - }, - { - "name": "Chameleon Twist (Europe)", - "region": "Europe", - "crc": "587DD983" - }, - { - "name": "Chameleon Twist (Europe)", - "region": "Europe", - "crc": "2AF50883" - }, - { - "name": "Chameleon Twist (Japan)", - "region": "Japan", - "crc": "6395C475" - }, - { - "name": "Chameleon Twist (Japan)", - "region": "Japan", - "crc": "A2DCF689" - }, - { - "name": "Chameleon Twist (USA)", - "region": "USA", - "crc": "7FE024C9" - }, - { - "name": "Chameleon Twist (USA)", - "region": "USA", - "crc": "4246EE14" - }, - { - "name": "Chameleon Twist (USA) (Rev 1)", - "region": "USA", - "crc": "7FF42FD0" - }, - { - "name": "Chameleon Twist (USA) (Rev 1)", - "region": "USA", - "crc": "C0345565" - }, - { - "name": "Chameleon Twist 2 (Europe)", - "region": "Europe", - "crc": "3B53519F" - }, - { - "name": "Chameleon Twist 2 (Europe)", - "region": "Europe", - "crc": "35958F55" - }, - { - "name": "Chameleon Twist 2 (Japan)", - "region": "Japan", - "crc": "08287CC8" - }, - { - "name": "Chameleon Twist 2 (Japan)", - "region": "Japan", - "crc": "F42BB75F" - }, - { - "name": "Chameleon Twist 2 (USA)", - "region": "USA", - "crc": "CDF26D67" - }, - { - "name": "Chameleon Twist 2 (USA)", - "region": "USA", - "crc": "356736C7" - }, - { - "name": "Charlie Blast's Territory (Europe)", - "region": "Europe", - "crc": "82C1D9E1" - }, - { - "name": "Charlie Blast's Territory (Europe)", - "region": "Europe", - "crc": "831C5A55" - }, - { - "name": "Charlie Blast's Territory (USA)", - "region": "USA", - "crc": "531F98B5" - }, - { - "name": "Charlie Blast's Territory (USA)", - "region": "USA", - "crc": "28153F07" - }, - { - "name": "Chopper Attack (Europe)", - "region": "Europe", - "crc": "C1DCD7AB" - }, - { - "name": "Chopper Attack (Europe)", - "region": "Europe", - "crc": "30CA7DBE" - }, - { - "name": "Chopper Attack (USA)", - "region": "USA", - "crc": "AA5D76A9" - }, - { - "name": "Chopper Attack (USA)", - "region": "USA", - "crc": "2C22147D" - }, - { - "name": "Choro Q 64 (Japan)", - "region": "Japan", - "crc": "231F9284" - }, - { - "name": "Choro Q 64 (Japan)", - "region": "Japan", - "crc": "C5722B49" - }, - { - "name": "Choro Q 64 2 - Hacha Mecha Grand Prix Race (Japan)", - "region": "Japan", - "crc": "5C565AD6" - }, - { - "name": "Choro Q 64 2 - Hacha Mecha Grand Prix Race (Japan)", - "region": "Japan", - "crc": "42AE9174" - }, - { - "name": "Chou Kuukan Nighter Pro Yakyuu King (Japan)", - "region": "Japan", - "crc": "5F75634E" - }, - { - "name": "Chou Kuukan Nighter Pro Yakyuu King (Japan)", - "region": "Japan", - "crc": "C73E336C" - }, - { - "name": "Chou Kuukan Nighter Pro Yakyuu King 2 (Japan)", - "region": "Japan", - "crc": "479643E2" - }, - { - "name": "Chou Kuukan Nighter Pro Yakyuu King 2 (Japan)", - "region": "Japan", - "crc": "C0FC82C0" - }, - { - "name": "Chou Snobo Kids (Japan)", - "region": "Japan", - "crc": "DD4E84E4" - }, - { - "name": "Chou Snobo Kids (Japan)", - "region": "Japan", - "crc": "AB53586B" - }, - { - "name": "City-Tour GP - Zen-Nihon GT Senshuken (Japan)", - "region": "Japan", - "crc": "E272BDF6" - }, - { - "name": "City-Tour GP - Zen-Nihon GT Senshuken (Japan)", - "region": "Japan", - "crc": "7A59354B" - }, - { - "name": "Clay Fighter - Sculptor's Cut (USA)", - "region": "USA", - "crc": "434DE656" - }, - { - "name": "Clay Fighter - Sculptor's Cut (USA)", - "region": "USA", - "crc": "F59FD73E" - }, - { - "name": "Clay Fighter 63 1-3 (Europe)", - "region": "Europe", - "crc": "82263E5D" - }, - { - "name": "Clay Fighter 63 1-3 (Europe)", - "region": "Europe", - "crc": "DCCCB22F" - }, - { - "name": "Clay Fighter 63 1-3 (USA)", - "region": "USA", - "crc": "3FA647DD" - }, - { - "name": "Clay Fighter 63 1-3 (USA)", - "region": "USA", - "crc": "E7C70F25" - }, - { - "name": "Clay Fighter 63 1-3 (USA) (Beta) (1997-08-21)", - "region": "USA", - "crc": "18F4166A" - }, - { - "name": "Clay Fighter 63 1-3 (USA) (Beta) (1997-08-21)", - "region": "USA", - "crc": "23139027" - }, - { - "name": "Command & Conquer (Europe) (En,Fr)", - "region": "Europe", - "crc": "F3DA8A26" - }, - { - "name": "Command & Conquer (Europe) (En,Fr)", - "region": "Europe", - "crc": "CE383B3E" - }, - { - "name": "Command & Conquer (Germany)", - "region": "Germany", - "crc": "6CD0FC99" - }, - { - "name": "Command & Conquer (Germany)", - "region": "Germany", - "crc": "B312E838" - }, - { - "name": "Command & Conquer (USA)", - "region": "USA", - "crc": "3E9069EF" - }, - { - "name": "Command & Conquer (USA)", - "region": "USA", - "crc": "8D23BA95" - }, - { - "name": "Conker's Bad Fur Day (Europe)", - "region": "Europe", - "crc": "4667CFE9" - }, - { - "name": "Conker's Bad Fur Day (Europe)", - "region": "Europe", - "crc": "96DBA949" - }, - { - "name": "Conker's Bad Fur Day (USA)", - "region": "USA", - "crc": "CE8CC172" - }, - { - "name": "Conker's Bad Fur Day (USA)", - "region": "USA", - "crc": "6ACDB3FC" - }, - { - "name": "Conker's Bad Fur Day (USA) (Beta) (2000-10-25)", - "region": "USA", - "crc": "4F73408C" - }, - { - "name": "Conker's Bad Fur Day (USA) (Beta) (2000-10-25)", - "region": "USA", - "crc": "C9822F5C" - }, - { - "name": "Conker's Bad Fur Day (USA) (Beta) (ECTS 2000) (2000-08-26)", - "region": "USA", - "crc": "E1CDA95A" - }, - { - "name": "Conker's Bad Fur Day (USA) (Beta) (ECTS 2000) (2000-08-26)", - "region": "USA", - "crc": "AF9C5972" - }, - { - "name": "Cruis'n Exotica (USA)", - "region": "USA", - "crc": "867A2CED" - }, - { - "name": "Cruis'n Exotica (USA)", - "region": "USA", - "crc": "20E7DBD7" - }, - { - "name": "Cruis'n USA (Europe)", - "region": "Europe", - "crc": "8935A8D9" - }, - { - "name": "Cruis'n USA (Europe)", - "region": "Europe", - "crc": "6767ECA4" - }, - { - "name": "Cruis'n USA (USA)", - "region": "USA", - "crc": "5238B727" - }, - { - "name": "Cruis'n USA (USA)", - "region": "USA", - "crc": "EE925406" - }, - { - "name": "Cruis'n USA (USA) (Rev 1)", - "region": "USA", - "crc": "4655BA2D" - }, - { - "name": "Cruis'n USA (USA) (Rev 1)", - "region": "USA", - "crc": "615AC9BC" - }, - { - "name": "Cruis'n USA (USA) (Rev 2)", - "region": "USA", - "crc": "C3B52701" - }, - { - "name": "Cruis'n USA (USA) (Rev 2)", - "region": "USA", - "crc": "E466C124" - }, - { - "name": "Cruis'n USA (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "8FC564F9" - }, - { - "name": "Cruis'n USA (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "F9692E16" - }, - { - "name": "Cruis'n World (Europe)", - "region": "Europe", - "crc": "E46CE079" - }, - { - "name": "Cruis'n World (Europe)", - "region": "Europe", - "crc": "2A8B3094" - }, - { - "name": "Cruis'n World (Europe) (Rev 1)", - "region": "Europe", - "crc": "EBAED1F9" - }, - { - "name": "Cruis'n World (Europe) (Rev 1)", - "region": "Europe", - "crc": "E962672D" - }, - { - "name": "Cruis'n World (USA)", - "region": "USA", - "crc": "A123769F" - }, - { - "name": "Cruis'n World (USA)", - "region": "USA", - "crc": "41877D7D" - }, - { - "name": "Custom Robo (Japan)", - "region": "Japan", - "crc": "F2FAE693" - }, - { - "name": "Custom Robo (Japan)", - "region": "Japan", - "crc": "30C84F36" - }, - { - "name": "Custom Robo V2 (Japan)", - "region": "Japan", - "crc": "C8201454" - }, - { - "name": "Custom Robo V2 (Japan)", - "region": "Japan", - "crc": "7BEF9ECC" - }, - { - "name": "CyberTiger (Europe)", - "region": "Europe", - "crc": "7319D9AF" - }, - { - "name": "CyberTiger (Europe)", - "region": "Europe", - "crc": "BCF1071F" - }, - { - "name": "CyberTiger (USA)", - "region": "USA", - "crc": "10CC5F15" - }, - { - "name": "CyberTiger (USA)", - "region": "USA", - "crc": "F0F89BB9" - }, - { - "name": "Daffy Duck Starring as Duck Dodgers (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "C61F6BB9" - }, - { - "name": "Daffy Duck Starring as Duck Dodgers (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "6CB4B600" - }, - { - "name": "Dance Dance Revolution - Disney Dancing Museum (Japan)", - "region": "Japan", - "crc": "43EE0117" - }, - { - "name": "Dance Dance Revolution - Disney Dancing Museum (Japan)", - "region": "Japan", - "crc": "E085AFA0" - }, - { - "name": "Dark Rift (Europe)", - "region": "Europe", - "crc": "D2A19C71" - }, - { - "name": "Dark Rift (Europe)", - "region": "Europe", - "crc": "375D8FFE" - }, - { - "name": "Dark Rift (USA)", - "region": "USA", - "crc": "83FD222F" - }, - { - "name": "Dark Rift (USA)", - "region": "USA", - "crc": "2AFAB93C" - }, - { - "name": "Deadly Arts (USA)", - "region": "USA", - "crc": "3DB8130E" - }, - { - "name": "Deadly Arts (USA)", - "region": "USA", - "crc": "B4AFBF51" - }, - { - "name": "Defi au Tetris Magique (France)", - "region": "France", - "crc": "E7EF60E8" - }, - { - "name": "Defi au Tetris Magique (France)", - "region": "France", - "crc": "EB304709" - }, - { - "name": "Densha de Go! 64 (Japan)", - "region": "Japan", - "crc": "7BFC71E0" - }, - { - "name": "Densha de Go! 64 (Japan)", - "region": "Japan", - "crc": "8F63474F" - }, - { - "name": "Derby Stallion 64 (Japan)", - "region": "Japan", - "crc": "A9417994" - }, - { - "name": "Derby Stallion 64 (Japan)", - "region": "Japan", - "crc": "922506AD" - }, - { - "name": "Destruction Derby 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "7AD9E429" - }, - { - "name": "Destruction Derby 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "212A8000" - }, - { - "name": "Destruction Derby 64 (USA)", - "region": "USA", - "crc": "38F1B5D9" - }, - { - "name": "Destruction Derby 64 (USA)", - "region": "USA", - "crc": "EF680F71" - }, - { - "name": "Dezaemon 3D (Japan)", - "region": "Japan", - "crc": "9E978488" - }, - { - "name": "Dezaemon 3D (Japan)", - "region": "Japan", - "crc": "8CE25C68" - }, - { - "name": "Diddy Kong Racing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "4A13323C" - }, - { - "name": "Diddy Kong Racing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "883DF1EA" - }, - { - "name": "Diddy Kong Racing (Europe) (En,Fr,De) (Rev 1)", - "region": "Europe", - "crc": "B1E87639" - }, - { - "name": "Diddy Kong Racing (Europe) (En,Fr,De) (Rev 1)", - "region": "Europe", - "crc": "F05D414F" - }, - { - "name": "Diddy Kong Racing (Japan)", - "region": "Japan", - "crc": "B566FB94" - }, - { - "name": "Diddy Kong Racing (Japan)", - "region": "Japan", - "crc": "95931356" - }, - { - "name": "Diddy Kong Racing (USA) (En,Fr)", - "region": "USA", - "crc": "EB759206" - }, - { - "name": "Diddy Kong Racing (USA) (En,Fr)", - "region": "USA", - "crc": "9D2935A1" - }, - { - "name": "Diddy Kong Racing (USA) (En,Fr) (Rev 1)", - "region": "USA", - "crc": "5ACCA298" - }, - { - "name": "Diddy Kong Racing (USA) (En,Fr) (Rev 1)", - "region": "USA", - "crc": "96C32BB6" - }, - { - "name": "Die Hard 64 (USA) (Proto) (Level 1)", - "region": "USA", - "crc": "5E826037" - }, - { - "name": "Die Hard 64 (USA) (Proto) (Level 1)", - "region": "USA", - "crc": "782B63F8" - }, - { - "name": "Die Hard 64 (USA) (Proto) (Level 2)", - "region": "USA", - "crc": "D1D593CF" - }, - { - "name": "Die Hard 64 (USA) (Proto) (Level 2)", - "region": "USA", - "crc": "E6E08E3E" - }, - { - "name": "Die Hard 64 (USA) (Proto) (Level 3)", - "region": "USA", - "crc": "70943925" - }, - { - "name": "Die Hard 64 (USA) (Proto) (Level 3)", - "region": "USA", - "crc": "E5703251" - }, - { - "name": "Dinosaur Planet (USA) (Proto) (2000-12-01)", - "region": "USA", - "crc": "A7DAF9FA" - }, - { - "name": "Dinosaur Planet (USA) (Proto) (2000-12-01)", - "region": "USA", - "crc": "F3B8ADD3" - }, - { - "name": "Donald Duck - Goin' Quackers (USA) (En,Fr,De,Es,It)", - "region": "USA", - "crc": "0075D32B" - }, - { - "name": "Donald Duck - Goin' Quackers (USA) (En,Fr,De,Es,It)", - "region": "USA", - "crc": "EDEFC39B" - }, - { - "name": "Donald Duck - Quack Attack (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "E6220482" - }, - { - "name": "Donald Duck - Quack Attack (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "725758EE" - }, - { - "name": "Dongwu Senlin (China) (v8)", - "region": "China", - "crc": "4703C2DD" - }, - { - "name": "Donkey Kong 64 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "A28C71C6" - }, - { - "name": "Donkey Kong 64 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "FC37B06A" - }, - { - "name": "Donkey Kong 64 (Japan)", - "region": "Japan", - "crc": "919F7E74" - }, - { - "name": "Donkey Kong 64 (Japan)", - "region": "Japan", - "crc": "FBD001B9" - }, - { - "name": "Donkey Kong 64 (USA)", - "region": "USA", - "crc": "D44B4FC6" - }, - { - "name": "Donkey Kong 64 (USA)", - "region": "USA", - "crc": "96972D67" - }, - { - "name": "Donkey Kong 64 (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "C83DFA15" - }, - { - "name": "Donkey Kong 64 (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "54BB87F6" - }, - { - "name": "Doom 64 (Europe)", - "region": "Europe", - "crc": "D985C356" - }, - { - "name": "Doom 64 (Europe)", - "region": "Europe", - "crc": "0227DB4B" - }, - { - "name": "Doom 64 (Japan)", - "region": "Japan", - "crc": "C8F3AF5B" - }, - { - "name": "Doom 64 (Japan)", - "region": "Japan", - "crc": "95526F13" - }, - { - "name": "Doom 64 (USA)", - "region": "USA", - "crc": "5CC1ADE6" - }, - { - "name": "Doom 64 (USA)", - "region": "USA", - "crc": "4DFB7CB6" - }, - { - "name": "Doom 64 (USA) (Rev 1)", - "region": "USA", - "crc": "1D3A17B5" - }, - { - "name": "Doom 64 (USA) (Rev 1)", - "region": "USA", - "crc": "0F080D06" - }, - { - "name": "Doraemon - Nobita to 3tsu no Seireiseki (Japan)", - "region": "Japan", - "crc": "154E8B33" - }, - { - "name": "Doraemon - Nobita to 3tsu no Seireiseki (Japan)", - "region": "Japan", - "crc": "CAD0A2C2" - }, - { - "name": "Doraemon 2 - Nobita to Hikari no Shinden (Japan)", - "region": "Japan", - "crc": "0C1A0C38" - }, - { - "name": "Doraemon 2 - Nobita to Hikari no Shinden (Japan)", - "region": "Japan", - "crc": "2DE1A873" - }, - { - "name": "Doraemon 3 - Nobita no Machi SOS! (Japan)", - "region": "Japan", - "crc": "D3B68BE4" - }, - { - "name": "Doraemon 3 - Nobita no Machi SOS! (Japan)", - "region": "Japan", - "crc": "0B3AD913" - }, - { - "name": "Doubutsu Banchou (Japan) (Proto)", - "region": "Japan", - "crc": "6F8E3E71" - }, - { - "name": "Doubutsu Banchou (Japan) (Proto)", - "region": "Japan", - "crc": "7A2E1B99" - }, - { - "name": "Doubutsu no Mori (Japan)", - "region": "Japan", - "crc": "9503E3F1" - }, - { - "name": "Doubutsu no Mori (Japan)", - "region": "Japan", - "crc": "B9FCD0A6" - }, - { - "name": "Dr. Mario 64 (USA)", - "region": "USA", - "crc": "A4701927" - }, - { - "name": "Dr. Mario 64 (USA)", - "region": "USA", - "crc": "F7C44B5B" - }, - { - "name": "Dragon Sword 64 (Europe) (Proto)", - "region": "Europe", - "crc": "55DED416" - }, - { - "name": "Dragon Sword 64 (Europe) (Proto)", - "region": "Europe", - "crc": "3F174786" - }, - { - "name": "Dragon Sword 64 (USA) (Proto) (1999-08-25)", - "region": "USA", - "crc": "2296B50D" - }, - { - "name": "Dragon Sword 64 (USA) (Proto) (1999-08-25)", - "region": "USA", - "crc": "BC5A6081" - }, - { - "name": "Dual Heroes (Europe)", - "region": "Europe", - "crc": "5A7E226B" - }, - { - "name": "Dual Heroes (Europe)", - "region": "Europe", - "crc": "04064347" - }, - { - "name": "Dual Heroes (Japan)", - "region": "Japan", - "crc": "20F23DDE" - }, - { - "name": "Dual Heroes (Japan)", - "region": "Japan", - "crc": "A3565FFC" - }, - { - "name": "Dual Heroes (USA)", - "region": "USA", - "crc": "D09F4DA8" - }, - { - "name": "Dual Heroes (USA)", - "region": "USA", - "crc": "8968D989" - }, - { - "name": "Duck Dodgers Starring Daffy Duck (USA) (En,Fr,Es)", - "region": "USA", - "crc": "3177A905" - }, - { - "name": "Duck Dodgers Starring Daffy Duck (USA) (En,Fr,Es)", - "region": "USA", - "crc": "B67BBC15" - }, - { - "name": "Duck Dodgers Starring Daffy Duck (USA) (En,Fr,Es) (Beta) (2000-03-07)", - "region": "USA", - "crc": "1E48D155" - }, - { - "name": "Duck Dodgers Starring Daffy Duck (USA) (En,Fr,Es) (Beta) (2000-03-07)", - "region": "USA", - "crc": "79D52B78" - }, - { - "name": "Duke Nukem - Zero Hour (Europe)", - "region": "Europe", - "crc": "EA82F037" - }, - { - "name": "Duke Nukem - Zero Hour (Europe)", - "region": "Europe", - "crc": "C2B3DF4D" - }, - { - "name": "Duke Nukem - Zero Hour (France)", - "region": "France", - "crc": "7ECDFB28" - }, - { - "name": "Duke Nukem - Zero Hour (France)", - "region": "France", - "crc": "8FBF7447" - }, - { - "name": "Duke Nukem - Zero Hour (USA)", - "region": "USA", - "crc": "9A3258D7" - }, - { - "name": "Duke Nukem - Zero Hour (USA)", - "region": "USA", - "crc": "912F576F" - }, - { - "name": "Duke Nukem 64 (Europe)", - "region": "Europe", - "crc": "3275ADB0" - }, - { - "name": "Duke Nukem 64 (Europe)", - "region": "Europe", - "crc": "9817B026" - }, - { - "name": "Duke Nukem 64 (Europe) (Beta)", - "region": "Europe", - "crc": "4A82D036" - }, - { - "name": "Duke Nukem 64 (Europe) (Beta)", - "region": "Europe", - "crc": "1FF97BAD" - }, - { - "name": "Duke Nukem 64 (France)", - "region": "France", - "crc": "B9C9F07A" - }, - { - "name": "Duke Nukem 64 (France)", - "region": "France", - "crc": "CBE8C78A" - }, - { - "name": "Duke Nukem 64 (USA)", - "region": "USA", - "crc": "DBFD5A53" - }, - { - "name": "Duke Nukem 64 (USA)", - "region": "USA", - "crc": "1BF948C3" - }, - { - "name": "ECW Hardcore Revolution (Europe)", - "region": "Europe", - "crc": "BE8FEEAD" - }, - { - "name": "ECW Hardcore Revolution (Europe)", - "region": "Europe", - "crc": "2FAC2C41" - }, - { - "name": "ECW Hardcore Revolution (Germany)", - "region": "Germany", - "crc": "E9CF3D9F" - }, - { - "name": "ECW Hardcore Revolution (Germany)", - "region": "Germany", - "crc": "61503331" - }, - { - "name": "ECW Hardcore Revolution (USA)", - "region": "USA", - "crc": "36D368EF" - }, - { - "name": "ECW Hardcore Revolution (USA)", - "region": "USA", - "crc": "4BA40079" - }, - { - "name": "ECW Hardcore Revolution (USA) (Beta)", - "region": "USA", - "crc": "0A239818" - }, - { - "name": "ECW Hardcore Revolution (USA) (Beta)", - "region": "USA", - "crc": "DAC9188B" - }, - { - "name": "Earthworm Jim 3D (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "61A56330" - }, - { - "name": "Earthworm Jim 3D (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "20C3ADAB" - }, - { - "name": "Earthworm Jim 3D (USA)", - "region": "USA", - "crc": "9E6579C5" - }, - { - "name": "Earthworm Jim 3D (USA)", - "region": "USA", - "crc": "1017BE14" - }, - { - "name": "Eikou no Saint Andrews (Japan)", - "region": "Japan", - "crc": "1699D2D6" - }, - { - "name": "Eikou no Saint Andrews (Japan)", - "region": "Japan", - "crc": "CE55BB19" - }, - { - "name": "Eltale Monsters (Japan)", - "region": "Japan", - "crc": "24D937BF" - }, - { - "name": "Eltale Monsters (Japan)", - "region": "Japan", - "crc": "3541C4BB" - }, - { - "name": "Excitebike 64 (Europe)", - "region": "Europe", - "crc": "0B881E60" - }, - { - "name": "Excitebike 64 (Europe)", - "region": "Europe", - "crc": "8FEEDE6F" - }, - { - "name": "Excitebike 64 (Japan)", - "region": "Japan", - "crc": "03BFD065" - }, - { - "name": "Excitebike 64 (Japan)", - "region": "Japan", - "crc": "9DFEB099" - }, - { - "name": "Excitebike 64 (USA)", - "region": "USA", - "crc": "FC459192" - }, - { - "name": "Excitebike 64 (USA)", - "region": "USA", - "crc": "EF78ED7E" - }, - { - "name": "Excitebike 64 (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "BE6298B0" - }, - { - "name": "Excitebike 64 (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "13157DF7" - }, - { - "name": "Excitebike 64 (USA) (Rev 1)", - "region": "USA", - "crc": "143926CE" - }, - { - "name": "Excitebike 64 (USA) (Rev 1)", - "region": "USA", - "crc": "820A12DB" - }, - { - "name": "Extreme-G (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "0B71B1EA" - }, - { - "name": "Extreme-G (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "1BE8E42B" - }, - { - "name": "Extreme-G (Japan)", - "region": "Japan", - "crc": "750DC9A7" - }, - { - "name": "Extreme-G (Japan)", - "region": "Japan", - "crc": "856227BB" - }, - { - "name": "Extreme-G (USA)", - "region": "USA", - "crc": "04CB74EC" - }, - { - "name": "Extreme-G (USA)", - "region": "USA", - "crc": "A43A0B17" - }, - { - "name": "Extreme-G XG2 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "1A57F416" - }, - { - "name": "Extreme-G XG2 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "32FC980C" - }, - { - "name": "Extreme-G XG2 (Japan)", - "region": "Japan", - "crc": "7C8A36DA" - }, - { - "name": "Extreme-G XG2 (Japan)", - "region": "Japan", - "crc": "62202D15" - }, - { - "name": "Extreme-G XG2 (USA)", - "region": "USA", - "crc": "81A4C28B" - }, - { - "name": "Extreme-G XG2 (USA)", - "region": "USA", - "crc": "849C18CE" - }, - { - "name": "F-1 World Grand Prix (Europe)", - "region": "Europe", - "crc": "1F4E651A" - }, - { - "name": "F-1 World Grand Prix (Europe)", - "region": "Europe", - "crc": "8C08A7A8" - }, - { - "name": "F-1 World Grand Prix (Europe) (Beta)", - "region": "Europe", - "crc": "BEBBC6C8" - }, - { - "name": "F-1 World Grand Prix (Europe) (Beta)", - "region": "Europe", - "crc": "338777BF" - }, - { - "name": "F-1 World Grand Prix (France)", - "region": "France", - "crc": "57CD299D" - }, - { - "name": "F-1 World Grand Prix (France)", - "region": "France", - "crc": "02E9695D" - }, - { - "name": "F-1 World Grand Prix (Germany)", - "region": "Germany", - "crc": "0F1984DC" - }, - { - "name": "F-1 World Grand Prix (Germany)", - "region": "Germany", - "crc": "7D8DDEFB" - }, - { - "name": "F-1 World Grand Prix (Japan)", - "region": "Japan", - "crc": "F7BACBC3" - }, - { - "name": "F-1 World Grand Prix (Japan)", - "region": "Japan", - "crc": "386E127D" - }, - { - "name": "F-1 World Grand Prix (USA)", - "region": "USA", - "crc": "7DC9EF2C" - }, - { - "name": "F-1 World Grand Prix (USA)", - "region": "USA", - "crc": "4D203242" - }, - { - "name": "F-1 World Grand Prix II (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "803D33DF" - }, - { - "name": "F-1 World Grand Prix II (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "484C801D" - }, - { - "name": "F-Zero X (Europe)", - "region": "Europe", - "crc": "2D6F7E8B" - }, - { - "name": "F-Zero X (Europe)", - "region": "Europe", - "crc": "3D95DF80" - }, - { - "name": "F-Zero X (Japan)", - "region": "Japan", - "crc": "6B1CEF83" - }, - { - "name": "F-Zero X (Japan)", - "region": "Japan", - "crc": "927387B6" - }, - { - "name": "F-Zero X (USA)", - "region": "USA", - "crc": "0B561FBA" - }, - { - "name": "F-Zero X (USA)", - "region": "USA", - "crc": "9408C2C1" - }, - { - "name": "F-Zero X (USA) (Beta) (The Legend of Zelda Ocarina of Time Overdump)", - "region": "USA", - "crc": "68FE1CEC" - }, - { - "name": "F-Zero X (USA) (Beta) (The Legend of Zelda Ocarina of Time Overdump)", - "region": "USA", - "crc": "A6AD2A1C" - }, - { - "name": "F-Zero X Weilai Saiche (China) (v2) (Manual)", - "region": "China", - "crc": "88331AD2" - }, - { - "name": "F-Zero X Weilai Saiche (China) (v3)", - "region": "China", - "crc": "3D30385A" - }, - { - "name": "F-Zero X Weilai Saiche (China) (v4) (Manual)", - "region": "China", - "crc": "E602FFD3" - }, - { - "name": "F1 Pole Position 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "ED750623" - }, - { - "name": "F1 Pole Position 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "EC8B6DB6" - }, - { - "name": "F1 Pole Position 64 (USA) (En,Fr,De)", - "region": "USA", - "crc": "30A24D89" - }, - { - "name": "F1 Pole Position 64 (USA) (En,Fr,De)", - "region": "USA", - "crc": "873BC45A" - }, - { - "name": "F1 Racing Championship (En,Fr)", - "region": "Brazil", - "crc": "C05B9184" - }, - { - "name": "F1 Racing Championship (En,Fr)", - "region": "Brazil", - "crc": "7AD98002" - }, - { - "name": "F1 Racing Championship (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "2DA744F5" - }, - { - "name": "F1 Racing Championship (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "0605E5AC" - }, - { - "name": "F1 Racing Championship (USA) (Beta)", - "region": "USA", - "crc": "5A55B4C2" - }, - { - "name": "F1 Racing Championship (USA) (Beta)", - "region": "USA", - "crc": "0FFFB6BD" - }, - { - "name": "FIFA - Road to World Cup 98 (Europe) (En,Fr,De,Es,It,Nl,Sv)", - "region": "Europe", - "crc": "137CB3CC" - }, - { - "name": "FIFA - Road to World Cup 98 (Europe) (En,Fr,De,Es,It,Nl,Sv)", - "region": "Europe", - "crc": "2D49F99A" - }, - { - "name": "FIFA - Road to World Cup 98 (USA) (En,Fr,De,Es,It,Nl,Sv)", - "region": "USA", - "crc": "28B1221C" - }, - { - "name": "FIFA - Road to World Cup 98 (USA) (En,Fr,De,Es,It,Nl,Sv)", - "region": "USA", - "crc": "9504B3DF" - }, - { - "name": "FIFA - Road to World Cup 98 - World Cup e no Michi (Japan)", - "region": "Japan", - "crc": "AE346DF6" - }, - { - "name": "FIFA - Road to World Cup 98 - World Cup e no Michi (Japan)", - "region": "Japan", - "crc": "6AE6B8ED" - }, - { - "name": "FIFA 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "AE2583FB" - }, - { - "name": "FIFA 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "26F52456" - }, - { - "name": "FIFA 99 (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv)", - "region": "Europe", - "crc": "6EAE1E6E" - }, - { - "name": "FIFA 99 (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv)", - "region": "Europe", - "crc": "CE258860" - }, - { - "name": "FIFA 99 (USA) (En,Fr,De,Es,It,Nl,Pt,Sv)", - "region": "USA", - "crc": "6B2473A9" - }, - { - "name": "FIFA 99 (USA) (En,Fr,De,Es,It,Nl,Pt,Sv)", - "region": "USA", - "crc": "D69DD3D1" - }, - { - "name": "FIFA Soccer 64 (USA) (En,Fr,De)", - "region": "USA", - "crc": "57DE7CAB" - }, - { - "name": "FIFA Soccer 64 (USA) (En,Fr,De)", - "region": "USA", - "crc": "3B22F9F2" - }, - { - "name": "Famista 64 (Japan)", - "region": "Japan", - "crc": "9FB0E6C9" - }, - { - "name": "Famista 64 (Japan)", - "region": "Japan", - "crc": "51E0B88A" - }, - { - "name": "Fighter Destiny 2 (USA)", - "region": "USA", - "crc": "BB2563C6" - }, - { - "name": "Fighter Destiny 2 (USA)", - "region": "USA", - "crc": "7FC19103" - }, - { - "name": "Fighters Destiny (Europe)", - "region": "Europe", - "crc": "C9225511" - }, - { - "name": "Fighters Destiny (Europe)", - "region": "Europe", - "crc": "83F9299E" - }, - { - "name": "Fighters Destiny (France)", - "region": "France", - "crc": "B3A034D6" - }, - { - "name": "Fighters Destiny (France)", - "region": "France", - "crc": "BE7EBB32" - }, - { - "name": "Fighters Destiny (Germany)", - "region": "Germany", - "crc": "5052168C" - }, - { - "name": "Fighters Destiny (Germany)", - "region": "Germany", - "crc": "0A89CA02" - }, - { - "name": "Fighters Destiny (USA)", - "region": "USA", - "crc": "F45EA789" - }, - { - "name": "Fighters Destiny (USA)", - "region": "USA", - "crc": "A625973F" - }, - { - "name": "Fighting Cup (Japan)", - "region": "Japan", - "crc": "8A1C261E" - }, - { - "name": "Fighting Cup (Japan)", - "region": "Japan", - "crc": "A9740235" - }, - { - "name": "Fighting Force 64 (Europe)", - "region": "Europe", - "crc": "4052C176" - }, - { - "name": "Fighting Force 64 (Europe)", - "region": "Europe", - "crc": "8D0CB2FF" - }, - { - "name": "Fighting Force 64 (USA)", - "region": "USA", - "crc": "8456841E" - }, - { - "name": "Fighting Force 64 (USA)", - "region": "USA", - "crc": "ADE7DCA4" - }, - { - "name": "Flying Dragon (Europe)", - "region": "Europe", - "crc": "C3066E59" - }, - { - "name": "Flying Dragon (Europe)", - "region": "Europe", - "crc": "00FFF973" - }, - { - "name": "Flying Dragon (USA)", - "region": "USA", - "crc": "91BC9AEB" - }, - { - "name": "Flying Dragon (USA)", - "region": "USA", - "crc": "87E4E352" - }, - { - "name": "Forsaken (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "5ED736D9" - }, - { - "name": "Forsaken (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "6C65EF7A" - }, - { - "name": "Forsaken (Germany)", - "region": "Germany", - "crc": "9793ABC2" - }, - { - "name": "Forsaken (Germany)", - "region": "Germany", - "crc": "C3FA1B8E" - }, - { - "name": "Forsaken 64 (USA)", - "region": "USA", - "crc": "76C4333D" - }, - { - "name": "Forsaken 64 (USA)", - "region": "USA", - "crc": "F970E4A4" - }, - { - "name": "Fox Sports College Hoops '99 (USA)", - "region": "USA", - "crc": "67EAF0F3" - }, - { - "name": "Fox Sports College Hoops '99 (USA)", - "region": "USA", - "crc": "FEDE3EF8" - }, - { - "name": "Freak Boy (Unknown) (Proto)", - "crc": "40632BEC" - }, - { - "name": "Freak Boy (Unknown) (Proto)", - "crc": "C15147FA" - }, - { - "name": "Frogger 2 (USA) (Proto 1)", - "region": "USA", - "crc": "8D62268E" - }, - { - "name": "Frogger 2 (USA) (Proto 1)", - "region": "USA", - "crc": "8FC19A6E" - }, - { - "name": "Frogger 2 (USA) (Proto 2)", - "region": "USA", - "crc": "A8F81F39" - }, - { - "name": "Frogger 2 (USA) (Proto 2)", - "region": "USA", - "crc": "DC59ECE7" - }, - { - "name": "Fushigi no Dungeon - Fuurai no Shiren 2 - Oni Shuurai! Shiren Jou! (Japan)", - "region": "Japan", - "crc": "2AA6D2A1" - }, - { - "name": "Fushigi no Dungeon - Fuurai no Shiren 2 - Oni Shuurai! Shiren Jou! (Japan)", - "region": "Japan", - "crc": "AF0A22EF" - }, - { - "name": "G.A.S.P!! Fighters' NEXTream (Europe)", - "region": "Europe", - "crc": "5AA63B04" - }, - { - "name": "G.A.S.P!! Fighters' NEXTream (Europe)", - "region": "Europe", - "crc": "B0D256B8" - }, - { - "name": "G.A.S.P!! Fighters' NEXTream (Japan)", - "region": "Japan", - "crc": "6EAD2D89" - }, - { - "name": "G.A.S.P!! Fighters' NEXTream (Japan)", - "region": "Japan", - "crc": "0E05EA3A" - }, - { - "name": "GT 64 - Championship Edition (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "6DFB4747" - }, - { - "name": "GT 64 - Championship Edition (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "B06D0D85" - }, - { - "name": "GT 64 - Championship Edition (USA)", - "region": "USA", - "crc": "BC627DA7" - }, - { - "name": "GT 64 - Championship Edition (USA)", - "region": "USA", - "crc": "B6DC4CFD" - }, - { - "name": "GT 64 - Championship Edition (USA) (Beta) (1998-05-25)", - "region": "USA", - "crc": "191BF504" - }, - { - "name": "GT 64 - Championship Edition (USA) (Beta) (1998-05-25)", - "region": "USA", - "crc": "DB690612" - }, - { - "name": "GameBooster 64 (Europe) (v1.1) (Unl)", - "region": "Europe", - "crc": "35B99BD9" - }, - { - "name": "GameBooster 64 (Europe) (v1.1) (Unl)", - "region": "Europe", - "crc": "35B99BD9" - }, - { - "name": "GameBooster 64 (USA) (v1.1) (Unl)", - "region": "USA", - "crc": "E0B8EDAE" - }, - { - "name": "GameBooster 64 (USA) (v1.1) (Unl)", - "region": "USA", - "crc": "E0B8EDAE" - }, - { - "name": "GameShark Pro (USA) (v2.0) (Unl)", - "region": "USA", - "crc": "EF9EDF87" - }, - { - "name": "GameShark Pro (USA) (v2.0) (Unl)", - "region": "USA", - "crc": "EF9EDF87" - }, - { - "name": "GameShark Pro (USA) (v3.3) (Unl)", - "region": "USA", - "crc": "7CC07BBC" - }, - { - "name": "GameShark Pro (USA) (v3.3) (Unl)", - "region": "USA", - "crc": "7CC07BBC" - }, - { - "name": "Ganbare Goemon - Dero Dero Douchuu Obake Tenkomori (Japan)", - "region": "Japan", - "crc": "08C41E0E" - }, - { - "name": "Ganbare Goemon - Dero Dero Douchuu Obake Tenkomori (Japan)", - "region": "Japan", - "crc": "163C3C51" - }, - { - "name": "Ganbare Goemon - Neo Momoyama Bakufu no Odori (Japan)", - "region": "Japan", - "crc": "2E91EFC9" - }, - { - "name": "Ganbare Goemon - Neo Momoyama Bakufu no Odori (Japan)", - "region": "Japan", - "crc": "69964858" - }, - { - "name": "Ganbare! Nippon! Olympics 2000 (Japan)", - "region": "Japan", - "crc": "73133CD2" - }, - { - "name": "Ganbare! Nippon! Olympics 2000 (Japan)", - "region": "Japan", - "crc": "1C7E5D6E" - }, - { - "name": "Gauntlet Legends (Europe)", - "region": "Europe", - "crc": "B7B3A489" - }, - { - "name": "Gauntlet Legends (Europe)", - "region": "Europe", - "crc": "D9F765AD" - }, - { - "name": "Gauntlet Legends (Japan)", - "region": "Japan", - "crc": "8D133DB0" - }, - { - "name": "Gauntlet Legends (Japan)", - "region": "Japan", - "crc": "1F3A1912" - }, - { - "name": "Gauntlet Legends (USA)", - "region": "USA", - "crc": "64765E82" - }, - { - "name": "Gauntlet Legends (USA)", - "region": "USA", - "crc": "7E90C0A8" - }, - { - "name": "Getter Love!! - Cho Renai Party Game Tanjou (Japan)", - "region": "Japan", - "crc": "724ECAE7" - }, - { - "name": "Getter Love!! - Cho Renai Party Game Tanjou (Japan)", - "region": "Japan", - "crc": "BBD2AE37" - }, - { - "name": "Gex 3 - Deep Cover Gecko (Europe) (En,Es,It)", - "region": "Europe", - "crc": "6BC4A056" - }, - { - "name": "Gex 3 - Deep Cover Gecko (Europe) (En,Es,It)", - "region": "Europe", - "crc": "1110CC22" - }, - { - "name": "Gex 3 - Deep Cover Gecko (Europe) (Fr,De)", - "region": "Europe", - "crc": "A43CB8E4" - }, - { - "name": "Gex 3 - Deep Cover Gecko (Europe) (Fr,De)", - "region": "Europe", - "crc": "D169E9F8" - }, - { - "name": "Gex 3 - Deep Cover Gecko (USA)", - "region": "USA", - "crc": "87A7D099" - }, - { - "name": "Gex 3 - Deep Cover Gecko (USA)", - "region": "USA", - "crc": "3571F3BD" - }, - { - "name": "Gex 64 - Enter the Gecko (Europe)", - "region": "Europe", - "crc": "A7C92BEA" - }, - { - "name": "Gex 64 - Enter the Gecko (Europe)", - "region": "Europe", - "crc": "3E20EAFD" - }, - { - "name": "Gex 64 - Enter the Gecko (USA)", - "region": "USA", - "crc": "C545CE80" - }, - { - "name": "Gex 64 - Enter the Gecko (USA)", - "region": "USA", - "crc": "3F90E7C2" - }, - { - "name": "Glover (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "90ECEB4A" - }, - { - "name": "Glover (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "93DAE2A0" - }, - { - "name": "Glover (USA)", - "region": "USA", - "crc": "F874571C" - }, - { - "name": "Glover (USA)", - "region": "USA", - "crc": "D024893A" - }, - { - "name": "Glover (USA) (Proto) (1998-09-04)", - "region": "USA", - "crc": "5E091E8F" - }, - { - "name": "Glover (USA) (Proto) (1998-09-04)", - "region": "USA", - "crc": "DB8A6C7C" - }, - { - "name": "Glover 2 (USA) (Proto 1)", - "region": "USA", - "crc": "1A526DCD" - }, - { - "name": "Glover 2 (USA) (Proto 1)", - "region": "USA", - "crc": "3B47A56D" - }, - { - "name": "Glover 2 (USA) (Proto 2)", - "region": "USA", - "crc": "29966555" - }, - { - "name": "Glover 2 (USA) (Proto 2)", - "region": "USA", - "crc": "DFED852C" - }, - { - "name": "Goemon - Mononoke Sugoroku (Japan)", - "region": "Japan", - "crc": "965C4575" - }, - { - "name": "Goemon - Mononoke Sugoroku (Japan)", - "region": "Japan", - "crc": "F6BACBAC" - }, - { - "name": "Goemon's Great Adventure (USA)", - "region": "USA", - "crc": "52D418E1" - }, - { - "name": "Goemon's Great Adventure (USA)", - "region": "USA", - "crc": "E3C7A184" - }, - { - "name": "Golden Nugget 64 (USA)", - "region": "USA", - "crc": "70594D3C" - }, - { - "name": "Golden Nugget 64 (USA)", - "region": "USA", - "crc": "05A0DB83" - }, - { - "name": "GoldenEye 007 (Europe)", - "region": "Europe", - "crc": "9EC14AEB" - }, - { - "name": "GoldenEye 007 (Europe)", - "region": "Europe", - "crc": "7425AE2D" - }, - { - "name": "GoldenEye 007 (Japan)", - "region": "Japan", - "crc": "A6BE19DD" - }, - { - "name": "GoldenEye 007 (Japan)", - "region": "Japan", - "crc": "C26FED78" - }, - { - "name": "GoldenEye 007 (USA)", - "region": "USA", - "crc": "B6330846" - }, - { - "name": "GoldenEye 007 (USA)", - "region": "USA", - "crc": "8B70CB5B" - }, - { - "name": "HSV Adventure Racing! (Australia)", - "region": "Australia", - "crc": "C0BA9440" - }, - { - "name": "HSV Adventure Racing! (Australia)", - "region": "Australia", - "crc": "FB1294C3" - }, - { - "name": "Hamster Monogatari 64 (Japan)", - "region": "Japan", - "crc": "C1D98B78" - }, - { - "name": "Hamster Monogatari 64 (Japan)", - "region": "Japan", - "crc": "689191C6" - }, - { - "name": "Harukanaru Augusta - Masters '98 (Japan)", - "region": "Japan", - "crc": "51228F0C" - }, - { - "name": "Harukanaru Augusta - Masters '98 (Japan)", - "region": "Japan", - "crc": "AD4720E2" - }, - { - "name": "Harvest Moon 64 (USA)", - "region": "USA", - "crc": "DECDC0AD" - }, - { - "name": "Harvest Moon 64 (USA)", - "region": "USA", - "crc": "D2EE315F" - }, - { - "name": "Heiwa Pachinko World 64 (Japan)", - "region": "Japan", - "crc": "99A427FA" - }, - { - "name": "Heiwa Pachinko World 64 (Japan)", - "region": "Japan", - "crc": "3ACB817E" - }, - { - "name": "Hercules - The Legendary Journeys (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "B1954B08" - }, - { - "name": "Hercules - The Legendary Journeys (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "65517307" - }, - { - "name": "Hercules - The Legendary Journeys (USA)", - "region": "USA", - "crc": "4948892B" - }, - { - "name": "Hercules - The Legendary Journeys (USA)", - "region": "USA", - "crc": "CF90BC4A" - }, - { - "name": "Hercules - The Legendary Journeys (Unknown) (Beta)", - "crc": "A7759499" - }, - { - "name": "Hercules - The Legendary Journeys (Unknown) (Beta)", - "crc": "0E2A01ED" - }, - { - "name": "Hexen (Europe)", - "region": "Europe", - "crc": "5369EFB4" - }, - { - "name": "Hexen (Europe)", - "region": "Europe", - "crc": "ED939F94" - }, - { - "name": "Hexen (France)", - "region": "France", - "crc": "E373FA31" - }, - { - "name": "Hexen (France)", - "region": "France", - "crc": "4B3DC7FE" - }, - { - "name": "Hexen (Germany)", - "region": "Germany", - "crc": "E4821C4B" - }, - { - "name": "Hexen (Germany)", - "region": "Germany", - "crc": "176C6242" - }, - { - "name": "Hexen (Japan)", - "region": "Japan", - "crc": "571DA09A" - }, - { - "name": "Hexen (Japan)", - "region": "Japan", - "crc": "3D157140" - }, - { - "name": "Hexen (USA)", - "region": "USA", - "crc": "1D35E110" - }, - { - "name": "Hexen (USA)", - "region": "USA", - "crc": "D2B22343" - }, - { - "name": "Hey You, Pikachu! (USA)", - "region": "USA", - "crc": "B18B2734" - }, - { - "name": "Hey You, Pikachu! (USA)", - "region": "USA", - "crc": "0149E9F6" - }, - { - "name": "Hiryuu no Ken Twin (Japan)", - "region": "Japan", - "crc": "BA6A687E" - }, - { - "name": "Hiryuu no Ken Twin (Japan)", - "region": "Japan", - "crc": "4A07511C" - }, - { - "name": "Holy Magic Century (Europe)", - "region": "Europe", - "crc": "84FF9890" - }, - { - "name": "Holy Magic Century (Europe)", - "region": "Europe", - "crc": "2F886030" - }, - { - "name": "Holy Magic Century (France)", - "region": "France", - "crc": "284170ED" - }, - { - "name": "Holy Magic Century (France)", - "region": "France", - "crc": "CEFC98AA" - }, - { - "name": "Holy Magic Century (Germany)", - "region": "Germany", - "crc": "D1934CF6" - }, - { - "name": "Holy Magic Century (Germany)", - "region": "Germany", - "crc": "40796997" - }, - { - "name": "Hoshi no Kirby 64 (Japan)", - "region": "Japan", - "crc": "AE7CB69D" - }, - { - "name": "Hoshi no Kirby 64 (Japan)", - "region": "Japan", - "crc": "FCACBE90" - }, - { - "name": "Hoshi no Kirby 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "A263C1B9" - }, - { - "name": "Hoshi no Kirby 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "E2FAB230" - }, - { - "name": "Hoshi no Kirby 64 (Japan) (Rev 2)", - "region": "Japan", - "crc": "F4589AA8" - }, - { - "name": "Hoshi no Kirby 64 (Japan) (Rev 2)", - "region": "Japan", - "crc": "32D2D11E" - }, - { - "name": "Hoshi no Kirby 64 (Japan) (Rev 3)", - "region": "Japan", - "crc": "6D5E1332" - }, - { - "name": "Hoshi no Kirby 64 (Japan) (Rev 3)", - "region": "Japan", - "crc": "3DEC34A5" - }, - { - "name": "Hot Wheels - Turbo Racing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "850633A7" - }, - { - "name": "Hot Wheels - Turbo Racing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "EF63C780" - }, - { - "name": "Hot Wheels - Turbo Racing (USA)", - "region": "USA", - "crc": "A5C92148" - }, - { - "name": "Hot Wheels - Turbo Racing (USA)", - "region": "USA", - "crc": "525E8F8F" - }, - { - "name": "Human Grand Prix - The New Generation (Japan)", - "region": "Japan", - "crc": "31E102E3" - }, - { - "name": "Human Grand Prix - The New Generation (Japan)", - "region": "Japan", - "crc": "A3FC9AEA" - }, - { - "name": "Hybrid Heaven (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "E76627FF" - }, - { - "name": "Hybrid Heaven (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "8696B2A2" - }, - { - "name": "Hybrid Heaven (Japan)", - "region": "Japan", - "crc": "E769DE96" - }, - { - "name": "Hybrid Heaven (Japan)", - "region": "Japan", - "crc": "42E44701" - }, - { - "name": "Hybrid Heaven (USA)", - "region": "USA", - "crc": "15B57EF8" - }, - { - "name": "Hybrid Heaven (USA)", - "region": "USA", - "crc": "7CE3EA39" - }, - { - "name": "Hydro Thunder (Europe)", - "region": "Europe", - "crc": "863AB8F3" - }, - { - "name": "Hydro Thunder (Europe)", - "region": "Europe", - "crc": "B75696AE" - }, - { - "name": "Hydro Thunder (France)", - "region": "France", - "crc": "010F6242" - }, - { - "name": "Hydro Thunder (France)", - "region": "France", - "crc": "C8CD9B5D" - }, - { - "name": "Hydro Thunder (USA)", - "region": "USA", - "crc": "E744456F" - }, - { - "name": "Hydro Thunder (USA)", - "region": "USA", - "crc": "6B78C2C8" - }, - { - "name": "Hyper Olympics in Nagano 64 (Japan)", - "region": "Japan", - "crc": "C1EA5D33" - }, - { - "name": "Hyper Olympics in Nagano 64 (Japan)", - "region": "Japan", - "crc": "F7C2BB67" - }, - { - "name": "Ide Yosuke no Mahjong Juku (Japan)", - "region": "Japan", - "crc": "DBD96DEB" - }, - { - "name": "Ide Yosuke no Mahjong Juku (Japan)", - "region": "Japan", - "crc": "32003C85" - }, - { - "name": "Iggy's Reckin' Balls (Europe)", - "region": "Europe", - "crc": "9BF26065" - }, - { - "name": "Iggy's Reckin' Balls (Europe)", - "region": "Europe", - "crc": "76530405" - }, - { - "name": "Iggy's Reckin' Balls (USA)", - "region": "USA", - "crc": "6A6FBD5D" - }, - { - "name": "Iggy's Reckin' Balls (USA)", - "region": "USA", - "crc": "5953567E" - }, - { - "name": "Iggy-kun no Bura Bura Poyon (Japan)", - "region": "Japan", - "crc": "26CC1266" - }, - { - "name": "Iggy-kun no Bura Bura Poyon (Japan)", - "region": "Japan", - "crc": "E133064B" - }, - { - "name": "In-Fisherman - Bass Hunter 64 (Europe)", - "region": "Europe", - "crc": "00DA3704" - }, - { - "name": "In-Fisherman - Bass Hunter 64 (Europe)", - "region": "Europe", - "crc": "930E6F44" - }, - { - "name": "In-Fisherman - Bass Hunter 64 (USA)", - "region": "USA", - "crc": "D8EB5E6E" - }, - { - "name": "In-Fisherman - Bass Hunter 64 (USA)", - "region": "USA", - "crc": "88279F19" - }, - { - "name": "Indiana Jones and the Infernal Machine (Australia) (Proto)", - "region": "Australia", - "crc": "337219BE" - }, - { - "name": "Indiana Jones and the Infernal Machine (Australia) (Proto)", - "region": "Australia", - "crc": "DE726D5A" - }, - { - "name": "Indiana Jones and the Infernal Machine (USA)", - "region": "USA", - "crc": "4978EB57" - }, - { - "name": "Indiana Jones and the Infernal Machine (USA)", - "region": "USA", - "crc": "A8F86EB6" - }, - { - "name": "Indy Racing 2000 (USA)", - "region": "USA", - "crc": "A5163F29" - }, - { - "name": "Indy Racing 2000 (USA)", - "region": "USA", - "crc": "1C622354" - }, - { - "name": "International Superstar Soccer '98 (Europe)", - "region": "Europe", - "crc": "BF23945D" - }, - { - "name": "International Superstar Soccer '98 (Europe)", - "region": "Europe", - "crc": "463FE8B7" - }, - { - "name": "International Superstar Soccer '98 (Europe) (Beta) (1998-03-02)", - "region": "Europe", - "crc": "A63DCB66" - }, - { - "name": "International Superstar Soccer '98 (Europe) (Beta) (1998-03-02)", - "region": "Europe", - "crc": "20E1CEA3" - }, - { - "name": "International Superstar Soccer '98 (USA)", - "region": "USA", - "crc": "B85FA721" - }, - { - "name": "International Superstar Soccer '98 (USA)", - "region": "USA", - "crc": "D685F2EC" - }, - { - "name": "International Superstar Soccer 2000 (Europe) (En,De)", - "region": "Europe", - "crc": "69572558" - }, - { - "name": "International Superstar Soccer 2000 (Europe) (En,De)", - "region": "Europe", - "crc": "61799242" - }, - { - "name": "International Superstar Soccer 2000 (Europe) (Fr,It)", - "region": "Europe", - "crc": "8A16A6A9" - }, - { - "name": "International Superstar Soccer 2000 (Europe) (Fr,It)", - "region": "Europe", - "crc": "7D7DCD46" - }, - { - "name": "International Superstar Soccer 2000 (USA) (En,Es)", - "region": "USA", - "crc": "DCD0538F" - }, - { - "name": "International Superstar Soccer 2000 (USA) (En,Es)", - "region": "USA", - "crc": "BB1824B8" - }, - { - "name": "International Superstar Soccer 2000 (USA) (En,Es) (Rev 1)", - "region": "USA", - "crc": "4C433FDD" - }, - { - "name": "International Superstar Soccer 2000 (USA) (En,Es) (Rev 1)", - "region": "USA", - "crc": "8B73672C" - }, - { - "name": "International Superstar Soccer 64 (Europe)", - "region": "Europe", - "crc": "8C839268" - }, - { - "name": "International Superstar Soccer 64 (Europe)", - "region": "Europe", - "crc": "662DA098" - }, - { - "name": "International Superstar Soccer 64 (USA)", - "region": "USA", - "crc": "0EA249B9" - }, - { - "name": "International Superstar Soccer 64 (USA)", - "region": "USA", - "crc": "91CB6803" - }, - { - "name": "International Track & Field - Summer Games (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "B3181EE0" - }, - { - "name": "International Track & Field - Summer Games (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "73073EC2" - }, - { - "name": "International Track & Field 2000 (USA)", - "region": "USA", - "crc": "DA443F0B" - }, - { - "name": "International Track & Field 2000 (USA)", - "region": "USA", - "crc": "F93868A3" - }, - { - "name": "Itoi Shigesato no Bass Tsuri No. 1 Kettei Ban! (Japan)", - "region": "Japan", - "crc": "576915D4" - }, - { - "name": "Itoi Shigesato no Bass Tsuri No. 1 Kettei Ban! (Japan)", - "region": "Japan", - "crc": "5E9C3E4B" - }, - { - "name": "J.League Dynamite Soccer 64 (Japan)", - "region": "Japan", - "crc": "DC0B2C8F" - }, - { - "name": "J.League Dynamite Soccer 64 (Japan)", - "region": "Japan", - "crc": "67EC5F38" - }, - { - "name": "J.League Eleven Beat 1997 (Japan)", - "region": "Japan", - "crc": "7D0EED6A" - }, - { - "name": "J.League Eleven Beat 1997 (Japan)", - "region": "Japan", - "crc": "B2D77747" - }, - { - "name": "J.League Live 64 (Japan)", - "region": "Japan", - "crc": "4C536DD7" - }, - { - "name": "J.League Live 64 (Japan)", - "region": "Japan", - "crc": "8A48DE94" - }, - { - "name": "J.League Tactics Soccer (Japan)", - "region": "Japan", - "crc": "976A2D12" - }, - { - "name": "J.League Tactics Soccer (Japan)", - "region": "Japan", - "crc": "0B6C70EA" - }, - { - "name": "J.League Tactics Soccer (Japan) (Rev 1)", - "region": "Japan", - "crc": "156E705E" - }, - { - "name": "J.League Tactics Soccer (Japan) (Rev 1)", - "region": "Japan", - "crc": "71FA1D4D" - }, - { - "name": "Jangou Simulation Mahjong Dou 64 (Japan)", - "region": "Japan", - "crc": "D1C1681E" - }, - { - "name": "Jangou Simulation Mahjong Dou 64 (Japan)", - "region": "Japan", - "crc": "27E9853C" - }, - { - "name": "Jeopardy! (USA)", - "region": "USA", - "crc": "E739947C" - }, - { - "name": "Jeopardy! (USA)", - "region": "USA", - "crc": "82B2AB69" - }, - { - "name": "Jeremy McGrath Supercross 2000 (Europe)", - "region": "Europe", - "crc": "5BF42EC4" - }, - { - "name": "Jeremy McGrath Supercross 2000 (Europe)", - "region": "Europe", - "crc": "EFE0B465" - }, - { - "name": "Jeremy McGrath Supercross 2000 (USA)", - "region": "USA", - "crc": "2A5C9A06" - }, - { - "name": "Jeremy McGrath Supercross 2000 (USA)", - "region": "USA", - "crc": "67BF293B" - }, - { - "name": "Jet Force Gemini (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "CFBED88C" - }, - { - "name": "Jet Force Gemini (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "FEC297EF" - }, - { - "name": "Jet Force Gemini (USA)", - "region": "USA", - "crc": "6753D5A3" - }, - { - "name": "Jet Force Gemini (USA)", - "region": "USA", - "crc": "B5932174" - }, - { - "name": "Jet Force Gemini (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "FA061B96" - }, - { - "name": "Jet Force Gemini (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "6E75735B" - }, - { - "name": "Jikkyou G1 Stable (Japan)", - "region": "Japan", - "crc": "0A796C3E" - }, - { - "name": "Jikkyou G1 Stable (Japan)", - "region": "Japan", - "crc": "97669431" - }, - { - "name": "Jikkyou G1 Stable (Japan) (Rev 1)", - "region": "Japan", - "crc": "6FC0A31B" - }, - { - "name": "Jikkyou G1 Stable (Japan) (Rev 1)", - "region": "Japan", - "crc": "142072BF" - }, - { - "name": "Jikkyou J.League 1999 - Perfect Striker 2 (Japan)", - "region": "Japan", - "crc": "2EF647D3" - }, - { - "name": "Jikkyou J.League 1999 - Perfect Striker 2 (Japan)", - "region": "Japan", - "crc": "8E2F8322" - }, - { - "name": "Jikkyou J.League 1999 - Perfect Striker 2 (Japan) (Rev 1)", - "region": "Japan", - "crc": "F96C2BE1" - }, - { - "name": "Jikkyou J.League 1999 - Perfect Striker 2 (Japan) (Rev 1)", - "region": "Japan", - "crc": "B4131606" - }, - { - "name": "Jikkyou J.League Perfect Striker (Japan)", - "region": "Japan", - "crc": "8ED60DEA" - }, - { - "name": "Jikkyou J.League Perfect Striker (Japan)", - "region": "Japan", - "crc": "97E1BCE2" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu - Basic Ban 2001 (Japan)", - "region": "Japan", - "crc": "6A9E24D7" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu - Basic Ban 2001 (Japan)", - "region": "Japan", - "crc": "4E102917" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu - Basic Ban 2001 (Japan) (Rev 1)", - "region": "Japan", - "crc": "BC7EF5C1" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu - Basic Ban 2001 (Japan) (Rev 1)", - "region": "Japan", - "crc": "68D57DF2" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 2000 (Japan)", - "region": "Japan", - "crc": "351CDE48" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 2000 (Japan)", - "region": "Japan", - "crc": "45E04C78" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 2000 (Japan) (Rev 1)", - "region": "Japan", - "crc": "753706EF" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 2000 (Japan) (Rev 1)", - "region": "Japan", - "crc": "4E6FAA43" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 4 (Japan)", - "region": "Japan", - "crc": "480B953E" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 4 (Japan)", - "region": "Japan", - "crc": "8D2540F1" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 4 (Japan) (Beta) (1996-11-30)", - "region": "Japan", - "crc": "1B8B9A68" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 4 (Japan) (Beta) (1996-11-30)", - "region": "Japan", - "crc": "5243F075" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 4 (Japan) (Rev 1)", - "region": "Japan", - "crc": "40E3AC61" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 4 (Japan) (Rev 1)", - "region": "Japan", - "crc": "435126AE" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 5 (Japan)", - "region": "Japan", - "crc": "FEEC34F6" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 5 (Japan)", - "region": "Japan", - "crc": "94FA55FF" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 5 (Japan) (Rev 1)", - "region": "Japan", - "crc": "7994C2FE" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 5 (Japan) (Rev 1)", - "region": "Japan", - "crc": "A25D1427" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 5 (Japan) (Rev 2)", - "region": "Japan", - "crc": "C02FD9BE" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 5 (Japan) (Rev 2)", - "region": "Japan", - "crc": "5B39C8B2" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 6 (Japan)", - "region": "Japan", - "crc": "1E53A7BA" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 6 (Japan)", - "region": "Japan", - "crc": "A54E4397" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 6 (Japan) (Rev 1)", - "region": "Japan", - "crc": "7AB7EBF4" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 6 (Japan) (Rev 1)", - "region": "Japan", - "crc": "5B6351F1" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 6 (Japan) (Rev 2)", - "region": "Japan", - "crc": "FCC10356" - }, - { - "name": "Jikkyou Powerful Pro Yakyuu 6 (Japan) (Rev 2)", - "region": "Japan", - "crc": "E8D4A365" - }, - { - "name": "Jikkyou World Soccer - World Cup France '98 (Japan)", - "region": "Japan", - "crc": "5C721850" - }, - { - "name": "Jikkyou World Soccer - World Cup France '98 (Japan)", - "region": "Japan", - "crc": "970A3C24" - }, - { - "name": "Jikkyou World Soccer - World Cup France '98 (Japan) (Rev 1)", - "region": "Japan", - "crc": "68DBCC04" - }, - { - "name": "Jikkyou World Soccer - World Cup France '98 (Japan) (Rev 1)", - "region": "Japan", - "crc": "7576897D" - }, - { - "name": "Jikkyou World Soccer - World Cup France '98 (Japan) (Rev 2)", - "region": "Japan", - "crc": "F63F9A5E" - }, - { - "name": "Jikkyou World Soccer - World Cup France '98 (Japan) (Rev 2)", - "region": "Japan", - "crc": "55551947" - }, - { - "name": "Jikkyou World Soccer 3 (Japan)", - "region": "Japan", - "crc": "3BA9E644" - }, - { - "name": "Jikkyou World Soccer 3 (Japan)", - "region": "Japan", - "crc": "8755DE1F" - }, - { - "name": "Jikuu Senshi Turok (Japan)", - "region": "Japan", - "crc": "E6BD65D5" - }, - { - "name": "Jikuu Senshi Turok (Japan)", - "region": "Japan", - "crc": "C3230021" - }, - { - "name": "Jinsei Game 64 (Japan)", - "region": "Japan", - "crc": "67A1A22C" - }, - { - "name": "Jinsei Game 64 (Japan)", - "region": "Japan", - "crc": "888B4265" - }, - { - "name": "John Romero's Daikatana (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "F88AC3CE" - }, - { - "name": "John Romero's Daikatana (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "48F3E888" - }, - { - "name": "John Romero's Daikatana (Japan)", - "region": "Japan", - "crc": "44B80FD7" - }, - { - "name": "John Romero's Daikatana (Japan)", - "region": "Japan", - "crc": "BEFB1434" - }, - { - "name": "John Romero's Daikatana (USA)", - "region": "USA", - "crc": "494950C6" - }, - { - "name": "John Romero's Daikatana (USA)", - "region": "USA", - "crc": "CB126EA9" - }, - { - "name": "Kakutou Denshou - F-Cup Maniax (Japan)", - "region": "Japan", - "crc": "DB40A155" - }, - { - "name": "Kakutou Denshou - F-Cup Maniax (Japan)", - "region": "Japan", - "crc": "1E967AC0" - }, - { - "name": "Ken Griffey Jr.'s Slugfest (USA)", - "region": "USA", - "crc": "12D8F3E9" - }, - { - "name": "Ken Griffey Jr.'s Slugfest (USA)", - "region": "USA", - "crc": "42D1ED42" - }, - { - "name": "Killer Instinct Gold (Europe)", - "region": "Europe", - "crc": "5D0EE5D2" - }, - { - "name": "Killer Instinct Gold (Europe)", - "region": "Europe", - "crc": "19421CA7" - }, - { - "name": "Killer Instinct Gold (USA)", - "region": "USA", - "crc": "31C76BE7" - }, - { - "name": "Killer Instinct Gold (USA)", - "region": "USA", - "crc": "B279E4B2" - }, - { - "name": "Killer Instinct Gold (USA) (Rev 1)", - "region": "USA", - "crc": "49EF8F2B" - }, - { - "name": "Killer Instinct Gold (USA) (Rev 1)", - "region": "USA", - "crc": "24AC459C" - }, - { - "name": "Killer Instinct Gold (USA) (Rev 2)", - "region": "USA", - "crc": "0B5B5DF8" - }, - { - "name": "Killer Instinct Gold (USA) (Rev 2)", - "region": "USA", - "crc": "8E1DB3C2" - }, - { - "name": "King Hill 64 - Extreme Snowboarding (Japan)", - "region": "Japan", - "crc": "F120CC52" - }, - { - "name": "King Hill 64 - Extreme Snowboarding (Japan)", - "region": "Japan", - "crc": "BC5F8666" - }, - { - "name": "Kiratto Kaiketsu! 64 Tanteidan (Japan)", - "region": "Japan", - "crc": "7FDC3784" - }, - { - "name": "Kiratto Kaiketsu! 64 Tanteidan (Japan)", - "region": "Japan", - "crc": "9FC8A30C" - }, - { - "name": "Kirby 64 - The Crystal Shards (Europe)", - "region": "Europe", - "crc": "5B8B89EF" - }, - { - "name": "Kirby 64 - The Crystal Shards (Europe)", - "region": "Europe", - "crc": "F1EB0F93" - }, - { - "name": "Kirby 64 - The Crystal Shards (USA)", - "region": "USA", - "crc": "20A1C120" - }, - { - "name": "Kirby 64 - The Crystal Shards (USA)", - "region": "USA", - "crc": "8EB3565B" - }, - { - "name": "Knife Edge - Nose Gunner (Europe)", - "region": "Europe", - "crc": "B77783BE" - }, - { - "name": "Knife Edge - Nose Gunner (Europe)", - "region": "Europe", - "crc": "35B61293" - }, - { - "name": "Knife Edge - Nose Gunner (Japan)", - "region": "Japan", - "crc": "3BC93017" - }, - { - "name": "Knife Edge - Nose Gunner (Japan)", - "region": "Japan", - "crc": "2FF68ED9" - }, - { - "name": "Knife Edge - Nose Gunner (USA)", - "region": "USA", - "crc": "255EE1DD" - }, - { - "name": "Knife Edge - Nose Gunner (USA)", - "region": "USA", - "crc": "CF204405" - }, - { - "name": "Knockout Kings 2000 (Europe)", - "region": "Europe", - "crc": "58CE7D80" - }, - { - "name": "Knockout Kings 2000 (Europe)", - "region": "Europe", - "crc": "3C38BA4A" - }, - { - "name": "Knockout Kings 2000 (USA)", - "region": "USA", - "crc": "074690D6" - }, - { - "name": "Knockout Kings 2000 (USA)", - "region": "USA", - "crc": "A26533FC" - }, - { - "name": "Kobe Bryant in NBA Courtside (Europe)", - "region": "Europe", - "crc": "1355A826" - }, - { - "name": "Kobe Bryant in NBA Courtside (Europe)", - "region": "Europe", - "crc": "5ADEF1C7" - }, - { - "name": "Kobe Bryant in NBA Courtside (USA)", - "region": "USA", - "crc": "86360BFB" - }, - { - "name": "Kobe Bryant in NBA Courtside (USA)", - "region": "USA", - "crc": "0FF8C439" - }, - { - "name": "LEGO Racers (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)", - "region": "Europe", - "crc": "C7D9B21C" - }, - { - "name": "LEGO Racers (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)", - "region": "Europe", - "crc": "3D4679E5" - }, - { - "name": "LEGO Racers (USA) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)", - "region": "USA", - "crc": "4D1E1897" - }, - { - "name": "LEGO Racers (USA) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)", - "region": "USA", - "crc": "3339E557" - }, - { - "name": "Last Legion UX (Japan)", - "region": "Japan", - "crc": "9DB99881" - }, - { - "name": "Last Legion UX (Japan)", - "region": "Japan", - "crc": "C3DAF1AA" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "9EAD1608" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "9267F4BA" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es) (Rev 1)", - "region": "Europe", - "crc": "E2E6823D" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es) (Rev 1)", - "region": "Europe", - "crc": "1D4D50B7" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es) (Rev 1) (Debug)", - "region": "Europe", - "crc": "04EA55EA" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es) (Rev 1) (Debug)", - "region": "Europe", - "crc": "CE90243C" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es) (Rev 1) (Wii Virtual Console)", - "region": "Europe", - "crc": "A83ABF72" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (En,Fr,De,Es) (Rev 1) (Wii Virtual Console)", - "region": "Europe", - "crc": "7C7D4246" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (GameCube)", - "region": "Europe", - "crc": "12836E19" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (Europe) (GameCube)", - "region": "Europe", - "crc": "19139E89" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (USA)", - "region": "USA", - "crc": "B428D8A7" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (USA)", - "region": "USA", - "crc": "3EE3F2D1" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "DCC110A0" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "F97C02CE" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (USA) (GameCube)", - "region": "USA", - "crc": "B008458F" - }, - { - "name": "Legend of Zelda, The - Majora's Mask (USA) (GameCube)", - "region": "USA", - "crc": "52245ACB" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (Beta) (2003-02-13) (GameCube)", - "region": "Europe", - "crc": "C188ACDA" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (Beta) (2003-02-13) (GameCube)", - "region": "Europe", - "crc": "2796B9EB" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (Debug) (GameCube)", - "region": "Europe", - "crc": "5D1B2996" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (Debug) (GameCube)", - "region": "Europe", - "crc": "9F742B66" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "946FD0F7" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "9852A62F" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (En,Fr,De) (Rev 1)", - "region": "Europe", - "crc": "A108F6E3" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (En,Fr,De) (Rev 1)", - "region": "Europe", - "crc": "AB0FFBAE" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (GameCube)", - "region": "Europe", - "crc": "3FBD519F" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (Europe) (GameCube)", - "region": "Europe", - "crc": "6E658036" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA)", - "region": "USA", - "crc": "CD16C529" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA)", - "region": "USA", - "crc": "EC95702D" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (Beta)", - "region": "USA", - "crc": "6C348AA8" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (Beta)", - "region": "USA", - "crc": "F0931693" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (GameCube)", - "region": "USA", - "crc": "346DE3AE" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (GameCube)", - "region": "USA", - "crc": "84BBC39F" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (Rev 1)", - "region": "USA", - "crc": "3FD2151E" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (Rev 1)", - "region": "USA", - "crc": "AA07C8BA" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (Rev 2)", - "region": "USA", - "crc": "32120C23" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time (USA) (Rev 2)", - "region": "USA", - "crc": "3C7DBF5B" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time - Master Quest (Europe) (Debug) (GameCube)", - "region": "Europe", - "crc": "A1D34E08" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time - Master Quest (Europe) (Debug) (GameCube)", - "region": "Europe", - "crc": "08044FBA" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time - Master Quest (Europe) (GameCube)", - "region": "Europe", - "crc": "832D6449" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time - Master Quest (Europe) (GameCube)", - "region": "Europe", - "crc": "177FA73A" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time - Master Quest (USA) (GameCube)", - "region": "USA", - "crc": "C744C4DB" - }, - { - "name": "Legend of Zelda, The - Ocarina of Time - Master Quest (USA) (GameCube)", - "region": "USA", - "crc": "7B89B13F" - }, - { - "name": "Let's Smash (Japan)", - "region": "Japan", - "crc": "455A1770" - }, - { - "name": "Let's Smash (Japan)", - "region": "Japan", - "crc": "33543946" - }, - { - "name": "Lode Runner 3-D (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "7148251D" - }, - { - "name": "Lode Runner 3-D (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "78707CEA" - }, - { - "name": "Lode Runner 3-D (Japan)", - "region": "Japan", - "crc": "1D4FB466" - }, - { - "name": "Lode Runner 3-D (Japan)", - "region": "Japan", - "crc": "8A1AE6A7" - }, - { - "name": "Lode Runner 3-D (USA)", - "region": "USA", - "crc": "4EA07453" - }, - { - "name": "Lode Runner 3-D (USA)", - "region": "USA", - "crc": "5AF38589" - }, - { - "name": "Lylat Wars (Australia) (En,Fr,De)", - "region": "Australia", - "crc": "9A3425DA" - }, - { - "name": "Lylat Wars (Australia) (En,Fr,De)", - "region": "Australia", - "crc": "ED1249A5" - }, - { - "name": "Lylatwars (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "50A9C0B1" - }, - { - "name": "Lylatwars (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "94A1A16A" - }, - { - "name": "MRC - Multi-Racing Championship (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "BC966B10" - }, - { - "name": "MRC - Multi-Racing Championship (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "9C7D29EC" - }, - { - "name": "MRC - Multi-Racing Championship (Japan)", - "region": "Japan", - "crc": "BEA43300" - }, - { - "name": "MRC - Multi-Racing Championship (Japan)", - "region": "Japan", - "crc": "4CA898E3" - }, - { - "name": "MRC - Multi-Racing Championship (USA)", - "region": "USA", - "crc": "1DC1C812" - }, - { - "name": "MRC - Multi-Racing Championship (USA)", - "region": "USA", - "crc": "2F30B7FE" - }, - { - "name": "Mace - The Dark Age (Europe)", - "region": "Europe", - "crc": "57DDEDE1" - }, - { - "name": "Mace - The Dark Age (Europe)", - "region": "Europe", - "crc": "4BDF2D6C" - }, - { - "name": "Mace - The Dark Age (USA)", - "region": "USA", - "crc": "D2A363A6" - }, - { - "name": "Mace - The Dark Age (USA)", - "region": "USA", - "crc": "E49D603A" - }, - { - "name": "Mace - The Dark Age (USA) (Beta)", - "region": "USA", - "crc": "2B23212F" - }, - { - "name": "Mace - The Dark Age (USA) (Beta)", - "region": "USA", - "crc": "32F629E5" - }, - { - "name": "Madden Football 64 (Europe)", - "region": "Europe", - "crc": "FAB3E50D" - }, - { - "name": "Madden Football 64 (Europe)", - "region": "Europe", - "crc": "C39F863D" - }, - { - "name": "Madden Football 64 (USA)", - "region": "USA", - "crc": "42E5FAFA" - }, - { - "name": "Madden Football 64 (USA)", - "region": "USA", - "crc": "43B28179" - }, - { - "name": "Madden NFL 2000 (USA)", - "region": "USA", - "crc": "EF5F997B" - }, - { - "name": "Madden NFL 2000 (USA)", - "region": "USA", - "crc": "F41B2332" - }, - { - "name": "Madden NFL 2001 (USA)", - "region": "USA", - "crc": "245EAEE8" - }, - { - "name": "Madden NFL 2001 (USA)", - "region": "USA", - "crc": "3B49EC33" - }, - { - "name": "Madden NFL 2002 (USA)", - "region": "USA", - "crc": "F573F107" - }, - { - "name": "Madden NFL 2002 (USA)", - "region": "USA", - "crc": "0C711DFE" - }, - { - "name": "Madden NFL 99 (Europe)", - "region": "Europe", - "crc": "D0929942" - }, - { - "name": "Madden NFL 99 (Europe)", - "region": "Europe", - "crc": "5DCAD836" - }, - { - "name": "Madden NFL 99 (USA)", - "region": "USA", - "crc": "2EB64FC2" - }, - { - "name": "Madden NFL 99 (USA)", - "region": "USA", - "crc": "AC21A5BF" - }, - { - "name": "Madden NFL 99 (USA) (Beta) (1998-08-05)", - "region": "USA", - "crc": "033A2CA6" - }, - { - "name": "Madden NFL 99 (USA) (Beta) (1998-08-05)", - "region": "USA", - "crc": "E7F33CC8" - }, - { - "name": "Madden NFL 99 (USA) (Rev 1)", - "region": "USA", - "crc": "56769A44" - }, - { - "name": "Madden NFL 99 (USA) (Rev 1)", - "region": "USA", - "crc": "E05B574D" - }, - { - "name": "Magical Tetris Challenge (Europe)", - "region": "Europe", - "crc": "AF3B099E" - }, - { - "name": "Magical Tetris Challenge (Europe)", - "region": "Europe", - "crc": "BF53BF3D" - }, - { - "name": "Magical Tetris Challenge (Germany)", - "region": "Germany", - "crc": "377F18E9" - }, - { - "name": "Magical Tetris Challenge (Germany)", - "region": "Germany", - "crc": "16995CF8" - }, - { - "name": "Magical Tetris Challenge (USA)", - "region": "USA", - "crc": "22FE979C" - }, - { - "name": "Magical Tetris Challenge (USA)", - "region": "USA", - "crc": "167C60E6" - }, - { - "name": "Magical Tetris Challenge featuring Mickey (Japan)", - "region": "Japan", - "crc": "7EFB2F1E" - }, - { - "name": "Magical Tetris Challenge featuring Mickey (Japan)", - "region": "Japan", - "crc": "6D3EBA8B" - }, - { - "name": "Mahjong 64 (Japan)", - "region": "Japan", - "crc": "DBE7D51A" - }, - { - "name": "Mahjong 64 (Japan)", - "region": "Japan", - "crc": "0FB9138C" - }, - { - "name": "Mahjong Hourouki Classic (Japan)", - "region": "Japan", - "crc": "990A8E54" - }, - { - "name": "Mahjong Hourouki Classic (Japan)", - "region": "Japan", - "crc": "FD9EA0D5" - }, - { - "name": "Mahjong Master (Japan)", - "region": "Japan", - "crc": "B68D596F" - }, - { - "name": "Mahjong Master (Japan)", - "region": "Japan", - "crc": "DB0B662A" - }, - { - "name": "Major League Baseball Featuring Ken Griffey Jr. (Australia)", - "region": "Australia", - "crc": "E08F7578" - }, - { - "name": "Major League Baseball Featuring Ken Griffey Jr. (Australia)", - "region": "Australia", - "crc": "10CC55BA" - }, - { - "name": "Major League Baseball Featuring Ken Griffey Jr. (USA)", - "region": "USA", - "crc": "2EF1EA20" - }, - { - "name": "Major League Baseball Featuring Ken Griffey Jr. (USA)", - "region": "USA", - "crc": "7542D45D" - }, - { - "name": "Maliou Kadingche (China) (v2) (Manual)", - "region": "China", - "crc": "1AD7F15B" - }, - { - "name": "Maliou Kadingche (China) (v4)", - "region": "China", - "crc": "EA0813C7" - }, - { - "name": "Maliou Kadingche (China) (v5)", - "region": "China", - "crc": "47E69F28" - }, - { - "name": "Maliou Kadingche (China) (v6) (Manual)", - "region": "China", - "crc": "ABDBF9F4" - }, - { - "name": "Maliou Yisheng (China) (v2) (Manual)", - "region": "China", - "crc": "B862A4FE" - }, - { - "name": "Maliou Yisheng (China) (v4)", - "region": "China", - "crc": "61848015" - }, - { - "name": "Maliou Yisheng (China) (v4) (Manual)", - "region": "China", - "crc": "84A5F66C" - }, - { - "name": "Mario Golf (Europe)", - "region": "Europe", - "crc": "A5071A77" - }, - { - "name": "Mario Golf (Europe)", - "region": "Europe", - "crc": "1CA3E8B6" - }, - { - "name": "Mario Golf (USA)", - "region": "USA", - "crc": "2FC4C216" - }, - { - "name": "Mario Golf (USA)", - "region": "USA", - "crc": "CEB5B977" - }, - { - "name": "Mario Golf 64 (Japan)", - "region": "Japan", - "crc": "BB03A1A6" - }, - { - "name": "Mario Golf 64 (Japan)", - "region": "Japan", - "crc": "889091FD" - }, - { - "name": "Mario Golf 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "AD1758AC" - }, - { - "name": "Mario Golf 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "B2672604" - }, - { - "name": "Mario Kart 64 (Europe)", - "region": "Europe", - "crc": "FAA6B083" - }, - { - "name": "Mario Kart 64 (Europe)", - "region": "Europe", - "crc": "6787E212" - }, - { - "name": "Mario Kart 64 (Europe) (Rev 1)", - "region": "Europe", - "crc": "0248F6C3" - }, - { - "name": "Mario Kart 64 (Europe) (Rev 1)", - "region": "Europe", - "crc": "032625B0" - }, - { - "name": "Mario Kart 64 (Japan)", - "region": "Japan", - "crc": "5D9696DF" - }, - { - "name": "Mario Kart 64 (Japan)", - "region": "Japan", - "crc": "4711A9DC" - }, - { - "name": "Mario Kart 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "6CED6472" - }, - { - "name": "Mario Kart 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "26450AAB" - }, - { - "name": "Mario Kart 64 (USA)", - "region": "USA", - "crc": "434389C1" - }, - { - "name": "Mario Kart 64 (USA)", - "region": "USA", - "crc": "2BCEE11C" - }, - { - "name": "Mario Party (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "DA98A5D3" - }, - { - "name": "Mario Party (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "6973373F" - }, - { - "name": "Mario Party (Japan)", - "region": "Japan", - "crc": "4F1ADC7B" - }, - { - "name": "Mario Party (Japan)", - "region": "Japan", - "crc": "7D387E37" - }, - { - "name": "Mario Party (USA)", - "region": "USA", - "crc": "4D60ABE5" - }, - { - "name": "Mario Party (USA)", - "region": "USA", - "crc": "4717CD1B" - }, - { - "name": "Mario Party 2 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "DC00357A" - }, - { - "name": "Mario Party 2 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "8C0C8BEC" - }, - { - "name": "Mario Party 2 (Europe) (En,Fr,De,Es,It) (Wii Virtual Console)", - "region": "Europe", - "crc": "DCA66040" - }, - { - "name": "Mario Party 2 (Europe) (En,Fr,De,Es,It) (Wii Virtual Console)", - "region": "Europe", - "crc": "5EDFDA76" - }, - { - "name": "Mario Party 2 (Japan)", - "region": "Japan", - "crc": "7457B081" - }, - { - "name": "Mario Party 2 (Japan)", - "region": "Japan", - "crc": "E19BF21C" - }, - { - "name": "Mario Party 2 (Japan) (Wii Virtual Console)", - "region": "Japan", - "crc": "FDC89220" - }, - { - "name": "Mario Party 2 (Japan) (Wii Virtual Console)", - "region": "Japan", - "crc": "4BDA017A" - }, - { - "name": "Mario Party 2 (USA)", - "region": "USA", - "crc": "E58A1955" - }, - { - "name": "Mario Party 2 (USA)", - "region": "USA", - "crc": "3BA5471B" - }, - { - "name": "Mario Party 2 (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "6F45853E" - }, - { - "name": "Mario Party 2 (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "3C93829C" - }, - { - "name": "Mario Party 3 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "813B13F2" - }, - { - "name": "Mario Party 3 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "A5D78A36" - }, - { - "name": "Mario Party 3 (Japan)", - "region": "Japan", - "crc": "3FC04053" - }, - { - "name": "Mario Party 3 (Japan)", - "region": "Japan", - "crc": "703586D1" - }, - { - "name": "Mario Party 3 (USA)", - "region": "USA", - "crc": "B7445DDC" - }, - { - "name": "Mario Party 3 (USA)", - "region": "USA", - "crc": "A1BEE3F8" - }, - { - "name": "Mario Party 3 (USA) (Beta)", - "region": "USA", - "crc": "697C1777" - }, - { - "name": "Mario Party 3 (USA) (Beta)", - "region": "USA", - "crc": "8E7BA0BC" - }, - { - "name": "Mario Story (Japan)", - "region": "Japan", - "crc": "BD60CA66" - }, - { - "name": "Mario Story (Japan)", - "region": "Japan", - "crc": "1FB7E59A" - }, - { - "name": "Mario Tennis (Europe)", - "region": "Europe", - "crc": "29AA5DF4" - }, - { - "name": "Mario Tennis (Europe)", - "region": "Europe", - "crc": "0F03CA6E" - }, - { - "name": "Mario Tennis (Europe) (Wii Virtual Console)", - "region": "Europe", - "crc": "E9BA8E6B" - }, - { - "name": "Mario Tennis (Europe) (Wii Virtual Console)", - "region": "Europe", - "crc": "8A67EA55" - }, - { - "name": "Mario Tennis (USA)", - "region": "USA", - "crc": "4E9560F6" - }, - { - "name": "Mario Tennis (USA)", - "region": "USA", - "crc": "995C9F24" - }, - { - "name": "Mario Tennis (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "8E85B369" - }, - { - "name": "Mario Tennis (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "1C38BF1F" - }, - { - "name": "Mario Tennis 64 (Japan)", - "region": "Japan", - "crc": "C665301D" - }, - { - "name": "Mario Tennis 64 (Japan)", - "region": "Japan", - "crc": "17C5515B" - }, - { - "name": "Mario Tennis 64 (Japan) (Wii Virtual Console)", - "region": "Japan", - "crc": "32C58AF3" - }, - { - "name": "Mario Tennis 64 (Japan) (Wii Virtual Console)", - "region": "Japan", - "crc": "4C11FC09" - }, - { - "name": "Mario no Photopie (Japan)", - "region": "Japan", - "crc": "176B3683" - }, - { - "name": "Mario no Photopie (Japan)", - "region": "Japan", - "crc": "49E24AE2" - }, - { - "name": "Mega Man 64 (USA)", - "region": "USA", - "crc": "1BFC71F0" - }, - { - "name": "Mega Man 64 (USA)", - "region": "USA", - "crc": "B57DD0E4" - }, - { - "name": "Mega Man 64 (USA) (Beta)", - "region": "USA", - "crc": "D3562B57" - }, - { - "name": "Mega Man 64 (USA) (Beta)", - "region": "USA", - "crc": "421D2A09" - }, - { - "name": "Mia Hamm Soccer 64 (USA) (En,Es)", - "region": "USA", - "crc": "2DB3D3D6" - }, - { - "name": "Mia Hamm Soccer 64 (USA) (En,Es)", - "region": "USA", - "crc": "3AA4BFF5" - }, - { - "name": "Michael Owen's WLS 2000 (Europe)", - "region": "Europe", - "crc": "BB680CBE" - }, - { - "name": "Michael Owen's WLS 2000 (Europe)", - "region": "Europe", - "crc": "FAFFEF70" - }, - { - "name": "Mickey no Racing Challenge USA (Japan)", - "region": "Japan", - "crc": "1AECFC56" - }, - { - "name": "Mickey no Racing Challenge USA (Japan)", - "region": "Japan", - "crc": "915A43EE" - }, - { - "name": "Mickey's Speedway USA (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "0AE51EA5" - }, - { - "name": "Mickey's Speedway USA (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "DDF28F6B" - }, - { - "name": "Mickey's Speedway USA (USA)", - "region": "USA", - "crc": "2D4F8F1B" - }, - { - "name": "Mickey's Speedway USA (USA)", - "region": "USA", - "crc": "6E589C47" - }, - { - "name": "Micro Machines 64 Turbo (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "10B9FF1F" - }, - { - "name": "Micro Machines 64 Turbo (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "14030FF5" - }, - { - "name": "Micro Machines 64 Turbo (USA)", - "region": "USA", - "crc": "A62A2763" - }, - { - "name": "Micro Machines 64 Turbo (USA)", - "region": "USA", - "crc": "02790693" - }, - { - "name": "Midway's Greatest Arcade Hits - Volume 1 (USA)", - "region": "USA", - "crc": "E5C1FEDC" - }, - { - "name": "Midway's Greatest Arcade Hits - Volume 1 (USA)", - "region": "USA", - "crc": "202A2718" - }, - { - "name": "Mike Piazza's Strike Zone (USA)", - "region": "USA", - "crc": "CC253CAB" - }, - { - "name": "Mike Piazza's Strike Zone (USA)", - "region": "USA", - "crc": "9042CB8E" - }, - { - "name": "Milo's Astro Lanes (Europe)", - "region": "Europe", - "crc": "C08CE624" - }, - { - "name": "Milo's Astro Lanes (Europe)", - "region": "Europe", - "crc": "165D7BC2" - }, - { - "name": "Milo's Astro Lanes (USA)", - "region": "USA", - "crc": "172FCA97" - }, - { - "name": "Milo's Astro Lanes (USA)", - "region": "USA", - "crc": "DFF1BA7E" - }, - { - "name": "Mini Racers (USA) (Ja) (Proto)", - "region": "USA", - "crc": "F59F881B" - }, - { - "name": "Mini Racers (USA) (Ja) (Proto)", - "region": "USA", - "crc": "CF4C80B0" - }, - { - "name": "Mischief Makers (Europe)", - "region": "Europe", - "crc": "68A4F072" - }, - { - "name": "Mischief Makers (Europe)", - "region": "Europe", - "crc": "12529953" - }, - { - "name": "Mischief Makers (USA)", - "region": "USA", - "crc": "7D222D3F" - }, - { - "name": "Mischief Makers (USA)", - "region": "USA", - "crc": "AF7B3820" - }, - { - "name": "Mischief Makers (USA) (Rev 1)", - "region": "USA", - "crc": "C3300CEF" - }, - { - "name": "Mischief Makers (USA) (Rev 1)", - "region": "USA", - "crc": "C454E245" - }, - { - "name": "Mission - Impossible (Europe)", - "region": "Europe", - "crc": "2C7131D6" - }, - { - "name": "Mission - Impossible (Europe)", - "region": "Europe", - "crc": "C41A1EBE" - }, - { - "name": "Mission - Impossible (France)", - "region": "France", - "crc": "282A350D" - }, - { - "name": "Mission - Impossible (France)", - "region": "France", - "crc": "67895343" - }, - { - "name": "Mission - Impossible (Germany)", - "region": "Germany", - "crc": "67C30A2D" - }, - { - "name": "Mission - Impossible (Germany)", - "region": "Germany", - "crc": "20067768" - }, - { - "name": "Mission - Impossible (Italy)", - "region": "Italy", - "crc": "2D789D98" - }, - { - "name": "Mission - Impossible (Italy)", - "region": "Italy", - "crc": "AC5C1408" - }, - { - "name": "Mission - Impossible (Spain)", - "region": "Spain", - "crc": "EBB060DC" - }, - { - "name": "Mission - Impossible (Spain)", - "region": "Spain", - "crc": "E33B52F0" - }, - { - "name": "Mission - Impossible (Spain) (Rev 1)", - "region": "Spain", - "crc": "E1C470FD" - }, - { - "name": "Mission - Impossible (Spain) (Rev 1)", - "region": "Spain", - "crc": "FFD3F31E" - }, - { - "name": "Mission - Impossible (USA)", - "region": "USA", - "crc": "3677A8B8" - }, - { - "name": "Mission - Impossible (USA)", - "region": "USA", - "crc": "2DBD815D" - }, - { - "name": "Monaco Grand Prix (USA)", - "region": "USA", - "crc": "E2BBEAC1" - }, - { - "name": "Monaco Grand Prix (USA)", - "region": "USA", - "crc": "30079403" - }, - { - "name": "Monaco Grand Prix - Racing Simulation 2 (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "3F5E5830" - }, - { - "name": "Monaco Grand Prix - Racing Simulation 2 (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "0A54FDF0" - }, - { - "name": "Monopoly (USA)", - "region": "USA", - "crc": "C8CAD8F6" - }, - { - "name": "Monopoly (USA)", - "region": "USA", - "crc": "416B1F8F" - }, - { - "name": "Monster Truck Madness 64 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "4731DF5C" - }, - { - "name": "Monster Truck Madness 64 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "CE6779FB" - }, - { - "name": "Monster Truck Madness 64 (USA)", - "region": "USA", - "crc": "3FD0604D" - }, - { - "name": "Monster Truck Madness 64 (USA)", - "region": "USA", - "crc": "B8D03FFB" - }, - { - "name": "Morita Shougi 64 (Japan)", - "region": "Japan", - "crc": "88C83511" - }, - { - "name": "Morita Shougi 64 (Japan)", - "region": "Japan", - "crc": "377B7580" - }, - { - "name": "Mortal Kombat 4 (Europe)", - "region": "Europe", - "crc": "635ADECA" - }, - { - "name": "Mortal Kombat 4 (Europe)", - "region": "Europe", - "crc": "70B72A07" - }, - { - "name": "Mortal Kombat 4 (USA)", - "region": "USA", - "crc": "B7F46516" - }, - { - "name": "Mortal Kombat 4 (USA)", - "region": "USA", - "crc": "AA865B66" - }, - { - "name": "Mortal Kombat Mythologies - Sub-Zero (Europe)", - "region": "Europe", - "crc": "EA21015A" - }, - { - "name": "Mortal Kombat Mythologies - Sub-Zero (Europe)", - "region": "Europe", - "crc": "1063DA05" - }, - { - "name": "Mortal Kombat Mythologies - Sub-Zero (USA)", - "region": "USA", - "crc": "51A07FD9" - }, - { - "name": "Mortal Kombat Mythologies - Sub-Zero (USA)", - "region": "USA", - "crc": "F0F7D9E3" - }, - { - "name": "Mortal Kombat Trilogy (Europe)", - "region": "Europe", - "crc": "BC04C62F" - }, - { - "name": "Mortal Kombat Trilogy (Europe)", - "region": "Europe", - "crc": "6A805380" - }, - { - "name": "Mortal Kombat Trilogy (USA)", - "region": "USA", - "crc": "50A99D60" - }, - { - "name": "Mortal Kombat Trilogy (USA)", - "region": "USA", - "crc": "58E57C08" - }, - { - "name": "Mortal Kombat Trilogy (USA) (Beta) (1996-05-13)", - "region": "USA", - "crc": "EAB22C06" - }, - { - "name": "Mortal Kombat Trilogy (USA) (Beta) (1996-05-13)", - "region": "USA", - "crc": "D969F8EA" - }, - { - "name": "Mortal Kombat Trilogy (USA) (Rev 1)", - "region": "USA", - "crc": "BA64F824" - }, - { - "name": "Mortal Kombat Trilogy (USA) (Rev 1)", - "region": "USA", - "crc": "B1C6DF97" - }, - { - "name": "Mortal Kombat Trilogy (USA) (Rev 2)", - "region": "USA", - "crc": "0F323D00" - }, - { - "name": "Mortal Kombat Trilogy (USA) (Rev 2)", - "region": "USA", - "crc": "E58597D7" - }, - { - "name": "Ms. Pac-Man - Maze Madness (USA)", - "region": "USA", - "crc": "E34C7060" - }, - { - "name": "Ms. Pac-Man - Maze Madness (USA)", - "region": "USA", - "crc": "D5017D76" - }, - { - "name": "Mystical Ninja 2 Starring Goemon (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "3502DBBE" - }, - { - "name": "Mystical Ninja 2 Starring Goemon (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "6DF2BDA6" - }, - { - "name": "Mystical Ninja Starring Goemon (Europe)", - "region": "Europe", - "crc": "3BD9059A" - }, - { - "name": "Mystical Ninja Starring Goemon (Europe)", - "region": "Europe", - "crc": "7DFE2E7A" - }, - { - "name": "Mystical Ninja Starring Goemon (USA)", - "region": "USA", - "crc": "4180C296" - }, - { - "name": "Mystical Ninja Starring Goemon (USA)", - "region": "USA", - "crc": "81C10AD9" - }, - { - "name": "NASCAR 2000 (USA)", - "region": "USA", - "crc": "02BF7C2D" - }, - { - "name": "NASCAR 2000 (USA)", - "region": "USA", - "crc": "D07AD471" - }, - { - "name": "NASCAR 99 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "76E79CEA" - }, - { - "name": "NASCAR 99 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "5351ACB5" - }, - { - "name": "NASCAR 99 (USA)", - "region": "USA", - "crc": "3D8EB950" - }, - { - "name": "NASCAR 99 (USA)", - "region": "USA", - "crc": "2D234FB7" - }, - { - "name": "NBA Courtside 2 featuring Kobe Bryant (USA)", - "region": "USA", - "crc": "A7CC4CE2" - }, - { - "name": "NBA Courtside 2 featuring Kobe Bryant (USA)", - "region": "USA", - "crc": "7A8FD7D3" - }, - { - "name": "NBA Hangtime (Europe)", - "region": "Europe", - "crc": "7E6D00AE" - }, - { - "name": "NBA Hangtime (Europe)", - "region": "Europe", - "crc": "BF05FE53" - }, - { - "name": "NBA Hangtime (USA)", - "region": "USA", - "crc": "714CF532" - }, - { - "name": "NBA Hangtime (USA)", - "region": "USA", - "crc": "72E2ACA9" - }, - { - "name": "NBA Jam 2000 (Europe)", - "region": "Europe", - "crc": "9F95485E" - }, - { - "name": "NBA Jam 2000 (Europe)", - "region": "Europe", - "crc": "84DEB3BD" - }, - { - "name": "NBA Jam 2000 (USA)", - "region": "USA", - "crc": "163DADF9" - }, - { - "name": "NBA Jam 2000 (USA)", - "region": "USA", - "crc": "65A3BD1C" - }, - { - "name": "NBA Jam 99 (Europe)", - "region": "Europe", - "crc": "90E4275B" - }, - { - "name": "NBA Jam 99 (Europe)", - "region": "Europe", - "crc": "8CCE0AE4" - }, - { - "name": "NBA Jam 99 (USA)", - "region": "USA", - "crc": "559CD6B1" - }, - { - "name": "NBA Jam 99 (USA)", - "region": "USA", - "crc": "F40C871A" - }, - { - "name": "NBA Live 2000 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "0E4B944C" - }, - { - "name": "NBA Live 2000 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "4C7282D6" - }, - { - "name": "NBA Live 2000 (USA) (En,Fr,De,Es)", - "region": "USA", - "crc": "7C3BC95E" - }, - { - "name": "NBA Live 2000 (USA) (En,Fr,De,Es)", - "region": "USA", - "crc": "94FC12D3" - }, - { - "name": "NBA Live 99 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "A316DF37" - }, - { - "name": "NBA Live 99 (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "086E322E" - }, - { - "name": "NBA Live 99 (USA) (En,Fr,De,Es,It)", - "region": "USA", - "crc": "9BE0A7AC" - }, - { - "name": "NBA Live 99 (USA) (En,Fr,De,Es,It)", - "region": "USA", - "crc": "74BC96BE" - }, - { - "name": "NBA Pro 98 (Europe)", - "region": "Europe", - "crc": "04B75CCC" - }, - { - "name": "NBA Pro 98 (Europe)", - "region": "Europe", - "crc": "7FABF1EA" - }, - { - "name": "NBA Pro 99 (Europe)", - "region": "Europe", - "crc": "588E60E8" - }, - { - "name": "NBA Pro 99 (Europe)", - "region": "Europe", - "crc": "6EA32E08" - }, - { - "name": "NBA Showtime - NBA on NBC (USA)", - "region": "USA", - "crc": "A4E378F4" - }, - { - "name": "NBA Showtime - NBA on NBC (USA)", - "region": "USA", - "crc": "A9302DEE" - }, - { - "name": "NBA in the Zone '98 (Japan)", - "region": "Japan", - "crc": "AED2700A" - }, - { - "name": "NBA in the Zone '98 (Japan)", - "region": "Japan", - "crc": "58B2150D" - }, - { - "name": "NBA in the Zone '98 (USA)", - "region": "USA", - "crc": "A245D737" - }, - { - "name": "NBA in the Zone '98 (USA)", - "region": "USA", - "crc": "2BB83C0E" - }, - { - "name": "NBA in the Zone '99 (USA)", - "region": "USA", - "crc": "EAB083B8" - }, - { - "name": "NBA in the Zone '99 (USA)", - "region": "USA", - "crc": "C15AFC69" - }, - { - "name": "NBA in the Zone 2 (Japan)", - "region": "Japan", - "crc": "41093B73" - }, - { - "name": "NBA in the Zone 2 (Japan)", - "region": "Japan", - "crc": "F736D01A" - }, - { - "name": "NBA in the Zone 2000 (Europe)", - "region": "Europe", - "crc": "A4973197" - }, - { - "name": "NBA in the Zone 2000 (Europe)", - "region": "Europe", - "crc": "AD6CD2AE" - }, - { - "name": "NBA in the Zone 2000 (USA)", - "region": "USA", - "crc": "CBB4B730" - }, - { - "name": "NBA in the Zone 2000 (USA)", - "region": "USA", - "crc": "D850CFA3" - }, - { - "name": "NFL Blitz (USA)", - "region": "USA", - "crc": "9BCD670F" - }, - { - "name": "NFL Blitz (USA)", - "region": "USA", - "crc": "5FA3455D" - }, - { - "name": "NFL Blitz - Special Edition (USA)", - "region": "USA", - "crc": "5D1907F7" - }, - { - "name": "NFL Blitz - Special Edition (USA)", - "region": "USA", - "crc": "3DD3EB64" - }, - { - "name": "NFL Blitz 2000 (USA)", - "region": "USA", - "crc": "7F471773" - }, - { - "name": "NFL Blitz 2000 (USA)", - "region": "USA", - "crc": "E4B42E6A" - }, - { - "name": "NFL Blitz 2000 (USA) (Rev 1)", - "region": "USA", - "crc": "B591CFEA" - }, - { - "name": "NFL Blitz 2000 (USA) (Rev 1)", - "region": "USA", - "crc": "F77108C0" - }, - { - "name": "NFL Blitz 2001 (USA)", - "region": "USA", - "crc": "18EEB41B" - }, - { - "name": "NFL Blitz 2001 (USA)", - "region": "USA", - "crc": "E18211E5" - }, - { - "name": "NFL QB Club 2001 (USA)", - "region": "USA", - "crc": "6D849E17" - }, - { - "name": "NFL QB Club 2001 (USA)", - "region": "USA", - "crc": "2C0D748B" - }, - { - "name": "NFL Quarterback Club 2000 (Europe)", - "region": "Europe", - "crc": "CEEF5C29" - }, - { - "name": "NFL Quarterback Club 2000 (Europe)", - "region": "Europe", - "crc": "11225980" - }, - { - "name": "NFL Quarterback Club 2000 (USA)", - "region": "USA", - "crc": "8F54F999" - }, - { - "name": "NFL Quarterback Club 2000 (USA)", - "region": "USA", - "crc": "B61D114A" - }, - { - "name": "NFL Quarterback Club 98 (Europe)", - "region": "Europe", - "crc": "34A21417" - }, - { - "name": "NFL Quarterback Club 98 (Europe)", - "region": "Europe", - "crc": "129928ED" - }, - { - "name": "NFL Quarterback Club 98 (USA)", - "region": "USA", - "crc": "ABF0E8F2" - }, - { - "name": "NFL Quarterback Club 98 (USA)", - "region": "USA", - "crc": "94368DB1" - }, - { - "name": "NFL Quarterback Club 99 (Europe)", - "region": "Europe", - "crc": "F688BDF3" - }, - { - "name": "NFL Quarterback Club 99 (Europe)", - "region": "Europe", - "crc": "8D95305F" - }, - { - "name": "NFL Quarterback Club 99 (USA)", - "region": "USA", - "crc": "44496B26" - }, - { - "name": "NFL Quarterback Club 99 (USA)", - "region": "USA", - "crc": "CE525BC5" - }, - { - "name": "NHL 99 (Europe) (En,De,Sv,Fi)", - "region": "Europe", - "crc": "F3B2AA4D" - }, - { - "name": "NHL 99 (Europe) (En,De,Sv,Fi)", - "region": "Europe", - "crc": "5CAABE5A" - }, - { - "name": "NHL 99 (USA)", - "region": "USA", - "crc": "E5DF2AFE" - }, - { - "name": "NHL 99 (USA)", - "region": "USA", - "crc": "50F81F73" - }, - { - "name": "NHL Blades of Steel '99 (USA)", - "region": "USA", - "crc": "1EE678FE" - }, - { - "name": "NHL Blades of Steel '99 (USA)", - "region": "USA", - "crc": "5EF28CFB" - }, - { - "name": "NHL Breakaway 98 (Europe)", - "region": "Europe", - "crc": "8C0C9669" - }, - { - "name": "NHL Breakaway 98 (Europe)", - "region": "Europe", - "crc": "30B28491" - }, - { - "name": "NHL Breakaway 98 (USA)", - "region": "USA", - "crc": "49D86C00" - }, - { - "name": "NHL Breakaway 98 (USA)", - "region": "USA", - "crc": "F59B082F" - }, - { - "name": "NHL Breakaway 99 (Europe)", - "region": "Europe", - "crc": "572060E0" - }, - { - "name": "NHL Breakaway 99 (Europe)", - "region": "Europe", - "crc": "B7E1618A" - }, - { - "name": "NHL Breakaway 99 (USA)", - "region": "USA", - "crc": "75F75B9D" - }, - { - "name": "NHL Breakaway 99 (USA)", - "region": "USA", - "crc": "9F774781" - }, - { - "name": "NHL Pro 99 (Europe)", - "region": "Europe", - "crc": "E272867E" - }, - { - "name": "NHL Pro 99 (Europe)", - "region": "Europe", - "crc": "36696571" - }, - { - "name": "Nagano Winter Olympics '98 (Europe)", - "region": "Europe", - "crc": "C44DE11C" - }, - { - "name": "Nagano Winter Olympics '98 (Europe)", - "region": "Europe", - "crc": "36D43F29" - }, - { - "name": "Nagano Winter Olympics '98 (USA)", - "region": "USA", - "crc": "EE8288D4" - }, - { - "name": "Nagano Winter Olympics '98 (USA)", - "region": "USA", - "crc": "1F8D21FB" - }, - { - "name": "Namco Museum 64 (USA)", - "region": "USA", - "crc": "CE361F92" - }, - { - "name": "Namco Museum 64 (USA)", - "region": "USA", - "crc": "03776764" - }, - { - "name": "Neon Genesis Evangelion (Japan)", - "region": "Japan", - "crc": "A10A86AF" - }, - { - "name": "Neon Genesis Evangelion (Japan)", - "region": "Japan", - "crc": "4B8B23DC" - }, - { - "name": "New Tetris, The (Europe)", - "region": "Europe", - "crc": "983263D7" - }, - { - "name": "New Tetris, The (Europe)", - "region": "Europe", - "crc": "88B774C4" - }, - { - "name": "New Tetris, The (USA)", - "region": "USA", - "crc": "528A07FA" - }, - { - "name": "New Tetris, The (USA)", - "region": "USA", - "crc": "82A76DC9" - }, - { - "name": "Nightmare Creatures (USA)", - "region": "USA", - "crc": "6A1EB795" - }, - { - "name": "Nightmare Creatures (USA)", - "region": "USA", - "crc": "134C09DA" - }, - { - "name": "Nintama Rantarou 64 Game Gallery (Japan)", - "region": "Japan", - "crc": "8C7C2DCA" - }, - { - "name": "Nintama Rantarou 64 Game Gallery (Japan)", - "region": "Japan", - "crc": "2D96C091" - }, - { - "name": "Nintendo 64 Modem (Japan) (Add-on)", - "region": "Japan", - "crc": "77D7B7E3" - }, - { - "name": "Nintendo 64 Modem (Japan) (Add-on)", - "region": "Japan", - "crc": "8D5FB2F3" - }, - { - "name": "Nintendo All-Star! Dairantou Smash Brothers (Japan)", - "region": "Japan", - "crc": "04C9D3B1" - }, - { - "name": "Nintendo All-Star! Dairantou Smash Brothers (Japan)", - "region": "Japan", - "crc": "A2D1D0D1" - }, - { - "name": "Nuclear Strike 64 (Europe) (En,Fr)", - "region": "Europe", - "crc": "9AFBFCAF" - }, - { - "name": "Nuclear Strike 64 (Europe) (En,Fr)", - "region": "Europe", - "crc": "575FBFE5" - }, - { - "name": "Nuclear Strike 64 (Germany)", - "region": "Germany", - "crc": "D175397B" - }, - { - "name": "Nuclear Strike 64 (Germany)", - "region": "Germany", - "crc": "D7200CC0" - }, - { - "name": "Nuclear Strike 64 (Germany) (Beta) (1999-11-17)", - "region": "Germany", - "crc": "020B6443" - }, - { - "name": "Nuclear Strike 64 (Germany) (Beta) (1999-11-17)", - "region": "Germany", - "crc": "B1D7EBD1" - }, - { - "name": "Nuclear Strike 64 (USA)", - "region": "USA", - "crc": "D7467294" - }, - { - "name": "Nuclear Strike 64 (USA)", - "region": "USA", - "crc": "1A70D11A" - }, - { - "name": "Nushi Zuri 64 (Japan)", - "region": "Japan", - "crc": "A6800EC0" - }, - { - "name": "Nushi Zuri 64 (Japan)", - "region": "Japan", - "crc": "02FF4DF5" - }, - { - "name": "Nushi Zuri 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "16414AE4" - }, - { - "name": "Nushi Zuri 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "DF59E5F2" - }, - { - "name": "Nushi Zuri 64 - Shiokaze ni Notte (Japan)", - "region": "Japan", - "crc": "F0BCD1CE" - }, - { - "name": "Nushi Zuri 64 - Shiokaze ni Notte (Japan)", - "region": "Japan", - "crc": "6A81744D" - }, - { - "name": "O.D.T. (Europe) (En,Fr,De,Es,It) (Proto)", - "region": "Europe", - "crc": "7B870026" - }, - { - "name": "O.D.T. (Europe) (En,Fr,De,Es,It) (Proto)", - "region": "Europe", - "crc": "6E79473E" - }, - { - "name": "O.D.T. (USA) (En,Fr,Es) (Proto)", - "region": "USA", - "crc": "1D4A8659" - }, - { - "name": "O.D.T. (USA) (En,Fr,Es) (Proto)", - "region": "USA", - "crc": "AE7E3A9E" - }, - { - "name": "Off Road Challenge (Europe)", - "region": "Europe", - "crc": "D9FE9EE7" - }, - { - "name": "Off Road Challenge (Europe)", - "region": "Europe", - "crc": "E1938232" - }, - { - "name": "Off Road Challenge (USA)", - "region": "USA", - "crc": "1A45C5AB" - }, - { - "name": "Off Road Challenge (USA)", - "region": "USA", - "crc": "129BADCC" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (Japan) (Rev 1)", - "region": "Japan", - "crc": "845B8711" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (Japan) (Rev 1)", - "region": "Japan", - "crc": "D52F01FD" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (Japan) (Rev 1) (Wii Virtual Console)", - "region": "Japan", - "crc": "991F569C" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (Japan) (Rev 1) (Wii Virtual Console)", - "region": "Japan", - "crc": "F6B80668" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (USA)", - "region": "USA", - "crc": "A05AEA85" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (USA)", - "region": "USA", - "crc": "C650BE3C" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (USA) (Rev 1)", - "region": "USA", - "crc": "410F6510" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (USA) (Rev 1)", - "region": "USA", - "crc": "50C78D4C" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (USA, Europe) (Rev 1) (Wii Virtual Console)", - "crc": "19337B04" - }, - { - "name": "Ogre Battle 64 - Person of Lordly Caliber (USA, Europe) (Rev 1) (Wii Virtual Console)", - "crc": "3752AD39" - }, - { - "name": "Olympic Hockey 98 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "5A805C2E" - }, - { - "name": "Olympic Hockey 98 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "1C7B9ED5" - }, - { - "name": "Olympic Hockey 98 (Japan)", - "region": "Japan", - "crc": "9E98FCE8" - }, - { - "name": "Olympic Hockey 98 (Japan)", - "region": "Japan", - "crc": "436CC625" - }, - { - "name": "Olympic Hockey 98 (USA)", - "region": "USA", - "crc": "2D777652" - }, - { - "name": "Olympic Hockey 98 (USA)", - "region": "USA", - "crc": "C57C7643" - }, - { - "name": "Onegai Monsters (Japan)", - "region": "Japan", - "crc": "AC72A1C7" - }, - { - "name": "Onegai Monsters (Japan)", - "region": "Japan", - "crc": "FF03EC8A" - }, - { - "name": "Operation WinBack (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "FB96F166" - }, - { - "name": "Operation WinBack (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "15F1C325" - }, - { - "name": "PD Ultraman Battle Collection 64 (Japan)", - "region": "Japan", - "crc": "86CC80B5" - }, - { - "name": "PD Ultraman Battle Collection 64 (Japan)", - "region": "Japan", - "crc": "AB901B83" - }, - { - "name": "PGA European Tour (USA)", - "region": "USA", - "crc": "7CDFCDAA" - }, - { - "name": "PGA European Tour (USA)", - "region": "USA", - "crc": "2B07F591" - }, - { - "name": "PGA European Tour Golf (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "6B5FF959" - }, - { - "name": "PGA European Tour Golf (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "07B0271E" - }, - { - "name": "Pachinko 365 Nichi (Japan)", - "region": "Japan", - "crc": "42D06E32" - }, - { - "name": "Pachinko 365 Nichi (Japan)", - "region": "Japan", - "crc": "41363874" - }, - { - "name": "Paper Mario (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "9FC00CE3" - }, - { - "name": "Paper Mario (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "406CE874" - }, - { - "name": "Paper Mario (USA)", - "region": "USA", - "crc": "A7F5CD7E" - }, - { - "name": "Paper Mario (USA)", - "region": "USA", - "crc": "D56D1C89" - }, - { - "name": "Paperboy (Europe)", - "region": "Europe", - "crc": "F00C5053" - }, - { - "name": "Paperboy (Europe)", - "region": "Europe", - "crc": "D98C3F8B" - }, - { - "name": "Paperboy (USA)", - "region": "USA", - "crc": "F27114E6" - }, - { - "name": "Paperboy (USA)", - "region": "USA", - "crc": "2FCEEFF6" - }, - { - "name": "Parlor! Pro 64 - Pachinko Jikki Simulation {Japan)", - "region": "Japan", - "crc": "C5DC4795" - }, - { - "name": "Parlor! Pro 64 - Pachinko Jikki Simulation {Japan)", - "region": "Japan", - "crc": "4061BB82" - }, - { - "name": "Penny Racers (Europe)", - "region": "Europe", - "crc": "A1D6EB5B" - }, - { - "name": "Penny Racers (Europe)", - "region": "Europe", - "crc": "DECD5D16" - }, - { - "name": "Penny Racers (USA)", - "region": "USA", - "crc": "C1E57337" - }, - { - "name": "Penny Racers (USA)", - "region": "USA", - "crc": "06BEEA3F" - }, - { - "name": "Perfect Dark (Europe) (Debug Version) (2000-04-26)", - "region": "Europe", - "crc": "B33C4A54" - }, - { - "name": "Perfect Dark (Europe) (Debug Version) (2000-04-26)", - "region": "Europe", - "crc": "5C6AC287" - }, - { - "name": "Perfect Dark (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "7718A714" - }, - { - "name": "Perfect Dark (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "C4639ED6" - }, - { - "name": "Perfect Dark (Japan)", - "region": "Japan", - "crc": "639C0DA9" - }, - { - "name": "Perfect Dark (Japan)", - "region": "Japan", - "crc": "D76471DB" - }, - { - "name": "Perfect Dark (USA)", - "region": "USA", - "crc": "68446AD4" - }, - { - "name": "Perfect Dark (USA)", - "region": "USA", - "crc": "F0FE18F0" - }, - { - "name": "Perfect Dark (USA) (Debug Version) (2000-03-22)", - "region": "USA", - "crc": "B568A9FE" - }, - { - "name": "Perfect Dark (USA) (Debug Version) (2000-03-22)", - "region": "USA", - "crc": "0EA2645F" - }, - { - "name": "Perfect Dark (USA) (Rev 1)", - "region": "USA", - "crc": "4C1677F7" - }, - { - "name": "Perfect Dark (USA) (Rev 1)", - "region": "USA", - "crc": "F85A16F1" - }, - { - "name": "Pikachuu Genki de Chuu (Japan)", - "region": "Japan", - "crc": "3F6245AE" - }, - { - "name": "Pikachuu Genki de Chuu (Japan)", - "region": "Japan", - "crc": "2AE884CD" - }, - { - "name": "Pilotwings 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "C902E57C" - }, - { - "name": "Pilotwings 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "334E88BF" - }, - { - "name": "Pilotwings 64 (Japan)", - "region": "Japan", - "crc": "3D3A84A9" - }, - { - "name": "Pilotwings 64 (Japan)", - "region": "Japan", - "crc": "001EE45B" - }, - { - "name": "Pilotwings 64 (USA)", - "region": "USA", - "crc": "728807E7" - }, - { - "name": "Pilotwings 64 (USA)", - "region": "USA", - "crc": "C445F7E3" - }, - { - "name": "Pokemon Puzzle League (Europe)", - "region": "Europe", - "crc": "75839254" - }, - { - "name": "Pokemon Puzzle League (Europe)", - "region": "Europe", - "crc": "443B309A" - }, - { - "name": "Pokemon Puzzle League (Europe) (Wii Virtual Console)", - "region": "Europe", - "crc": "13F996B2" - }, - { - "name": "Pokemon Puzzle League (Europe) (Wii Virtual Console)", - "region": "Europe", - "crc": "745A4853" - }, - { - "name": "Pokemon Puzzle League (France)", - "region": "France", - "crc": "C3AA0074" - }, - { - "name": "Pokemon Puzzle League (France)", - "region": "France", - "crc": "EE080CCD" - }, - { - "name": "Pokemon Puzzle League (France) (Wii Virtual Console)", - "region": "France", - "crc": "8671137D" - }, - { - "name": "Pokemon Puzzle League (France) (Wii Virtual Console)", - "region": "France", - "crc": "F2AED93C" - }, - { - "name": "Pokemon Puzzle League (Germany)", - "region": "Germany", - "crc": "AC543150" - }, - { - "name": "Pokemon Puzzle League (Germany)", - "region": "Germany", - "crc": "7D4C9859" - }, - { - "name": "Pokemon Puzzle League (Germany) (Wii Virtual Console)", - "region": "Germany", - "crc": "00E73008" - }, - { - "name": "Pokemon Puzzle League (Germany) (Wii Virtual Console)", - "region": "Germany", - "crc": "DFFD6A4E" - }, - { - "name": "Pokemon Puzzle League (USA)", - "region": "USA", - "crc": "8B9C598F" - }, - { - "name": "Pokemon Puzzle League (USA)", - "region": "USA", - "crc": "281AFB0D" - }, - { - "name": "Pokemon Puzzle League (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "91623130" - }, - { - "name": "Pokemon Puzzle League (USA) (Wii Virtual Console)", - "region": "USA", - "crc": "256A88EA" - }, - { - "name": "Pokemon Snap (Australia)", - "region": "Australia", - "crc": "CDEA6D4C" - }, - { - "name": "Pokemon Snap (Australia)", - "region": "Australia", - "crc": "8A8F6D5F" - }, - { - "name": "Pokemon Snap (Europe)", - "region": "Europe", - "crc": "F824A057" - }, - { - "name": "Pokemon Snap (Europe)", - "region": "Europe", - "crc": "E9100906" - }, - { - "name": "Pokemon Snap (France)", - "region": "France", - "crc": "EC843586" - }, - { - "name": "Pokemon Snap (France)", - "region": "France", - "crc": "116B2CAC" - }, - { - "name": "Pokemon Snap (Germany)", - "region": "Germany", - "crc": "10C27B3C" - }, - { - "name": "Pokemon Snap (Germany)", - "region": "Germany", - "crc": "482A6D8D" - }, - { - "name": "Pokemon Snap (Italy)", - "region": "Italy", - "crc": "DF56E922" - }, - { - "name": "Pokemon Snap (Italy)", - "region": "Italy", - "crc": "15C1AFB5" - }, - { - "name": "Pokemon Snap (Japan)", - "region": "Japan", - "crc": "A091BD56" - }, - { - "name": "Pokemon Snap (Japan)", - "region": "Japan", - "crc": "100AACC0" - }, - { - "name": "Pokemon Snap (Japan) (Rev 1) (Wii Virtual Console)", - "region": "Japan", - "crc": "A761DAEF" - }, - { - "name": "Pokemon Snap (Japan) (Rev 1) (Wii Virtual Console)", - "region": "Japan", - "crc": "A73738B5" - }, - { - "name": "Pokemon Snap (Spain)", - "region": "Spain", - "crc": "371B787F" - }, - { - "name": "Pokemon Snap (Spain)", - "region": "Spain", - "crc": "426A1919" - }, - { - "name": "Pokemon Snap (USA)", - "region": "USA", - "crc": "86A69756" - }, - { - "name": "Pokemon Snap (USA)", - "region": "USA", - "crc": "DF387848" - }, - { - "name": "Pokemon Snap Station (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "E22A00D0" - }, - { - "name": "Pokemon Snap Station (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "9BEF1033" - }, - { - "name": "Pokemon Stadium (Europe)", - "region": "Europe", - "crc": "DC57508D" - }, - { - "name": "Pokemon Stadium (Europe)", - "region": "Europe", - "crc": "7A8BF040" - }, - { - "name": "Pokemon Stadium (Europe) (Rev 1)", - "region": "Europe", - "crc": "DA889668" - }, - { - "name": "Pokemon Stadium (Europe) (Rev 1)", - "region": "Europe", - "crc": "60F6BE65" - }, - { - "name": "Pokemon Stadium (France)", - "region": "France", - "crc": "5DD92D4C" - }, - { - "name": "Pokemon Stadium (France)", - "region": "France", - "crc": "476848F6" - }, - { - "name": "Pokemon Stadium (Germany)", - "region": "Germany", - "crc": "9F22A945" - }, - { - "name": "Pokemon Stadium (Germany)", - "region": "Germany", - "crc": "3243A2A7" - }, - { - "name": "Pokemon Stadium (Italy)", - "region": "Italy", - "crc": "F155C465" - }, - { - "name": "Pokemon Stadium (Italy)", - "region": "Italy", - "crc": "77A59419" - }, - { - "name": "Pokemon Stadium (Japan)", - "region": "Japan", - "crc": "3139189C" - }, - { - "name": "Pokemon Stadium (Japan)", - "region": "Japan", - "crc": "3B2A3F7A" - }, - { - "name": "Pokemon Stadium (Spain)", - "region": "Spain", - "crc": "F02CD5EB" - }, - { - "name": "Pokemon Stadium (Spain)", - "region": "Spain", - "crc": "7ADFB7E0" - }, - { - "name": "Pokemon Stadium (USA)", - "region": "USA", - "crc": "72F66F05" - }, - { - "name": "Pokemon Stadium (USA)", - "region": "USA", - "crc": "3003E90C" - }, - { - "name": "Pokemon Stadium (USA) (Rev 1)", - "region": "USA", - "crc": "4B1BC2AC" - }, - { - "name": "Pokemon Stadium (USA) (Rev 1)", - "region": "USA", - "crc": "7BB7489F" - }, - { - "name": "Pokemon Stadium (USA) (Rev 2)", - "region": "USA", - "crc": "235B1842" - }, - { - "name": "Pokemon Stadium (USA) (Rev 2)", - "region": "USA", - "crc": "D2E2EC6D" - }, - { - "name": "Pokemon Stadium 2 (Europe)", - "region": "Europe", - "crc": "6B3096C4" - }, - { - "name": "Pokemon Stadium 2 (Europe)", - "region": "Europe", - "crc": "85F53D6C" - }, - { - "name": "Pokemon Stadium 2 (France)", - "region": "France", - "crc": "E2A78066" - }, - { - "name": "Pokemon Stadium 2 (France)", - "region": "France", - "crc": "A72C55AF" - }, - { - "name": "Pokemon Stadium 2 (Germany)", - "region": "Germany", - "crc": "1146A43A" - }, - { - "name": "Pokemon Stadium 2 (Germany)", - "region": "Germany", - "crc": "B0A291FC" - }, - { - "name": "Pokemon Stadium 2 (Italy)", - "region": "Italy", - "crc": "9FA5C095" - }, - { - "name": "Pokemon Stadium 2 (Italy)", - "region": "Italy", - "crc": "C849C481" - }, - { - "name": "Pokemon Stadium 2 (Japan)", - "region": "Japan", - "crc": "40AA4874" - }, - { - "name": "Pokemon Stadium 2 (Japan)", - "region": "Japan", - "crc": "C876BE9C" - }, - { - "name": "Pokemon Stadium 2 (Spain)", - "region": "Spain", - "crc": "283E7641" - }, - { - "name": "Pokemon Stadium 2 (Spain)", - "region": "Spain", - "crc": "391119B5" - }, - { - "name": "Pokemon Stadium 2 (USA)", - "region": "USA", - "crc": "A9998E09" - }, - { - "name": "Pokemon Stadium 2 (USA)", - "region": "USA", - "crc": "FBFF058A" - }, - { - "name": "Pokemon Stadium Kin Gin (Japan)", - "region": "Japan", - "crc": "CBC3B935" - }, - { - "name": "Pokemon Stadium Kin Gin (Japan)", - "region": "Japan", - "crc": "16701567" - }, - { - "name": "Polaris SnoCross (USA)", - "region": "USA", - "crc": "8DD735EF" - }, - { - "name": "Polaris SnoCross (USA)", - "region": "USA", - "crc": "BA3EC852" - }, - { - "name": "Power League 64 (Japan)", - "region": "Japan", - "crc": "AEC21C28" - }, - { - "name": "Power League 64 (Japan)", - "region": "Japan", - "crc": "BC7E2494" - }, - { - "name": "Power Rangers - Lightspeed Rescue (Europe)", - "region": "Europe", - "crc": "36EE5CA8" - }, - { - "name": "Power Rangers - Lightspeed Rescue (Europe)", - "region": "Europe", - "crc": "88250AB1" - }, - { - "name": "Power Rangers - Lightspeed Rescue (USA)", - "region": "USA", - "crc": "A5033311" - }, - { - "name": "Power Rangers - Lightspeed Rescue (USA)", - "region": "USA", - "crc": "EB8CE95B" - }, - { - "name": "Powerpuff Girls, The - Chemical X-Traction (USA)", - "region": "USA", - "crc": "9514DA0A" - }, - { - "name": "Powerpuff Girls, The - Chemical X-Traction (USA)", - "region": "USA", - "crc": "4EE7CD7E" - }, - { - "name": "Premier Manager 64 (Europe)", - "region": "Europe", - "crc": "81CDA888" - }, - { - "name": "Premier Manager 64 (Europe)", - "region": "Europe", - "crc": "74A0C415" - }, - { - "name": "Pro Mahjong Kiwame 64 (Japan)", - "region": "Japan", - "crc": "1F5907F9" - }, - { - "name": "Pro Mahjong Kiwame 64 (Japan)", - "region": "Japan", - "crc": "01E83335" - }, - { - "name": "Pro Mahjong Kiwame 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "B9BE7B90" - }, - { - "name": "Pro Mahjong Kiwame 64 (Japan) (Rev 1)", - "region": "Japan", - "crc": "FB5FCD97" - }, - { - "name": "Pro Mahjong Tsuwamono 64 - Jansou Battle ni Chousen (Japan)", - "region": "Japan", - "crc": "35461699" - }, - { - "name": "Pro Mahjong Tsuwamono 64 - Jansou Battle ni Chousen (Japan)", - "region": "Japan", - "crc": "388DA567" - }, - { - "name": "Puyo Puyo Sun 64 (Japan)", - "region": "Japan", - "crc": "355FF9DE" - }, - { - "name": "Puyo Puyo Sun 64 (Japan)", - "region": "Japan", - "crc": "C256544C" - }, - { - "name": "Puyo Puyoon Party (Japan)", - "region": "Japan", - "crc": "D59D2794" - }, - { - "name": "Puyo Puyoon Party (Japan)", - "region": "Japan", - "crc": "9C32ADEB" - }, - { - "name": "Puzzle Bobble 64 (Japan)", - "region": "Japan", - "crc": "EA837423" - }, - { - "name": "Puzzle Bobble 64 (Japan)", - "region": "Japan", - "crc": "30238199" - }, - { - "name": "Pyoro 64 (Italy) (Aftermarket) (Homebrew)", - "region": "Italy", - "crc": "1EE17B37" - }, - { - "name": "Pyoro 64 (USA) (Aftermarket) (Homebrew)", - "region": "USA", - "crc": "B7EBDDC6" - }, - { - "name": "Pyoro 64 (Unknown) (Aftermarket) (Homebrew)", - "crc": "B8874C09" - }, - { - "name": "Quake (Europe)", - "region": "Europe", - "crc": "28C10844" - }, - { - "name": "Quake (Europe)", - "region": "Europe", - "crc": "6F79B0EB" - }, - { - "name": "Quake (USA)", - "region": "USA", - "crc": "761F39D1" - }, - { - "name": "Quake (USA)", - "region": "USA", - "crc": "06D5CDBB" - }, - { - "name": "Quake II (Europe)", - "region": "Europe", - "crc": "82BECA21" - }, - { - "name": "Quake II (Europe)", - "region": "Europe", - "crc": "7098DF97" - }, - { - "name": "Quake II (USA)", - "region": "USA", - "crc": "E6B34387" - }, - { - "name": "Quake II (USA)", - "region": "USA", - "crc": "5D880E55" - }, - { - "name": "Quest 64 (USA)", - "region": "USA", - "crc": "D75B45C6" - }, - { - "name": "Quest 64 (USA)", - "region": "USA", - "crc": "E90856E8" - }, - { - "name": "R64 (World) (Aftermarket) (Homebrew)", - "crc": "9B30252E" - }, - { - "name": "R64 (World) (Aftermarket) (Homebrew)", - "crc": "467E5A00" - }, - { - "name": "RR64 - Ridge Racer 64 (Europe)", - "region": "Europe", - "crc": "DD9AE3A8" - }, - { - "name": "RR64 - Ridge Racer 64 (Europe)", - "region": "Europe", - "crc": "170F63B2" - }, - { - "name": "RR64 - Ridge Racer 64 (USA)", - "region": "USA", - "crc": "3C2C2D1C" - }, - { - "name": "RR64 - Ridge Racer 64 (USA)", - "region": "USA", - "crc": "7CCB9749" - }, - { - "name": "RTL World League Soccer 2000 (Germany)", - "region": "Germany", - "crc": "0A17DA7B" - }, - { - "name": "RTL World League Soccer 2000 (Germany)", - "region": "Germany", - "crc": "4E3C9433" - }, - { - "name": "Racing Simulation 2 (Germany)", - "region": "Germany", - "crc": "BA73A7E4" - }, - { - "name": "Racing Simulation 2 (Germany)", - "region": "Germany", - "crc": "D660A56F" - }, - { - "name": "Rakugakids (Europe)", - "region": "Europe", - "crc": "483129AA" - }, - { - "name": "Rakugakids (Europe)", - "region": "Europe", - "crc": "2146409A" - }, - { - "name": "Rakugakids (Japan)", - "region": "Japan", - "crc": "B9E53B06" - }, - { - "name": "Rakugakids (Japan)", - "region": "Japan", - "crc": "4C07C266" - }, - { - "name": "Rally '99 (Japan)", - "region": "Japan", - "crc": "99F25365" - }, - { - "name": "Rally '99 (Japan)", - "region": "Japan", - "crc": "CD45A55A" - }, - { - "name": "Rally Challenge 2000 (USA)", - "region": "USA", - "crc": "3EDEC7B0" - }, - { - "name": "Rally Challenge 2000 (USA)", - "region": "USA", - "crc": "FAF5C3B6" - }, - { - "name": "Rampage - World Tour (Europe)", - "region": "Europe", - "crc": "CDC458EC" - }, - { - "name": "Rampage - World Tour (Europe)", - "region": "Europe", - "crc": "DFBA19D6" - }, - { - "name": "Rampage - World Tour (USA)", - "region": "USA", - "crc": "211119DD" - }, - { - "name": "Rampage - World Tour (USA)", - "region": "USA", - "crc": "6FE4DA23" - }, - { - "name": "Rampage 2 - Universal Tour (Europe)", - "region": "Europe", - "crc": "FA6E097B" - }, - { - "name": "Rampage 2 - Universal Tour (Europe)", - "region": "Europe", - "crc": "0D4654A8" - }, - { - "name": "Rampage 2 - Universal Tour (USA)", - "region": "USA", - "crc": "7614EE0D" - }, - { - "name": "Rampage 2 - Universal Tour (USA)", - "region": "USA", - "crc": "75E8C2AD" - }, - { - "name": "Rat Attack (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "DD4FA798" - }, - { - "name": "Rat Attack (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "C3909721" - }, - { - "name": "Rat Attack! (USA) (En,Fr,De,Es,It,Nl)", - "region": "USA", - "crc": "2315FEA7" - }, - { - "name": "Rat Attack! (USA) (En,Fr,De,Es,It,Nl)", - "region": "USA", - "crc": "78F6FFA2" - }, - { - "name": "Rayman 2 - The Great Escape (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "169A5037" - }, - { - "name": "Rayman 2 - The Great Escape (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "B7E10177" - }, - { - "name": "Rayman 2 - The Great Escape (USA) (En,Fr,De,Es,It)", - "region": "USA", - "crc": "02BB4409" - }, - { - "name": "Rayman 2 - The Great Escape (USA) (En,Fr,De,Es,It)", - "region": "USA", - "crc": "72EE54B2" - }, - { - "name": "Razmoket, Les - La Chasse aux Tresors (France)", - "region": "France", - "crc": "66766469" - }, - { - "name": "Razmoket, Les - La Chasse aux Tresors (France)", - "region": "France", - "crc": "D6C8D961" - }, - { - "name": "Razor Freestyle Scooter (USA)", - "region": "USA", - "crc": "927CE621" - }, - { - "name": "Razor Freestyle Scooter (USA)", - "region": "USA", - "crc": "208350C5" - }, - { - "name": "Re-Volt (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "81D13A11" - }, - { - "name": "Re-Volt (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "194BB8A1" - }, - { - "name": "Re-Volt (USA)", - "region": "USA", - "crc": "FC0C86D0" - }, - { - "name": "Re-Volt (USA)", - "region": "USA", - "crc": "9366C53C" - }, - { - "name": "Ready 2 Rumble Boxing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "A69DF7B3" - }, - { - "name": "Ready 2 Rumble Boxing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "F2FE85AD" - }, - { - "name": "Ready 2 Rumble Boxing (USA)", - "region": "USA", - "crc": "2A554048" - }, - { - "name": "Ready 2 Rumble Boxing (USA)", - "region": "USA", - "crc": "E8D926F3" - }, - { - "name": "Ready 2 Rumble Boxing - Round 2 (USA)", - "region": "USA", - "crc": "052A0E04" - }, - { - "name": "Ready 2 Rumble Boxing - Round 2 (USA)", - "region": "USA", - "crc": "A64FDF28" - }, - { - "name": "Rentiantang Mingxing Daluan Dou (China) (Manual)", - "region": "China", - "crc": "007F3759" - }, - { - "name": "Rentiantang Mingxing Daluan Dou (China) (v5)", - "region": "China", - "crc": "D86E2EDF" - }, - { - "name": "Resident Evil 2 (Europe) (En,Fr)", - "region": "Europe", - "crc": "7C8EE011" - }, - { - "name": "Resident Evil 2 (Europe) (En,Fr)", - "region": "Europe", - "crc": "A9936CEC" - }, - { - "name": "Resident Evil 2 (USA)", - "region": "USA", - "crc": "832EA1DA" - }, - { - "name": "Resident Evil 2 (USA)", - "region": "USA", - "crc": "D890979F" - }, - { - "name": "Resident Evil 2 (USA) (Rev 1)", - "region": "USA", - "crc": "848FBC0D" - }, - { - "name": "Resident Evil 2 (USA) (Rev 1)", - "region": "USA", - "crc": "6A0D64BB" - }, - { - "name": "Road Rash 64 (Europe)", - "region": "Europe", - "crc": "3C664A7B" - }, - { - "name": "Road Rash 64 (Europe)", - "region": "Europe", - "crc": "E3BC7725" - }, - { - "name": "Road Rash 64 (Europe) (Beta) (1999-10-12)", - "region": "Europe", - "crc": "4FB98385" - }, - { - "name": "Road Rash 64 (Europe) (Beta) (1999-10-12)", - "region": "Europe", - "crc": "5556E78F" - }, - { - "name": "Road Rash 64 (USA)", - "region": "USA", - "crc": "600B3988" - }, - { - "name": "Road Rash 64 (USA)", - "region": "USA", - "crc": "2DA44585" - }, - { - "name": "Roadsters (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "997ED5AF" - }, - { - "name": "Roadsters (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "C2D2F16B" - }, - { - "name": "Roadsters (USA) (En,Fr,Es)", - "region": "USA", - "crc": "E4337B92" - }, - { - "name": "Roadsters (USA) (En,Fr,Es)", - "region": "USA", - "crc": "F68D139E" - }, - { - "name": "Robot Poncots 64 - 7tsu no Umi no Caramel (Japan)", - "region": "Japan", - "crc": "3B0F8061" - }, - { - "name": "Robot Poncots 64 - 7tsu no Umi no Caramel (Japan)", - "region": "Japan", - "crc": "49BB7DDE" - }, - { - "name": "Robotech - Crystal Dreams (USA) (Proto 1)", - "region": "USA", - "crc": "BAA8646D" - }, - { - "name": "Robotech - Crystal Dreams (USA) (Proto 1)", - "region": "USA", - "crc": "4D6016AB" - }, - { - "name": "Robotech - Crystal Dreams (USA) (Proto 2)", - "region": "USA", - "crc": "F9A7904E" - }, - { - "name": "Robotech - Crystal Dreams (USA) (Proto 2)", - "region": "USA", - "crc": "8AFC9639" - }, - { - "name": "Robotron 64 (Europe)", - "region": "Europe", - "crc": "23BF4956" - }, - { - "name": "Robotron 64 (Europe)", - "region": "Europe", - "crc": "862ACD20" - }, - { - "name": "Robotron 64 (USA)", - "region": "USA", - "crc": "3C95E84C" - }, - { - "name": "Robotron 64 (USA)", - "region": "USA", - "crc": "18C2AD70" - }, - { - "name": "Rocket - Robot on Wheels (Europe) (Beta) (1999-09-29)", - "region": "Europe", - "crc": "7D766772" - }, - { - "name": "Rocket - Robot on Wheels (Europe) (Beta) (1999-09-29)", - "region": "Europe", - "crc": "89A2ECB3" - }, - { - "name": "Rocket - Robot on Wheels (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "7DE5D20D" - }, - { - "name": "Rocket - Robot on Wheels (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "2960FF8B" - }, - { - "name": "Rocket - Robot on Wheels (USA)", - "region": "USA", - "crc": "E0399F23" - }, - { - "name": "Rocket - Robot on Wheels (USA)", - "region": "USA", - "crc": "B0F36D79" - }, - { - "name": "Rockman Dash - Hagane no Boukenshin (Japan)", - "region": "Japan", - "crc": "61EAEE83" - }, - { - "name": "Rockman Dash - Hagane no Boukenshin (Japan)", - "region": "Japan", - "crc": "E1919C66" - }, - { - "name": "Ronaldinho's Soccer 64", - "region": "Brazil", - "crc": "C296ECA1" - }, - { - "name": "Ronaldinho's Soccer 64", - "region": "Brazil", - "crc": "19380E04" - }, - { - "name": "Rugrats - Die grosse Schatzsuche (Germany)", - "region": "Germany", - "crc": "23AED3A2" - }, - { - "name": "Rugrats - Die grosse Schatzsuche (Germany)", - "region": "Germany", - "crc": "E1D7919F" - }, - { - "name": "Rugrats - Scavenger Hunt (USA)", - "region": "USA", - "crc": "A87FAF82" - }, - { - "name": "Rugrats - Scavenger Hunt (USA)", - "region": "USA", - "crc": "EA79AD0A" - }, - { - "name": "Rugrats - Treasure Hunt (Europe)", - "region": "Europe", - "crc": "3338B7C8" - }, - { - "name": "Rugrats - Treasure Hunt (Europe)", - "region": "Europe", - "crc": "65EDE901" - }, - { - "name": "Rugrats in Paris - The Movie (Europe)", - "region": "Europe", - "crc": "CD74B07E" - }, - { - "name": "Rugrats in Paris - The Movie (Europe)", - "region": "Europe", - "crc": "17BD7518" - }, - { - "name": "Rugrats in Paris - The Movie (USA)", - "region": "USA", - "crc": "A9CC2419" - }, - { - "name": "Rugrats in Paris - The Movie (USA)", - "region": "USA", - "crc": "8F09AF37" - }, - { - "name": "Rush 2 - Extreme Racing USA (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "30F21F89" - }, - { - "name": "Rush 2 - Extreme Racing USA (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "9B364DF7" - }, - { - "name": "Rush 2 - Extreme Racing USA (USA)", - "region": "USA", - "crc": "9EB14EA8" - }, - { - "name": "Rush 2 - Extreme Racing USA (USA)", - "region": "USA", - "crc": "5412916B" - }, - { - "name": "S.C.A.R.S. (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "4E37B6F2" - }, - { - "name": "S.C.A.R.S. (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "DA47D3C9" - }, - { - "name": "S.C.A.R.S. (USA)", - "region": "USA", - "crc": "22916735" - }, - { - "name": "S.C.A.R.S. (USA)", - "region": "USA", - "crc": "6594B593" - }, - { - "name": "SD Hiryuu no Ken Densetsu (Japan)", - "region": "Japan", - "crc": "CC083E34" - }, - { - "name": "SD Hiryuu no Ken Densetsu (Japan)", - "region": "Japan", - "crc": "ADF81B90" - }, - { - "name": "Saierda Chuanshuo - Shiguang zhi Di (China) (Proto)", - "region": "China", - "crc": "E8E3575F" - }, - { - "name": "Saierda Chuanshuo - Shiguang zhi Di (China) (v2) (Manual)", - "region": "China", - "crc": "F8D9250D" - }, - { - "name": "Saierda Chuanshuo - Shiguang zhi Di (China) (v4)", - "region": "China", - "crc": "F45C5532" - }, - { - "name": "Saierda Chuanshuo - Shiguang zhi Di (China) (v4) (Manual)", - "region": "China", - "crc": "07E6C6E6" - }, - { - "name": "Saikyou Habu Shougi (Japan)", - "region": "Japan", - "crc": "01794D62" - }, - { - "name": "Saikyou Habu Shougi (Japan)", - "region": "Japan", - "crc": "E8D0810B" - }, - { - "name": "San Francisco Rush - Extreme Racing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "E064962A" - }, - { - "name": "San Francisco Rush - Extreme Racing (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "78CD03C9" - }, - { - "name": "San Francisco Rush - Extreme Racing (USA) (En,Fr,De)", - "region": "USA", - "crc": "3E20070B" - }, - { - "name": "San Francisco Rush - Extreme Racing (USA) (En,Fr,De)", - "region": "USA", - "crc": "32E250BA" - }, - { - "name": "San Francisco Rush - Extreme Racing (USA) (En,Fr,De) (Rev 1)", - "region": "USA", - "crc": "886C5753" - }, - { - "name": "San Francisco Rush - Extreme Racing (USA) (En,Fr,De) (Rev 1)", - "region": "USA", - "crc": "BA05B83F" - }, - { - "name": "San Francisco Rush 2049 (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "E63B86C5" - }, - { - "name": "San Francisco Rush 2049 (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "DF85FDC3" - }, - { - "name": "San Francisco Rush 2049 (USA)", - "region": "USA", - "crc": "10941439" - }, - { - "name": "San Francisco Rush 2049 (USA)", - "region": "USA", - "crc": "1A03B9DE" - }, - { - "name": "Scooby-Doo! - Classic Creep Capers (Europe)", - "region": "Europe", - "crc": "0D737E6F" - }, - { - "name": "Scooby-Doo! - Classic Creep Capers (Europe)", - "region": "Europe", - "crc": "81CDD595" - }, - { - "name": "Scooby-Doo! - Classic Creep Capers (USA)", - "region": "USA", - "crc": "39068228" - }, - { - "name": "Scooby-Doo! - Classic Creep Capers (USA)", - "region": "USA", - "crc": "E3A2F1E7" - }, - { - "name": "Scooby-Doo! - Classic Creep Capers (USA) (Rev 1)", - "region": "USA", - "crc": "CDF86F5E" - }, - { - "name": "Scooby-Doo! - Classic Creep Capers (USA) (Rev 1)", - "region": "USA", - "crc": "91CC1ED6" - }, - { - "name": "Sesame Street - Elmo's Letter Adventure (USA)", - "region": "USA", - "crc": "92C3BA6F" - }, - { - "name": "Sesame Street - Elmo's Letter Adventure (USA)", - "region": "USA", - "crc": "710011FB" - }, - { - "name": "Sesame Street - Elmo's Number Journey (USA)", - "region": "USA", - "crc": "EA3B92D8" - }, - { - "name": "Sesame Street - Elmo's Number Journey (USA)", - "region": "USA", - "crc": "B7AD0F05" - }, - { - "name": "Shadow Man", - "region": "Brazil", - "crc": "61E6642C" - }, - { - "name": "Shadow Man", - "region": "Brazil", - "crc": "679487B0" - }, - { - "name": "Shadow Man (Europe) (En,Es,It)", - "region": "Europe", - "crc": "8D230306" - }, - { - "name": "Shadow Man (Europe) (En,Es,It)", - "region": "Europe", - "crc": "0CD66C1F" - }, - { - "name": "Shadow Man (France)", - "region": "France", - "crc": "6812D3A7" - }, - { - "name": "Shadow Man (France)", - "region": "France", - "crc": "CB221562" - }, - { - "name": "Shadow Man (Germany)", - "region": "Germany", - "crc": "EAF6ADD1" - }, - { - "name": "Shadow Man (Germany)", - "region": "Germany", - "crc": "1F171A3F" - }, - { - "name": "Shadow Man (USA)", - "region": "USA", - "crc": "5E20CC63" - }, - { - "name": "Shadow Man (USA)", - "region": "USA", - "crc": "47E05128" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Europe)", - "region": "Europe", - "crc": "FF7D7DF0" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Europe)", - "region": "Europe", - "crc": "ABB08BC3" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Europe) (Es,It)", - "region": "Europe", - "crc": "87F00472" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Europe) (Es,It)", - "region": "Europe", - "crc": "D49B5065" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Europe) (Fr,De,Nl)", - "region": "Europe", - "crc": "EEDC0BEA" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Europe) (Fr,De,Nl)", - "region": "Europe", - "crc": "DC3ED03E" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Japan)", - "region": "Japan", - "crc": "9F74A58C" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (Japan)", - "region": "Japan", - "crc": "F2665AFD" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (USA) (En,Es)", - "region": "USA", - "crc": "69983CC3" - }, - { - "name": "Shadowgate 64 - Trials of the Four Towers (USA) (En,Es)", - "region": "USA", - "crc": "1E9986E5" - }, - { - "name": "Shenyou Maliou (China) (v4)", - "region": "China", - "crc": "08B48CA1" - }, - { - "name": "Shenyou Maliou (China) (v6) (Manual)", - "region": "China", - "crc": "A5B65A64" - }, - { - "name": "Shin Nihon Pro Wrestling Toukon Road - Brave Spirits (Japan)", - "region": "Japan", - "crc": "E892ED43" - }, - { - "name": "Shin Nihon Pro Wrestling Toukon Road - Brave Spirits (Japan)", - "region": "Japan", - "crc": "62C99DE2" - }, - { - "name": "Shin Nihon Pro Wrestling Toukon Road 2 - The Next Generation (Japan)", - "region": "Japan", - "crc": "DEAC787F" - }, - { - "name": "Shin Nihon Pro Wrestling Toukon Road 2 - The Next Generation (Japan)", - "region": "Japan", - "crc": "BE0FC7E8" - }, - { - "name": "Shuishang Motuo (China) (v2) (Manual)", - "region": "China", - "crc": "B7547F54" - }, - { - "name": "Shuishang Motuo (China) (v4)", - "region": "China", - "crc": "30F45E87" - }, - { - "name": "Shuishang Motuo (China) (v4) (Manual)", - "region": "China", - "crc": "3EF9368E" - }, - { - "name": "SimCity 2000 (Japan)", - "region": "Japan", - "crc": "57767E45" - }, - { - "name": "SimCity 2000 (Japan)", - "region": "Japan", - "crc": "F7A96B81" - }, - { - "name": "Snobo Kids (Japan)", - "region": "Japan", - "crc": "213BF381" - }, - { - "name": "Snobo Kids (Japan)", - "region": "Japan", - "crc": "9B691DF4" - }, - { - "name": "Snow Speeder (Japan)", - "region": "Japan", - "crc": "30EA3FD7" - }, - { - "name": "Snow Speeder (Japan)", - "region": "Japan", - "crc": "5C98F900" - }, - { - "name": "Snowboard Kids (Europe, Australia)", - "crc": "5619A70D" - }, - { - "name": "Snowboard Kids (Europe, Australia)", - "crc": "4F2415F3" - }, - { - "name": "Snowboard Kids (USA)", - "region": "USA", - "crc": "020FB906" - }, - { - "name": "Snowboard Kids (USA)", - "region": "USA", - "crc": "540C190B" - }, - { - "name": "Snowboard Kids 2 (Australia)", - "region": "Australia", - "crc": "3A0B6214" - }, - { - "name": "Snowboard Kids 2 (Australia)", - "region": "Australia", - "crc": "43AFB227" - }, - { - "name": "Snowboard Kids 2 (USA)", - "region": "USA", - "crc": "D0DC8A8E" - }, - { - "name": "Snowboard Kids 2 (USA)", - "region": "USA", - "crc": "CA84AACE" - }, - { - "name": "Sonic Wings Assault (Japan)", - "region": "Japan", - "crc": "FC73FB79" - }, - { - "name": "Sonic Wings Assault (Japan)", - "region": "Japan", - "crc": "1D8F9D5E" - }, - { - "name": "South Park", - "region": "Brazil", - "crc": "235CAEC6" - }, - { - "name": "South Park", - "region": "Brazil", - "crc": "40AC4211" - }, - { - "name": "South Park (Europe) (En,Fr,Es)", - "region": "Europe", - "crc": "B2C3E123" - }, - { - "name": "South Park (Europe) (En,Fr,Es)", - "region": "Europe", - "crc": "ACD74F7F" - }, - { - "name": "South Park (Germany)", - "region": "Germany", - "crc": "5711E197" - }, - { - "name": "South Park (Germany)", - "region": "Germany", - "crc": "3445C078" - }, - { - "name": "South Park (USA)", - "region": "USA", - "crc": "7D666B9E" - }, - { - "name": "South Park (USA)", - "region": "USA", - "crc": "FAAE9AA3" - }, - { - "name": "South Park - Chef's Luv Shack (Europe)", - "region": "Europe", - "crc": "AC1628EB" - }, - { - "name": "South Park - Chef's Luv Shack (Europe)", - "region": "Europe", - "crc": "ED36FA49" - }, - { - "name": "South Park - Chef's Luv Shack (USA)", - "region": "USA", - "crc": "6B6B1D09" - }, - { - "name": "South Park - Chef's Luv Shack (USA)", - "region": "USA", - "crc": "67D38382" - }, - { - "name": "South Park Rally (Europe)", - "region": "Europe", - "crc": "296E3525" - }, - { - "name": "South Park Rally (Europe)", - "region": "Europe", - "crc": "DE023E0A" - }, - { - "name": "South Park Rally (Europe) (Beta)", - "region": "Europe", - "crc": "1C535592" - }, - { - "name": "South Park Rally (Europe) (Beta)", - "region": "Europe", - "crc": "18382025" - }, - { - "name": "South Park Rally (USA)", - "region": "USA", - "crc": "CCDD322A" - }, - { - "name": "South Park Rally (USA)", - "region": "USA", - "crc": "2C1F0859" - }, - { - "name": "Space Dynamites (Japan)", - "region": "Japan", - "crc": "8CB4B948" - }, - { - "name": "Space Dynamites (Japan)", - "region": "Japan", - "crc": "803F78AC" - }, - { - "name": "Space Invaders (USA)", - "region": "USA", - "crc": "60F7FF8E" - }, - { - "name": "Space Invaders (USA)", - "region": "USA", - "crc": "62E6DEFF" - }, - { - "name": "SpaceStation Silicon Valley (Europe) (En,Fr,De,Es,It,Nl,Pt)", - "region": "Europe", - "crc": "63042E36" - }, - { - "name": "SpaceStation Silicon Valley (Europe) (En,Fr,De,Es,It,Nl,Pt)", - "region": "Europe", - "crc": "6148B598" - }, - { - "name": "SpaceStation Silicon Valley (Japan) (Proto)", - "region": "Japan", - "crc": "DCEC9F8A" - }, - { - "name": "SpaceStation Silicon Valley (Japan) (Proto)", - "region": "Japan", - "crc": "7ABF7F2C" - }, - { - "name": "SpaceStation Silicon Valley (USA)", - "region": "USA", - "crc": "A606E8AE" - }, - { - "name": "SpaceStation Silicon Valley (USA)", - "region": "USA", - "crc": "76DB1DA1" - }, - { - "name": "SpaceStation Silicon Valley (USA) (Rev 1)", - "region": "USA", - "crc": "E32D9E7B" - }, - { - "name": "SpaceStation Silicon Valley (USA) (Rev 1)", - "region": "USA", - "crc": "0BCF3F2E" - }, - { - "name": "Spider-Man (USA)", - "region": "USA", - "crc": "696CC2A4" - }, - { - "name": "Spider-Man (USA)", - "region": "USA", - "crc": "A2866A6E" - }, - { - "name": "Star Fox 64 (Japan)", - "region": "Japan", - "crc": "411142A7" - }, - { - "name": "Star Fox 64 (Japan)", - "region": "Japan", - "crc": "879EDB89" - }, - { - "name": "Star Fox 64 (Japan) (Rev 1) (Wii Virtual Console)", - "region": "Japan", - "crc": "6508D4AD" - }, - { - "name": "Star Fox 64 (Japan) (Rev 1) (Wii Virtual Console)", - "region": "Japan", - "crc": "58BE093E" - }, - { - "name": "Star Fox 64 (USA)", - "region": "USA", - "crc": "B1FCAA9C" - }, - { - "name": "Star Fox 64 (USA)", - "region": "USA", - "crc": "363E7EE8" - }, - { - "name": "Star Fox 64 (USA) (Rev 1)", - "region": "USA", - "crc": "B1B5FC46" - }, - { - "name": "Star Fox 64 (USA) (Rev 1)", - "region": "USA", - "crc": "D71902B0" - }, - { - "name": "Star Soldier - Vanishing Earth (Japan)", - "region": "Japan", - "crc": "7EE5F51D" - }, - { - "name": "Star Soldier - Vanishing Earth (Japan)", - "region": "Japan", - "crc": "F51D4C7C" - }, - { - "name": "Star Soldier - Vanishing Earth (USA)", - "region": "USA", - "crc": "EA650DEF" - }, - { - "name": "Star Soldier - Vanishing Earth (USA)", - "region": "USA", - "crc": "052945E7" - }, - { - "name": "Star Twins (Japan)", - "region": "Japan", - "crc": "964506CE" - }, - { - "name": "Star Twins (Japan)", - "region": "Japan", - "crc": "C7BFB542" - }, - { - "name": "Star Wars - Rogue Squadron (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "6289645F" - }, - { - "name": "Star Wars - Rogue Squadron (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "13CC4CA4" - }, - { - "name": "Star Wars - Rogue Squadron (Europe) (En,Fr,De) (Rev 1)", - "region": "Europe", - "crc": "C88E5638" - }, - { - "name": "Star Wars - Rogue Squadron (Europe) (En,Fr,De) (Rev 1)", - "region": "Europe", - "crc": "A6E336CE" - }, - { - "name": "Star Wars - Rogue Squadron (USA)", - "region": "USA", - "crc": "83C225CC" - }, - { - "name": "Star Wars - Rogue Squadron (USA)", - "region": "USA", - "crc": "2D6FA165" - }, - { - "name": "Star Wars - Rogue Squadron (USA) (Rev 1)", - "region": "USA", - "crc": "65A8FBA0" - }, - { - "name": "Star Wars - Rogue Squadron (USA) (Rev 1)", - "region": "USA", - "crc": "4A479574" - }, - { - "name": "Star Wars - Shadows of the Empire (Europe)", - "region": "Europe", - "crc": "F0A191BF" - }, - { - "name": "Star Wars - Shadows of the Empire (Europe)", - "region": "Europe", - "crc": "675D15B2" - }, - { - "name": "Star Wars - Shadows of the Empire (USA)", - "region": "USA", - "crc": "3C0837B3" - }, - { - "name": "Star Wars - Shadows of the Empire (USA)", - "region": "USA", - "crc": "02FAE534" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Aftermarket) (Unl)", - "region": "USA", - "crc": "776A069B" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Aftermarket) (Unl)", - "region": "USA", - "crc": "A9693E97" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Beta) (1996-10-15)", - "region": "USA", - "crc": "28368603" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Beta) (1996-10-15)", - "region": "USA", - "crc": "448228CE" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Rev 1)", - "region": "USA", - "crc": "B0540688" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Rev 1)", - "region": "USA", - "crc": "B0DBB100" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Rev 2)", - "region": "USA", - "crc": "E8727549" - }, - { - "name": "Star Wars - Shadows of the Empire (USA) (Rev 2)", - "region": "USA", - "crc": "F4E546D1" - }, - { - "name": "Star Wars - Shutsugeki! Rogue Chuutai (Japan)", - "region": "Japan", - "crc": "EE7643B6" - }, - { - "name": "Star Wars - Shutsugeki! Rogue Chuutai (Japan)", - "region": "Japan", - "crc": "DE90ADE4" - }, - { - "name": "Star Wars - Teikoku no Kage (Japan)", - "region": "Japan", - "crc": "7CE71426" - }, - { - "name": "Star Wars - Teikoku no Kage (Japan)", - "region": "Japan", - "crc": "54F66BB9" - }, - { - "name": "Star Wars Episode I - Battle for Naboo (Europe)", - "region": "Europe", - "crc": "029104FD" - }, - { - "name": "Star Wars Episode I - Battle for Naboo (Europe)", - "region": "Europe", - "crc": "F6B20484" - }, - { - "name": "Star Wars Episode I - Battle for Naboo (USA)", - "region": "USA", - "crc": "99DEE3C0" - }, - { - "name": "Star Wars Episode I - Battle for Naboo (USA)", - "region": "USA", - "crc": "97EBBEA8" - }, - { - "name": "Star Wars Episode I - Racer (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "E0F46629" - }, - { - "name": "Star Wars Episode I - Racer (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "EDE47E8A" - }, - { - "name": "Star Wars Episode I - Racer (Japan)", - "region": "Japan", - "crc": "97C155C5" - }, - { - "name": "Star Wars Episode I - Racer (Japan)", - "region": "Japan", - "crc": "794412BF" - }, - { - "name": "Star Wars Episode I - Racer (USA)", - "region": "USA", - "crc": "C53C1035" - }, - { - "name": "Star Wars Episode I - Racer (USA)", - "region": "USA", - "crc": "494C667A" - }, - { - "name": "Star Wars Episode I - Racer (USA) (Aftermarket) (Unl)", - "region": "USA", - "crc": "FB401A2A" - }, - { - "name": "Star Wars Episode I - Racer (USA) (Aftermarket) (Unl)", - "region": "USA", - "crc": "EE6A1F8F" - }, - { - "name": "StarCraft 64 (Australia)", - "region": "Australia", - "crc": "2639DAE2" - }, - { - "name": "StarCraft 64 (Australia)", - "region": "Australia", - "crc": "D42FCFE1" - }, - { - "name": "StarCraft 64 (Germany) (Proto)", - "region": "Germany", - "crc": "2B1C9FE4" - }, - { - "name": "StarCraft 64 (Germany) (Proto)", - "region": "Germany", - "crc": "35F8C03C" - }, - { - "name": "StarCraft 64 (USA)", - "region": "USA", - "crc": "4E4C7EC9" - }, - { - "name": "StarCraft 64 (USA)", - "region": "USA", - "crc": "14C77369" - }, - { - "name": "StarCraft 64 (USA) (Beta)", - "region": "USA", - "crc": "B0E1654F" - }, - { - "name": "StarCraft 64 (USA) (Beta)", - "region": "USA", - "crc": "0DAB69E2" - }, - { - "name": "Starshot - Space Circus Fever (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "056D2218" - }, - { - "name": "Starshot - Space Circus Fever (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "171B80E1" - }, - { - "name": "Starshot - Space Circus Fever (USA) (En,Fr,Es)", - "region": "USA", - "crc": "7720E5F3" - }, - { - "name": "Starshot - Space Circus Fever (USA) (En,Fr,Es)", - "region": "USA", - "crc": "64370749" - }, - { - "name": "Stunt Racer 64 (USA)", - "region": "USA", - "crc": "3438B1AF" - }, - { - "name": "Stunt Racer 64 (USA)", - "region": "USA", - "crc": "F60613DE" - }, - { - "name": "Super B-Daman - Battle Phoenix 64 (Japan)", - "region": "Japan", - "crc": "5006DC88" - }, - { - "name": "Super B-Daman - Battle Phoenix 64 (Japan)", - "region": "Japan", - "crc": "0BE4F736" - }, - { - "name": "Super Bowling (Japan)", - "region": "Japan", - "crc": "BA2D8B2E" - }, - { - "name": "Super Bowling (Japan)", - "region": "Japan", - "crc": "BE01964B" - }, - { - "name": "Super Bowling (USA)", - "region": "USA", - "crc": "F6CCD04A" - }, - { - "name": "Super Bowling (USA)", - "region": "USA", - "crc": "E651CBAA" - }, - { - "name": "Super Mario 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "03048DE6" - }, - { - "name": "Super Mario 64 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "11FB579B" - }, - { - "name": "Super Mario 64 (Japan)", - "region": "Japan", - "crc": "DD801954" - }, - { - "name": "Super Mario 64 (Japan)", - "region": "Japan", - "crc": "5CF7952A" - }, - { - "name": "Super Mario 64 (Japan) (Rev 3) (Shindou Edition)", - "region": "Japan", - "crc": "A1E15117" - }, - { - "name": "Super Mario 64 (Japan) (Rev 3) (Shindou Edition)", - "region": "Japan", - "crc": "BC9FF5F2" - }, - { - "name": "Super Mario 64 (USA)", - "region": "USA", - "crc": "3CE60709" - }, - { - "name": "Super Mario 64 (USA)", - "region": "USA", - "crc": "42C43204" - }, - { - "name": "Super Robot Spirits (Japan)", - "region": "Japan", - "crc": "8C9216C1" - }, - { - "name": "Super Robot Spirits (Japan)", - "region": "Japan", - "crc": "DAB52B89" - }, - { - "name": "Super Robot Taisen 64 (Japan)", - "region": "Japan", - "crc": "85DF2771" - }, - { - "name": "Super Robot Taisen 64 (Japan)", - "region": "Japan", - "crc": "70FBC3B8" - }, - { - "name": "Super Smash Bros. (Australia)", - "region": "Australia", - "crc": "E96779FA" - }, - { - "name": "Super Smash Bros. (Australia)", - "region": "Australia", - "crc": "B2F04090" - }, - { - "name": "Super Smash Bros. (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "45A91CB1" - }, - { - "name": "Super Smash Bros. (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "7976248C" - }, - { - "name": "Super Smash Bros. (USA)", - "region": "USA", - "crc": "EB97929E" - }, - { - "name": "Super Smash Bros. (USA)", - "region": "USA", - "crc": "DD2DF6D9" - }, - { - "name": "Super Speed Race 64 (Japan)", - "region": "Japan", - "crc": "0F879A70" - }, - { - "name": "Super Speed Race 64 (Japan)", - "region": "Japan", - "crc": "D96B76C3" - }, - { - "name": "Supercross 2000 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "CB5482EC" - }, - { - "name": "Supercross 2000 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "0A6377EE" - }, - { - "name": "Supercross 2000 (USA)", - "region": "USA", - "crc": "094E2A48" - }, - { - "name": "Supercross 2000 (USA)", - "region": "USA", - "crc": "5A8A721D" - }, - { - "name": "Superman (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "BCA4FF8C" - }, - { - "name": "Superman (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "9CEF1753" - }, - { - "name": "Superman (USA) (Beta) (1998-09-06)", - "region": "USA", - "crc": "A0897826" - }, - { - "name": "Superman (USA) (Beta) (1998-09-06)", - "region": "USA", - "crc": "FEA2232B" - }, - { - "name": "Superman - The New Superman Aventures (USA) (En,Fr,Es)", - "region": "USA", - "crc": "437E3677" - }, - { - "name": "Superman - The New Superman Aventures (USA) (En,Fr,Es)", - "region": "USA", - "crc": "F10D66F0" - }, - { - "name": "Susume! Taisen Puzzle Dama - Toukon! Marutama Chou (Japan)", - "region": "Japan", - "crc": "4CD21372" - }, - { - "name": "Susume! Taisen Puzzle Dama - Toukon! Marutama Chou (Japan)", - "region": "Japan", - "crc": "DC83BCD3" - }, - { - "name": "Sydney 2000 (Europe) (Proto)", - "region": "Europe", - "crc": "DB7B31B9" - }, - { - "name": "Sydney 2000 (Europe) (Proto)", - "region": "Europe", - "crc": "CB366589" - }, - { - "name": "Sydney 2000 (USA) (Proto)", - "region": "USA", - "crc": "5B6673AA" - }, - { - "name": "Sydney 2000 (USA) (Proto)", - "region": "USA", - "crc": "9B7B6F51" - }, - { - "name": "TG Rally 2 (Europe)", - "region": "Europe", - "crc": "135C8EB0" - }, - { - "name": "TG Rally 2 (Europe)", - "region": "Europe", - "crc": "B5E8A1DF" - }, - { - "name": "Tamiya Racing 64 (USA) (Proto)", - "region": "USA", - "crc": "1B4C53FA" - }, - { - "name": "Tamiya Racing 64 (USA) (Proto)", - "region": "USA", - "crc": "7EA56078" - }, - { - "name": "Tarzan (Europe)", - "region": "Europe", - "crc": "7737ED9E" - }, - { - "name": "Tarzan (Europe)", - "region": "Europe", - "crc": "5FA14035" - }, - { - "name": "Tarzan (France)", - "region": "France", - "crc": "99C7649D" - }, - { - "name": "Tarzan (France)", - "region": "France", - "crc": "C1176831" - }, - { - "name": "Tarzan (Germany)", - "region": "Germany", - "crc": "0B0954C5" - }, - { - "name": "Tarzan (Germany)", - "region": "Germany", - "crc": "67DBEC0B" - }, - { - "name": "Tarzan (USA)", - "region": "USA", - "crc": "C38CA641" - }, - { - "name": "Tarzan (USA)", - "region": "USA", - "crc": "2B59490B" - }, - { - "name": "Taz Express (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "0712C306" - }, - { - "name": "Taz Express (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "85382541" - }, - { - "name": "Taz Express (USA) (Proto) (2000-05-15)", - "region": "USA", - "crc": "DDE7CFBD" - }, - { - "name": "Taz Express (USA) (Proto) (2000-05-15)", - "region": "USA", - "crc": "9E9B2ED5" - }, - { - "name": "Telefoot Soccer 2000 (France)", - "region": "France", - "crc": "7BD20931" - }, - { - "name": "Telefoot Soccer 2000 (France)", - "region": "France", - "crc": "FAD893EA" - }, - { - "name": "Tetris 64 (Japan) (En)", - "region": "Japan", - "crc": "F128CD17" - }, - { - "name": "Tetris 64 (Japan) (En)", - "region": "Japan", - "crc": "08B4CA1B" - }, - { - "name": "Tetrisphere (Europe)", - "region": "Europe", - "crc": "7CB31B0F" - }, - { - "name": "Tetrisphere (Europe)", - "region": "Europe", - "crc": "2E953131" - }, - { - "name": "Tetrisphere (USA)", - "region": "USA", - "crc": "70A3A5CE" - }, - { - "name": "Tetrisphere (USA)", - "region": "USA", - "crc": "5DB18578" - }, - { - "name": "Tigger's Honey Hunt (Europe) (En,Fr,De,Es,It,Nl,Da)", - "region": "Europe", - "crc": "D82D5736" - }, - { - "name": "Tigger's Honey Hunt (Europe) (En,Fr,De,Es,It,Nl,Da)", - "region": "Europe", - "crc": "CF7AA5EF" - }, - { - "name": "Tigger's Honey Hunt (USA)", - "region": "USA", - "crc": "68C2AC8F" - }, - { - "name": "Tigger's Honey Hunt (USA)", - "region": "USA", - "crc": "79F8BF3B" - }, - { - "name": "Tom Clancy's Rainbow Six (Europe)", - "region": "Europe", - "crc": "4B71E083" - }, - { - "name": "Tom Clancy's Rainbow Six (Europe)", - "region": "Europe", - "crc": "B430BA47" - }, - { - "name": "Tom Clancy's Rainbow Six (France)", - "region": "France", - "crc": "BBF7B6A8" - }, - { - "name": "Tom Clancy's Rainbow Six (France)", - "region": "France", - "crc": "31BC7B49" - }, - { - "name": "Tom Clancy's Rainbow Six (Germany)", - "region": "Germany", - "crc": "5D73E788" - }, - { - "name": "Tom Clancy's Rainbow Six (Germany)", - "region": "Germany", - "crc": "1F872C4A" - }, - { - "name": "Tom Clancy's Rainbow Six (USA)", - "region": "USA", - "crc": "53B0CC13" - }, - { - "name": "Tom Clancy's Rainbow Six (USA)", - "region": "USA", - "crc": "67F3D120" - }, - { - "name": "Tom and Jerry in Fists of Furry (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "9EA8A3B8" - }, - { - "name": "Tom and Jerry in Fists of Furry (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "B030A574" - }, - { - "name": "Tom and Jerry in Fists of Furry (USA)", - "region": "USA", - "crc": "6D685B83" - }, - { - "name": "Tom and Jerry in Fists of Furry (USA)", - "region": "USA", - "crc": "F0BFC6AB" - }, - { - "name": "Tommy Thunder (USA) (Proto) (1997-10-01)", - "region": "USA", - "crc": "66734854" - }, - { - "name": "Tommy Thunder (USA) (Proto) (1997-10-01)", - "region": "USA", - "crc": "E5BC76D8" - }, - { - "name": "Tonic Trouble (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "B4322403" - }, - { - "name": "Tonic Trouble (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "A10B0009" - }, - { - "name": "Tonic Trouble (USA) (Rev 1)", - "region": "USA", - "crc": "1C04BA12" - }, - { - "name": "Tonic Trouble (USA) (Rev 1)", - "region": "USA", - "crc": "0ECD289A" - }, - { - "name": "Tony Hawk's Pro Skater (USA)", - "region": "USA", - "crc": "F5C1B64F" - }, - { - "name": "Tony Hawk's Pro Skater (USA)", - "region": "USA", - "crc": "EE2C9008" - }, - { - "name": "Tony Hawk's Pro Skater (USA) (Beta)", - "region": "USA", - "crc": "F50204DA" - }, - { - "name": "Tony Hawk's Pro Skater (USA) (Beta)", - "region": "USA", - "crc": "0E3BD2EC" - }, - { - "name": "Tony Hawk's Pro Skater (USA) (Rev 1)", - "region": "USA", - "crc": "6182A092" - }, - { - "name": "Tony Hawk's Pro Skater (USA) (Rev 1)", - "region": "USA", - "crc": "1BE278C1" - }, - { - "name": "Tony Hawk's Pro Skater 2 (Europe)", - "region": "Europe", - "crc": "A1207132" - }, - { - "name": "Tony Hawk's Pro Skater 2 (Europe)", - "region": "Europe", - "crc": "DC703B1E" - }, - { - "name": "Tony Hawk's Pro Skater 2 (USA)", - "region": "USA", - "crc": "80AA83F3" - }, - { - "name": "Tony Hawk's Pro Skater 2 (USA)", - "region": "USA", - "crc": "6DF2D58E" - }, - { - "name": "Tony Hawk's Pro Skater 2 (USA) (Beta) (2001-04-20)", - "region": "USA", - "crc": "98D0D335" - }, - { - "name": "Tony Hawk's Pro Skater 2 (USA) (Beta) (2001-04-20)", - "region": "USA", - "crc": "0B00F2A0" - }, - { - "name": "Tony Hawk's Pro Skater 2 (USA) (v012) (Beta) (2001-06-04)", - "region": "USA", - "crc": "BCF178C5" - }, - { - "name": "Tony Hawk's Pro Skater 2 (USA) (v012) (Beta) (2001-06-04)", - "region": "USA", - "crc": "064E41B4" - }, - { - "name": "Tony Hawk's Pro Skater 3 (USA)", - "region": "USA", - "crc": "62A8CE7D" - }, - { - "name": "Tony Hawk's Pro Skater 3 (USA)", - "region": "USA", - "crc": "ED73272E" - }, - { - "name": "Tony Hawk's Skateboarding (Europe)", - "region": "Europe", - "crc": "39E4F766" - }, - { - "name": "Tony Hawk's Skateboarding (Europe)", - "region": "Europe", - "crc": "93A26129" - }, - { - "name": "Toon Panic (Japan) (Proto)", - "region": "Japan", - "crc": "CF396C4E" - }, - { - "name": "Toon Panic (Japan) (Proto)", - "region": "Japan", - "crc": "55C88F04" - }, - { - "name": "Top Gear Hyper Bike (Europe)", - "region": "Europe", - "crc": "BAE57EA7" - }, - { - "name": "Top Gear Hyper Bike (Europe)", - "region": "Europe", - "crc": "DC687AD1" - }, - { - "name": "Top Gear Hyper Bike (Japan)", - "region": "Japan", - "crc": "09B2CDA1" - }, - { - "name": "Top Gear Hyper Bike (Japan)", - "region": "Japan", - "crc": "D9AF869C" - }, - { - "name": "Top Gear Hyper-Bike (USA)", - "region": "USA", - "crc": "6EEBC26A" - }, - { - "name": "Top Gear Hyper-Bike (USA)", - "region": "USA", - "crc": "3E9CE2AF" - }, - { - "name": "Top Gear Hyper-Bike (USA) (Beta)", - "region": "USA", - "crc": "00C0278A" - }, - { - "name": "Top Gear Hyper-Bike (USA) (Beta)", - "region": "USA", - "crc": "DD714846" - }, - { - "name": "Top Gear Overdrive (Europe)", - "region": "Europe", - "crc": "0CC70580" - }, - { - "name": "Top Gear Overdrive (Europe)", - "region": "Europe", - "crc": "52D39097" - }, - { - "name": "Top Gear Overdrive (Japan)", - "region": "Japan", - "crc": "81AAFC2B" - }, - { - "name": "Top Gear Overdrive (Japan)", - "region": "Japan", - "crc": "E680B138" - }, - { - "name": "Top Gear Overdrive (USA)", - "region": "USA", - "crc": "F3E0FF21" - }, - { - "name": "Top Gear Overdrive (USA)", - "region": "USA", - "crc": "B5B23053" - }, - { - "name": "Top Gear Rally (Asia) (En)", - "region": "Asia", - "crc": "C4BC4DF8" - }, - { - "name": "Top Gear Rally (Asia) (En)", - "region": "Asia", - "crc": "C85372CF" - }, - { - "name": "Top Gear Rally (Europe)", - "region": "Europe", - "crc": "40B3BB21" - }, - { - "name": "Top Gear Rally (Europe)", - "region": "Europe", - "crc": "83B04584" - }, - { - "name": "Top Gear Rally (Japan)", - "region": "Japan", - "crc": "C6707CD6" - }, - { - "name": "Top Gear Rally (Japan)", - "region": "Japan", - "crc": "D8E793F5" - }, - { - "name": "Top Gear Rally (USA)", - "region": "USA", - "crc": "137287F5" - }, - { - "name": "Top Gear Rally (USA)", - "region": "USA", - "crc": "A04F1CC0" - }, - { - "name": "Top Gear Rally 2 (Europe)", - "region": "Europe", - "crc": "CB294D39" - }, - { - "name": "Top Gear Rally 2 (Europe)", - "region": "Europe", - "crc": "88C8A6EA" - }, - { - "name": "Top Gear Rally 2 (Europe) (Beta) (1999-08-31)", - "region": "Europe", - "crc": "3C77C5D6" - }, - { - "name": "Top Gear Rally 2 (Europe) (Beta) (1999-08-31)", - "region": "Europe", - "crc": "3985D3F3" - }, - { - "name": "Top Gear Rally 2 (Japan)", - "region": "Japan", - "crc": "AA136E07" - }, - { - "name": "Top Gear Rally 2 (Japan)", - "region": "Japan", - "crc": "CCD50A05" - }, - { - "name": "Top Gear Rally 2 (USA)", - "region": "USA", - "crc": "914CF9C4" - }, - { - "name": "Top Gear Rally 2 (USA)", - "region": "USA", - "crc": "7E4E181C" - }, - { - "name": "Toy Story 2 - Buzz Lightyear to the Rescue! (Europe)", - "region": "Europe", - "crc": "59574CB9" - }, - { - "name": "Toy Story 2 - Buzz Lightyear to the Rescue! (Europe)", - "region": "Europe", - "crc": "C1C5B460" - }, - { - "name": "Toy Story 2 - Buzz Lightyear to the Rescue! (USA)", - "region": "USA", - "crc": "B9570841" - }, - { - "name": "Toy Story 2 - Buzz Lightyear to the Rescue! (USA)", - "region": "USA", - "crc": "F7A72276" - }, - { - "name": "Toy Story 2 - Buzz Lightyear to the Rescue! (USA) (Rev 1)", - "region": "USA", - "crc": "C81F3321" - }, - { - "name": "Toy Story 2 - Buzz Lightyear to the Rescue! (USA) (Rev 1)", - "region": "USA", - "crc": "A11F0F8E" - }, - { - "name": "Toy Story 2 - Buzz l'Eclair a la Rescousse! (France)", - "region": "France", - "crc": "FB4BEA9A" - }, - { - "name": "Toy Story 2 - Buzz l'Eclair a la Rescousse! (France)", - "region": "France", - "crc": "2AF45190" - }, - { - "name": "Toy Story 2 - Captain Buzz Lightyear auf Rettungsmission! (Germany)", - "region": "Germany", - "crc": "C5E4C89F" - }, - { - "name": "Toy Story 2 - Captain Buzz Lightyear auf Rettungsmission! (Germany)", - "region": "Germany", - "crc": "33FD1561" - }, - { - "name": "Toy Story 2 - Captain Buzz Lightyear auf Rettungsmission! (Germany) (Rev 1)", - "region": "Germany", - "crc": "D1906DE4" - }, - { - "name": "Toy Story 2 - Captain Buzz Lightyear auf Rettungsmission! (Germany) (Rev 1)", - "region": "Germany", - "crc": "1878086F" - }, - { - "name": "Transformers - Beast Wars Metals 64 (Japan)", - "region": "Japan", - "crc": "338F1D45" - }, - { - "name": "Transformers - Beast Wars Metals 64 (Japan)", - "region": "Japan", - "crc": "5E972B7D" - }, - { - "name": "Transformers - Beast Wars Transmetals (USA)", - "region": "USA", - "crc": "85138B5A" - }, - { - "name": "Transformers - Beast Wars Transmetals (USA)", - "region": "USA", - "crc": "14C6B5E3" - }, - { - "name": "Triple Play 2000 (USA)", - "region": "USA", - "crc": "785DD0F8" - }, - { - "name": "Triple Play 2000 (USA)", - "region": "USA", - "crc": "BFB124FC" - }, - { - "name": "Tsumi to Batsu - Hoshi no Keishousha (Japan)", - "region": "Japan", - "crc": "CA2E5E49" - }, - { - "name": "Tsumi to Batsu - Hoshi no Keishousha (Japan)", - "region": "Japan", - "crc": "C0891399" - }, - { - "name": "Turok - Dinosaur Hunter (Europe)", - "region": "Europe", - "crc": "E8525687" - }, - { - "name": "Turok - Dinosaur Hunter (Europe)", - "region": "Europe", - "crc": "019A0C30" - }, - { - "name": "Turok - Dinosaur Hunter (Europe) (Rev 1)", - "region": "Europe", - "crc": "C2353283" - }, - { - "name": "Turok - Dinosaur Hunter (Europe) (Rev 1)", - "region": "Europe", - "crc": "5ED8AB71" - }, - { - "name": "Turok - Dinosaur Hunter (Europe) (Rev 2)", - "region": "Europe", - "crc": "312AF877" - }, - { - "name": "Turok - Dinosaur Hunter (Europe) (Rev 2)", - "region": "Europe", - "crc": "03D8CA48" - }, - { - "name": "Turok - Dinosaur Hunter (Germany)", - "region": "Germany", - "crc": "64631FF9" - }, - { - "name": "Turok - Dinosaur Hunter (Germany)", - "region": "Germany", - "crc": "70398790" - }, - { - "name": "Turok - Dinosaur Hunter (Germany) (Rev 1)", - "region": "Germany", - "crc": "2765DD8F" - }, - { - "name": "Turok - Dinosaur Hunter (Germany) (Rev 1)", - "region": "Germany", - "crc": "5A345522" - }, - { - "name": "Turok - Dinosaur Hunter (Germany) (Rev 2)", - "region": "Germany", - "crc": "72CA307A" - }, - { - "name": "Turok - Dinosaur Hunter (Germany) (Rev 2)", - "region": "Germany", - "crc": "01F632F6" - }, - { - "name": "Turok - Dinosaur Hunter (USA)", - "region": "USA", - "crc": "26C4F597" - }, - { - "name": "Turok - Dinosaur Hunter (USA)", - "region": "USA", - "crc": "777D75E6" - }, - { - "name": "Turok - Dinosaur Hunter (USA) (Rev 1)", - "region": "USA", - "crc": "7F2476F4" - }, - { - "name": "Turok - Dinosaur Hunter (USA) (Rev 1)", - "region": "USA", - "crc": "F26EC081" - }, - { - "name": "Turok - Dinosaur Hunter (USA) (Rev 2)", - "region": "USA", - "crc": "8C3BBC00" - }, - { - "name": "Turok - Dinosaur Hunter (USA) (Rev 2)", - "region": "USA", - "crc": "AF6EA1B8" - }, - { - "name": "Turok - Legenden des Verlorenen Landes (Germany)", - "region": "Germany", - "crc": "B937874F" - }, - { - "name": "Turok - Legenden des Verlorenen Landes (Germany)", - "region": "Germany", - "crc": "A2754CA4" - }, - { - "name": "Turok - Rage Wars (Europe)", - "region": "Europe", - "crc": "82B1E116" - }, - { - "name": "Turok - Rage Wars (Europe)", - "region": "Europe", - "crc": "6C391585" - }, - { - "name": "Turok - Rage Wars (Europe) (En,Fr,It)", - "region": "Europe", - "crc": "F4A2862B" - }, - { - "name": "Turok - Rage Wars (Europe) (En,Fr,It)", - "region": "Europe", - "crc": "6331B573" - }, - { - "name": "Turok - Rage Wars (USA)", - "region": "USA", - "crc": "422872A2" - }, - { - "name": "Turok - Rage Wars (USA)", - "region": "USA", - "crc": "416474D8" - }, - { - "name": "Turok - Rage Wars (USA) (Rev 1)", - "region": "USA", - "crc": "E28756F4" - }, - { - "name": "Turok - Rage Wars (USA) (Rev 1)", - "region": "USA", - "crc": "282319A0" - }, - { - "name": "Turok 2 - Seeds of Evil (Europe)", - "region": "Europe", - "crc": "E2D34BFE" - }, - { - "name": "Turok 2 - Seeds of Evil (Europe)", - "region": "Europe", - "crc": "9791B9CF" - }, - { - "name": "Turok 2 - Seeds of Evil (Europe) (Demo) (Kiosk)", - "region": "Europe", - "crc": "4E29B234" - }, - { - "name": "Turok 2 - Seeds of Evil (Europe) (Demo) (Kiosk)", - "region": "Europe", - "crc": "38B017D5" - }, - { - "name": "Turok 2 - Seeds of Evil (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "1FEBDE32" - }, - { - "name": "Turok 2 - Seeds of Evil (Europe) (En,Fr,Es,It)", - "region": "Europe", - "crc": "A309D9D3" - }, - { - "name": "Turok 2 - Seeds of Evil (Germany)", - "region": "Germany", - "crc": "C07877B6" - }, - { - "name": "Turok 2 - Seeds of Evil (Germany)", - "region": "Germany", - "crc": "FBE927CC" - }, - { - "name": "Turok 2 - Seeds of Evil (USA)", - "region": "USA", - "crc": "FF5E7636" - }, - { - "name": "Turok 2 - Seeds of Evil (USA)", - "region": "USA", - "crc": "FE3527AE" - }, - { - "name": "Turok 2 - Seeds of Evil (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "8D5B9BD0" - }, - { - "name": "Turok 2 - Seeds of Evil (USA) (Demo) (Kiosk)", - "region": "USA", - "crc": "46175228" - }, - { - "name": "Turok 2 - Seeds of Evil (USA) (Rev 1)", - "region": "USA", - "crc": "57F1FBF5" - }, - { - "name": "Turok 2 - Seeds of Evil (USA) (Rev 1)", - "region": "USA", - "crc": "ADEC745F" - }, - { - "name": "Turok 3 - Shadow of Oblivion (Europe)", - "region": "Europe", - "crc": "98D3114C" - }, - { - "name": "Turok 3 - Shadow of Oblivion (Europe)", - "region": "Europe", - "crc": "3113283E" - }, - { - "name": "Turok 3 - Shadow of Oblivion (Europe) (Beta) (2000-05-31)", - "region": "Europe", - "crc": "66E63BBC" - }, - { - "name": "Turok 3 - Shadow of Oblivion (Europe) (Beta) (2000-05-31)", - "region": "Europe", - "crc": "505E9066" - }, - { - "name": "Turok 3 - Shadow of Oblivion (Europe) (Beta) (2000-07-16)", - "region": "Europe", - "crc": "9A01A5F4" - }, - { - "name": "Turok 3 - Shadow of Oblivion (Europe) (Beta) (2000-07-16)", - "region": "Europe", - "crc": "43AEC886" - }, - { - "name": "Turok 3 - Shadow of Oblivion (USA)", - "region": "USA", - "crc": "CB297224" - }, - { - "name": "Turok 3 - Shadow of Oblivion (USA)", - "region": "USA", - "crc": "E0D0D902" - }, - { - "name": "Turok 3 - Shadow of Oblivion (USA) (Beta) (2000-06-26)", - "region": "USA", - "crc": "58C4C0AB" - }, - { - "name": "Turok 3 - Shadow of Oblivion (USA) (Beta) (2000-06-26)", - "region": "USA", - "crc": "264F32E0" - }, - { - "name": "Turok 3 - Shadow of Oblivion (USA) (Beta) (2000-07-21)", - "region": "USA", - "crc": "667B553D" - }, - { - "name": "Turok 3 - Shadow of Oblivion (USA) (Beta) (2000-07-21)", - "region": "USA", - "crc": "9577BC31" - }, - { - "name": "Twisted Edge - Extreme Snowboarding (USA)", - "region": "USA", - "crc": "BFBCC038" - }, - { - "name": "Twisted Edge - Extreme Snowboarding (USA)", - "region": "USA", - "crc": "3A572B23" - }, - { - "name": "Twisted Edge - Snowboarding (Europe)", - "region": "Europe", - "crc": "BF0C1291" - }, - { - "name": "Twisted Edge - Snowboarding (Europe)", - "region": "Europe", - "crc": "394BDD25" - }, - { - "name": "Utchan Nanchan no Hono no Challenger - Denryuu Ira Ira Bou (Japan)", - "region": "Japan", - "crc": "50CBE8A6" - }, - { - "name": "Utchan Nanchan no Hono no Challenger - Denryuu Ira Ira Bou (Japan)", - "region": "Japan", - "crc": "CB8C642B" - }, - { - "name": "V-Rally Edition 99 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "0735D7A2" - }, - { - "name": "V-Rally Edition 99 (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "D200F779" - }, - { - "name": "V-Rally Edition 99 (Japan)", - "region": "Japan", - "crc": "02475A01" - }, - { - "name": "V-Rally Edition 99 (Japan)", - "region": "Japan", - "crc": "7AAB7E2A" - }, - { - "name": "V-Rally Edition 99 (USA)", - "region": "USA", - "crc": "4803075E" - }, - { - "name": "V-Rally Edition 99 (USA)", - "region": "USA", - "crc": "BFEF13E9" - }, - { - "name": "Viewpoint 2064 (Japan) (Proto)", - "region": "Japan", - "crc": "93A630CB" - }, - { - "name": "Viewpoint 2064 (Japan) (Proto)", - "region": "Japan", - "crc": "DC8415F5" - }, - { - "name": "Vigilante 8 (Europe)", - "region": "Europe", - "crc": "0E9BB6D6" - }, - { - "name": "Vigilante 8 (Europe)", - "region": "Europe", - "crc": "959ECCE7" - }, - { - "name": "Vigilante 8 (France)", - "region": "France", - "crc": "11D23AB3" - }, - { - "name": "Vigilante 8 (France)", - "region": "France", - "crc": "C178EA87" - }, - { - "name": "Vigilante 8 (Germany)", - "region": "Germany", - "crc": "17F63C3F" - }, - { - "name": "Vigilante 8 (Germany)", - "region": "Germany", - "crc": "179EEC29" - }, - { - "name": "Vigilante 8 (USA)", - "region": "USA", - "crc": "330B73E6" - }, - { - "name": "Vigilante 8 (USA)", - "region": "USA", - "crc": "B445EDCD" - }, - { - "name": "Vigilante 8 - 2nd Offense (Europe)", - "region": "Europe", - "crc": "691AA971" - }, - { - "name": "Vigilante 8 - 2nd Offense (Europe)", - "region": "Europe", - "crc": "763D501E" - }, - { - "name": "Vigilante 8 - 2nd Offense (USA)", - "region": "USA", - "crc": "0293203F" - }, - { - "name": "Vigilante 8 - 2nd Offense (USA)", - "region": "USA", - "crc": "764ABDB3" - }, - { - "name": "Violence Killer - Turok New Generation (Japan)", - "region": "Japan", - "crc": "097F139F" - }, - { - "name": "Violence Killer - Turok New Generation (Japan)", - "region": "Japan", - "crc": "65785622" - }, - { - "name": "Virtual Chess 64 (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "AAE15243" - }, - { - "name": "Virtual Chess 64 (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "D20F855C" - }, - { - "name": "Virtual Chess 64 (USA) (En,Fr,Es)", - "region": "USA", - "crc": "620DE0B7" - }, - { - "name": "Virtual Chess 64 (USA) (En,Fr,Es)", - "region": "USA", - "crc": "3CD894C4" - }, - { - "name": "Virtual Pool 64 (Europe)", - "region": "Europe", - "crc": "9A6FB0BC" - }, - { - "name": "Virtual Pool 64 (Europe)", - "region": "Europe", - "crc": "F8498BD2" - }, - { - "name": "Virtual Pool 64 (USA)", - "region": "USA", - "crc": "AD628DED" - }, - { - "name": "Virtual Pool 64 (USA)", - "region": "USA", - "crc": "584B2A5F" - }, - { - "name": "Virtual Pro Wrestling 2 - Oudou Keishou (Japan)", - "region": "Japan", - "crc": "F620835D" - }, - { - "name": "Virtual Pro Wrestling 2 - Oudou Keishou (Japan)", - "region": "Japan", - "crc": "8E33C3AF" - }, - { - "name": "Virtual Pro Wrestling 64 (Japan)", - "region": "Japan", - "crc": "E6651803" - }, - { - "name": "Virtual Pro Wrestling 64 (Japan)", - "region": "Japan", - "crc": "2B4B9BEF" - }, - { - "name": "WCW Backstage Assault (USA)", - "region": "USA", - "crc": "5DCC2E4E" - }, - { - "name": "WCW Backstage Assault (USA)", - "region": "USA", - "crc": "D3A9F234" - }, - { - "name": "WCW Mayhem (Europe)", - "region": "Europe", - "crc": "864E066E" - }, - { - "name": "WCW Mayhem (Europe)", - "region": "Europe", - "crc": "E56874EE" - }, - { - "name": "WCW Mayhem (USA)", - "region": "USA", - "crc": "F1F9B6EB" - }, - { - "name": "WCW Mayhem (USA)", - "region": "USA", - "crc": "A1911368" - }, - { - "name": "WCW Nitro (USA)", - "region": "USA", - "crc": "455B0830" - }, - { - "name": "WCW Nitro (USA)", - "region": "USA", - "crc": "AC1909A9" - }, - { - "name": "WCW vs. nWo - World Tour (Europe)", - "region": "Europe", - "crc": "37F358EB" - }, - { - "name": "WCW vs. nWo - World Tour (Europe)", - "region": "Europe", - "crc": "B3A472A3" - }, - { - "name": "WCW vs. nWo - World Tour (USA)", - "region": "USA", - "crc": "DFBFC61F" - }, - { - "name": "WCW vs. nWo - World Tour (USA)", - "region": "USA", - "crc": "AD6FE55F" - }, - { - "name": "WCW vs. nWo - World Tour (USA) (Rev 1)", - "region": "USA", - "crc": "A74DA07A" - }, - { - "name": "WCW vs. nWo - World Tour (USA) (Rev 1)", - "region": "USA", - "crc": "85E0C097" - }, - { - "name": "WCW-nWo Revenge (Europe)", - "region": "Europe", - "crc": "8AF0F964" - }, - { - "name": "WCW-nWo Revenge (Europe)", - "region": "Europe", - "crc": "0ABBE702" - }, - { - "name": "WCW-nWo Revenge (USA)", - "region": "USA", - "crc": "54CBAAA8" - }, - { - "name": "WCW-nWo Revenge (USA)", - "region": "USA", - "crc": "2257FD02" - }, - { - "name": "WWF Attitude (Europe)", - "region": "Europe", - "crc": "804FE494" - }, - { - "name": "WWF Attitude (Europe)", - "region": "Europe", - "crc": "69CF2FE6" - }, - { - "name": "WWF Attitude (Germany)", - "region": "Germany", - "crc": "EA5D8359" - }, - { - "name": "WWF Attitude (Germany)", - "region": "Germany", - "crc": "F1F22E20" - }, - { - "name": "WWF Attitude (USA)", - "region": "USA", - "crc": "7A4B3686" - }, - { - "name": "WWF Attitude (USA)", - "region": "USA", - "crc": "F694DB1E" - }, - { - "name": "WWF No Mercy (Europe)", - "region": "Europe", - "crc": "E88D7E16" - }, - { - "name": "WWF No Mercy (Europe)", - "region": "Europe", - "crc": "EEB166B4" - }, - { - "name": "WWF No Mercy (Europe) (Rev 1)", - "region": "Europe", - "crc": "43BA9E7E" - }, - { - "name": "WWF No Mercy (Europe) (Rev 1)", - "region": "Europe", - "crc": "1A6F82E4" - }, - { - "name": "WWF No Mercy (USA)", - "region": "USA", - "crc": "B33F44F0" - }, - { - "name": "WWF No Mercy (USA)", - "region": "USA", - "crc": "EE8C16C1" - }, - { - "name": "WWF No Mercy (USA) (Rev 1)", - "region": "USA", - "crc": "BACEEA13" - }, - { - "name": "WWF No Mercy (USA) (Rev 1)", - "region": "USA", - "crc": "AFBC514E" - }, - { - "name": "WWF War Zone (Europe)", - "region": "Europe", - "crc": "90A0B609" - }, - { - "name": "WWF War Zone (Europe)", - "region": "Europe", - "crc": "58062C02" - }, - { - "name": "WWF War Zone (USA)", - "region": "USA", - "crc": "2FBB5507" - }, - { - "name": "WWF War Zone (USA)", - "region": "USA", - "crc": "6CFE13A8" - }, - { - "name": "WWF WrestleMania 2000 (Europe)", - "region": "Europe", - "crc": "09D710C7" - }, - { - "name": "WWF WrestleMania 2000 (Europe)", - "region": "Europe", - "crc": "ACB7BA1B" - }, - { - "name": "WWF WrestleMania 2000 (Japan)", - "region": "Japan", - "crc": "C2034D24" - }, - { - "name": "WWF WrestleMania 2000 (Japan)", - "region": "Japan", - "crc": "E4E3B9A3" - }, - { - "name": "WWF WrestleMania 2000 (USA)", - "region": "USA", - "crc": "0B50B4C6" - }, - { - "name": "WWF WrestleMania 2000 (USA)", - "region": "USA", - "crc": "D514E83C" - }, - { - "name": "Waialae Country Club - True Golf Classics (Europe)", - "region": "Europe", - "crc": "6858759A" - }, - { - "name": "Waialae Country Club - True Golf Classics (Europe)", - "region": "Europe", - "crc": "8E6F987B" - }, - { - "name": "Waialae Country Club - True Golf Classics (Europe) (Rev 1)", - "region": "Europe", - "crc": "6CB097B3" - }, - { - "name": "Waialae Country Club - True Golf Classics (Europe) (Rev 1)", - "region": "Europe", - "crc": "53A68628" - }, - { - "name": "Waialae Country Club - True Golf Classics (USA)", - "region": "USA", - "crc": "CCAB08D7" - }, - { - "name": "Waialae Country Club - True Golf Classics (USA)", - "region": "USA", - "crc": "CB760261" - }, - { - "name": "Waialae Country Club - True Golf Classics (USA) (Rev 1)", - "region": "USA", - "crc": "C65EE122" - }, - { - "name": "Waialae Country Club - True Golf Classics (USA) (Rev 1)", - "region": "USA", - "crc": "76EBDB18" - }, - { - "name": "War Gods (Europe)", - "region": "Europe", - "crc": "C73010C8" - }, - { - "name": "War Gods (Europe)", - "region": "Europe", - "crc": "FD10AAA6" - }, - { - "name": "War Gods (USA)", - "region": "USA", - "crc": "FFACF993" - }, - { - "name": "War Gods (USA)", - "region": "USA", - "crc": "10635B27" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Europe) (En,De)", - "region": "Europe", - "crc": "FB289893" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Europe) (En,De)", - "region": "Europe", - "crc": "D74117B0" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Japan)", - "region": "Japan", - "crc": "6C93FF83" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Japan)", - "region": "Japan", - "crc": "0580EEE7" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Japan) (Rev 1)", - "region": "Japan", - "crc": "54D190A8" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Japan) (Rev 1)", - "region": "Japan", - "crc": "A977CC81" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Japan) (Rev 2) (Shindou Edition)", - "region": "Japan", - "crc": "90044C4B" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (Japan) (Rev 2) (Shindou Edition)", - "region": "Japan", - "crc": "D3610A02" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (USA)", - "region": "USA", - "crc": "74A7B725" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (USA)", - "region": "USA", - "crc": "9EF814CD" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (USA) (Rev 1)", - "region": "USA", - "crc": "394948C4" - }, - { - "name": "Wave Race 64 - Kawasaki Jet Ski (USA) (Rev 1)", - "region": "USA", - "crc": "DD2BB7B6" - }, - { - "name": "Wayne Gretzky's 3D Hockey '98 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "6F6DC53D" - }, - { - "name": "Wayne Gretzky's 3D Hockey '98 (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "53751331" - }, - { - "name": "Wayne Gretzky's 3D Hockey '98 (USA)", - "region": "USA", - "crc": "355FB089" - }, - { - "name": "Wayne Gretzky's 3D Hockey '98 (USA)", - "region": "USA", - "crc": "5C593E2E" - }, - { - "name": "Wayne Gretzky's 3D Hockey (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "442A4F5F" - }, - { - "name": "Wayne Gretzky's 3D Hockey (Europe) (En,Fr,De,Es)", - "region": "Europe", - "crc": "0EFF3F94" - }, - { - "name": "Wayne Gretzky's 3D Hockey (Japan)", - "region": "Japan", - "crc": "485275ED" - }, - { - "name": "Wayne Gretzky's 3D Hockey (Japan)", - "region": "Japan", - "crc": "694B3B0B" - }, - { - "name": "Wayne Gretzky's 3D Hockey (USA)", - "region": "USA", - "crc": "9781F88D" - }, - { - "name": "Wayne Gretzky's 3D Hockey (USA)", - "region": "USA", - "crc": "2462389E" - }, - { - "name": "Wayne Gretzky's 3D Hockey (USA) (Rev 1)", - "region": "USA", - "crc": "C2678971" - }, - { - "name": "Wayne Gretzky's 3D Hockey (USA) (Rev 1)", - "region": "USA", - "crc": "6E5A30ED" - }, - { - "name": "Wetrix (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "EDDC5B06" - }, - { - "name": "Wetrix (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "E26CFF31" - }, - { - "name": "Wetrix (Japan)", - "region": "Japan", - "crc": "BAD08218" - }, - { - "name": "Wetrix (Japan)", - "region": "Japan", - "crc": "AEBAFD6D" - }, - { - "name": "Wetrix (USA) (En,Fr,De,Es,It,Nl)", - "region": "USA", - "crc": "50CD1F71" - }, - { - "name": "Wetrix (USA) (En,Fr,De,Es,It,Nl)", - "region": "USA", - "crc": "4EDA94C1" - }, - { - "name": "Whack 'n' Roll (USA) (Beta) (1998-02-05)", - "region": "USA", - "crc": "74DF67BB" - }, - { - "name": "Whack 'n' Roll (USA) (Beta) (1998-02-05)", - "region": "USA", - "crc": "FCDD8DB6" - }, - { - "name": "Wheel of Fortune (USA)", - "region": "USA", - "crc": "8D3EFA8D" - }, - { - "name": "Wheel of Fortune (USA)", - "region": "USA", - "crc": "C2571693" - }, - { - "name": "Wild Choppers (Japan)", - "region": "Japan", - "crc": "D6136DC5" - }, - { - "name": "Wild Choppers (Japan)", - "region": "Japan", - "crc": "FED21F49" - }, - { - "name": "Wildwaters (USA) (Proto) (1999-06-15)", - "region": "USA", - "crc": "40443C67" - }, - { - "name": "Wildwaters (USA) (Proto) (1999-06-15)", - "region": "USA", - "crc": "ECB6780C" - }, - { - "name": "WinBack (Japan)", - "region": "Japan", - "crc": "D35360B0" - }, - { - "name": "WinBack (Japan)", - "region": "Japan", - "crc": "A5364D80" - }, - { - "name": "WinBack (Japan) (Rev 1)", - "region": "Japan", - "crc": "927383BB" - }, - { - "name": "WinBack (Japan) (Rev 1)", - "region": "Japan", - "crc": "9FBD5973" - }, - { - "name": "WinBack - Covert Operations (USA)", - "region": "USA", - "crc": "64C817C5" - }, - { - "name": "WinBack - Covert Operations (USA)", - "region": "USA", - "crc": "1A6334D0" - }, - { - "name": "Wipeout 64 (Europe)", - "region": "Europe", - "crc": "38111048" - }, - { - "name": "Wipeout 64 (Europe)", - "region": "Europe", - "crc": "0C3497E4" - }, - { - "name": "Wipeout 64 (Europe) (Beta)", - "region": "Europe", - "crc": "944B258F" - }, - { - "name": "Wipeout 64 (Europe) (Beta)", - "region": "Europe", - "crc": "5B0DBFEE" - }, - { - "name": "Wipeout 64 (USA)", - "region": "USA", - "crc": "4888D0FE" - }, - { - "name": "Wipeout 64 (USA)", - "region": "USA", - "crc": "3E8CCE96" - }, - { - "name": "Wonder Project J2 - Koruro no Mori no Jozet (Japan)", - "region": "Japan", - "crc": "5E8FC436" - }, - { - "name": "Wonder Project J2 - Koruro no Mori no Jozet (Japan)", - "region": "Japan", - "crc": "8AAF74A3" - }, - { - "name": "World Cup 98 (Europe) (En,Fr,De,Es,It,Nl,Sv,Da)", - "region": "Europe", - "crc": "79F483F7" - }, - { - "name": "World Cup 98 (Europe) (En,Fr,De,Es,It,Nl,Sv,Da)", - "region": "Europe", - "crc": "163481D0" - }, - { - "name": "World Cup 98 (USA) (En,Fr,De,Es,It,Nl,Sv,Da)", - "region": "USA", - "crc": "13930C26" - }, - { - "name": "World Cup 98 (USA) (En,Fr,De,Es,It,Nl,Sv,Da)", - "region": "USA", - "crc": "B875CD94" - }, - { - "name": "World Driver Championship (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "76151DF6" - }, - { - "name": "World Driver Championship (Europe) (En,Fr,De,Es,It)", - "region": "Europe", - "crc": "74FAD5AA" - }, - { - "name": "World Driver Championship (USA)", - "region": "USA", - "crc": "5E4ACBFA" - }, - { - "name": "World Driver Championship (USA)", - "region": "USA", - "crc": "DCD9D2D2" - }, - { - "name": "Worms Armageddon (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "6BFFF27B" - }, - { - "name": "Worms Armageddon (Europe) (En,Fr,De,Es,It,Nl)", - "region": "Europe", - "crc": "4862EBBD" - }, - { - "name": "Worms Armageddon (USA) (En,Fr,Es)", - "region": "USA", - "crc": "5471AE3B" - }, - { - "name": "Worms Armageddon (USA) (En,Fr,Es)", - "region": "USA", - "crc": "556268B7" - }, - { - "name": "X'treme Roller (USA) (Proto)", - "region": "USA", - "crc": "FCE6875F" - }, - { - "name": "X'treme Roller (USA) (Proto)", - "region": "USA", - "crc": "9A47AF0F" - }, - { - "name": "Xena - Warrior Princess - The Talisman of Fate (Europe)", - "region": "Europe", - "crc": "D3932F88" - }, - { - "name": "Xena - Warrior Princess - The Talisman of Fate (Europe)", - "region": "Europe", - "crc": "2BB18EB6" - }, - { - "name": "Xena - Warrior Princess - The Talisman of Fate (USA)", - "region": "USA", - "crc": "7DA93999" - }, - { - "name": "Xena - Warrior Princess - The Talisman of Fate (USA)", - "region": "USA", - "crc": "7CB26725" - }, - { - "name": "Xingji Huohu (China) (v4)", - "region": "China", - "crc": "46522D7A" - }, - { - "name": "Xingji Huohu (China) (v4) (Manual)", - "region": "China", - "crc": "28CDA0EB" - }, - { - "name": "Xingji Huohu (China) (v5)", - "region": "China", - "crc": "0D28C3ED" - }, - { - "name": "Xplorer 64 (Germany) (v1.067) (Unl)", - "region": "Germany", - "crc": "656ACDF5" - }, - { - "name": "Xplorer 64 (Germany) (v1.067) (Unl)", - "region": "Germany", - "crc": "656ACDF5" - }, - { - "name": "Yakouchuu II - Satsujin Kouro (Japan)", - "region": "Japan", - "crc": "4538C41A" - }, - { - "name": "Yakouchuu II - Satsujin Kouro (Japan)", - "region": "Japan", - "crc": "5C97C412" - }, - { - "name": "Yaoxi Gushi (China)", - "region": "China", - "crc": "9A579A29" - }, - { - "name": "Yaoxi Gushi (China) (v2) (Manual)", - "region": "China", - "crc": "4B5AE477" - }, - { - "name": "Yaoxi Gushi (China) (v4) (Manual)", - "region": "China", - "crc": "5D76A1C1" - }, - { - "name": "Yaoxi Gushi (China) (v6) (Manual)", - "region": "China", - "crc": "5CEF9B96" - }, - { - "name": "Yoshi Story (Japan)", - "region": "Japan", - "crc": "4F44A9EF" - }, - { - "name": "Yoshi Story (Japan)", - "region": "Japan", - "crc": "FEAC925C" - }, - { - "name": "Yoshi's Story (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "F9FFC760" - }, - { - "name": "Yoshi's Story (Europe) (En,Fr,De)", - "region": "Europe", - "crc": "21A5F264" - }, - { - "name": "Yoshi's Story (USA) (En,Ja)", - "region": "USA", - "crc": "A1453E0D" - }, - { - "name": "Yoshi's Story (USA) (En,Ja)", - "region": "USA", - "crc": "01A4B1E9" - }, - { - "name": "Yueye Motuo (China) (v2) (Manual)", - "region": "China", - "crc": "3863DFE0" - }, - { - "name": "Yueye Motuo (China) (v8)", - "region": "China", - "crc": "D7AD44FD" - }, - { - "name": "Yuke Yuke!! Trouble Makers (Japan)", - "region": "Japan", - "crc": "B69D3068" - }, - { - "name": "Yuke Yuke!! Trouble Makers (Japan)", - "region": "Japan", - "crc": "F8BB860C" - }, - { - "name": "Zelda no Densetsu - Mujura no Kamen (Japan)", - "region": "Japan", - "crc": "0D33E1DB" - }, - { - "name": "Zelda no Densetsu - Mujura no Kamen (Japan)", - "region": "Japan", - "crc": "3361B649" - }, - { - "name": "Zelda no Densetsu - Mujura no Kamen (Japan) (GameCube)", - "region": "Japan", - "crc": "B9BF76DF" - }, - { - "name": "Zelda no Densetsu - Mujura no Kamen (Japan) (GameCube)", - "region": "Japan", - "crc": "766EBDEB" - }, - { - "name": "Zelda no Densetsu - Mujura no Kamen (Japan) (Rev 1)", - "region": "Japan", - "crc": "356C2E19" - }, - { - "name": "Zelda no Densetsu - Mujura no Kamen (Japan) (Rev 1)", - "region": "Japan", - "crc": "2DE56C55" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan)", - "region": "Japan", - "crc": "D423E8B0" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan)", - "region": "Japan", - "crc": "B58E98BB" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan) (GameCube, Zelda Collection)", - "region": "Japan", - "crc": "8C5B90C1" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan) (GameCube, Zelda Collection)", - "region": "Japan", - "crc": "683BF76E" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan) (Rev 1)", - "region": "Japan", - "crc": "26E73887" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan) (Rev 1)", - "region": "Japan", - "crc": "F31C202C" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan) (Rev 2)", - "region": "Japan", - "crc": "2B2721BA" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina (Japan) (Rev 2)", - "region": "Japan", - "crc": "656657CD" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina GC (Japan) (GameCube)", - "region": "Japan", - "crc": "1C6CE8CB" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina GC (Japan) (GameCube)", - "region": "Japan", - "crc": "0AC22DE8" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina GC Ura (Japan) (GameCube)", - "region": "Japan", - "crc": "122FF261" - }, - { - "name": "Zelda no Densetsu - Toki no Ocarina GC Ura (Japan) (GameCube)", - "region": "Japan", - "crc": "D97C20BA" - }, - { - "name": "Zhipian Maliou (China) (v2) (Manual)", - "region": "China", - "crc": "977E9A2F" - }, - { - "name": "Zhipian Maliou (China) (v4)", - "region": "China", - "crc": "18C02AD5" - }, - { - "name": "Zhipian Maliou (China) (v4) (Manual)", - "region": "China", - "crc": "B370284F" - }, - { - "name": "Zool - Majuu Tsukai Densetsu (Japan)", - "region": "Japan", - "crc": "756BA26D" - }, - { - "name": "Zool - Majuu Tsukai Densetsu (Japan)", - "region": "Japan", - "crc": "84428431" - }, - { - "name": "Zuhe Jiqiren (China) (v3)", - "region": "China", - "crc": "76742798" - }, - { - "name": "Zui yu Fa - Diqiu de Jicheng Zhe (China) (Manual)", - "region": "China", - "crc": "F336CC75" - }, - { - "name": "Zui yu Fa - Diqiu de Jicheng Zhe (China) (v3)", - "region": "China", - "crc": "E00DB418" - }, - { - "name": "iQue Club (China) (v2)", - "region": "China", - "crc": "4D193DE7" - }, - { - "name": "iQue Club (China) (v3)", - "region": "China", - "crc": "6A2C6DCB" - } - ] -} diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index cc46af63..04ebd54d 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include namespace n64 { @@ -18,18 +17,17 @@ void Core::Stop() { romLoaded = false; } -CartInfo Core::LoadROM(const std::string& rom_) { +void Core::LoadROM(const std::string& rom_) { rom = rom_; cpu->Reset(); cpu->mem.Reset(); pause = false; romLoaded = true; - CartInfo cartInfo = cpu->mem.LoadROM(rom); - isPAL = cartInfo.isPAL; - cpu->mem.mmio.si.pif.ExecutePIF(cpu->mem, cpu->regs, cartInfo); - - return cartInfo; + cpu->mem.LoadROM(rom); + GameDB::match(cpu->mem); + isPAL = cpu->mem.IsROMPAL(); + cpu->mem.mmio.si.pif.ExecutePIF(cpu->mem, cpu->regs); } void Core::Run(Window& window, float volumeL, float volumeR) { diff --git a/src/backend/Core.hpp b/src/backend/Core.hpp index 21100551..df01d5eb 100644 --- a/src/backend/Core.hpp +++ b/src/backend/Core.hpp @@ -19,7 +19,7 @@ struct Core { ~Core() { Stop(); } Core(); void Stop(); - CartInfo LoadROM(const std::string&); + void LoadROM(const std::string&); void Run(Window&, float volumeL, float volumeR); void TogglePause() { pause = !pause; } VI& GetVI() { return cpu->mem.mmio.vi; } diff --git a/src/backend/GameDB.cpp b/src/backend/GameDB.cpp new file mode 100644 index 00000000..4210ae3e --- /dev/null +++ b/src/backend/GameDB.cpp @@ -0,0 +1,36 @@ +#include +#include + +namespace n64 { +void GameDB::match(Mem& mem) { + ROM& rom = mem.rom; + for (const auto & i : gamedb) { + bool matches_code = i.code == rom.code; + bool matches_region = false; + + for (int j = 0; j < i.regions.size() && !matches_region; j++) { + if (i.regions[j] == rom.header.countryCode[0]) { + matches_region = true; + } + } + + if (matches_code) { + if (matches_region) { + mem.saveType = i.saveType; + mem.rom.gameNameDB = i.name; + Util::debug("Loaded {}\n", i.name); + return; + } else { + Util::warn("Matched code for %s, but not region! Game supposedly exists in regions [{}] but this image has region {}\n", + i.name, i.regions, rom.header.countryCode[0]); + } + } + + } + + Util::debug("Did not match any Game DB entries. Code: {} Region: {}\n", mem.rom.code, mem.rom.header.countryCode[0]); + + mem.rom.gameNameDB = ""; + mem.saveType = SAVE_NONE; +} +} \ No newline at end of file diff --git a/src/backend/GameDB.hpp b/src/backend/GameDB.hpp new file mode 100644 index 00000000..75870fa0 --- /dev/null +++ b/src/backend/GameDB.hpp @@ -0,0 +1,210 @@ +#pragma once +#include + +namespace n64 { +enum SaveType { + SAVE_NONE, + SAVE_EEPROM_4k, + SAVE_EEPROM_16k, + SAVE_FLASH_1m, + SAVE_SRAM_256k +}; + +struct Mem; +struct GameDBEntry { + std::string code; + std::string regions; + SaveType saveType; + const char *name; +}; + +namespace GameDB { +void match(Mem &mem); +} + +static const GameDBEntry gamedb[] = { + {"NNM", "E", SAVE_NONE, "Namco Museum 64"}, + {"NDM", "E", SAVE_NONE, "Doom 64"}, + {"NGN", "E", SAVE_EEPROM_4k, "GoldenEye 007"}, + // Copied from CEN64 with small edits: https://github.com/n64dev/cen64/blob/master/device/cart_db.c + {"CFZ", "EJ", SAVE_SRAM_256k, "F-Zero X (NTSC)"}, + {"CLB", "EJ", SAVE_EEPROM_4k, "Mario Party (NTSC)"}, + {"CP2", "J", SAVE_FLASH_1m, "Pokémon Stadium 2 (Japan)"}, + {"CPS", "J", SAVE_SRAM_256k, "Pokémon Stadium (Japan)"}, + {"CZL", "EJ", SAVE_SRAM_256k, "Legend of Zelda: Ocarina of Time (NTSC)"}, + {"N3D", "J", SAVE_EEPROM_16k, "Doraemon 3: Nobita no Machi SOS!"}, + {"N3H", "J", SAVE_SRAM_256k, "Ganbare! Nippon! Olympics 2000"}, + {"NA2", "J", SAVE_SRAM_256k, "Virtual Pro Wrestling 2"}, + {"NAB", "JP", SAVE_EEPROM_4k, "Air Boarder 64"}, + {"NAD", "E", SAVE_EEPROM_4k, "Worms Armageddon (USA)"}, + {"NAF", "J", SAVE_FLASH_1m, "Doubutsu no Mori"}, + {"NAG", "EJP", SAVE_EEPROM_4k, "AeroGauge"}, + {"NAL", "EJPU", SAVE_SRAM_256k, "Super Smash Bros"}, + {"NB5", "J", SAVE_SRAM_256k, "Biohazard 2"}, + {"NB6", "J", SAVE_EEPROM_4k, "Super B-Daman: Battle Phoenix 64"}, + {"NB7", "EJPU", SAVE_EEPROM_16k, "Banjo-Tooie"}, + {"NBC", "EJP", SAVE_EEPROM_4k, "Blast Corps"}, + {"NBD", "EJP", SAVE_EEPROM_4k, "Bomberman Hero"}, + {"NBH", "EP", SAVE_EEPROM_4k, "Body Harvest"}, + {"NBK", "EJP", SAVE_EEPROM_4k, "Banjo-Kazooie"}, + {"NBM", "EJP", SAVE_EEPROM_4k, "Bomberman 64"}, + {"NBN", "J", SAVE_EEPROM_4k, "Bakuretsu Muteki Bangaioh"}, + {"NBV", "EJ", SAVE_EEPROM_4k, "Bomberman 64: The Second Attack!"}, + {"NCC", "DEP", SAVE_FLASH_1m, "Command & Conquer"}, + {"NCG", "J", SAVE_EEPROM_4k, "Choro Q 64 2: Hacha-Mecha Grand Prix Race"}, + {"NCH", "EP", SAVE_EEPROM_4k, "Chopper Attack"}, + {"NCK", "E", SAVE_FLASH_1m, "NBA Courtside 2"}, + {"NCR", "EJP", SAVE_EEPROM_4k, "Penny Racers"}, + {"NCT", "EJP", SAVE_EEPROM_4k, "Chameleon Twist"}, + {"NCU", "EP", SAVE_EEPROM_4k, "Cruis'n USA"}, + {"NCW", "EP", SAVE_EEPROM_16k, "Cruis'n World"}, + {"NCX", "J", SAVE_EEPROM_4k, "Custom Robo"}, + {"NCZ", "J", SAVE_EEPROM_16k, "Custom Robo V2"}, + {"ND2", "J", SAVE_EEPROM_16k, "Doraemon 2: Nobita to Hikari no Shinden"}, + {"ND3", "J", SAVE_EEPROM_16k, "Akumajou Dracula Mokushiroku"}, + {"ND4", "J", SAVE_EEPROM_16k, "Akumajou Dracula Mokushiroku Gaiden: Legend of Cornell"}, + {"ND6", "J", SAVE_EEPROM_16k, "Densha de Go! 64"}, + {"NDA", "J", SAVE_FLASH_1m, "Derby Stallion 64"}, + {"NDK", "J", SAVE_EEPROM_4k, "Space Dynamites"}, + {"NDO", "EJP", SAVE_EEPROM_16k, "Donkey Kong 64"}, + {"NDP", "E", SAVE_FLASH_1m, "Dinosaur Planet"}, + {"NDR", "J", SAVE_EEPROM_4k, "Doraemon: Nobita to 3tsu no Seireiseki"}, + {"NDU", "EP", SAVE_EEPROM_4k, "Duck Dodgers"}, + {"NDY", "EJP", SAVE_EEPROM_4k, "Diddy Kong Racing"}, + {"NEA", "EP", SAVE_EEPROM_4k, "PGA European Tour"}, + {"NEP", "EJP", SAVE_EEPROM_16k, "Star Wars Episode I: Racer"}, + {"NER", "E", SAVE_EEPROM_4k, "AeroFighters Assault (USA)"}, + {"NEV", "J", SAVE_EEPROM_16k, "Neon Genesis Evangelion"}, + {"NF2", "P", SAVE_EEPROM_4k, "F-1 World Grand Prix II"}, + {"NFG", "E", SAVE_EEPROM_4k, "Fighter Destiny 2"}, + {"NFH", "EP", SAVE_EEPROM_4k, "Bass Hunter 64"}, + {"NFU", "EP", SAVE_EEPROM_16k, "Conker's Bad Fur Day"}, + {"NFW", "DEFJP", SAVE_EEPROM_4k, "F-1 World Grand Prix"}, + {"NFX", "EJPU", SAVE_EEPROM_4k, "Star Fox 64"}, + {"NFY", "J", SAVE_EEPROM_4k, "Kakutou Denshou: F-Cup Maniax"}, + {"NFZ", "P", SAVE_SRAM_256k, "F-Zero X (PAL)"}, + {"NG6", "J", SAVE_SRAM_256k, "Ganbare Goemon: Dero Dero Douchuu Obake Tenkomori"}, + {"NGC", "EP", SAVE_EEPROM_16k, "GT 64: Championship Edition"}, + {"NGE", "EJP", SAVE_EEPROM_4k, "GoldenEye 007"}, + {"NGL", "J", SAVE_EEPROM_4k, "Getter Love!!"}, + {"NGP", "J", SAVE_SRAM_256k, "Goemon: Mononoke Sugoroku"}, + {"NGT", "J", SAVE_EEPROM_16k, "City-Tour GP: Zen-Nihon GT Senshuken"}, + {"NGU", "J", SAVE_EEPROM_4k, "Tsumi to Batsu: Hoshi no Keishousha"}, + {"NGV", "EP", SAVE_EEPROM_4k, "Glover"}, + {"NHA", "J", SAVE_EEPROM_4k, "Bomber Man 64 (Japan)"}, + {"NHF", "J", SAVE_EEPROM_4k, "64 Hanafuda: Tenshi no Yakusoku"}, + {"NHP", "J", SAVE_EEPROM_4k, "Heiwa Pachinko World 64"}, + {"NHY", "J", SAVE_SRAM_256k, "Hybrid Heaven (Japan)"}, + {"NIB", "J", SAVE_SRAM_256k, "Itoi Shigesato no Bass Tsuri No. 1 Kettei Ban!"}, + {"NIC", "E", SAVE_EEPROM_4k, "Indy Racing 2000"}, + {"NIJ", "EP", SAVE_EEPROM_4k, "Indiana Jones and the Infernal Machine"}, + {"NIM", "J", SAVE_EEPROM_16k, "Ide Yosuke no Mahjong Juku"}, + {"NIR", "J", SAVE_EEPROM_4k, "Utchan Nanchan no Hono no Challenger: Denryuu Ira Ira Bou"}, + {"NJ5", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu 5"}, + {"NJD", "E", SAVE_FLASH_1m, "Jet Force Gemini (Kiosk Demo)"}, + {"NJF", "EJP", SAVE_FLASH_1m, "Jet Force Gemini"}, + {"NJG", "J", SAVE_SRAM_256k, "Jinsei Game 64"}, + {"NJM", "EP", SAVE_EEPROM_4k, "Earthworm Jim 3D"}, + {"NK2", "EJP", SAVE_EEPROM_4k, "Snowboard Kids 2"}, + {"NK4", "EJP", SAVE_EEPROM_16k, "Kirby 64: The Crystal Shards"}, + {"NKA", "DEFJP", SAVE_EEPROM_4k, "Fighters Destiny"}, + {"NKG", "EP", SAVE_SRAM_256k, "MLB featuring Ken Griffey Jr."}, + {"NKI", "EP", SAVE_EEPROM_4k, "Killer Instinct Gold"}, + {"NKJ", "E", SAVE_FLASH_1m, "Ken Griffey Jr.'s Slugfest"}, + {"NKT", "EJP", SAVE_EEPROM_4k, "Mario Kart 64"}, + {"NLB", "P", SAVE_EEPROM_4k, "Mario Party (PAL)"}, + {"NLL", "J", SAVE_EEPROM_4k, "Last Legion UX"}, + {"NLR", "EJP", SAVE_EEPROM_4k, "Lode Runner 3D"}, + {"NM6", "E", SAVE_FLASH_1m, "Mega Man 64"}, + {"NM8", "EJP", SAVE_EEPROM_16k, "Mario Tennis"}, + {"NMF", "EJP", SAVE_SRAM_256k, "Mario Golf"}, + {"NMG", "DEP", SAVE_EEPROM_4k, "Monaco Grand Prix"}, + {"NMI", "DEFIPS", SAVE_EEPROM_4k, "Mission: Impossible"}, + {"NML", "EJP", SAVE_EEPROM_4k, "Mickey's Speedway USA"}, + {"NMO", "E", SAVE_EEPROM_4k, "Monopoly"}, + {"NMQ", "EJP", SAVE_FLASH_1m, "Paper Mario"}, + {"NMR", "EJP", SAVE_EEPROM_4k, "Multi Racing Championship"}, + {"NMS", "J", SAVE_EEPROM_4k, "Morita Shougi 64"}, + {"NMU", "E", SAVE_EEPROM_4k, "Big Mountain 2000"}, + {"NMV", "EJP", SAVE_EEPROM_16k, "Mario Party 3"}, + {"NMW", "EJP", SAVE_EEPROM_4k, "Mario Party 2"}, + {"NMX", "EJP", SAVE_EEPROM_16k, "Excitebike 64"}, + {"NN6", "E", SAVE_EEPROM_4k, "Dr. Mario 64"}, + {"NNA", "EP", SAVE_EEPROM_4k, "Star Wars Episode I: Battle for Naboo"}, + {"NNB", "EP", SAVE_EEPROM_16k, "Kobe Bryant in NBA Courtside"}, + {"NOB", "EJ", SAVE_SRAM_256k, "Ogre Battle 64: Person of Lordly Caliber"}, + {"NOS", "J", SAVE_EEPROM_4k, "64 Oozumou"}, + {"NP2", "J", SAVE_EEPROM_4k, "Chou Kuukan Nighter Pro Yakyuu King 2"}, + {"NP3", "DEFIJPS", SAVE_FLASH_1m, "Pokémon Stadium 2"}, + {"NP6", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu 6"}, + {"NPA", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu 2000"}, + {"NPD", "EJP", SAVE_EEPROM_16k, "Perfect Dark"}, + {"NPE", "J", SAVE_SRAM_256k, "Jikkyou Powerful Pro Yakyuu Basic Ban 2001"}, + {"NPF", "DEFIJPSU", SAVE_FLASH_1m, "Pokémon Snap"}, + {"NPG", "EJ", SAVE_EEPROM_4k, "Hey You, Pikachu!"}, + {"NPH", "E", SAVE_FLASH_1m, "Pokémon Snap Station (Kiosk Demo)"}, + {"NPM", "P", SAVE_SRAM_256k, "Premier Manager 64"}, + {"NPN", "DEFP", SAVE_FLASH_1m, "Pokémon Puzzle League"}, + {"NPO", "DEFIPS", SAVE_FLASH_1m, "Pokémon Stadium (USA, PAL)"}, + {"NPP", "J", SAVE_EEPROM_16k, "Parlor! Pro 64: Pachinko Jikki Simulation Game"}, + {"NPS", "J", SAVE_SRAM_256k, "Jikkyou J.League 1999: Perfect Striker 2"}, + {"NPT", "J", SAVE_EEPROM_4k, "Puyo Puyon Party"}, + {"NPW", "EJP", SAVE_EEPROM_4k, "Pilotwings 64"}, + {"NPY", "J", SAVE_EEPROM_4k, "Puyo Puyo Sun 64"}, + {"NR7", "J", SAVE_EEPROM_16k, "Robot Poncots 64: 7tsu no Umi no Caramel"}, + {"NRA", "J", SAVE_EEPROM_4k, "Rally '99"}, + {"NRC", "EJP", SAVE_EEPROM_4k, "Top Gear Overdrive"}, + {"NRE", "EP", SAVE_SRAM_256k, "Resident Evil 2"}, + {"NRH", "J", SAVE_FLASH_1m, "Rockman Dash"}, + {"NRI", "EP", SAVE_SRAM_256k, "The New Tetris"}, + {"NRS", "EJP", SAVE_EEPROM_4k, "Star Wars: Rogue Squadron"}, + {"NRZ", "EP", SAVE_EEPROM_16k, "Ridge Racer 64"}, + {"NS4", "J", SAVE_SRAM_256k, "Super Robot Taisen 64"}, + {"NS6", "EJ", SAVE_EEPROM_4k, "Star Soldier: Vanishing Earth"}, + {"NSA", "JP", SAVE_EEPROM_4k, "AeroFighters Assault (PAL, Japan)"}, + {"NSC", "EP", SAVE_EEPROM_4k, "Starshot: Space Circus Fever"}, + {"NSI", "J", SAVE_SRAM_256k, "Fushigi no Dungeon: Fuurai no Shiren 2"}, + {"NSM", "EJP", SAVE_EEPROM_4k, "Super Mario 64"}, + {"NSN", "J", SAVE_EEPROM_4k, "Snow Speeder"}, + {"NSQ", "EP", SAVE_FLASH_1m, "StarCraft 64"}, + {"NSS", "J", SAVE_EEPROM_4k, "Super Robot Spirits"}, + {"NSU", "EP", SAVE_EEPROM_4k, "Rocket: Robot on Wheels"}, + {"NSV", "EP", SAVE_EEPROM_4k, "SpaceStation Silicon Valley"}, + {"NSW", "EJP", SAVE_EEPROM_4k, "Star Wars: Shadows of the Empire"}, + {"NT3", "J", SAVE_SRAM_256k, "Toukon Road 2"}, + {"NT6", "J", SAVE_EEPROM_4k, "Tetris 64"}, + {"NT9", "EP", SAVE_FLASH_1m, "Tigger's Honey Hunt"}, + {"NTB", "J", SAVE_EEPROM_4k, "Transformers: Beast Wars Metals 64"}, + {"NTC", "J", SAVE_EEPROM_4k, "64 Trump Collection"}, + {"NTE", "AP", SAVE_SRAM_256k, "1080 Snowboarding"}, + {"NTJ", "EP", SAVE_EEPROM_4k, "Tom and Jerry in Fists of Furry"}, + {"NTM", "EJP", SAVE_EEPROM_4k, "Mischief Makers"}, + {"NTN", "EP", SAVE_EEPROM_4k, "All-Star Tennis 99"}, + {"NTP", "EP", SAVE_EEPROM_4k, "Tetrisphere"}, + {"NTR", "JP", SAVE_EEPROM_4k, "Top Gear Rally (PAL, Japan)"}, + {"NTW", "J", SAVE_EEPROM_4k, "64 de Hakken!! Tamagotchi"}, + {"NTX", "EP", SAVE_EEPROM_4k, "Taz Express"}, + {"NUB", "J", SAVE_EEPROM_16k, "PD Ultraman Battle Collection 64"}, + {"NUM", "J", SAVE_SRAM_256k, "Nushi Zuri 64: Shiokaze ni Notte"}, + {"NUT", "J", SAVE_SRAM_256k, "Nushi Zuri 64"}, + {"NVB", "J", SAVE_SRAM_256k, "Bass Rush: ECOGEAR PowerWorm Championship"}, + {"NVL", "EP", SAVE_EEPROM_4k, "V-Rally 99 (USA, PAL)"}, + {"NVP", "J", SAVE_SRAM_256k, "Virtual Pro Wrestling 64"}, + {"NVY", "J", SAVE_EEPROM_4k, "V-Rally 99 (Japan)"}, + {"NW2", "EP", SAVE_SRAM_256k, "WCW/nWo Revenge"}, + {"NW4", "EP", SAVE_FLASH_1m, "WWF No Mercy"}, + {"NWC", "J", SAVE_EEPROM_4k, "Wild Choppers"}, + {"NWL", "EP", SAVE_SRAM_256k, "Waialae Country Club: True Golf Classics"}, + {"NWQ", "E", SAVE_EEPROM_4k, "Rally Challenge 2000"}, + {"NWR", "EJP", SAVE_EEPROM_4k, "Wave Race 64"}, + {"NWT", "J", SAVE_EEPROM_4k, "Wetrix (Japan)"}, + {"NWU", "P", SAVE_EEPROM_4k, "Worms Armageddon (PAL)"}, + {"NWX", "EJP", SAVE_SRAM_256k, "WWF WrestleMania 2000"}, + {"NXO", "E", SAVE_EEPROM_4k, "Cruis'n Exotica"}, + {"NYK", "J", SAVE_EEPROM_4k, "Yakouchuu II: Satsujin Kouro"}, + {"NYS", "EJP", SAVE_EEPROM_16k, "Yoshi's Story"}, + {"NYW", "EJ", SAVE_SRAM_256k, "Harvest Moon 64"}, + {"NZL", "P", SAVE_SRAM_256k, "Legend of Zelda: Ocarina of Time (PAL)"}, + {"NZS", "EJP", SAVE_FLASH_1m, "Legend of Zelda: Majora's Mask"}, +}; +} \ No newline at end of file diff --git a/src/backend/RomHelpers.hpp b/src/backend/RomHelpers.hpp index 0129846c..8fff9d4f 100644 --- a/src/backend/RomHelpers.hpp +++ b/src/backend/RomHelpers.hpp @@ -3,66 +3,25 @@ #include namespace Util { -enum RomTypes { - Z64 = 0x80371240, - N64 = 0x40123780, - V64 = 0x37804012 -}; - -inline void GetRomCRC(size_t size, u8 *rom, u32 &crc) { - RomTypes endianness; - memcpy(&endianness, rom, 4); - endianness = static_cast(be32toh(endianness)); +#define Z64 0x80371240 +#define N64 0x40123780 +#define V64 0x37804012 +template +inline void SwapN64Rom(size_t size, u8 *rom, u32 endianness) { switch (endianness) { - case RomTypes::V64: { + case V64: SwapBuffer16(size, rom); - crc = crc32(0, rom, size); - } + if constexpr(!toBE) + SwapBuffer32(size, rom); break; - case RomTypes::N64: { - SwapBuffer32(size, rom); - crc = crc32(0, rom, size); - } + case N64: + if constexpr(toBE) + SwapBuffer32(size, rom); break; - case RomTypes::Z64: - crc = crc32(0, rom, size); - break; - default: - panic("Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!\n"); - } -} - -inline void SwapN64Rom(size_t size, u8 *rom, u32 &crc, u32 &cicChecksum) { - RomTypes endianness; - memcpy(&endianness, rom, 4); - endianness = static_cast(be32toh(endianness)); - - switch (endianness) { - case RomTypes::V64: { - u8 *temp = (u8 *) calloc(size, 1); - memcpy(temp, rom, size); - SwapBuffer16(size, temp); - crc = crc32(0, temp, size); - cicChecksum = crc32(0, &temp[0x40], 0x9c0); - free(temp); - SwapBuffer32(size, rom); - SwapBuffer16(size, rom); - } - break; - case RomTypes::N64: { - u8 *temp = (u8 *) calloc(size, 1); - memcpy(temp, rom, size); - SwapBuffer32(size, temp); - crc = crc32(0, temp, size); - cicChecksum = crc32(0, &temp[0x40], 0x9c0); - free(temp); - } - break; - case RomTypes::Z64: - crc = crc32(0, rom, size); - cicChecksum = crc32(0, &rom[0x40], 0x9c0); - SwapBuffer32(size, rom); + case Z64: + if constexpr(!toBE) + SwapBuffer32(size, rom); break; default: panic("Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!\n"); diff --git a/src/backend/core/Mem.cpp b/src/backend/core/Mem.cpp index 291e52a2..270a17c7 100644 --- a/src/backend/core/Mem.cpp +++ b/src/backend/core/Mem.cpp @@ -8,7 +8,7 @@ namespace n64 { Mem::Mem() { - cart = (u8*)calloc(CART_SIZE, 1); + rom.cart = (u8*)calloc(CART_SIZE, 1); sram = (u8*)calloc(SRAM_SIZE, 1); Reset(); } @@ -24,12 +24,27 @@ void Mem::Reset() { writePages[i] = pointer; } - memset(cart, 0, CART_SIZE); + memset(rom.cart, 0, CART_SIZE); memset(sram, 0, SRAM_SIZE); mmio.Reset(); } -CartInfo Mem::LoadROM(const std::string& filename) { +inline void SetROMCIC(u32 checksum, ROM& rom) { + switch (checksum) { + case 0xEC8B1325: rom.cicType = CIC_NUS_7102; break; // 7102 + case 0x1DEB51A9: rom.cicType = CIC_NUS_6101; break; // 6101 + case 0xC08E5BD6: rom.cicType = CIC_NUS_6102_7101; break; + case 0x03B8376A: rom.cicType = CIC_NUS_6103_7103; break; + case 0xCF7F41DC: rom.cicType = CIC_NUS_6105_7105; break; + case 0xD1059C6A: rom.cicType = CIC_NUS_6106_7106; break; + default: + Util::warn("Could not determine CIC TYPE! Checksum: 0x{:08X} is unknown!\n", checksum); + rom.cicType = UNKNOWN_CIC_TYPE; + break; + } +} + +void Mem::LoadROM(const std::string& filename) { std::ifstream file(filename, std::ios::binary); file.unsetf(std::ios::skipws); @@ -39,24 +54,46 @@ CartInfo Mem::LoadROM(const std::string& filename) { file.seekg(0, std::ios::end); size_t size = file.tellg(); - size_t sizeAdjusted = Util::NextPow2(size); - romMask = sizeAdjusted - 1; file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(cart), size); - + size_t sizeAdjusted = Util::NextPow2(size); + rom.mask = sizeAdjusted - 1; + u8* buf = (u8*)malloc(sizeAdjusted); + file.read(reinterpret_cast(buf), size); file.close(); - CartInfo result{}; + u32 endianness = be32toh(*reinterpret_cast(buf)); + Util::SwapN64Rom(sizeAdjusted, buf, endianness); - u32 cicChecksum; - Util::SwapN64Rom(sizeAdjusted, cart, result.crc, cicChecksum); - memcpy(mmio.rsp.dmem, cart, 0x1000); + memcpy(rom.cart, buf, sizeAdjusted); + rom.size = sizeAdjusted; + memcpy(&rom.header, buf, sizeof(ROMHeader)); + memcpy(rom.gameNameCart, rom.header.imageName, sizeof(rom.header.imageName)); - SetCICType(result.cicType, cicChecksum); - result.isPAL = IsROMPAL(); + rom.header.clockRate = be32toh(rom.header.clockRate); + rom.header.programCounter = be32toh(rom.header.programCounter); + rom.header.release = be32toh(rom.header.release); + rom.header.crc1 = be32toh(rom.header.crc1); + rom.header.crc2 = be32toh(rom.header.crc2); + rom.header.unknown = be64toh(rom.header.unknown); + rom.header.unknown2 = be32toh(rom.header.unknown2); + rom.header.manufacturerId = be32toh(rom.header.manufacturerId); + rom.header.cartridgeId = be16toh(rom.header.cartridgeId); - return result; + rom.code[0] = rom.header.manufacturerId & 0xFF; + rom.code[1] = (rom.header.cartridgeId >> 8) & 0xFF; + rom.code[2] = rom.header.cartridgeId & 0xFF; + rom.code[3] = '\0'; + + for (int i = sizeof(rom.header.imageName) - 1; rom.gameNameCart[i] == ' '; i--) { + rom.gameNameCart[i] = '\0'; + } + + u32 checksum = Util::crc32(0, &rom.cart[0x40], 0x9c0); + SetROMCIC(checksum, rom); + endianness = be32toh(*reinterpret_cast(rom.cart)); + Util::SwapN64Rom(sizeAdjusted, rom.cart, endianness); + rom.pal = IsROMPAL(); } u8 Mem::Read8(n64::Registers ®s, u32 paddr) { @@ -88,7 +125,7 @@ u8 Mem::Read8(n64::Registers ®s, u32 paddr) { } case CART_REGION: paddr = (paddr + 2) & ~2; - return cart[BYTE_ADDRESS(paddr) & romMask]; + return rom.cart[BYTE_ADDRESS(paddr) & rom.mask]; case 0x1FC00000 ... 0x1FC007BF: return si.pif.pifBootrom[BYTE_ADDRESS(paddr) - 0x1FC00000]; case PIF_RAM_REGION: @@ -128,7 +165,7 @@ u16 Mem::Read16(n64::Registers ®s, u32 paddr) { return mmio.Read(paddr); case 0x10000000 ... 0x1FBFFFFF: paddr = (paddr + 2) & ~3; - return Util::ReadAccess(cart, HALF_ADDRESS(paddr) & romMask); + return Util::ReadAccess(rom.cart, HALF_ADDRESS(paddr) & rom.mask); case 0x1FC00000 ... 0x1FC007BF: return Util::ReadAccess(si.pif.pifBootrom, HALF_ADDRESS(paddr) - 0x1FC00000); case PIF_RAM_REGION: @@ -165,7 +202,7 @@ u32 Mem::Read32(n64::Registers ®s, u32 paddr) { case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: return mmio.Read(paddr); case 0x10000000 ... 0x1FBFFFFF: - return Util::ReadAccess(cart, paddr & romMask); + return Util::ReadAccess(rom.cart, paddr & rom.mask); case 0x1FC00000 ... 0x1FC007BF: return Util::ReadAccess(si.pif.pifBootrom, paddr - 0x1FC00000); case PIF_RAM_REGION: @@ -201,7 +238,7 @@ u64 Mem::Read64(n64::Registers ®s, u32 paddr) { case 0x04500000 ... 0x048FFFFF: return mmio.Read(paddr); case 0x10000000 ... 0x1FBFFFFF: - return Util::ReadAccess(cart, paddr & romMask); + return Util::ReadAccess(rom.cart, paddr & rom.mask); case 0x1FC00000 ... 0x1FC007BF: return Util::ReadAccess(si.pif.pifBootrom, paddr - 0x1FC00000); case PIF_RAM_REGION: @@ -274,7 +311,10 @@ void Mem::Write8(Registers& regs, u32 paddr, u32 val) { case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF: case 0x08000000 ... 0x0FFFFFFF: + Util::debug("SRAM 8 bit write {:02X}\n", val); + break; case 0x04900000 ... 0x07FFFFFF: + case 0x1FC00000 ... 0x1FC007BF: case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break; @@ -323,6 +363,7 @@ void Mem::Write16(Registers& regs, u32 paddr, u32 val) { case 0x04200000 ... 0x042FFFFF: case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF: + case 0x1FC00000 ... 0x1FC007BF: case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break; @@ -373,7 +414,8 @@ void Mem::Write32(Registers& regs, u32 paddr, u32 val) { break; case 0x00800000 ... 0x03FFFFFF: case 0x04200000 ... 0x042FFFFF: case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF: - case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break; + case 0x1FC00000 ... 0x1FC007BF: case 0x1FC00800 ... 0x7FFFFFFF: + case 0x80000000 ... 0xFFFFFFFF: break; default: Util::panic("Unimplemented 32-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", paddr, val, (u64)regs.pc); } } @@ -414,6 +456,7 @@ void Mem::Write64(Registers& regs, u32 paddr, u64 val) { case 0x04200000 ... 0x042FFFFF: case 0x08000000 ... 0x0FFFFFFF: case 0x04900000 ... 0x07FFFFFF: + case 0x1FC00000 ... 0x1FC007BF: case 0x1FC00800 ... 0x7FFFFFFF: case 0x80000000 ... 0xFFFFFFFF: break; diff --git a/src/backend/core/Mem.hpp b/src/backend/core/Mem.hpp index f6abd6a9..a128c12f 100644 --- a/src/backend/core/Mem.hpp +++ b/src/backend/core/Mem.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace n64 { struct CartInfo { @@ -17,13 +18,41 @@ struct CartInfo { struct JIT; +struct ROMHeader { + u8 initialValues[4]; + u32 clockRate; + u32 programCounter; + u32 release; + u32 crc1; + u32 crc2; + u64 unknown; + char imageName[20]; + u32 unknown2; + u32 manufacturerId; + u16 cartridgeId; + char countryCode[2]; + u8 bootCode[4032]; +}; + +struct ROM { + u8* cart; + size_t size; + size_t mask; + ROMHeader header; + CICType cicType; + char gameNameCart[20]; + std::string gameNameDB; + char code[4]; + bool pal; +}; + struct Mem { ~Mem() { free(sram); } Mem(); void Reset(); - CartInfo LoadROM(const std::string&); + void LoadROM(const std::string&); [[nodiscard]] auto GetRDRAM() const -> u8* { return mmio.rdp.rdram; } @@ -73,47 +102,21 @@ struct Mem { fclose(fp); } uintptr_t writePages[PAGE_COUNT], readPages[PAGE_COUNT]; + ROM rom; + SaveType saveType; private: friend struct SI; friend struct PI; friend struct AI; friend struct RSP; friend struct Core; - u8* sram, *cart; + u8* sram; u8 isviewer[ISVIEWER_SIZE]{}; - size_t romMask = 0; - - static void SetCICType(u32& cicType, u32 checksum) { - switch(checksum) { - case 0xEC8B1325: // 7102 - cicType = CIC_NUS_7102; - break; - case 0x1DEB51A9: // 6101 - cicType = CIC_NUS_6101; - break; - case 0xC08E5BD6: - cicType = CIC_NUS_6102_7101; - break; - case 0x03B8376A: - cicType = CIC_NUS_6103_7103; - break; - case 0xCF7F41DC: - cicType = CIC_NUS_6105_7105; - break; - case 0xD1059C6A: - cicType = CIC_NUS_6106_7106; - break; - default: - Util::warn("Could not determine CIC TYPE! Checksum: {:08X} is unknown!\n", checksum); - cicType = UNKNOWN_CIC_TYPE; - break; - } - } bool IsROMPAL() { static const char pal_codes[] = {'D', 'F', 'I', 'P', 'S', 'U', 'X', 'Y'}; return std::any_of(std::begin(pal_codes), std::end(pal_codes), [this](char a) { - return cart[0x3e] == a; + return rom.cart[0x3e] == a; }); } }; diff --git a/src/backend/core/mmio/PI.cpp b/src/backend/core/mmio/PI.cpp index 89b49d99..11151f4b 100644 --- a/src/backend/core/mmio/PI.cpp +++ b/src/backend/core/mmio/PI.cpp @@ -52,7 +52,7 @@ void PI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { } rdLen = len; for(int i = 0; i < len; i++) { - mem.cart[BYTE_ADDRESS(cart_addr + i) & mem.romMask] = mem.mmio.rdp.rdram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE]; + mem.rom.cart[BYTE_ADDRESS(cart_addr + i) & mem.rom.mask] = mem.mmio.rdp.rdram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE]; } dramAddr = dram_addr + len; cartAddr = cart_addr + len; @@ -68,7 +68,7 @@ void PI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { } wrLen = len; for(int i = 0; i < len; i++) { - mem.mmio.rdp.rdram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE] = mem.cart[BYTE_ADDRESS(cart_addr + i) & mem.romMask]; + mem.mmio.rdp.rdram[BYTE_ADDRESS(dram_addr + i) & RDRAM_DSIZE] = mem.rom.cart[BYTE_ADDRESS(cart_addr + i) & mem.rom.mask]; } dramAddr = dram_addr + len; cartAddr = cart_addr + len; diff --git a/src/backend/core/mmio/PIF.cpp b/src/backend/core/mmio/PIF.cpp index d90d4ff0..94f8aa15 100644 --- a/src/backend/core/mmio/PIF.cpp +++ b/src/backend/core/mmio/PIF.cpp @@ -55,7 +55,22 @@ void PIF::ProcessPIFCommands(Mem& mem) { res[2] = controller.joy_x; res[3] = controller.joy_y; break; - case 2: case 3: res[0] = 0; break; + case 2: + Util::print("MEMPAK READ\n"); + res[0] = 0; + break; + case 3: + Util::print("MEMPAK WRITE\n"); + res[0] = 0; + break; + case 4: + Util::print("EEPROM READ\n"); + res[0] = 0; + break; + case 5: + Util::print("EEPROM WRITE\n"); + res[0] = 0; + break; default: Util::panic("Unimplemented PIF command {}", cmd[2]); } @@ -163,9 +178,7 @@ void PIF::UpdateController() { } } -void PIF::DoPIFHLE(Mem& mem, Registers& regs, CartInfo cartInfo) { - u32 cicType = cartInfo.cicType; - bool pal = cartInfo.isPAL; +void PIF::DoPIFHLE(Mem& mem, Registers& regs, bool pal, CICType cicType) { mem.Write32(regs, PIF_RAM_REGION_START + 0x24, cicSeeds[cicType]); switch(cicType) { @@ -426,12 +439,13 @@ void PIF::DoPIFHLE(Mem& mem, Registers& regs, CartInfo cartInfo) { regs.gpr[22] = (cicSeeds[cicType] >> 8) & 0xFF; regs.cop0.Reset(); mem.Write32(regs, 0x04300004, 0x01010101); + memcpy(mem.mmio.rsp.dmem, mem.rom.cart, 0x1000); regs.SetPC32(0xA4000040); } -void PIF::ExecutePIF(Mem& mem, Registers& regs, CartInfo cartInfo) { - u32 cicType = cartInfo.cicType; - bool pal = cartInfo.isPAL; +void PIF::ExecutePIF(Mem& mem, Registers& regs) { + CICType cicType = mem.rom.cicType; + bool pal = mem.rom.pal; mem.Write32(regs, PIF_RAM_REGION_START + 0x24, cicSeeds[cicType]); switch(cicType) { case UNKNOWN_CIC_TYPE: @@ -447,6 +461,6 @@ void PIF::ExecutePIF(Mem& mem, Registers& regs, CartInfo cartInfo) { break; } - DoPIFHLE(mem, regs, cartInfo); + DoPIFHLE(mem, regs, pal, cicType); } } \ No newline at end of file diff --git a/src/backend/core/mmio/PIF.hpp b/src/backend/core/mmio/PIF.hpp index cb671e1f..2f89c7a4 100644 --- a/src/backend/core/mmio/PIF.hpp +++ b/src/backend/core/mmio/PIF.hpp @@ -44,10 +44,10 @@ constexpr u32 cicSeeds[] = { 0x0, 0x00043F3F, // CIC_NUS_6101 0x00043F3F, // CIC_NUS_7102 - 0x00003F3F, // CIC_NUS_6102_7101 - 0x0000783F, // CIC_NUS_6103_7103 - 0x0000913F, // CIC_NUS_6105_7105 - 0x0000853F, // CIC_NUS_6106_7106 + 0x00043F3F, // CIC_NUS_6102_7101 + 0x00047878, // CIC_NUS_6103_7103 + 0x00049191, // CIC_NUS_6105_7105 + 0x00048585, // CIC_NUS_6106_7106 }; enum CICType { @@ -64,12 +64,23 @@ struct CartInfo; struct PIF { void ProcessPIFCommands(Mem&); - void ExecutePIF(Mem& mem, Registers& regs, CartInfo cartInfo); - void DoPIFHLE(Mem& mem, Registers& regs, CartInfo cartInfo); + void ExecutePIF(Mem& mem, Registers& regs); + void DoPIFHLE(Mem& mem, Registers& regs, bool pal, CICType cicType); void UpdateController(); bool gamepadConnected = false; SDL_GameController* gamepad; Controller controller; u8 pifBootrom[PIF_BOOTROM_SIZE]{}, pifRam[PIF_RAM_SIZE]; + u8 Read(u32 addr) { + addr &= 0x7FF; + if(addr < 0x7c0) return pifBootrom[addr]; + return pifRam[addr]; + } + + void Write(u32 addr, u8 val) { + addr &= 0x7FF; + if(addr < 0x7c0) return; + pifRam[addr] = val; + } }; } \ No newline at end of file diff --git a/src/backend/core/mmio/SI.cpp b/src/backend/core/mmio/SI.cpp index 94aa4df7..1d08a609 100644 --- a/src/backend/core/mmio/SI.cpp +++ b/src/backend/core/mmio/SI.cpp @@ -44,7 +44,6 @@ void DMA(Mem& mem, Registers& regs) { for(int i = 0; i < 64; i++) { si.pif.pifRam[i] = mem.mmio.rdp.rdram[BYTE_ADDRESS(si.dramAddr + i)]; } - Util::debug("SI DMA from PIF RAM to RDRAM ({:08X} to {:08X})\n", si.pifAddr, si.dramAddr); si.pif.ProcessPIFCommands(mem); } InterruptRaise(mem.mmio.mi, regs, Interrupt::SI); @@ -66,7 +65,7 @@ void SI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { pifAddr = val & 0x1FFFFFFF; status.dmaBusy = true; toDram = false; - scheduler.enqueueRelative({SI_DMA_DELAY, DMA}); + scheduler.enqueueRelative({4065*3, DMA}); Util::debug("SI DMA from RDRAM to PIF RAM ({:08X} to {:08X})\n", dramAddr, pifAddr); } break; case 0x04800018: diff --git a/src/frontend/imgui/Window.cpp b/src/frontend/imgui/Window.cpp index 19ef7b89..2fd212d5 100644 --- a/src/frontend/imgui/Window.cpp +++ b/src/frontend/imgui/Window.cpp @@ -151,22 +151,8 @@ ImDrawData* Window::Present(n64::Core& core) { void Window::LoadROM(n64::Core& core, const std::string &path) { if(!path.empty()) { - n64::CartInfo cartInfo = core.LoadROM(path); - std::ifstream gameDbFile("resources/db.json"); - json gameDb = json::parse(gameDbFile); - gameName = ""; - - for(const auto& item : gameDb["items"]) { - auto crc = item["crc"]; - if(!crc.empty()) { - if(crc.get() == fmt::format("{:08X}", cartInfo.crc)) { - auto name = item["name"]; - if(!name.empty()) { - gameName = name.get(); - } - } - } - }; + core.LoadROM(path); + gameName = core.cpu->mem.rom.gameNameDB; if(gameName.empty()) { gameName = fs::path(path).stem().string(); @@ -177,7 +163,6 @@ void Window::LoadROM(n64::Core& core, const std::string &path) { shadowWindowTitle = windowTitle; SDL_SetWindowTitle(window, windowTitle.c_str()); - gameDbFile.close(); } }