|
@@ -437,7 +437,7 @@
|
|
======================================
|
|
======================================
|
|
|
|
|
|
Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
|
|
Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
|
|
- A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure.
|
|
|
|
|
|
+ A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure (e.g. if (ImGui::GetIO().WantCaptureMouse) { ... } )
|
|
- When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application.
|
|
- When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application.
|
|
- When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application.
|
|
- When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application.
|
|
- When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS).
|
|
- When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS).
|
|
@@ -452,31 +452,111 @@
|
|
were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)
|
|
were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)
|
|
|
|
|
|
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?
|
|
- A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function.
|
|
|
|
- Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry!
|
|
|
|
- It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc.
|
|
|
|
- At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render.
|
|
|
|
- Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing.
|
|
|
|
- (C++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!)
|
|
|
|
- To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions.
|
|
|
|
- Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use.
|
|
|
|
- You may call ImGui::ShowMetricsWindow() to explore active draw lists and visualize/understand how the draw data is generated.
|
|
|
|
- It is your responsibility to get textures uploaded to your GPU.
|
|
|
|
|
|
+ A: Short explanation:
|
|
|
|
+ - You may use functions such as ImGui::Image(), ImGui::ImageButton() or lower-level ImDrawList::AddImage() to emit draw calls that will use your own textures.
|
|
|
|
+ - Actual textures are identified in a way that is up to the user/engine.
|
|
|
|
+ - Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason).
|
|
|
|
+ Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward.
|
|
|
|
+
|
|
|
|
+ Long explanation:
|
|
|
|
+ - Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices.
|
|
|
|
+ At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code
|
|
|
|
+ to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.).
|
|
|
|
+ - Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API.
|
|
|
|
+ We carry the information to identify a "texture" in the ImTextureID type.
|
|
|
|
+ ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice.
|
|
|
|
+ Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function.
|
|
|
|
+ - In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying
|
|
|
|
+ an image from the end-user perspective. This is what the _examples_ rendering functions are using:
|
|
|
|
+
|
|
|
|
+ OpenGL: ImTextureID = GLuint (see ImGui_ImplGlfwGL3_RenderDrawData() function in imgui_impl_glfw_gl3.cpp)
|
|
|
|
+ DirectX9: ImTextureID = LPDIRECT3DTEXTURE9 (see ImGui_ImplDX9_RenderDrawData() function in imgui_impl_dx9.cpp)
|
|
|
|
+ DirectX11: ImTextureID = ID3D11ShaderResourceView* (see ImGui_ImplDX11_RenderDrawData() function in imgui_impl_dx11.cpp)
|
|
|
|
+ DirectX12: ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData() function in imgui_impl_dx12.cpp)
|
|
|
|
+
|
|
|
|
+ For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID.
|
|
|
|
+ Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure
|
|
|
|
+ tying together both the texture and information about its format and how to read it.
|
|
|
|
+ - If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about
|
|
|
|
+ the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase
|
|
|
|
+ is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them.
|
|
|
|
+ If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID
|
|
|
|
+ representation suggested by the example bindings is probably the best choice.
|
|
|
|
+ (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer)
|
|
|
|
+
|
|
|
|
+ User code may do:
|
|
|
|
+
|
|
|
|
+ // Cast our texture type to ImTextureID / void*
|
|
|
|
+ MyTexture* texture = g_CoffeeTableTexture;
|
|
|
|
+ ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height));
|
|
|
|
+
|
|
|
|
+ The renderer function called after ImGui::Render() will receive that same value that the user code passed:
|
|
|
|
+
|
|
|
|
+ // Cast ImTextureID / void* stored in the draw command as our texture type
|
|
|
|
+ MyTexture* texture = (MyTexture*)pcmd->TextureId;
|
|
|
|
+ MyEngineBindTexture2D(texture);
|
|
|
|
+
|
|
|
|
+ Once you understand this design you will understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui.
|
|
|
|
+ This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them.
|
|
|
|
+ If you want to display an image file (e.g. PNG file) into the screen, please refer to documentation and tutorials for the graphics API you are using.
|
|
|
|
+
|
|
|
|
+ Here's a simplified OpenGL example using stb_image.h:
|
|
|
|
+
|
|
|
|
+ // Use stb_image.h to load a PNG from disk and turn it into raw RGBA pixel data:
|
|
|
|
+ #define STB_IMAGE_IMPLEMENTATION
|
|
|
|
+ #include <stb_image.h>
|
|
|
|
+ [...]
|
|
|
|
+ int my_image_width, my_image_height;
|
|
|
|
+ unsigned char* my_image_data = stbi_load("my_image.png", &my_image_width, &my_image_height, NULL, 4);
|
|
|
|
+
|
|
|
|
+ // Turn the RGBA pixel data into an OpenGL texture:
|
|
|
|
+ GLuint my_opengl_texture;
|
|
|
|
+ glGenTextures(1, &my_opengl_texture);
|
|
|
|
+ glBindTexture(GL_TEXTURE_2D, my_opengl_texture);
|
|
|
|
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
|
|
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
|
|
|
|
+
|
|
|
|
+ // Now that we have an OpenGL texture, assuming our imgui rendering function (imgui_impl_xxx.cpp file) takes GLuint as ImTextureID, we can display it:
|
|
|
|
+ ImGui::Image((void*)(intptr_t)my_opengl_texture, ImVec2(my_image_width, my_image_height));
|
|
|
|
+
|
|
|
|
+ C/C++ tip: a void* is pointer-sized storage. You may safely store any pointer or integer into it by casting your value to ImTexture / void*, and vice-versa.
|
|
|
|
+ Because both end-points (user code and rendering function) are under your control, you know exactly what is stored inside the ImTexture / void*.
|
|
|
|
+ Examples:
|
|
|
|
+
|
|
|
|
+ GLuint my_tex = XXX;
|
|
|
|
+ void* my_void_ptr;
|
|
|
|
+ my_void_ptr = (void*)(intptr_t)my_tex; // cast a GLuint into a void* (we don't take its address! we literally store the value inside the pointer)
|
|
|
|
+ my_tex = (GLuint)(intptr_t)my_void_ptr; // cast a void* into a GLuint
|
|
|
|
+
|
|
|
|
+ ID3D11ShaderResourceView* my_dx11_srv = XXX;
|
|
|
|
+ void* my_void_ptr;
|
|
|
|
+ my_void_ptr = (void*)my_dx11_srv; // cast a ID3D11ShaderResourceView* into an opaque void*
|
|
|
|
+ my_dx11_srv = (ID3D11ShaderResourceView*)my_void_ptr; // cast a void* into a ID3D11ShaderResourceView*
|
|
|
|
+
|
|
|
|
+ Finally, you may call ImGui::ShowMetricsWindow() to explore/visualize/understand how the ImDrawList are generated.
|
|
|
|
|
|
Q: How can I have multiple widgets with the same label or without a label?
|
|
Q: How can I have multiple widgets with the same label or without a label?
|
|
|
|
+ Q: I have multiple widgets with the same label, and only the first one works. Why is that?
|
|
A: A primer on labels and the ID Stack...
|
|
A: A primer on labels and the ID Stack...
|
|
|
|
|
|
- - Elements that are typically not clickable, such as Text() items don't need an ID.
|
|
|
|
|
|
+ Dear ImGui internally need to uniquely identify UI elements.
|
|
|
|
+ Elements that are typically not clickable (such as calls to the Text functions) don't need an ID.
|
|
|
|
+ Interactive widgets (such as calls to Button buttons) need a unique ID.
|
|
|
|
+ Unique ID are used internally to track active widgets and occasionally associate state to widgets.
|
|
|
|
+ Unique ID are implicitly built from the hash of multiple elements that identify the "path" to the UI element.
|
|
|
|
|
|
- - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui
|
|
|
|
- often needs to remember what is the "active" widget). To do so they need a unique ID. Unique ID
|
|
|
|
- are typically derived from a string label, an integer index or a pointer.
|
|
|
|
|
|
+ - Unique ID are often derived from a string label:
|
|
|
|
|
|
- Button("OK"); // Label = "OK", ID = top of id stack + hash of "OK"
|
|
|
|
- Button("Cancel"); // Label = "Cancel", ID = top of id stack + hash of "Cancel"
|
|
|
|
|
|
+ Button("OK"); // Label = "OK", ID = hash of (..., "OK")
|
|
|
|
+ Button("Cancel"); // Label = "Cancel", ID = hash of (..., "Cancel")
|
|
|
|
|
|
- ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having
|
|
- ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having
|
|
two buttons labeled "OK" in different windows or different tree locations is fine.
|
|
two buttons labeled "OK" in different windows or different tree locations is fine.
|
|
|
|
+ We used "..." above to signify whatever was already pushed to the ID stack previously:
|
|
|
|
+
|
|
|
|
+ Begin("MyWindow");
|
|
|
|
+ Button("OK"); // Label = "OK", ID = hash of ("MyWindow", "OK")
|
|
|
|
+ End();
|
|
|
|
|
|
- If you have a same ID twice in the same location, you'll have a conflict:
|
|
- If you have a same ID twice in the same location, you'll have a conflict:
|
|
|
|
|
|
@@ -491,20 +571,22 @@
|
|
This helps solving the simple collision cases when you know e.g. at compilation time which items
|
|
This helps solving the simple collision cases when you know e.g. at compilation time which items
|
|
are going to be created:
|
|
are going to be created:
|
|
|
|
|
|
- Button("Play"); // Label = "Play", ID = top of id stack + hash of "Play"
|
|
|
|
- Button("Play##foo1"); // Label = "Play", ID = top of id stack + hash of "Play##foo1" (different from above)
|
|
|
|
- Button("Play##foo2"); // Label = "Play", ID = top of id stack + hash of "Play##foo2" (different from above)
|
|
|
|
|
|
+ Begin("MyWindow");
|
|
|
|
+ Button("Play"); // Label = "Play", ID = hash of ("MyWindow", "Play")
|
|
|
|
+ Button("Play##foo1"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo1") // Different from above
|
|
|
|
+ Button("Play##foo2"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo2") // Different from above
|
|
|
|
+ End();
|
|
|
|
|
|
- If you want to completely hide the label, but still need an ID:
|
|
- If you want to completely hide the label, but still need an ID:
|
|
|
|
|
|
- Checkbox("##On", &b); // Label = "", ID = top of id stack + hash of "##On" (no label!)
|
|
|
|
|
|
+ Checkbox("##On", &b); // Label = "", ID = hash of (..., "##On") // No visible label!
|
|
|
|
|
|
- Occasionally/rarely you might want change a label while preserving a constant ID. This allows
|
|
- Occasionally/rarely you might want change a label while preserving a constant ID. This allows
|
|
you to animate labels. For example you may want to include varying information in a window title bar,
|
|
you to animate labels. For example you may want to include varying information in a window title bar,
|
|
but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID:
|
|
but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID:
|
|
|
|
|
|
- Button("Hello###ID"; // Label = "Hello", ID = top of id stack + hash of "ID"
|
|
|
|
- Button("World###ID"; // Label = "World", ID = top of id stack + hash of "ID" (same as above)
|
|
|
|
|
|
+ Button("Hello###ID"; // Label = "Hello", ID = hash of (..., "ID")
|
|
|
|
+ Button("World###ID"; // Label = "World", ID = hash of (..., "ID") // Same as above, even though the label looks different
|
|
|
|
|
|
sprintf(buf, "My game (%f FPS)###MyGame", fps);
|
|
sprintf(buf, "My game (%f FPS)###MyGame", fps);
|
|
Begin(buf); // Variable label, ID = hash of "MyGame"
|
|
Begin(buf); // Variable label, ID = hash of "MyGame"
|
|
@@ -516,45 +598,45 @@
|
|
You can push a pointer, a string or an integer value into the ID stack.
|
|
You can push a pointer, a string or an integer value into the ID stack.
|
|
Remember that ID are formed from the concatenation of _everything_ in the ID stack!
|
|
Remember that ID are formed from the concatenation of _everything_ in the ID stack!
|
|
|
|
|
|
|
|
+ Begin("Window");
|
|
for (int i = 0; i < 100; i++)
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
{
|
|
- PushID(i);
|
|
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of integer + hash of "Click"
|
|
|
|
|
|
+ PushID(i); // Push i to the id tack
|
|
|
|
+ Button("Click"); // Label = "Click", ID = Hash of ("Window", i, "Click")
|
|
PopID();
|
|
PopID();
|
|
}
|
|
}
|
|
-
|
|
|
|
for (int i = 0; i < 100; i++)
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
{
|
|
MyObject* obj = Objects[i];
|
|
MyObject* obj = Objects[i];
|
|
PushID(obj);
|
|
PushID(obj);
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of pointer + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = Hash of ("Window", obj pointer, "Click")
|
|
PopID();
|
|
PopID();
|
|
}
|
|
}
|
|
-
|
|
|
|
for (int i = 0; i < 100; i++)
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
{
|
|
MyObject* obj = Objects[i];
|
|
MyObject* obj = Objects[i];
|
|
PushID(obj->Name);
|
|
PushID(obj->Name);
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of string + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = Hash of ("Window", obj->Name, "Click")
|
|
PopID();
|
|
PopID();
|
|
}
|
|
}
|
|
|
|
+ End();
|
|
|
|
|
|
- More example showing that you can stack multiple prefixes into the ID stack:
|
|
- More example showing that you can stack multiple prefixes into the ID stack:
|
|
|
|
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = hash of (..., "Click")
|
|
PushID("node");
|
|
PushID("node");
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of "node" + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click")
|
|
PushID(my_ptr);
|
|
PushID(my_ptr);
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of "node" + hash of ptr + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = hash of (..., "node", my_ptr, "Click")
|
|
PopID();
|
|
PopID();
|
|
PopID();
|
|
PopID();
|
|
|
|
|
|
- Tree nodes implicitly creates a scope for you by calling PushID().
|
|
- Tree nodes implicitly creates a scope for you by calling PushID().
|
|
|
|
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = hash of (..., "Click")
|
|
if (TreeNode("node"))
|
|
if (TreeNode("node"))
|
|
{
|
|
{
|
|
- Button("Click"); // Label = "Click", ID = top of id stack + hash of "node" + hash of "Click"
|
|
|
|
|
|
+ Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click")
|
|
TreePop();
|
|
TreePop();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2105,7 +2187,14 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
g.ActiveIdIsJustActivated = (g.ActiveId != id);
|
|
g.ActiveIdIsJustActivated = (g.ActiveId != id);
|
|
if (g.ActiveIdIsJustActivated)
|
|
if (g.ActiveIdIsJustActivated)
|
|
|
|
+ {
|
|
g.ActiveIdTimer = 0.0f;
|
|
g.ActiveIdTimer = 0.0f;
|
|
|
|
+ if (id != 0)
|
|
|
|
+ {
|
|
|
|
+ g.LastActiveId = id;
|
|
|
|
+ g.LastActiveIdTimer = 0.0f;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
g.ActiveId = id;
|
|
g.ActiveId = id;
|
|
g.ActiveIdAllowNavDirFlags = 0;
|
|
g.ActiveIdAllowNavDirFlags = 0;
|
|
g.ActiveIdAllowOverlap = false;
|
|
g.ActiveIdAllowOverlap = false;
|
|
@@ -2268,6 +2357,15 @@ static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
|
|
const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)
|
|
const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)
|
|
g.NavScoringCount++;
|
|
g.NavScoringCount++;
|
|
|
|
|
|
|
|
+ if (window != g.NavWindow)
|
|
|
|
+ {
|
|
|
|
+ // When crossing through a NavFlattened border, we score items on the other windows fully clipped
|
|
|
|
+ IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
|
|
|
|
+ if (!window->ClipRect.Contains(cand))
|
|
|
|
+ return false;
|
|
|
|
+ cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
|
|
|
|
+ }
|
|
|
|
+
|
|
// We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
|
|
// We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
|
|
// For example, this ensure that items in one column are not reached when moving vertically from items in another column.
|
|
// For example, this ensure that items in one column are not reached when moving vertically from items in another column.
|
|
NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
|
|
NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
|
|
@@ -2437,6 +2535,8 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con
|
|
|
|
|
|
const ImGuiItemFlags item_flags = window->DC.ItemFlags;
|
|
const ImGuiItemFlags item_flags = window->DC.ItemFlags;
|
|
const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
|
|
const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
|
|
|
|
+
|
|
|
|
+ // Process Init Request
|
|
if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
|
|
if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
|
|
{
|
|
{
|
|
// Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
|
|
// Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
|
|
@@ -2452,9 +2552,9 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Scoring for navigation
|
|
|
|
|
|
+ // Process Move Request (scoring for navigation)
|
|
// FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
|
|
// FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
|
|
- if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav))
|
|
|
|
|
|
+ if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav))
|
|
{
|
|
{
|
|
ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
|
|
ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
|
|
#if IMGUI_DEBUG_NAV_SCORING
|
|
#if IMGUI_DEBUG_NAV_SCORING
|
|
@@ -2468,10 +2568,20 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con
|
|
if (new_best)
|
|
if (new_best)
|
|
{
|
|
{
|
|
result->ID = id;
|
|
result->ID = id;
|
|
- result->ParentID = window->IDStack.back();
|
|
|
|
result->Window = window;
|
|
result->Window = window;
|
|
result->RectRel = nav_bb_rel;
|
|
result->RectRel = nav_bb_rel;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ const float VISIBLE_RATIO = 0.70f;
|
|
|
|
+ if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb))
|
|
|
|
+ if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO)
|
|
|
|
+ if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb))
|
|
|
|
+ {
|
|
|
|
+ result = &g.NavMoveResultLocalVisibleSet;
|
|
|
|
+ result->ID = id;
|
|
|
|
+ result->Window = window;
|
|
|
|
+ result->RectRel = nav_bb_rel;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
// Update window-relative bounding box of navigated item
|
|
// Update window-relative bounding box of navigated item
|
|
@@ -3055,10 +3165,10 @@ static void ImGui::NavUpdateWindowing()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Scroll to keep newly navigated item fully into view
|
|
// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
|
|
// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
|
|
static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect)
|
|
static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect)
|
|
{
|
|
{
|
|
- // Scroll to keep newly navigated item fully into view
|
|
|
|
ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1));
|
|
ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1));
|
|
//GetOverlayDrawList(window)->AddRect(window_rect_rel.Min, window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG]
|
|
//GetOverlayDrawList(window)->AddRect(window_rect_rel.Min, window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG]
|
|
if (window_rect.Contains(item_rect))
|
|
if (window_rect.Contains(item_rect))
|
|
@@ -3096,12 +3206,16 @@ static void ImGui::NavUpdate()
|
|
if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
|
|
if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
|
|
#endif
|
|
#endif
|
|
|
|
|
|
- if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad))
|
|
|
|
|
|
+ bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
|
|
|
|
+ bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
|
|
|
+
|
|
|
|
+ // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard)
|
|
|
|
+ if (nav_gamepad_active)
|
|
if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
|
|
if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
|
|
g.NavInputSource = ImGuiInputSource_NavGamepad;
|
|
g.NavInputSource = ImGuiInputSource_NavGamepad;
|
|
|
|
|
|
// Update Keyboard->Nav inputs mapping
|
|
// Update Keyboard->Nav inputs mapping
|
|
- if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
|
|
|
|
|
|
+ if (nav_keyboard_active)
|
|
{
|
|
{
|
|
#define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
|
|
#define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
|
|
NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
|
|
NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
|
|
@@ -3142,23 +3256,32 @@ static void ImGui::NavUpdate()
|
|
{
|
|
{
|
|
// Select which result to use
|
|
// Select which result to use
|
|
ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
|
|
ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
|
|
- if (g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) // Maybe entering a flattened child? In this case solve the tie using the regular scoring rules
|
|
|
|
- if ((g.NavMoveResultOther.DistBox < g.NavMoveResultLocal.DistBox) || (g.NavMoveResultOther.DistBox == g.NavMoveResultLocal.DistBox && g.NavMoveResultOther.DistCenter < g.NavMoveResultLocal.DistCenter))
|
|
|
|
- result = &g.NavMoveResultOther;
|
|
|
|
|
|
|
|
|
|
+ // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
|
|
|
|
+ if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
|
|
|
|
+ if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId)
|
|
|
|
+ result = &g.NavMoveResultLocalVisibleSet;
|
|
|
|
+
|
|
|
|
+ // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
|
|
|
|
+ if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
|
|
|
|
+ if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
|
|
|
|
+ result = &g.NavMoveResultOther;
|
|
IM_ASSERT(g.NavWindow && result->Window);
|
|
IM_ASSERT(g.NavWindow && result->Window);
|
|
|
|
|
|
- // Scroll to keep newly navigated item fully into view. Also scroll parent window if necessary.
|
|
|
|
|
|
+ // Scroll to keep newly navigated item fully into view.
|
|
if (g.NavLayer == 0)
|
|
if (g.NavLayer == 0)
|
|
{
|
|
{
|
|
ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
|
|
ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
|
|
NavScrollToBringItemIntoView(result->Window, rect_abs);
|
|
NavScrollToBringItemIntoView(result->Window, rect_abs);
|
|
- if (result->Window->Flags & ImGuiWindowFlags_ChildWindow)
|
|
|
|
- NavScrollToBringItemIntoView(result->Window->ParentWindow, rect_abs);
|
|
|
|
|
|
|
|
// Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate()
|
|
// Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate()
|
|
ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false);
|
|
ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false);
|
|
- result->RectRel.Translate(result->Window->Scroll - next_scroll);
|
|
|
|
|
|
+ ImVec2 delta_scroll = result->Window->Scroll - next_scroll;
|
|
|
|
+ result->RectRel.Translate(delta_scroll);
|
|
|
|
+
|
|
|
|
+ // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy).
|
|
|
|
+ if (result->Window->Flags & ImGuiWindowFlags_ChildWindow)
|
|
|
|
+ NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll));
|
|
}
|
|
}
|
|
|
|
|
|
// Apply result from previous frame navigation directional move request
|
|
// Apply result from previous frame navigation directional move request
|
|
@@ -3203,8 +3326,6 @@ static void ImGui::NavUpdate()
|
|
NavUpdateWindowing();
|
|
NavUpdateWindowing();
|
|
|
|
|
|
// Set output flags for user application
|
|
// Set output flags for user application
|
|
- bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
|
|
|
|
- bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
|
|
|
g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
|
|
g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
|
|
g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
|
|
g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
|
|
|
|
|
|
@@ -3297,6 +3418,45 @@ static void ImGui::NavUpdate()
|
|
g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
|
|
g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // PageUp/PageDown scroll
|
|
|
|
+ float nav_scoring_rect_offset_y = 0.0f;
|
|
|
|
+ if (nav_keyboard_active && g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0)
|
|
|
|
+ {
|
|
|
|
+ ImGuiWindow* window = g.NavWindow;
|
|
|
|
+ bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up));
|
|
|
|
+ bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down));
|
|
|
|
+ if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held))
|
|
|
|
+ {
|
|
|
|
+ if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll)
|
|
|
|
+ {
|
|
|
|
+ // Fallback manual-scroll when window has no navigable item
|
|
|
|
+ if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
|
|
|
|
+ SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight());
|
|
|
|
+ else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
|
|
|
|
+ SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
|
|
|
|
+ const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
|
|
|
|
+ if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
|
|
|
|
+ {
|
|
|
|
+ nav_scoring_rect_offset_y = -page_offset_y;
|
|
|
|
+ g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
|
|
|
|
+ g.NavMoveClipDir = ImGuiDir_Up;
|
|
|
|
+ g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
|
|
|
|
+ }
|
|
|
|
+ else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
|
|
|
|
+ {
|
|
|
|
+ nav_scoring_rect_offset_y = +page_offset_y;
|
|
|
|
+ g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
|
|
|
|
+ g.NavMoveClipDir = ImGuiDir_Down;
|
|
|
|
+ g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
if (g.NavMoveDir != ImGuiDir_None)
|
|
if (g.NavMoveDir != ImGuiDir_None)
|
|
{
|
|
{
|
|
g.NavMoveRequest = true;
|
|
g.NavMoveRequest = true;
|
|
@@ -3316,7 +3476,7 @@ static void ImGui::NavUpdate()
|
|
// Scrolling
|
|
// Scrolling
|
|
if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
|
|
if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
|
|
{
|
|
{
|
|
- // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item
|
|
|
|
|
|
+ // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
|
|
ImGuiWindow* window = g.NavWindow;
|
|
ImGuiWindow* window = g.NavWindow;
|
|
const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
|
|
const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
|
|
if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
|
|
if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
|
|
@@ -3344,6 +3504,7 @@ static void ImGui::NavUpdate()
|
|
|
|
|
|
// Reset search results
|
|
// Reset search results
|
|
g.NavMoveResultLocal.Clear();
|
|
g.NavMoveResultLocal.Clear();
|
|
|
|
+ g.NavMoveResultLocalVisibleSet.Clear();
|
|
g.NavMoveResultOther.Clear();
|
|
g.NavMoveResultOther.Clear();
|
|
|
|
|
|
// When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
|
|
// When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
|
|
@@ -3364,6 +3525,7 @@ static void ImGui::NavUpdate()
|
|
// For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items)
|
|
// For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items)
|
|
ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
|
|
ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
|
|
g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : ImRect(0,0,0,0);
|
|
g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : ImRect(0,0,0,0);
|
|
|
|
+ g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y);
|
|
g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
|
|
g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
|
|
g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
|
|
g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
|
|
IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
|
|
IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
|
|
@@ -3822,7 +3984,8 @@ static void ImGui::UpdateMouseInputs()
|
|
{
|
|
{
|
|
if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime)
|
|
if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime)
|
|
{
|
|
{
|
|
- if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
|
|
|
|
|
|
+ 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.MouseDoubleClicked[i] = true;
|
|
g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click
|
|
g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click
|
|
}
|
|
}
|
|
@@ -3836,10 +3999,11 @@ static void ImGui::UpdateMouseInputs()
|
|
}
|
|
}
|
|
else if (g.IO.MouseDown[i])
|
|
else if (g.IO.MouseDown[i])
|
|
{
|
|
{
|
|
- ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i];
|
|
|
|
- g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x);
|
|
|
|
- g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y);
|
|
|
|
- g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta));
|
|
|
|
|
|
+ // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold
|
|
|
|
+ ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
|
|
|
|
+ g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
|
|
|
|
+ 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.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
|
|
if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
|
|
g.NavDisableMouseHover = false;
|
|
g.NavDisableMouseHover = false;
|
|
@@ -4017,6 +4181,7 @@ void ImGui::NewFrame()
|
|
ClearActiveID();
|
|
ClearActiveID();
|
|
if (g.ActiveId)
|
|
if (g.ActiveId)
|
|
g.ActiveIdTimer += g.IO.DeltaTime;
|
|
g.ActiveIdTimer += g.IO.DeltaTime;
|
|
|
|
+ g.LastActiveIdTimer += g.IO.DeltaTime;
|
|
g.ActiveIdPreviousFrame = g.ActiveId;
|
|
g.ActiveIdPreviousFrame = g.ActiveId;
|
|
g.ActiveIdIsAlive = false;
|
|
g.ActiveIdIsAlive = false;
|
|
g.ActiveIdIsJustActivated = false;
|
|
g.ActiveIdIsJustActivated = false;
|
|
@@ -5179,9 +5344,14 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect
|
|
|
|
+ ImRect unclipped_rect = window->ClipRect;
|
|
|
|
+ if (g.NavMoveRequest)
|
|
|
|
+ unclipped_rect.Add(g.NavScoringRectScreen);
|
|
|
|
+
|
|
const ImVec2 pos = window->DC.CursorPos;
|
|
const ImVec2 pos = window->DC.CursorPos;
|
|
- int start = (int)((window->ClipRect.Min.y - pos.y) / items_height);
|
|
|
|
- int end = (int)((window->ClipRect.Max.y - pos.y) / items_height);
|
|
|
|
|
|
+ int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
|
|
|
|
+ int end = (int)((unclipped_rect.Max.y - pos.y) / items_height);
|
|
|
|
|
|
// When performing a navigation request, ensure we have one item extra in the direction we are moving to
|
|
// When performing a navigation request, ensure we have one item extra in the direction we are moving to
|
|
if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up)
|
|
if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up)
|
|
@@ -7125,7 +7295,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
|
window->DC.ChildWindows.resize(0);
|
|
window->DC.ChildWindows.resize(0);
|
|
window->DC.LayoutType = ImGuiLayoutType_Vertical;
|
|
window->DC.LayoutType = ImGuiLayoutType_Vertical;
|
|
window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
|
|
window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
|
|
- window->DC.ItemFlags = ImGuiItemFlags_Default_;
|
|
|
|
|
|
+ window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_;
|
|
window->DC.ItemWidth = window->ItemWidthDefault;
|
|
window->DC.ItemWidth = window->ItemWidthDefault;
|
|
window->DC.TextWrapPos = -1.0f; // disabled
|
|
window->DC.TextWrapPos = -1.0f; // disabled
|
|
window->DC.ItemFlagsStack.resize(0);
|
|
window->DC.ItemFlagsStack.resize(0);
|
|
@@ -12144,28 +12314,28 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
|
ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
|
|
ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
pos.y += window->DC.CurrentLineTextBaseOffset;
|
|
pos.y += window->DC.CurrentLineTextBaseOffset;
|
|
- ImRect bb(pos, pos + size);
|
|
|
|
- ItemSize(bb);
|
|
|
|
|
|
+ ImRect bb_inner(pos, pos + size);
|
|
|
|
+ ItemSize(bb_inner);
|
|
|
|
|
|
// Fill horizontal space.
|
|
// Fill horizontal space.
|
|
ImVec2 window_padding = window->WindowPadding;
|
|
ImVec2 window_padding = window->WindowPadding;
|
|
float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
|
|
float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
|
|
float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
|
|
float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
|
|
ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
|
|
ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
|
|
- ImRect bb_with_spacing(pos, pos + size_draw);
|
|
|
|
|
|
+ ImRect bb(pos, pos + size_draw);
|
|
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
|
|
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
|
|
- bb_with_spacing.Max.x += window_padding.x;
|
|
|
|
|
|
+ bb.Max.x += window_padding.x;
|
|
|
|
|
|
// Selectables are tightly packed together, we extend the box to cover spacing between selectable.
|
|
// Selectables are tightly packed together, we extend the box to cover spacing between selectable.
|
|
float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
|
|
float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
|
|
float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
|
|
float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
|
|
float spacing_R = style.ItemSpacing.x - spacing_L;
|
|
float spacing_R = style.ItemSpacing.x - spacing_L;
|
|
float spacing_D = style.ItemSpacing.y - spacing_U;
|
|
float spacing_D = style.ItemSpacing.y - spacing_U;
|
|
- bb_with_spacing.Min.x -= spacing_L;
|
|
|
|
- bb_with_spacing.Min.y -= spacing_U;
|
|
|
|
- bb_with_spacing.Max.x += spacing_R;
|
|
|
|
- bb_with_spacing.Max.y += spacing_D;
|
|
|
|
- if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
|
|
|
|
|
|
+ bb.Min.x -= spacing_L;
|
|
|
|
+ bb.Min.y -= spacing_U;
|
|
|
|
+ bb.Max.x += spacing_R;
|
|
|
|
+ bb.Max.y += spacing_D;
|
|
|
|
+ if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
|
|
{
|
|
{
|
|
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
|
|
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
|
|
PushColumnClipRect();
|
|
PushColumnClipRect();
|
|
@@ -12180,7 +12350,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
|
if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
|
|
if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
|
|
if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
|
|
if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
|
|
bool hovered, held;
|
|
bool hovered, held;
|
|
- bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags);
|
|
|
|
|
|
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
|
|
if (flags & ImGuiSelectableFlags_Disabled)
|
|
if (flags & ImGuiSelectableFlags_Disabled)
|
|
selected = false;
|
|
selected = false;
|
|
|
|
|
|
@@ -12196,18 +12366,18 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
|
if (hovered || selected)
|
|
if (hovered || selected)
|
|
{
|
|
{
|
|
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
|
|
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
|
|
- RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f);
|
|
|
|
- RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
|
|
|
|
|
|
+ RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
|
|
|
|
+ RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
|
|
}
|
|
}
|
|
|
|
|
|
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
|
|
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
|
|
{
|
|
{
|
|
PushColumnClipRect();
|
|
PushColumnClipRect();
|
|
- bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x);
|
|
|
|
|
|
+ bb.Max.x -= (GetContentRegionMax().x - max_x);
|
|
}
|
|
}
|
|
|
|
|
|
if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
|
|
if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
|
|
- RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
|
|
|
|
|
|
+ RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
|
|
if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
|
|
if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
|
|
|
|
|
|
// Automatically close popups
|
|
// Automatically close popups
|
|
@@ -14068,6 +14238,7 @@ void ImGui::ClearDragDrop()
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiContext& g = *GImGui;
|
|
g.DragDropActive = false;
|
|
g.DragDropActive = false;
|
|
g.DragDropPayload.Clear();
|
|
g.DragDropPayload.Clear();
|
|
|
|
+ g.DragDropAcceptFlags = 0;
|
|
g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
|
|
g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
|
|
g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
|
|
g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
|
|
g.DragDropAcceptFrameCount = -1;
|
|
g.DragDropAcceptFrameCount = -1;
|
|
@@ -14152,16 +14323,15 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
|
|
|
|
|
|
if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
|
|
if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
|
|
{
|
|
{
|
|
- // FIXME-DRAG
|
|
|
|
- //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding);
|
|
|
|
- //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :(
|
|
|
|
-
|
|
|
|
- // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor)
|
|
|
|
- // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
|
|
|
|
- ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
|
|
|
|
- SetNextWindowPos(tooltip_pos);
|
|
|
|
- PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f));
|
|
|
|
- BeginTooltip();
|
|
|
|
|
|
+ // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit)
|
|
|
|
+ // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents.
|
|
|
|
+ BeginDragDropTooltip();
|
|
|
|
+ if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
|
|
|
|
+ {
|
|
|
|
+ ImGuiWindow* tooltip_window = g.CurrentWindow;
|
|
|
|
+ tooltip_window->SkipItems = true;
|
|
|
|
+ tooltip_window->HiddenFrames = 1;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
|
|
if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
|
|
@@ -14178,17 +14348,32 @@ void ImGui::EndDragDropSource()
|
|
IM_ASSERT(g.DragDropActive);
|
|
IM_ASSERT(g.DragDropActive);
|
|
|
|
|
|
if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
|
|
if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
|
|
- {
|
|
|
|
- EndTooltip();
|
|
|
|
- PopStyleColor();
|
|
|
|
- //PopStyleVar();
|
|
|
|
- }
|
|
|
|
|
|
+ EndDragDropTooltip();
|
|
|
|
|
|
// Discard the drag if have not called SetDragDropPayload()
|
|
// Discard the drag if have not called SetDragDropPayload()
|
|
if (g.DragDropPayload.DataFrameCount == -1)
|
|
if (g.DragDropPayload.DataFrameCount == -1)
|
|
ClearDragDrop();
|
|
ClearDragDrop();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+void ImGui::BeginDragDropTooltip()
|
|
|
|
+{
|
|
|
|
+ // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor)
|
|
|
|
+ // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
|
|
|
|
+ // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do.
|
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
|
+ //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
|
|
|
|
+ ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
|
|
|
|
+ SetNextWindowPos(tooltip_pos);
|
|
|
|
+ SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
|
|
|
|
+ //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
|
|
|
|
+ BeginTooltipEx(0, true);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ImGui::EndDragDropTooltip()
|
|
|
|
+{
|
|
|
|
+ EndTooltip();
|
|
|
|
+}
|
|
|
|
+
|
|
// Use 'cond' to choose to submit payload on drag start or every frame
|
|
// Use 'cond' to choose to submit payload on drag start or every frame
|
|
bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
|
|
bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
|
|
{
|
|
{
|
|
@@ -14198,7 +14383,7 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s
|
|
cond = ImGuiCond_Always;
|
|
cond = ImGuiCond_Always;
|
|
|
|
|
|
IM_ASSERT(type != NULL);
|
|
IM_ASSERT(type != NULL);
|
|
- IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 12 characters long");
|
|
|
|
|
|
+ IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
|
|
IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
|
|
IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
|
|
IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
|
|
IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
|
|
IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
|
|
IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
|
|
@@ -14302,6 +14487,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop
|
|
float r_surface = r.GetWidth() * r.GetHeight();
|
|
float r_surface = r.GetWidth() * r.GetHeight();
|
|
if (r_surface < g.DragDropAcceptIdCurrRectSurface)
|
|
if (r_surface < g.DragDropAcceptIdCurrRectSurface)
|
|
{
|
|
{
|
|
|
|
+ g.DragDropAcceptFlags = flags;
|
|
g.DragDropAcceptIdCurr = g.DragDropTargetId;
|
|
g.DragDropAcceptIdCurr = g.DragDropTargetId;
|
|
g.DragDropAcceptIdCurrRectSurface = r_surface;
|
|
g.DragDropAcceptIdCurrRectSurface = r_surface;
|
|
}
|
|
}
|