Merge branch 'master' into docking

# Conflicts:
#	backends/imgui_impl_vulkan.cpp
#	imgui.cpp
This commit is contained in:
ocornut 2021-11-03 11:51:37 +01:00
commit 0b4edf4e94
10 changed files with 281 additions and 168 deletions

View File

@ -25,6 +25,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2021-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 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-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-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. // 2021-02-18: Vulkan: Change blending equation to preserve alpha in output buffer.
@ -569,6 +570,16 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm
global_idx_offset += cmd_list->IdxBuffer.Size; global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.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.
// 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) bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer)

View File

@ -117,6 +117,21 @@ Breaking Changes:
Other 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)
- 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
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] - Misc: Fix MinGW DLL build issue (when IMGUI_API is defined). [@rokups]
- CI: Add MinGW DLL build to test suite. [@rokups] - CI: Add MinGW DLL build to test suite. [@rokups]

View File

@ -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 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) | | [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** | | **Q&A: Usage** |
| **[About the ID Stack system..<br>Why is my widget not reacting when I click on it?<br>How can I have widgets with an empty label?<br>How can I have multiple widgets with the same label?](#q-about-the-id-stack-system)** | | **[About the ID Stack system..<br>Why is my widget not reacting when I click on it?<br>How can I have widgets with an empty label?<br>How can I have multiple widgets with the same label?<br>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 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 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) | | [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: 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 widgets with an empty label?
### Q: How can I have multiple widgets with the same 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... A primer on labels and the ID Stack...
@ -474,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`. - 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 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 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. - 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) ##### [Return to Index](#index)

View File

@ -1,8 +1,11 @@
## 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 - 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. - 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. - Then build using `make` while in the `example_emscripten_opengl3/` directory.
## How to Run ## 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. - 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 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`. - 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: ## Obsolete features:

View File

@ -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 - 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. - 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. - 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):<br>
_"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 cant load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers youll 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).

207
imgui.cpp
View File

@ -733,9 +733,11 @@ CODE
Q&A: Usage Q&A: Usage
---------- ----------
Q: Why is my widget not reacting when I click on it? Q: About the ID Stack system..
Q: How can I have widgets with an empty label? - Why is my widget not reacting when I click on it?
Q: How can I have multiple widgets with the same label? - How can I have widgets with an empty label?
- How can I have multiple widgets with the same label?
- How can I have multiple windows with the same label?
Q: How can I display an image? What is ImTextureID, how does it works? Q: How can I display an image? What is ImTextureID, how does it works?
Q: How can I use my own math types instead of ImVec2/ImVec4? Q: How can I use my own math types instead of ImVec2/ImVec4?
Q: How can I interact with standard C++ types (such as std::string and std::vector)? Q: How can I interact with standard C++ types (such as std::string and std::vector)?
@ -928,6 +930,7 @@ static void NavUpdateCancelRequest();
static void NavUpdateCreateMoveRequest(); static void NavUpdateCreateMoveRequest();
static float NavUpdatePageUpPageDown(); static float NavUpdatePageUpPageDown();
static inline void NavUpdateAnyRequestFlag(); static inline void NavUpdateAnyRequestFlag();
static void NavUpdateCreateWrappingRequest();
static void NavEndFrame(); static void NavEndFrame();
static bool NavScoreItem(ImGuiNavItemData* result); static bool NavScoreItem(ImGuiNavItemData* result);
static void NavApplyItemToResult(ImGuiNavItemData* result); static void NavApplyItemToResult(ImGuiNavItemData* result);
@ -936,6 +939,7 @@ static ImVec2 NavCalcPreferredRefPos();
static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window);
static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
static void NavRestoreLayer(ImGuiNavLayer layer); static void NavRestoreLayer(ImGuiNavLayer layer);
static void NavRestoreHighlightAfterMove();
static int FindWindowFocusIndex(ImGuiWindow* window); static int FindWindowFocusIndex(ImGuiWindow* window);
// Error Checking and Debug Tools // Error Checking and Debug Tools
@ -2302,7 +2306,7 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items
if (g.NavMoveScoringItems) if (g.NavMoveScoringItems)
unclipped_rect.Add(g.NavScoringRect); unclipped_rect.Add(g.NavScoringRect);
if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId) 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; const ImVec2 pos = window->DC.CursorPos;
int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
@ -3835,25 +3839,26 @@ static void ImGui::UpdateMouseInputs()
for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) 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.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.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; 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.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;
if (g.IO.MouseClicked[i]) if (g.IO.MouseClicked[i])
{ {
bool is_repeated_click = false;
if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime) 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); 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) if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
g.IO.MouseDoubleClicked[i] = true; is_repeated_click = 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
} }
if (is_repeated_click)
g.IO.MouseClickedLastCount[i]++;
else else
{ g.IO.MouseClickedLastCount[i] = 1;
g.IO.MouseClickedTime[i] = g.Time; g.IO.MouseClickedTime[i] = g.Time;
}
g.IO.MouseClickedPos[i] = g.IO.MousePos; g.IO.MouseClickedPos[i] = g.IO.MousePos;
g.IO.MouseDownWasDoubleClick[i] = g.IO.MouseDoubleClicked[i]; g.IO.MouseClickedCount[i] = g.IO.MouseClickedLastCount[i];
g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
} }
@ -3865,9 +3870,12 @@ 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].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); 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; // We provide io.MouseDoubleClicked[] as a legacy service
if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation 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; g.NavDisableMouseHover = false;
} }
} }
@ -5042,7 +5050,14 @@ bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
return g.IO.MouseDoubleClicked[button]; 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.MouseClickedCount[button] == 3;
} }
// Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. // Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame.
@ -5765,7 +5780,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s
if (hovered || held) if (hovered || held)
g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) if (held && g.IO.MouseClickedCount[0] == 2 && resize_grip_n == 0)
{ {
// Manual auto-fit when double-clicking // Manual auto-fit when double-clicking
size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit); size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit);
@ -5795,7 +5810,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s
bool hovered, held; bool hovered, held;
ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING); ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING);
ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() 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)); //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) if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held)
{ {
@ -6028,6 +6043,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None); 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) // 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; const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
@ -6400,7 +6416,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. // 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(); 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.MouseClickedCount[0] == 2)
window->WantCollapseToggle = true; window->WantCollapseToggle = true;
if (window->WantCollapseToggle) if (window->WantCollapseToggle)
{ {
@ -6622,7 +6638,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Inner rectangle // Inner rectangle
// Not affected by window border size. Used by: // Not affected by window border size. Used by:
// - InnerClipRect // - InnerClipRect
// - ScrollToBringRectIntoView() // - ScrollToRectEx()
// - NavUpdatePageUpPageDown() // - NavUpdatePageUpPageDown()
// - Scrollbar() // - Scrollbar()
window->InnerRect.Min.x = window->Pos.x; window->InnerRect.Min.x = window->Pos.x;
@ -7248,11 +7264,16 @@ void ImGui::PopTextWrapPos()
static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy, bool dock_hierarchy) static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy, bool dock_hierarchy)
{ {
ImGuiWindow* last_window = NULL;
while (last_window != window)
{
last_window = window;
window = window->RootWindow; window = window->RootWindow;
if (popup_hierarchy) if (popup_hierarchy)
window = window->RootWindowPopupTree; window = window->RootWindowPopupTree;
if (dock_hierarchy) if (dock_hierarchy)
window = window->RootWindowDockTree; window = window->RootWindowDockTree;
}
return window; return window;
} }
@ -7704,7 +7725,7 @@ void ImGui::SetItemDefaultFocus()
g.NavInitRequest = false; g.NavInitRequest = false;
g.NavInitResultId = g.LastItemData.ID; 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(); NavUpdateAnyRequestFlag();
// Scroll could be done in NavInitRequestApplyResult() via a opt-in flag (we however don't want regular init requests to scroll) // Scroll could be done in NavInitRequestApplyResult() via a opt-in flag (we however don't want regular init requests to scroll)
@ -8658,8 +8679,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_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 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_x = (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= window_rect.GetWidth();
const bool can_be_fully_visible_y = item_rect.GetHeight() <= window_rect.GetHeight(); 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) if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x)
{ {
@ -9436,8 +9457,6 @@ void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id
g.NavFocusScopeId = focus_scope_id; g.NavFocusScopeId = focus_scope_id;
g.NavWindow->NavLastIds[nav_layer] = id; g.NavWindow->NavLastIds[nav_layer] = id;
g.NavWindow->NavRectRel[nav_layer] = rect_rel; g.NavWindow->NavRectRel[nav_layer] = rect_rel;
//g.NavDisableHighlight = false;
//g.NavDisableMouseHover = g.NavMousePosDirty = true;
} }
void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
@ -9456,7 +9475,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent;
window->NavLastIds[nav_layer] = id; window->NavLastIds[nav_layer] = id;
if (g.LastItemData.ID == 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) if (g.ActiveIdSource == ImGuiInputSource_Nav)
g.NavDisableMouseHover = true; g.NavDisableMouseHover = true;
@ -9636,7 +9655,7 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result)
result->ID = g.LastItemData.ID; result->ID = g.LastItemData.ID;
result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; result->FocusScopeId = window->DC.NavFocusScopeIdCurrent;
result->InFlags = g.LastItemData.InFlags; 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) // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
@ -9657,7 +9676,7 @@ static void ImGui::NavProcessItem()
if (candidate_for_nav_default_focus || g.NavInitResultId == 0) if (candidate_for_nav_default_focus || g.NavInitResultId == 0)
{ {
g.NavInitResultId = id; 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) if (candidate_for_nav_default_focus)
{ {
@ -9704,7 +9723,7 @@ static void ImGui::NavProcessItem()
g.NavLayer = window->DC.NavLayerCurrent; g.NavLayer = window->DC.NavLayerCurrent;
g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent;
g.NavIdIsAlive = true; 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)
} }
} }
@ -9815,6 +9834,11 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer)
g.NavLayer = layer; g.NavLayer = layer;
NavInitWindow(window, true); NavInitWindow(window, true);
} }
}
void ImGui::NavRestoreHighlightAfterMove()
{
ImGuiContext& g = *GImGui;
g.NavDisableHighlight = false; g.NavDisableHighlight = false;
g.NavDisableMouseHover = g.NavMousePosDirty = true; g.NavDisableMouseHover = g.NavMousePosDirty = true;
} }
@ -9863,7 +9887,8 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
static ImVec2 ImGui::NavCalcPreferredRefPos() static ImVec2 ImGui::NavCalcPreferredRefPos()
{ {
ImGuiContext& g = *GImGui; 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) // Mouse (we need a fallback in case the mouse becomes invalid after being used)
if (IsMousePosValid(&g.IO.MousePos)) if (IsMousePosValid(&g.IO.MousePos))
@ -9872,10 +9897,16 @@ static ImVec2 ImGui::NavCalcPreferredRefPos()
} }
else else
{ {
// When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item. // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item
const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?)
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(window, window->NavRectRel[g.NavLayer]);
ImGuiViewport* viewport = g.NavWindow->Viewport; 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 = window->Viewport;
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. 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.
} }
} }
@ -9972,19 +10003,12 @@ static void ImGui::NavUpdate()
g.NavTabbingInputableRemaining = 0; g.NavTabbingInputableRemaining = 0;
g.NavMoveSubmitted = g.NavMoveScoringItems = false; 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) 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) if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
{ set_mouse_pos = true;
io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos();
io.WantSetMousePos = true;
//IMGUI_DEBUG_LOG("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y);
}
g.NavMousePosDirty = false; g.NavMousePosDirty = false;
}
g.NavIdIsAlive = false; g.NavIdIsAlive = false;
g.NavJustTabbedId = 0; g.NavJustTabbedId = 0;
IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu); IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu);
@ -10078,7 +10102,16 @@ static void ImGui::NavUpdate()
if (!nav_keyboard_active && !nav_gamepad_active) if (!nav_keyboard_active && !nav_gamepad_active)
{ {
g.NavDisableHighlight = true; g.NavDisableHighlight = true;
g.NavDisableMouseHover = g.NavMousePosDirty = false; g.NavDisableMouseHover = set_mouse_pos = 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] // [DEBUG]
@ -10087,7 +10120,7 @@ static void ImGui::NavUpdate()
if (g.NavWindow) if (g.NavWindow)
{ {
ImDrawList* draw_list = GetForegroundDrawList(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); } 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 #endif
@ -10106,10 +10139,7 @@ void ImGui::NavInitRequestApplyResult()
SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel);
g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result
if (g.NavInitRequestFromMove) if (g.NavInitRequestFromMove)
{ NavRestoreHighlightAfterMove();
g.NavDisableHighlight = false;
g.NavDisableMouseHover = g.NavMousePosDirty = true;
}
} }
void ImGui::NavUpdateCreateMoveRequest() void ImGui::NavUpdateCreateMoveRequest()
@ -10180,7 +10210,7 @@ void ImGui::NavUpdateCreateMoveRequest()
// (can't focus a visible object like we can with the mouse). // (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) 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])) if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
{ {
IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel\n"); IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel\n");
@ -10196,7 +10226,7 @@ void ImGui::NavUpdateCreateMoveRequest()
if (window != NULL) if (window != NULL)
{ {
ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0); 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.TranslateY(scoring_rect_offset_y);
scoring_rect.Min.x = ImMin(scoring_rect.Min.x + 1.0f, scoring_rect.Max.x); scoring_rect.Min.x = ImMin(scoring_rect.Min.x + 1.0f, scoring_rect.Max.x);
scoring_rect.Max.x = scoring_rect.Min.x; scoring_rect.Max.x = scoring_rect.Min.x;
@ -10224,10 +10254,7 @@ void ImGui::NavMoveRequestApplyResult()
if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing)
g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight; g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight;
if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0) if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0)
{ NavRestoreHighlightAfterMove();
g.NavDisableHighlight = false;
g.NavDisableMouseHover = true;
}
return; return;
} }
@ -10245,23 +10272,17 @@ void ImGui::NavMoveRequestApplyResult()
// Scroll to keep newly navigated item fully into view. // Scroll to keep newly navigated item fully into view.
if (g.NavLayer == ImGuiNavLayer_Main) if (g.NavLayer == ImGuiNavLayer_Main)
{ {
ImVec2 delta_scroll;
if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY) if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY)
{ {
// FIXME: Should remove this // FIXME: Should remove this
float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f;
delta_scroll.y = result->Window->Scroll.y - scroll_target;
SetScrollY(result->Window, scroll_target); SetScrollY(result->Window, scroll_target);
} }
else 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); 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(); ClearActiveID();
@ -10295,10 +10316,7 @@ void ImGui::NavMoveRequestApplyResult()
// Enable nav highlight // Enable nav highlight
if ((g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0) if ((g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0)
{ NavRestoreHighlightAfterMove();
g.NavDisableHighlight = false;
g.NavDisableMouseHover = g.NavMousePosDirty = true;
}
} }
// Process NavCancel input (to close a popup, get back to parent, clear focus) // Process NavCancel input (to close a popup, get back to parent, clear focus)
@ -10321,6 +10339,7 @@ static void ImGui::NavUpdateCancelRequest()
{ {
// Leave the "menu" layer // Leave the "menu" layer
NavRestoreLayer(ImGuiNavLayer_Main); NavRestoreLayer(ImGuiNavLayer_Main);
NavRestoreHighlightAfterMove();
} }
else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
{ {
@ -10330,7 +10349,8 @@ static void ImGui::NavUpdateCancelRequest()
IM_ASSERT(child_window->ChildId != 0); IM_ASSERT(child_window->ChildId != 0);
ImRect child_rect = child_window->Rect(); ImRect child_rect = child_window->Rect();
FocusWindow(parent_window); 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));
NavRestoreHighlightAfterMove();
} }
else if (g.OpenPopupStack.Size > 0) else if (g.OpenPopupStack.Size > 0)
{ {
@ -10357,7 +10377,7 @@ static float ImGui::NavUpdatePageUpPageDown()
ImGuiIO& io = g.IO; ImGuiIO& io = g.IO;
ImGuiWindow* window = g.NavWindow; 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; return 0.0f;
const bool page_up_held = IsKeyDown(io.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp); const bool page_up_held = IsKeyDown(io.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp);
@ -10367,6 +10387,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 if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out
return 0.0f; return 0.0f;
if (g.NavLayer != ImGuiNavLayer_Main)
NavRestoreLayer(ImGuiNavLayer_Main);
if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll) if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll)
{ {
// Fallback manual-scroll when window has no navigable item // Fallback manual-scroll when window has no navigable item
@ -10403,7 +10426,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 // 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. // 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. // 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()) if (nav_rect_rel.IsInverted())
nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
g.NavMoveDir = ImGuiDir_Down; g.NavMoveDir = ImGuiDir_Down;
@ -10412,7 +10435,7 @@ static float ImGui::NavUpdatePageUpPageDown()
} }
else if (end_pressed) 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()) if (nav_rect_rel.IsInverted())
nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
g.NavMoveDir = ImGuiDir_Up; g.NavMoveDir = ImGuiDir_Up;
@ -10433,64 +10456,66 @@ static void ImGui::NavEndFrame()
NavUpdateWindowingOverlay(); NavUpdateWindowingOverlay();
// Perform wrap-around in menus // 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. // 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; 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; bool do_forward = false;
ImRect bb_rel = window->NavRectRel[g.NavLayer]; ImRect bb_rel = window->NavRectRel[g.NavLayer];
ImGuiDir clip_dir = g.NavMoveDir; ImGuiDir clip_dir = g.NavMoveDir;
const ImGuiNavMoveFlags move_flags = g.NavMoveFlags;
if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
{ {
bb_rel.Min.x = bb_rel.Max.x = bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x;
ImMax(window->SizeFull.x, window->ContentSize.x + window->WindowPadding.x * 2.0f) - window->Scroll.x;
if (move_flags & ImGuiNavMoveFlags_WrapX) if (move_flags & ImGuiNavMoveFlags_WrapX)
{ {
bb_rel.TranslateY(-bb_rel.GetHeight()); bb_rel.TranslateY(-bb_rel.GetHeight()); // Previous row
clip_dir = ImGuiDir_Up; clip_dir = ImGuiDir_Up;
} }
do_forward = true; do_forward = true;
} }
if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
{ {
bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; bb_rel.Min.x = bb_rel.Max.x = -window->WindowPadding.x;
if (move_flags & ImGuiNavMoveFlags_WrapX) if (move_flags & ImGuiNavMoveFlags_WrapX)
{ {
bb_rel.TranslateY(+bb_rel.GetHeight()); bb_rel.TranslateY(+bb_rel.GetHeight()); // Next row
clip_dir = ImGuiDir_Down; clip_dir = ImGuiDir_Down;
} }
do_forward = true; do_forward = true;
} }
const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
{ {
bb_rel.Min.y = bb_rel.Max.y = bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y;
ImMax(window->SizeFull.y, window->ContentSize.y + window->WindowPadding.y * 2.0f) - window->Scroll.y + decoration_up_height;
if (move_flags & ImGuiNavMoveFlags_WrapY) if (move_flags & ImGuiNavMoveFlags_WrapY)
{ {
bb_rel.TranslateX(-bb_rel.GetWidth()); bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column
clip_dir = ImGuiDir_Left; clip_dir = ImGuiDir_Left;
} }
do_forward = true; do_forward = true;
} }
if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) 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; bb_rel.Min.y = bb_rel.Max.y = -window->WindowPadding.y;
if (move_flags & ImGuiNavMoveFlags_WrapY) if (move_flags & ImGuiNavMoveFlags_WrapY)
{ {
bb_rel.TranslateX(+bb_rel.GetWidth()); bb_rel.TranslateX(+bb_rel.GetWidth()); // Next column
clip_dir = ImGuiDir_Right; clip_dir = ImGuiDir_Right;
} }
do_forward = true; do_forward = true;
} }
if (do_forward) if (!do_forward)
{ return;
window->NavRectRel[g.NavLayer] = bb_rel; window->NavRectRel[g.NavLayer] = bb_rel;
NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags);
}
}
} }
static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) static int ImGui::FindWindowFocusIndex(ImGuiWindow* window)
@ -10653,8 +10678,7 @@ static void ImGui::NavUpdateWindowing()
{ {
ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL; ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL;
ClearActiveID(); ClearActiveID();
g.NavDisableHighlight = false; NavRestoreHighlightAfterMove();
g.NavDisableMouseHover = true;
apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
ClosePopupsOverWindow(apply_focus_window, false); ClosePopupsOverWindow(apply_focus_window, false);
FocusWindow(apply_focus_window); FocusWindow(apply_focus_window);
@ -10706,6 +10730,7 @@ static void ImGui::NavUpdateWindowing()
if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id) if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id)
g.NavWindow->NavLastIds[new_nav_layer] = 0; g.NavWindow->NavLastIds[new_nav_layer] = 0;
NavRestoreLayer(new_nav_layer); NavRestoreLayer(new_nav_layer);
NavRestoreHighlightAfterMove();
} }
} }
} }

12
imgui.h
View File

@ -65,7 +65,7 @@ Index of this file:
// Version // 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) // (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 "1.86 WIP"
#define IMGUI_VERSION_NUM 18502 #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_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx))
#define IMGUI_HAS_TABLE #define IMGUI_HAS_TABLE
#define IMGUI_HAS_VIEWPORT // Viewport WIP branch #define IMGUI_HAS_VIEWPORT // Viewport WIP branch
@ -933,6 +933,7 @@ namespace ImGui
IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down) 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 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 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 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 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? IMGUI_API bool IsAnyMouseDown(); // is any mouse button held?
@ -1819,7 +1820,7 @@ struct ImVector
inline void pop_back() { IM_ASSERT(Size > 0); Size--; } 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 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) { 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* 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 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; } 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; }
@ -2015,12 +2016,13 @@ struct ImGuiIO
ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) 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 ImVec2 MouseClickedPos[5]; // Position at time of clicking
double MouseClickedTime[5]; // Time of last click (used to figure out double-click) double MouseClickedTime[5]; // Time of last click (used to figure out double-click)
bool MouseClicked[5]; // Mouse button went from !Down to Down bool MouseClicked[5]; // Mouse button went from !Down to Down (same as MouseClickedCount[x] != 0)
bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? 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 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 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 MouseDownOwnedUnlessPopupClose[5];//Track if button was clicked inside a dear imgui window.
bool MouseDownWasDoubleClick[5]; // Track if button down was a double-click
float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) 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 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 ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point

View File

@ -844,6 +844,7 @@ static void ShowDemoWindowWidgets()
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
{ {
// Disable the default "open on single-click behavior" + set Selected flag according to our selection. // 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; ImGuiTreeNodeFlags node_flags = base_flags;
const bool is_selected = (selection_mask & (1 << i)) != 0; const bool is_selected = (selection_mask & (1 << i)) != 0;
if (is_selected) if (is_selected)
@ -852,7 +853,7 @@ static void ShowDemoWindowWidgets()
{ {
// Items 0..2 are Tree Node // Items 0..2 are Tree Node
bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); 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; node_clicked = i;
if (test_drag_and_drop && ImGui::BeginDragDropSource()) if (test_drag_and_drop && ImGui::BeginDragDropSource())
{ {
@ -873,7 +874,7 @@ static void ShowDemoWindowWidgets()
// use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text(). // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text().
node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet
ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i);
if (ImGui::IsItemClicked()) if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
node_clicked = i; node_clicked = i;
if (test_drag_and_drop && ImGui::BeginDragDropSource()) if (test_drag_and_drop && ImGui::BeginDragDropSource())
{ {
@ -5591,10 +5592,16 @@ static void ShowDemoWindowMisc()
else else
ImGui::Text("Mouse pos: <INVALID>"); ImGui::Text("Mouse pos: <INVALID>");
ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); 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); } int count = IM_ARRAYSIZE(io.MouseDown);
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 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 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 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("Mouse wheel: %.1f", io.MouseWheel);
ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused
ImGui::TreePop(); ImGui::TreePop();

View File

@ -2614,6 +2614,8 @@ namespace ImGui
IMGUI_API void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0); 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 SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0);
IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size);
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 // Windows: Display Order and Focus Order
IMGUI_API void FocusWindow(ImGuiWindow* window); IMGUI_API void FocusWindow(ImGuiWindow* window);

View File

@ -564,7 +564,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
SetFocusID(id, window); SetFocusID(id, window);
FocusWindow(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.MouseClickedCount[mouse_button_clicked] == 2))
{ {
pressed = true; pressed = true;
if (flags & ImGuiButtonFlags_NoHoldingActiveId) 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) if ((release_in || release_anywhere) && !g.DragDropActive)
{ {
// Report as pressed when releasing the mouse (this is the most common path) // 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.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 <on release> bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
if (!is_double_click_release && !is_repeating_already) if (!is_double_click_release && !is_repeating_already)
pressed = true; pressed = true;
@ -2415,7 +2415,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 input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
const bool clicked = (hovered && g.IO.MouseClicked[0]); const bool clicked = (hovered && g.IO.MouseClicked[0]);
const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]); const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2);
if (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id) if (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id)
{ {
SetActiveID(id, window); SetActiveID(id, window);
@ -3685,17 +3685,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. // 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_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; } 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 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 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_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { 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
#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h #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) static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
{ {
@ -3887,11 +3888,12 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
// Generic named filters // Generic named filters
if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))) 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 '.'. // 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. // 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: // Change the default decimal_point with:
// ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point; // 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; ImGuiContext& g = *GImGui;
const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint; const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint;
@ -4182,19 +4184,49 @@ 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 float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
const bool is_osx = io.ConfigMacOSXBehaviors; const bool is_osx = io.ConfigMacOSXBehaviors;
if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0])) if (select_all)
{ {
state->SelectAll(); state->SelectAll();
state->SelectedAllMouseLock = true; state->SelectedAllMouseLock = true;
} }
else if (hovered && is_osx && io.MouseDoubleClicked[0]) else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
{ {
// Double-click select a word only, OS X style (by simulating keystrokes) 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_WORDLEFT);
state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); //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
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();
} }
else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
{ {
// FIXME: unselect on late click could be done release?
if (hovered) if (hovered)
{ {
stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
@ -5928,7 +5960,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
toggled = true; toggled = true;
if (flags & ImGuiTreeNodeFlags_OpenOnArrow) if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job 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.MouseClickedCount[0] == 2)
toggled = true; toggled = true;
} }
else if (pressed && g.DragDropHoldJustPressedId == id) else if (pressed && g.DragDropHoldJustPressedId == id)
@ -6235,7 +6267,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
{ {
if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) 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; g.NavDisableHighlight = true;
} }
} }