From bbd5119a267adc8cd082df9a3f1ef372f5c44d01 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 Oct 2021 12:01:35 +0200 Subject: [PATCH 01/19] Backends: Vulkan: comments about user using VK_DYNAMIC_STATE_VIEWPORT / VK_DYNAMIC_STATE_SCISSOR and not calling vkCmdSetViewport() / vkCmdSetScissor(). (#4644) --- backends/imgui_impl_vulkan.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 296304fa..280edea7 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -537,6 +537,14 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm global_idx_offset += cmd_list->IdxBuffer.Size; global_vtx_offset += cmd_list->VtxBuffer.Size; } + + // 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. + // 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. + // If we add a call to vkCmdSetScissor() to cover the full viewport that would fix things for majority of users BUT would made it harder to notice your bug. (See github #4644) } bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) From 19c3773cc09fffdf950c3e88273f0a15adad1176 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 Oct 2021 12:11:11 +0200 Subject: [PATCH 02/19] Backends: Vulkan: Call vkCmdSetScissor() with a full-viewport at end of render. (#4644) --- backends/imgui_impl_vulkan.cpp | 5 ++++- docs/CHANGELOG.txt | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 280edea7..e4f12f2b 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -23,6 +23,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 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. // 2021-02-18: Vulkan: Change blending equation to preserve alpha in output buffer. @@ -544,7 +545,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // - And you forgot to call vkCmdSetViewport() and vkCmdSetScissor() yourself to explicitely 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. - // If we add a call to vkCmdSetScissor() to cover the full viewport that would fix things for majority of users BUT would made it harder to notice your bug. (See github #4644) + // 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) + VkRect2D scissor = { { 0, 0 }, { (uint32_t)fb_width, (uint32_t)fb_height } }; + vkCmdSetScissor(command_buffer, 0, 1, &scissor); } bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2ff3373a..7d58039c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -38,6 +38,9 @@ Breaking Changes: Other Changes: +- Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce + likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling + vkCmdSetScissor() explicitly every frame. (#4644) - Misc: Fix MinGW DLL build issue (when IMGUI_API is defined). [@rokups] - CI: Add MinGW DLL build to test suite. [@rokups] From ad3f54ff1a8e380fc79079d657ef108fa1cf8e2d Mon Sep 17 00:00:00 2001 From: liam Date: Tue, 19 Oct 2021 14:13:44 +0200 Subject: [PATCH 03/19] Examples: Emscripten comments (#4650, #4662), standardize readme --- examples/example_emscripten_opengl3/README.md | 6 +++++- examples/example_emscripten_wgpu/README.md | 20 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/examples/example_emscripten_opengl3/README.md b/examples/example_emscripten_opengl3/README.md index fddd9229..e2b19e44 100644 --- a/examples/example_emscripten_opengl3/README.md +++ b/examples/example_emscripten_opengl3/README.md @@ -1,8 +1,11 @@ ## How to Build - You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions -- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. + - Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools. + +- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. + - Then build using `make` while in the `example_emscripten_opengl3/` directory. ## How to Run @@ -15,6 +18,7 @@ _"Unfortunately several browsers (including Chrome, Safari, and Internet Explore - Emscripten SDK has a handy `emrun` command: `emrun web/example_emscripten_opengl3.html --browser firefox` which will spawn a temporary local webserver (in Firefox). See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details. - You may use Python 3 builtin webserver: `python -m http.server -d web` (this is what `make serve` uses). - You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`. + - If you are accessing the files over a network, certain browsers, such as Firefox, will restrict Gamepad API access to secure contexts only (e.g. https only). ## Obsolete features: diff --git a/examples/example_emscripten_wgpu/README.md b/examples/example_emscripten_wgpu/README.md index 73fabddc..c4c4dec7 100644 --- a/examples/example_emscripten_wgpu/README.md +++ b/examples/example_emscripten_wgpu/README.md @@ -1,10 +1,24 @@ - -# How to Build +## How to Build - You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions - Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools. +- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. + - Then build using `make` while in the `example_emscripten_wgpu/` directory. -- Requires Emscripten 2.0.10 (December 2020) due to GLFW adaptations +- Requires recent Emscripten as WGPU is still a work-in-progress API. + +## How to Run + +To run on a local machine: +- Make sure your browse supports WGPU and it is enabled. WGPU is still WIP not enabled by default in most browser. +- `make serve` will use Python3 to spawn a local webserver, you can then browse http://localhost:8000 to access your build. +- Otherwise, generally you will need a local webserver: + - Quoting [https://emscripten.org/docs/getting_started](https://emscripten.org/docs/getting_started/Tutorial.html#generating-html):
+_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can’t load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you’ll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_ + - Emscripten SDK has a handy `emrun` command: `emrun web/example_emscripten_opengl3.html --browser firefox` which will spawn a temporary local webserver (in Firefox). See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details. + - You may use Python 3 builtin webserver: `python -m http.server -d web` (this is what `make serve` uses). + - You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`. + - If you are accessing the files over a network, certain browsers, such as Firefox, will restrict Gamepad API access to secure contexts only (e.g. https only). From 39b99072fb0184cef373b2164ede1d15ca038a9a Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Fri, 22 Oct 2021 14:52:13 +0200 Subject: [PATCH 04/19] ImVector: fix Clang warning (implicit conversion changes signedness: 'const ptrdiff_t' (aka 'const long long') to 'unsigned long long') --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index cd41d540..584ced66 100644 --- a/imgui.h +++ b/imgui.h @@ -1742,7 +1742,7 @@ struct ImVector inline void pop_back() { IM_ASSERT(Size > 0); Size--; } inline void push_front(const T& v) { if (Size == 0) push_back(v); else insert(Data, v); } inline T* erase(const T* it) { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(T)); Size--; return Data + off; } - inline T* erase(const T* it, const T* it_last){ IM_ASSERT(it >= Data && it < Data + Size && it_last > it && it_last <= Data + Size); const ptrdiff_t count = it_last - it; const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - count) * sizeof(T)); Size -= (int)count; return Data + off; } + inline T* erase(const T* it, const T* it_last){ IM_ASSERT(it >= Data && it < Data + Size && it_last > it && it_last <= Data + Size); const ptrdiff_t count = it_last - it; const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - (size_t)count) * sizeof(T)); Size -= (int)count; return Data + off; } inline T* erase_unsorted(const T* it) { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; if (it < Data + Size - 1) memcpy(Data + off, Data + Size - 1, sizeof(T)); Size--; return Data + off; } inline T* insert(const T* it, const T& v) { IM_ASSERT(it >= Data && it <= Data + Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(T)); memcpy(&Data[off], &v, sizeof(v)); Size++; return Data + off; } inline bool contains(const T& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } From 2318c764cff691804cedd66e7a1194e8fc53ec3d Mon Sep 17 00:00:00 2001 From: Chris Savoie Date: Tue, 22 Jun 2021 15:00:46 +0200 Subject: [PATCH 05/19] Inputs: Mouse: Add support for tracking multiple clicks more than just double (#3229) --- imgui.cpp | 30 +++++++++++++++++++++--------- imgui.h | 6 ++++-- imgui_demo.cpp | 10 ++++++---- imgui_widgets.cpp | 12 ++++++------ 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 63682668..10ded4b8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3695,22 +3695,26 @@ static void ImGui::UpdateMouseInputs() g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; - g.IO.MouseDoubleClicked[i] = false; + g.IO.MouseMultiClickCount[i] = 0; if (g.IO.MouseClicked[i]) { if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime) { ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) - g.IO.MouseDoubleClicked[i] = true; - g.IO.MouseClickedTime[i] = -g.IO.MouseDoubleClickTime * 2.0f; // Mark as "old enough" so the third click isn't turned into a double-click + g.IO.MouseMultiClickTracker[i]++; + else + g.IO.MouseMultiClickTracker[i] = 1; } else { - g.IO.MouseClickedTime[i] = g.Time; + g.IO.MouseMultiClickTracker[i] = 1; } + + g.IO.MouseClickedTime[i] = g.Time; g.IO.MouseClickedPos[i] = g.IO.MousePos; - g.IO.MouseDownWasDoubleClick[i] = g.IO.MouseDoubleClicked[i]; + g.IO.MouseMultiClickCount[i] = g.IO.MouseMultiClickTracker[i]; + g.IO.MouseDownMultiClickCount[i] = g.IO.MouseMultiClickTracker[i]; g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; } @@ -3722,8 +3726,9 @@ static void ImGui::UpdateMouseInputs() g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x); g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); } + if (!g.IO.MouseDown[i] && !g.IO.MouseReleased[i]) - g.IO.MouseDownWasDoubleClick[i] = false; + g.IO.MouseDownMultiClickCount[i] = 0; if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation g.NavDisableMouseHover = false; } @@ -4765,7 +4770,14 @@ bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseDoubleClicked[button]; + return g.IO.MouseMultiClickCount[button] == 2; +} + +bool ImGui::IsMouseTripleClicked(ImGuiMouseButton button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseMultiClickCount[button] == 3; } // Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. @@ -5445,7 +5457,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s if (hovered || held) g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; - if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + if (held && g.IO.MouseMultiClickCount[0] == 2 && resize_grip_n == 0) { // Manual auto-fit when double-clicking size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit); @@ -5980,7 +5992,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. ImRect title_bar_rect = window->TitleBarRect(); - if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseMultiClickCount[0] == 2) window->WantCollapseToggle = true; if (window->WantCollapseToggle) { diff --git a/imgui.h b/imgui.h index 584ced66..f641ddbf 100644 --- a/imgui.h +++ b/imgui.h @@ -900,6 +900,7 @@ namespace ImGui IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down) IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down) IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? (note that a double-click will also report IsMouseClicked() == true) + IMGUI_API bool IsMouseTripleClicked(ImGuiMouseButton button); // did mouse button triple-clicked? (note that a triple-click will also report IsMouseClicked() == true) IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available IMGUI_API bool IsAnyMouseDown(); // is any mouse button held? @@ -1931,11 +1932,12 @@ struct ImGuiIO ImVec2 MouseClickedPos[5]; // Position at time of clicking double MouseClickedTime[5]; // Time of last click (used to figure out double-click) bool MouseClicked[5]; // Mouse button went from !Down to Down - bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + char MouseMultiClickTracker[5]; // Track multiple clicks over multiple frames + char MouseMultiClickCount[5]; // Has mouse button been clicked multiple times in a row? bool MouseReleased[5]; // Mouse button went from Down to !Down bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds. bool MouseDownOwnedUnlessPopupClose[5];//Track if button was clicked inside a dear imgui window. - bool MouseDownWasDoubleClick[5]; // Track if button down was a double-click + char MouseDownMultiClickCount[5]; // Track number of mouse down clicks in a row float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) float MouseDownDurationPrev[5]; // Previous time the mouse button has been down ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 6af064af..13e92478 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -5518,10 +5518,12 @@ static void ShowDemoWindowMisc() else ImGui::Text("Mouse pos: "); ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); - ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } - ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); 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 clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse tripleclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseTripleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse clickcount:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseMultiClickTracker[i]) { ImGui::SameLine(); ImGui::Text("b%d (%d)", i, io.MouseMultiClickTracker[i]); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused ImGui::TreePop(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 0ad4f9a1..c83f064c 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -564,7 +564,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetFocusID(id, window); FocusWindow(window); } - if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[mouse_button_clicked])) + if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseMultiClickCount[mouse_button_clicked] == 2)) { pressed = true; if (flags & ImGuiButtonFlags_NoHoldingActiveId) @@ -641,7 +641,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if ((release_in || release_anywhere) && !g.DragDropActive) { // Report as pressed when releasing the mouse (this is the most common path) - bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[mouse_button]; + bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownMultiClickCount[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) pressed = true; @@ -2409,7 +2409,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, { 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.MouseDoubleClicked[0]); + const bool double_clicked = (hovered && g.IO.MouseMultiClickCount[0] == 2); if (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id) { SetActiveID(id, window); @@ -4176,12 +4176,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ 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 || (hovered && !is_osx && io.MouseDoubleClicked[0])) + if (select_all || (hovered && !is_osx && io.MouseMultiClickCount[0] == 2)) { state->SelectAll(); state->SelectedAllMouseLock = true; } - else if (hovered && is_osx && io.MouseDoubleClicked[0]) + else if (hovered && is_osx && io.MouseMultiClickCount[0] == 2) { // Double-click select a word only, OS X style (by simulating keystrokes) state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); @@ -5919,7 +5919,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l toggled = true; if (flags & ImGuiTreeNodeFlags_OpenOnArrow) toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job - if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0]) + if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseMultiClickCount[0] == 2) toggled = true; } else if (pressed && g.DragDropHoldJustPressedId == id) From 9c78fc928a18ffa4693d3a1fddcf65fb2ea9878d Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Jun 2021 15:27:30 +0200 Subject: [PATCH 06/19] Inputs: Mouse: Amend c8e3a01 for tracking multiple clicks, renaming. (#3229) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 33 ++++++++++++++++----------------- imgui.h | 8 ++++---- imgui_demo.cpp | 16 ++++++++++------ imgui_widgets.cpp | 12 ++++++------ 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 7d58039c..57e660a4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -38,6 +38,7 @@ Breaking Changes: Other Changes: +- Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] - Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling vkCmdSetScissor() explicitly every frame. (#4644) diff --git a/imgui.cpp b/imgui.cpp index 10ded4b8..2a7433ef 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3692,29 +3692,26 @@ static void ImGui::UpdateMouseInputs() for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseClickedCount[i] = 0; // Will be filled below g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; - g.IO.MouseMultiClickCount[i] = 0; if (g.IO.MouseClicked[i]) { + bool is_repeated_click = false; if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime) { ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) - g.IO.MouseMultiClickTracker[i]++; - else - g.IO.MouseMultiClickTracker[i] = 1; + is_repeated_click = true; } + if (is_repeated_click) + g.IO.MouseClickedLastCount[i]++; else - { - g.IO.MouseMultiClickTracker[i] = 1; - } - + g.IO.MouseClickedLastCount[i] = 1; g.IO.MouseClickedTime[i] = g.Time; g.IO.MouseClickedPos[i] = g.IO.MousePos; - g.IO.MouseMultiClickCount[i] = g.IO.MouseMultiClickTracker[i]; - g.IO.MouseDownMultiClickCount[i] = g.IO.MouseMultiClickTracker[i]; + g.IO.MouseClickedCount[i] = g.IO.MouseClickedLastCount[i]; g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; } @@ -3727,9 +3724,11 @@ static void ImGui::UpdateMouseInputs() g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); } - if (!g.IO.MouseDown[i] && !g.IO.MouseReleased[i]) - g.IO.MouseDownMultiClickCount[i] = 0; - if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + // We provide io.MouseDoubleClicked[] as a legacy service + g.IO.MouseDoubleClicked[i] = (g.IO.MouseClickedCount[i] == 2); + + // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + if (g.IO.MouseClicked[i]) g.NavDisableMouseHover = false; } } @@ -4770,14 +4769,14 @@ bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseMultiClickCount[button] == 2; + return g.IO.MouseClickedCount[button] == 2; } bool ImGui::IsMouseTripleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseMultiClickCount[button] == 3; + return g.IO.MouseClickedCount[button] == 3; } // Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. @@ -5457,7 +5456,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s if (hovered || held) g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; - if (held && g.IO.MouseMultiClickCount[0] == 2 && resize_grip_n == 0) + if (held && g.IO.MouseClickedCount[0] == 2 && resize_grip_n == 0) { // Manual auto-fit when double-clicking size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit); @@ -5992,7 +5991,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. ImRect title_bar_rect = window->TitleBarRect(); - if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseMultiClickCount[0] == 2) + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseClickedCount[0] == 2) window->WantCollapseToggle = true; if (window->WantCollapseToggle) { diff --git a/imgui.h b/imgui.h index f641ddbf..53c9971b 100644 --- a/imgui.h +++ b/imgui.h @@ -1931,13 +1931,13 @@ struct ImGuiIO 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 double MouseClickedTime[5]; // Time of last click (used to figure out double-click) - bool MouseClicked[5]; // Mouse button went from !Down to Down - char MouseMultiClickTracker[5]; // Track multiple clicks over multiple frames - char MouseMultiClickCount[5]; // Has mouse button been clicked multiple times in a row? + bool MouseClicked[5]; // Mouse button went from !Down to Down (same as MouseClickedCount[x] != 0) + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? (same as MouseClickedCount[x] == 2) + ImU16 MouseClickedCount[5]; // == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to Down + ImU16 MouseClickedLastCount[5]; // Count successive number of clicks. Stays valid after mouse release. Reset after another click is done. bool MouseReleased[5]; // Mouse button went from Down to !Down bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds. bool MouseDownOwnedUnlessPopupClose[5];//Track if button was clicked inside a dear imgui window. - char MouseDownMultiClickCount[5]; // Track number of mouse down clicks in a row float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) float MouseDownDurationPrev[5]; // Previous time the mouse button has been down ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 13e92478..195d89d2 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -5518,12 +5518,16 @@ static void ShowDemoWindowMisc() else ImGui::Text("Mouse pos: "); ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); - ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } - ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse tripleclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseTripleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse clickcount:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseMultiClickTracker[i]) { ImGui::SameLine(); ImGui::Text("b%d (%d)", i, io.MouseMultiClickTracker[i]); } - ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + + 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", i); } + ImGui::Text(" - clicked double:"); for (int i = 0; i < count; i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text(" - clicked triple:"); for (int i = 0; i < count; i++) if (ImGui::IsMouseTripleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text(" - clicked count:"); for (int i = 0; i < count; i++) if (io.MouseClickedCount[i]) { ImGui::SameLine(); ImGui::Text("b%d (%d)", i, io.MouseClickedCount[i]); } + //ImGui::Text(" - last count:"); for (int i = 0; i < count; i++) if (io.MouseClickedLastCount[i]) { ImGui::SameLine(); ImGui::Text("b%d (%d)", i, io.MouseClickedLastCount[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 wheel: %.1f", io.MouseWheel); ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused ImGui::TreePop(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c83f064c..687baa8e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -564,7 +564,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetFocusID(id, window); FocusWindow(window); } - if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseMultiClickCount[mouse_button_clicked] == 2)) + if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) { pressed = true; if (flags & ImGuiButtonFlags_NoHoldingActiveId) @@ -641,7 +641,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if ((release_in || release_anywhere) && !g.DragDropActive) { // Report as pressed when releasing the mouse (this is the most common path) - bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownMultiClickCount[mouse_button] == 2; + 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) pressed = true; @@ -2409,7 +2409,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, { 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.MouseMultiClickCount[0] == 2); + const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2); if (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id) { SetActiveID(id, window); @@ -4176,12 +4176,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ 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 || (hovered && !is_osx && io.MouseMultiClickCount[0] == 2)) + if (select_all || (hovered && !is_osx && io.MouseClickedCount[0] == 2)) { state->SelectAll(); state->SelectedAllMouseLock = true; } - else if (hovered && is_osx && io.MouseMultiClickCount[0] == 2) + else if (hovered && is_osx && io.MouseClickedCount[0] == 2) { // Double-click select a word only, OS X style (by simulating keystrokes) state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); @@ -5919,7 +5919,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l toggled = true; if (flags & ImGuiTreeNodeFlags_OpenOnArrow) toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job - if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseMultiClickCount[0] == 2) + if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2) toggled = true; } else if (pressed && g.DragDropHoldJustPressedId == id) From 126a6f894fd50dcfc1f6707048892ccb281cc420 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 25 Oct 2021 16:34:46 +0200 Subject: [PATCH 07/19] InputText: made double-click select word, triple-line select line. Word delimitation logic differs slightly from the one used by CTRL+arrows. (#2244) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 52 +++++++++++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 57e660a4..90033f79 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -39,6 +39,8 @@ Breaking Changes: Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] +- InputText: made double-click select word, triple-line select line. Word delimitation logic differs + slightly from the one used by CTRL+arrows. (#2244) - Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling vkCmdSetScissor() explicitly every frame. (#4644) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 687baa8e..71fca734 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3679,17 +3679,18 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* ob } // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. -static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r'; } static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; } +static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx])) : 1; } 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; } -#ifdef __APPLE__ // FIXME: Move setting to IO structure -static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; } -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(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; } -#else -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(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; } -#endif +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; } #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 +#ifdef __APPLE__ // FIXME: Move setting to IO structure +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_MAC +#else +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_WIN +#endif static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) { @@ -3881,11 +3882,12 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f // Generic named filters if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))) { - // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf. + // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'. // The standard mandate that programs starts in the "C" locale where the decimal point is '.'. // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point. // Change the default decimal_point with: // ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point; + // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions. ImGuiContext& g = *GImGui; const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint; @@ -4176,19 +4178,41 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ 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 || (hovered && !is_osx && io.MouseClickedCount[0] == 2)) + if (select_all) { state->SelectAll(); state->SelectedAllMouseLock = true; } - else if (hovered && is_osx && io.MouseClickedCount[0] == 2) + else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift) { - // Double-click select a word only, OS X style (by simulating keystrokes) - state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); - state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + const int multiclick_count = (io.MouseClickedCount[0] - 2); + if ((multiclick_count % 2) == 0) + { + // Double-click: Select word + // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: + // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) + const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n'; + if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + if (!STB_TEXT_HAS_SELECTION(&state->Stb)) + ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb); + state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor); + state->Stb.select_end = state->Stb.cursor; + ImStb::stb_textedit_clamp(state, &state->Stb); + } + else + { + // Triple-click: Select line + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); + state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); + } + state->CursorAnimReset(); } 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); From 99552420a259076e6a622ec3f9a1a1be14853198 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 25 Oct 2021 17:13:49 +0200 Subject: [PATCH 08/19] InputText: triple-click avoid scroll + include end of line character. (#2244) --- imgui_widgets.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 71fca734..784e1fc3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4205,8 +4205,16 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { // Triple-click: Select line + const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n'; state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); + state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); + if (!is_eol && is_multiline) + { + ImSwap(state->Stb.select_start, state->Stb.select_end); + state->Stb.cursor = state->Stb.select_end; + } + state->CursorFollow = false; } state->CursorAnimReset(); } From 0f2898ea884eb54d257c7826ef2231ce0211ef54 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 25 Oct 2021 18:31:16 +0200 Subject: [PATCH 09/19] Demo: Tree selection demo use !IsItemToggledOpen() (#1896) + update FAQ (#4366) --- docs/FAQ.md | 3 ++- imgui.cpp | 8 +++++--- imgui_demo.cpp | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 479bce06..8f67d4e7 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -23,7 +23,7 @@ or view this file with any Markdown viewer. | [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) | | [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | | **Q&A: Usage** | -| **[About the ID Stack system..
Why is my widget not reacting when I click on it?
How can I have widgets with an empty label?
How can I have multiple widgets with the same label?](#q-about-the-id-stack-system)** | +| **[About the ID Stack system..
Why is my widget not reacting when I click on it?
How can I have widgets with an empty label?
How can I have multiple widgets with the same label?]
How can I have multiple windows with the same label?](#q-about-the-id-stack-system)** | | [How can I display an image? What is ImTextureID, how does it work?](#q-how-can-i-display-an-image-what-is-imtextureid-how-does-it-work)| | [How can I use my own math types instead of ImVec2/ImVec4?](#q-how-can-i-use-my-own-math-types-instead-of-imvec2imvec4) | | [How can I interact with standard C++ types (such as std::string and std::vector)?](#q-how-can-i-interact-with-standard-c-types-such-as-stdstring-and-stdvector) | @@ -190,6 +190,7 @@ Refer to rendering backends in the [examples/](https://github.com/ocornut/imgui/ ### Q: Why is my widget not reacting when I click on it? ### Q: How can I have widgets with an empty label? ### Q: How can I have multiple widgets with the same label? +### Q: How can I have multiple windows with the same label? A primer on labels and the ID Stack... diff --git a/imgui.cpp b/imgui.cpp index 2a7433ef..880c9334 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -723,9 +723,11 @@ CODE Q&A: Usage ---------- - Q: Why is my widget not reacting when I click on it? - Q: How can I have widgets with an empty label? - Q: How can I have multiple widgets with the same label? + Q: About the ID Stack system.. + - Why is my widget not reacting when I click on it? + - How can I have widgets with an empty label? + - How can I have multiple widgets with the same label? + - How can I have multiple windows with the same label? Q: How can I display an image? What is ImTextureID, how does it works? 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)? diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 195d89d2..29bcf132 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -786,6 +786,7 @@ static void ShowDemoWindowWidgets() for (int i = 0; i < 6; i++) { // Disable the default "open on single-click behavior" + set Selected flag according to our selection. + // To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection. ImGuiTreeNodeFlags node_flags = base_flags; const bool is_selected = (selection_mask & (1 << i)) != 0; if (is_selected) @@ -794,7 +795,7 @@ static void ShowDemoWindowWidgets() { // Items 0..2 are Tree Node bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); - if (ImGui::IsItemClicked()) + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) node_clicked = i; if (test_drag_and_drop && ImGui::BeginDragDropSource()) { @@ -815,7 +816,7 @@ static void ShowDemoWindowWidgets() // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text(). node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); - if (ImGui::IsItemClicked()) + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) node_clicked = i; if (test_drag_and_drop && ImGui::BeginDragDropSource()) { From c363b6df2b4d4aeb7978b71b03ae5002bd5ae6ca Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Oct 2021 12:02:42 +0200 Subject: [PATCH 10/19] Modals: fixed issue hovering popups inside a child inside a modal. Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676)(#4676, #4527) Amend/fix 6b1e094f, fc4988ffb (Sep 24) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 90033f79..0ae0d0b8 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -39,6 +39,8 @@ Breaking Changes: Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] +- Modals: fixed issue hovering popups inside a child inside a modal. (#4676, #4527) +- Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676) - InputText: made double-click select word, triple-line select line. Word delimitation logic differs slightly from the one used by CTRL+arrows. (#2244) - Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce diff --git a/imgui.cpp b/imgui.cpp index 880c9334..912d6c9d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6719,9 +6719,14 @@ void ImGui::PopTextWrapPos() static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy) { - window = window->RootWindow; - if (popup_hierarchy) - window = window->RootWindowPopupTree; + ImGuiWindow* last_window = NULL; + while (last_window != window) + { + last_window = window; + window = window->RootWindow; + if (popup_hierarchy) + window = window->RootWindowPopupTree; + } return window; } From 19c72cd52a47a5184159c640b9cffe67063aa3f3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Oct 2021 12:28:01 +0200 Subject: [PATCH 11/19] Nav: fixed absolute mouse position (with NavEnableSetMousePos config flag) when using Home/End leads to scrolling. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 2 +- imgui.h | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0ae0d0b8..e1eaca44 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,8 @@ Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] - Modals: fixed issue hovering popups inside a child inside a modal. (#4676, #4527) - Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676) +- Nav: fixed absolute mouse position (with NavEnableSetMousePos config flag) when using Home/End + leads to scrolling. - InputText: made double-click select word, triple-line select line. Word delimitation logic differs slightly from the one used by CTRL+arrows. (#2244) - Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce diff --git a/imgui.cpp b/imgui.cpp index 912d6c9d..4841b472 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9619,7 +9619,7 @@ void ImGui::NavMoveRequestApplyResult() { // FIXME: Should remove this float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; - delta_scroll.y = result->Window->Scroll.y - scroll_target; + delta_scroll.y = scroll_target - result->Window->Scroll.y; SetScrollY(result->Window, scroll_target); } else diff --git a/imgui.h b/imgui.h index 53c9971b..db1d96cb 100644 --- a/imgui.h +++ b/imgui.h @@ -64,7 +64,7 @@ Index of this file: // 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.86 WIP" -#define IMGUI_VERSION_NUM 18502 +#define IMGUI_VERSION_NUM 18503 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE From 8361ed1f543a74c1d5fba1df4a2c5084f889a237 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 26 Oct 2021 19:23:04 +0200 Subject: [PATCH 12/19] Nav: wrap abs<>rel rectangle conversions before we attempt to switch the reference point from window->Pos to window->DC.CursorStartPos. This commit should have no effect. Current point makes rectangle invalid right after a scroll, for interactive actions it's rarely a problem but e.g. clipper will want to use g.NavID rect rel while scrolling. (#3841) --- imgui.cpp | 26 +++++++++++++------------- imgui_internal.h | 2 ++ imgui_widgets.cpp | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4841b472..045ac2a7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2268,7 +2268,7 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items if (g.NavMoveScoringItems) unclipped_rect.Add(g.NavScoringRect); if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId) - unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max)); // Could store and use NavJustMovedToRectRel + unclipped_rect.Add(WindowRectRelToAbs(window, window->NavRectRel[0])); // Could store and use NavJustMovedToRectRel const ImVec2 pos = window->DC.CursorPos; int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); @@ -7127,7 +7127,7 @@ void ImGui::SetItemDefaultFocus() g.NavInitRequest = false; g.NavInitResultId = g.LastItemData.ID; - g.NavInitResultRectRel = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos); + 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) @@ -8829,7 +8829,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) - window->NavRectRel[nav_layer] = ImRect(g.LastItemData.NavRect.Min - window->Pos, g.LastItemData.NavRect.Max - window->Pos); + window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); if (g.ActiveIdSource == ImGuiInputSource_Nav) g.NavDisableMouseHover = true; @@ -9009,7 +9009,7 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) result->ID = g.LastItemData.ID; result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; result->InFlags = g.LastItemData.InFlags; - result->RectRel = ImRect(g.LastItemData.NavRect.Min - window->Pos, g.LastItemData.NavRect.Max - window->Pos); + result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); } // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) @@ -9030,7 +9030,7 @@ static void ImGui::NavProcessItem() if (candidate_for_nav_default_focus || g.NavInitResultId == 0) { g.NavInitResultId = id; - g.NavInitResultRectRel = ImRect(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + g.NavInitResultRectRel = WindowRectAbsToRel(window, nav_bb); } if (candidate_for_nav_default_focus) { @@ -9077,7 +9077,7 @@ static void ImGui::NavProcessItem() g.NavLayer = window->DC.NavLayerCurrent; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavIdIsAlive = true; - window->NavRectRel[window->DC.NavLayerCurrent] = ImRect(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); // Store item bounding box (relative to window position) + window->NavRectRel[window->DC.NavLayerCurrent] = WindowRectAbsToRel(window, nav_bb); // Store item bounding box (relative to window position) } } @@ -9242,8 +9242,8 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() else { // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item. - const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; - ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImRect rect_rel = WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[g.NavLayer]); + ImVec2 pos = ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); ImGuiViewport* viewport = GetMainViewport(); return ImFloor(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImFloor() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. } @@ -9456,7 +9456,7 @@ static void ImGui::NavUpdate() if (g.NavWindow) { ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow); - if (1) { for (int layer = 0; layer < 2; layer++) draw_list->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (1) { for (int layer = 0; layer < 2; layer++) { ImRect r = WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[layer]); draw_list->AddRect(r.Min, r.Max, IM_COL32(255,200,0,255)); } // [DEBUG] if (1) { ImU32 col = (!g.NavWindow->Hidden) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); draw_list->AddCircleFilled(p, 3.0f, col); draw_list->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } } #endif @@ -9549,7 +9549,7 @@ void ImGui::NavUpdateCreateMoveRequest() // (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) { - ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); + ImRect window_rect_rel = WindowRectAbsToRel(window, ImRect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1))); if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) { IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel\n"); @@ -9565,7 +9565,7 @@ void ImGui::NavUpdateCreateMoveRequest() if (window != NULL) { ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0); - scoring_rect = ImRect(window->Pos + nav_rect_rel.Min, window->Pos + nav_rect_rel.Max); + scoring_rect = WindowRectRelToAbs(window, nav_rect_rel); 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; @@ -9624,7 +9624,7 @@ void ImGui::NavMoveRequestApplyResult() } else { - ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); + ImRect rect_abs = WindowRectRelToAbs(result->Window, result->RectRel); delta_scroll = ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags); } @@ -9699,7 +9699,7 @@ static void ImGui::NavUpdateCancelRequest() IM_ASSERT(child_window->ChildId != 0); ImRect child_rect = child_window->Rect(); FocusWindow(parent_window); - SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, ImRect(child_rect.Min - parent_window->Pos, child_rect.Max - parent_window->Pos)); + SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); } else if (g.OpenPopupStack.Size > 0) { diff --git a/imgui_internal.h b/imgui_internal.h index 0447b5aa..cb41487e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2388,6 +2388,8 @@ namespace ImGui IMGUI_API void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0); IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); + inline ImRect WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->Pos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); } + inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->Pos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } // Windows: Display Order and Focus Order IMGUI_API void FocusWindow(ImGuiWindow* window); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 784e1fc3..96c0e2cb 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6258,7 +6258,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, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); // (bb == NavRect) + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, WindowRectAbsToRel(window, bb)); // (bb == NavRect) g.NavDisableHighlight = true; } } From 8d361c47fbd99d1e9d827c221210608ed3fa335b Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Oct 2021 12:41:49 +0200 Subject: [PATCH 13/19] Nav: rework NavEnableSetMousePos application so scrolling is applied more consistently in NavCalcPreferredRefPos() + moved at end of NavUpdate() after the last SetScroll have been done. --- imgui.cpp | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 045ac2a7..0554b347 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9232,7 +9232,8 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) static ImVec2 ImGui::NavCalcPreferredRefPos() { ImGuiContext& g = *GImGui; - if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow) + ImGuiWindow* window = g.NavWindow; + if (g.NavDisableHighlight || !g.NavDisableMouseHover || !window) { // Mouse (we need a fallback in case the mouse becomes invalid after being used) if (IsMousePosValid(&g.IO.MousePos)) @@ -9241,8 +9242,14 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() } else { - // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item. - ImRect rect_rel = WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[g.NavLayer]); + // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item + // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?) + ImRect rect_rel = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]); + if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX)) + { + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); + rect_rel.Translate(window->Scroll - next_scroll); + } ImVec2 pos = ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); ImGuiViewport* viewport = GetMainViewport(); return ImFloor(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImFloor() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. @@ -9341,19 +9348,12 @@ static void ImGui::NavUpdate() g.NavTabbingInputableRemaining = 0; g.NavMoveSubmitted = g.NavMoveScoringItems = false; - // Apply application mouse position movement, after we had a chance to process move request result. + // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling) + bool set_mouse_pos = false; if (g.NavMousePosDirty && g.NavIdIsAlive) - { - // Set mouse position given our knowledge of the navigated item position from last frame - if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) - if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) - { - io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos(); - io.WantSetMousePos = true; - //IMGUI_DEBUG_LOG("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); - } - g.NavMousePosDirty = false; - } + if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + set_mouse_pos = true; + g.NavMousePosDirty = false; g.NavIdIsAlive = false; g.NavJustTabbedId = 0; IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu); @@ -9450,6 +9450,15 @@ static void ImGui::NavUpdate() g.NavDisableMouseHover = g.NavMousePosDirty = false; } + // Update mouse position if requested + // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied) + if (set_mouse_pos && (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) + { + io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos(); + io.WantSetMousePos = true; + //IMGUI_DEBUG_LOG("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); + } + // [DEBUG] g.NavScoringDebugCount = 0; #if IMGUI_DEBUG_NAV_RECTS @@ -9614,23 +9623,17 @@ void ImGui::NavMoveRequestApplyResult() // Scroll to keep newly navigated item fully into view. if (g.NavLayer == ImGuiNavLayer_Main) { - ImVec2 delta_scroll; if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY) { // FIXME: Should remove this float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; - delta_scroll.y = scroll_target - result->Window->Scroll.y; SetScrollY(result->Window, scroll_target); } else { ImRect rect_abs = WindowRectRelToAbs(result->Window, result->RectRel); - delta_scroll = ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags); + ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags); } - - // Offset our result position so mouse position can be applied immediately after in NavUpdate() - result->RectRel.TranslateX(-delta_scroll.x); - result->RectRel.TranslateY(-delta_scroll.y); } ClearActiveID(); From a67f7dce3143d970f6ab8b1aa94470de67e43d7a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 27 Oct 2021 14:07:04 +0200 Subject: [PATCH 14/19] Nav: relative rectangle positions are now stored relative to CursorStartPos to be independent of scrolling. Will facilitate rework of clipper (#3841) + Extracted code into NavUpdateCreateWrappingRequest() + Fix for PVS in NavUpdate() assigning g.NavMousePosDirty twice. --- imgui.cpp | 115 ++++++++++++++++++++++++----------------------- imgui.h | 2 +- imgui_internal.h | 4 +- 3 files changed, 62 insertions(+), 59 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0554b347..344a804f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -917,6 +917,7 @@ static void NavUpdateCancelRequest(); static void NavUpdateCreateMoveRequest(); static float NavUpdatePageUpPageDown(); static inline void NavUpdateAnyRequestFlag(); +static void NavUpdateCreateWrappingRequest(); static void NavEndFrame(); static bool NavScoreItem(ImGuiNavItemData* result); static void NavApplyItemToResult(ImGuiNavItemData* result); @@ -9447,7 +9448,7 @@ static void ImGui::NavUpdate() if (!nav_keyboard_active && !nav_gamepad_active) { g.NavDisableHighlight = true; - g.NavDisableMouseHover = g.NavMousePosDirty = false; + g.NavDisableMouseHover = set_mouse_pos = false; } // Update mouse position if requested @@ -9465,7 +9466,7 @@ static void ImGui::NavUpdate() if (g.NavWindow) { ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow); - if (1) { for (int layer = 0; layer < 2; layer++) { ImRect r = WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[layer]); draw_list->AddRect(r.Min, r.Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (1) { for (int layer = 0; layer < 2; layer++) { ImRect r = WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[layer]); draw_list->AddRect(r.Min, r.Max, IM_COL32(255,200,0,255)); } } // [DEBUG] if (1) { ImU32 col = (!g.NavWindow->Hidden) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); draw_list->AddCircleFilled(p, 3.0f, col); draw_list->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } } #endif @@ -9775,7 +9776,7 @@ static float ImGui::NavUpdatePageUpPageDown() // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdgeY flag, we don't scroll immediately to avoid scrolling happening before nav result. // Preserve current horizontal position if we have any. - nav_rect_rel.Min.y = nav_rect_rel.Max.y = -window->Scroll.y; + nav_rect_rel.Min.y = nav_rect_rel.Max.y = 0.0f; if (nav_rect_rel.IsInverted()) nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; g.NavMoveDir = ImGuiDir_Down; @@ -9784,7 +9785,7 @@ static float ImGui::NavUpdatePageUpPageDown() } else if (end_pressed) { - nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ScrollMax.y + window->SizeFull.y - window->Scroll.y; + nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ContentSize.y; if (nav_rect_rel.IsInverted()) nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; g.NavMoveDir = ImGuiDir_Up; @@ -9805,64 +9806,66 @@ static void ImGui::NavEndFrame() NavUpdateWindowingOverlay(); // Perform wrap-around in menus + // FIXME-NAV: Wrap may need to apply a weight bias on the other axis. e.g. 4x4 grid with 2 last items missing on last item won't handle LoopY/WrapY correctly. // FIXME-NAV: Wrap (not Loop) support could be handled by the scoring function and then WrapX would function without an extra frame. - ImGuiWindow* window = g.NavWindow; - const ImGuiNavMoveFlags move_flags = g.NavMoveFlags; const ImGuiNavMoveFlags wanted_flags = ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY; - if (window && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & wanted_flags) && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) + if (g.NavWindow && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & wanted_flags) && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) + NavUpdateCreateWrappingRequest(); +} + +static void ImGui::NavUpdateCreateWrappingRequest() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.NavWindow; + + bool do_forward = false; + ImRect bb_rel = window->NavRectRel[g.NavLayer]; + ImGuiDir clip_dir = g.NavMoveDir; + const ImGuiNavMoveFlags move_flags = g.NavMoveFlags; + if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) { - bool do_forward = false; - ImRect bb_rel = window->NavRectRel[g.NavLayer]; - ImGuiDir clip_dir = g.NavMoveDir; - if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { - bb_rel.Min.x = bb_rel.Max.x = - ImMax(window->SizeFull.x, window->ContentSize.x + window->WindowPadding.x * 2.0f) - window->Scroll.x; - if (move_flags & ImGuiNavMoveFlags_WrapX) - { - bb_rel.TranslateY(-bb_rel.GetHeight()); - clip_dir = ImGuiDir_Up; - } - do_forward = true; - } - if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) - { - bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; - if (move_flags & ImGuiNavMoveFlags_WrapX) - { - bb_rel.TranslateY(+bb_rel.GetHeight()); - clip_dir = ImGuiDir_Down; - } - do_forward = true; - } - const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); - if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) - { - bb_rel.Min.y = bb_rel.Max.y = - ImMax(window->SizeFull.y, window->ContentSize.y + window->WindowPadding.y * 2.0f) - window->Scroll.y + decoration_up_height; - if (move_flags & ImGuiNavMoveFlags_WrapY) - { - bb_rel.TranslateX(-bb_rel.GetWidth()); - clip_dir = ImGuiDir_Left; - } - do_forward = true; - } - if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) - { - bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y + decoration_up_height; - if (move_flags & ImGuiNavMoveFlags_WrapY) - { - bb_rel.TranslateX(+bb_rel.GetWidth()); - clip_dir = ImGuiDir_Right; - } - do_forward = true; - } - if (do_forward) - { - window->NavRectRel[g.NavLayer] = bb_rel; - NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); + bb_rel.TranslateY(-bb_rel.GetHeight()); // Previous row + clip_dir = ImGuiDir_Up; } + do_forward = true; } + if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = -window->WindowPadding.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) + { + bb_rel.TranslateY(+bb_rel.GetHeight()); // Next row + clip_dir = ImGuiDir_Down; + } + do_forward = true; + } + if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) + { + bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column + clip_dir = ImGuiDir_Left; + } + do_forward = true; + } + if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = -window->WindowPadding.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) + { + bb_rel.TranslateX(+bb_rel.GetWidth()); // Next column + clip_dir = ImGuiDir_Right; + } + do_forward = true; + } + if (!do_forward) + return; + window->NavRectRel[g.NavLayer] = bb_rel; + NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); } static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) diff --git a/imgui.h b/imgui.h index db1d96cb..e3d2b41e 100644 --- a/imgui.h +++ b/imgui.h @@ -64,7 +64,7 @@ Index of this file: // 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.86 WIP" -#define IMGUI_VERSION_NUM 18503 +#define IMGUI_VERSION_NUM 18504 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE diff --git a/imgui_internal.h b/imgui_internal.h index cb41487e..3373db06 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2388,8 +2388,8 @@ namespace ImGui IMGUI_API void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0); IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); - inline ImRect WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->Pos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); } - inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->Pos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } + inline ImRect WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); } + inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } // Windows: Display Order and Focus Order IMGUI_API void FocusWindow(ImGuiWindow* window); From b1a68d899ce33d7eb20a1a641512c86ee6eeee2a Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 2 Nov 2021 04:51:22 -0700 Subject: [PATCH 15/19] Fix misplaced backtick in FAQ.md (#4669) --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 8f67d4e7..46c1556d 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -475,7 +475,7 @@ ImGui::End(); - Math operators: if you have setup `IM_VEC2_CLASS_EXTRA` in `imconfig.h` to bind your own math types, you can use your own math types and their natural operators instead of ImVec2. ImVec2 by default doesn't export any math operators in the public API. You may use `#define IMGUI_DEFINE_MATH_OPERATORS` `#include "imgui_internal.h"` to use the internally defined math operators, but instead prefer using your own math library and set it up in `imconfig.h`. - You can use `ImGui::GetBackgroundDrawList()` or `ImGui::GetForegroundDrawList()` to access draw lists which will be displayed behind and over every other dear imgui windows (one bg/fg drawlist per viewport). This is very convenient if you need to quickly display something on the screen that is not associated to a dear imgui window. - You can also create your own empty window and draw inside it. Call Begin() with the NoBackground | NoDecoration | NoSavedSettings | NoInputs flags (The `ImGuiWindowFlags_NoDecoration` flag itself is a shortcut for NoTitleBar | NoResize | NoScrollbar | NoCollapse). Then you can retrieve the ImDrawList* via `GetWindowDrawList()` and draw to it in any way you like. -`- You can create your own ImDrawList instance. You'll need to initialize them with `ImGui::GetDrawListSharedData()`, or create your own instancing ImDrawListSharedData`, and then call your renderer function with your own ImDrawList or ImDrawData data. +- You can create your own ImDrawList instance. You'll need to initialize them with `ImGui::GetDrawListSharedData()`, or create your own instancing `ImDrawListSharedData`, and then call your renderer function with your own ImDrawList or ImDrawData data. - Looking for fun? The [ImDrawList coding party 2020](https://github.com/ocornut/imgui/issues/3606) thread is full of "don't do this at home" extreme uses of the ImDrawList API. ##### [Return to Index](#index) From eb654f1badd03454e69c196086e6ac3773db4cfc Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 2 Nov 2021 12:57:05 +0100 Subject: [PATCH 16/19] Typo --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 46c1556d..009027c1 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -23,7 +23,7 @@ or view this file with any Markdown viewer. | [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) | | [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | | **Q&A: Usage** | -| **[About the ID Stack system..
Why is my widget not reacting when I click on it?
How can I have widgets with an empty label?
How can I have multiple widgets with the same label?]
How can I have multiple windows with the same label?](#q-about-the-id-stack-system)** | +| **[About the ID Stack system..
Why is my widget not reacting when I click on it?
How can I have widgets with an empty label?
How can I have multiple widgets with the same label?
How can I have multiple windows with the same label?](#q-about-the-id-stack-system)** | | [How can I display an image? What is ImTextureID, how does it work?](#q-how-can-i-display-an-image-what-is-imtextureid-how-does-it-work)| | [How can I use my own math types instead of ImVec2/ImVec4?](#q-how-can-i-use-my-own-math-types-instead-of-imvec2imvec4) | | [How can I interact with standard C++ types (such as std::string and std::vector)?](#q-how-can-i-interact-with-standard-c-types-such-as-stdstring-and-stdvector) | From d1eb7b57576eefa30b9ebf675cd259f636fdd6f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Nov 2021 16:16:58 +0100 Subject: [PATCH 17/19] ScrollToItem: amend fully visible tests to take account of spacing used (fix nav in one axis scrolling back and forth between axises when space is tight by just < ItemSpacing*2) (#3692, #2812, #4242, #2900) Amend 8f495e55 --- imgui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 344a804f..3ba7219e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6164,7 +6164,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Inner rectangle // Not affected by window border size. Used by: // - InnerClipRect - // - ScrollToBringRectIntoView() + // - ScrollToRectEx() // - NavUpdatePageUpPageDown() // - Scrollbar() window->InnerRect.Min.x = window->Pos.x; @@ -8042,8 +8042,8 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui 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() <= window_rect.GetWidth(); - const bool can_be_fully_visible_y = item_rect.GetHeight() <= window_rect.GetHeight(); + 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(); if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x) { From 978598b174e6cb0ddc0e1c38023d11cacc2a554e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Nov 2021 16:29:21 +0100 Subject: [PATCH 18/19] Nav: pressing PageUp/PageDown/Home/End when in Menu layer automatically moves back to Main layer. + fixed resizing window from borders setting navigation to Menu layer. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e1eaca44..56795d54 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,8 @@ Other Changes: - Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676) - Nav: fixed absolute mouse position (with NavEnableSetMousePos config flag) when using Home/End leads to scrolling. +- Nav: pressing PageUp/PageDown/Home/End when in Menu layer automatically moves back to Main layer. +- Nav: fixed resizing window from borders setting navigation to Menu layer. - InputText: made double-click select word, triple-line select line. Word delimitation logic differs slightly from the one used by CTRL+arrows. (#2244) - Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce diff --git a/imgui.cpp b/imgui.cpp index 3ba7219e..165b4579 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5489,7 +5489,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() - ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren); + 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) { @@ -5678,6 +5678,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None); // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer) + // FIXME-NAV: Might want (or not?) to set the equivalent of ImGuiButtonFlags_NoNavFocus so that mouse clicks on standard title bar items don't necessarily set nav/keyboard ref? const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; @@ -9730,7 +9731,7 @@ static float ImGui::NavUpdatePageUpPageDown() ImGuiIO& io = g.IO; ImGuiWindow* window = g.NavWindow; - if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL || g.NavLayer != ImGuiNavLayer_Main) + if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL) return 0.0f; const bool page_up_held = IsKeyDown(io.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp); @@ -9740,6 +9741,9 @@ static float ImGui::NavUpdatePageUpPageDown() if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out return 0.0f; + if (g.NavLayer != ImGuiNavLayer_Main) + NavRestoreLayer(ImGuiNavLayer_Main); + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll) { // Fallback manual-scroll when window has no navigable item From 14466a6d19ca97f4dcf07688ebe4411aeb51958a Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Nov 2021 16:59:48 +0100 Subject: [PATCH 19/19] Nav: Page/Home/End doesn't immediately restore highlight (before results comes) + pressing Esc to exit a child window re-enable the Nav highlight if it was disabled by mouse. Also fix a move request fail restoring highlight from not moving mouse cursor. Add NavRestoreHighlightAfterMove() helper a little bit of extra sanity. --- docs/CHANGELOG.txt | 7 +++++-- imgui.cpp | 29 +++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 56795d54..59cb8d87 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,10 +41,13 @@ Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] - Modals: fixed issue hovering popups inside a child inside a modal. (#4676, #4527) - Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676) -- Nav: fixed absolute mouse position (with NavEnableSetMousePos config flag) when using Home/End - leads to scrolling. - Nav: pressing PageUp/PageDown/Home/End when in Menu layer automatically moves back to Main layer. - Nav: fixed resizing window from borders setting navigation to Menu layer. +- Nav: pressing Esc to exit a child window reactivates the Nav highlight if it was disabled by mouse. +- Nav: (with ImGuiConfigFlags_NavEnableSetMousePos): fixed absolute mouse position when using Home/End + leads to scrolling. +- Nav: (with ImGuiConfigFlags_NavEnableSetMousePos): fixed not setting mouse position when a failed + move request (e.g. already at edge) reactivate the navigation highlight. - InputText: made double-click select word, triple-line select line. Word delimitation logic differs slightly from the one used by CTRL+arrows. (#2244) - Backends: Vulkan: Call vkCmdSetScissor() at the end of render with a full-viewport to reduce diff --git a/imgui.cpp b/imgui.cpp index 165b4579..95a799df 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -926,6 +926,7 @@ static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void NavRestoreLayer(ImGuiNavLayer layer); +static void NavRestoreHighlightAfterMove(); static int FindWindowFocusIndex(ImGuiWindow* window); // Error Checking and Debug Tools @@ -8811,8 +8812,6 @@ void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id g.NavFocusScopeId = focus_scope_id; g.NavWindow->NavLastIds[nav_layer] = id; g.NavWindow->NavRectRel[nav_layer] = rect_rel; - //g.NavDisableHighlight = false; - //g.NavDisableMouseHover = g.NavMousePosDirty = true; } void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) @@ -9187,6 +9186,11 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer) g.NavLayer = layer; NavInitWindow(window, true); } +} + +void ImGui::NavRestoreHighlightAfterMove() +{ + ImGuiContext& g = *GImGui; g.NavDisableHighlight = false; g.NavDisableMouseHover = g.NavMousePosDirty = true; } @@ -9486,10 +9490,7 @@ void ImGui::NavInitRequestApplyResult() SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result if (g.NavInitRequestFromMove) - { - g.NavDisableHighlight = false; - g.NavDisableMouseHover = g.NavMousePosDirty = true; - } + NavRestoreHighlightAfterMove(); } void ImGui::NavUpdateCreateMoveRequest() @@ -9604,10 +9605,7 @@ void ImGui::NavMoveRequestApplyResult() if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight; if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0) - { - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; - } + NavRestoreHighlightAfterMove(); return; } @@ -9669,10 +9667,7 @@ void ImGui::NavMoveRequestApplyResult() // Enable nav highlight if ((g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0) - { - g.NavDisableHighlight = false; - g.NavDisableMouseHover = g.NavMousePosDirty = true; - } + NavRestoreHighlightAfterMove(); } // Process NavCancel input (to close a popup, get back to parent, clear focus) @@ -9695,6 +9690,7 @@ static void ImGui::NavUpdateCancelRequest() { // Leave the "menu" layer NavRestoreLayer(ImGuiNavLayer_Main); + NavRestoreHighlightAfterMove(); } else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) { @@ -9705,6 +9701,7 @@ static void ImGui::NavUpdateCancelRequest() ImRect child_rect = child_window->Rect(); FocusWindow(parent_window); SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); + NavRestoreHighlightAfterMove(); } else if (g.OpenPopupStack.Size > 0) { @@ -10031,8 +10028,7 @@ static void ImGui::NavUpdateWindowing() if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) { ClearActiveID(); - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; + NavRestoreHighlightAfterMove(); apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); ClosePopupsOverWindow(apply_focus_window, false); FocusWindow(apply_focus_window); @@ -10079,6 +10075,7 @@ static void ImGui::NavUpdateWindowing() if (new_nav_layer == ImGuiNavLayer_Menu) g.NavWindow->NavLastIds[new_nav_layer] = 0; NavRestoreLayer(new_nav_layer); + NavRestoreHighlightAfterMove(); } } }