Merge branch 'master' into navigation
# Conflicts:
# imgui.h
diff --git a/imgui.cpp b/imgui.cpp
index a048e64..097b049 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -1,6 +1,9 @@
// dear imgui, v1.52 WIP
// (main code and documentation)
+// ** EXPERIMENTAL GAMEPAD/KEYBOARD NAVIGATION BRANCH
+// ** Grep for FIXME-NAVIGATION
+
// See ImGui::ShowTestWindow() in imgui_demo.cpp for demo code.
// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase.
// Get latest version at https://github.com/ocornut/imgui
@@ -19,6 +22,7 @@
- Read first
- How to update to a newer version of Dear ImGui
- Getting started with integrating Dear ImGui in your code/engine
+ - Using gamepad/keyboard navigation [BETA]
- API BREAKING CHANGES (read me when you update!)
- ISSUES & TODO LIST
- FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
@@ -77,6 +81,7 @@
- ESCAPE to revert text to its original value
- You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
- Controls are automatically adjusted for OSX to match standard OSX text editing operations.
+ - Gamepad/keyboard navigation are in beta-phase, see Programmer Guide below.
PROGRAMMER GUIDE
@@ -195,6 +200,37 @@
They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide
mouse inputs from the rest of your application. Read the FAQ below for more information about those flags.
+ USING GAMEPAD/KEYBOARD NAVIGATION [BETA]
+
+ - Gamepad/keyboard navigation support is available, currently in Beta with some issues. Your feedback and bug reports are welcome.
+ - See https://github.com/ocornut/imgui/issues/787 discussion thread and ask questions there.
+ - The current primary focus is to support game controllers.
+ - Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
+ - Consider using Synergy host (on your computer) + uSynergy.c (in your console/tablet/phone app) to use PC mouse/keyboard.
+ - Your inputs are passed to imgui by filling the io.NavInputs[] array. See 'enum ImGuiNavInput_' in imgui.h for a description of available inputs.
+ - For gamepad use, the easiest approach is to go all-or-nothing, with a buttons combo that toggle your inputs between imgui and your game/application.
+ Sharing inputs in a more advanced or granular way between imgui and your game/application may be tricky and requires further work on imgui.
+ For more advanced uses, you may want to use:
+ - io.NavUsable: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
+ - io.NavActive: true when the navigation cursor is visible (and usually goes false when mouse is used).
+ - query focus information with IsWindowFocused(), IsAnyWindowFocused(), IsAnyItemFocused() functions.
+ The reality is more complex than what those flags can express. Please discuss your issues and usage scenario in the thread above.
+ As we head toward more keyboard-oriented development this aspect will need to be improved.
+ - It is recommended that you enable the 'io.NavMovesMouse' option. Enabling it instructs ImGui that it can move your move cursor to track navigated items and ease readability.
+ When enabled and using directional navigation (with d-pad or arrow keys), the NewFrame() functions may alter 'io.MousePos' and set 'io.WantMoveMouse' to notify you that it did so.
+ When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. The examples binding in examples/ do that.
+ (Important: It you set 'io.NavMovesMouse' to true but don't honor 'io.WantMoveMouse' properly, imgui will misbehave as it will think your mouse is moving back and forth.)
+
+ // Application init
+ io.NavMovesMouse = true;
+
+ // Application main loop
+ if (io.WantMoveMouse)
+ MyFuncToSetMousePosition(io.MousePos.x, io.MousePos.y);
+ ImGui::NewFrame();
+
+ In a setup when you may not have easy control over the mouse cursor (e.g. uSynergy.c doesn't expose moving remote mouse cursor),
+ you might want to set a boolean to ignore your other external mouse positions until they move again.
API BREAKING CHANGES
@@ -551,7 +587,6 @@
#include <ctype.h> // toupper, isprint
#include <stdlib.h> // NULL, malloc, free, qsort, atoi
#include <stdio.h> // vsnprintf, sscanf, printf
-#include <limits.h> // INT_MIN, INT_MAX
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
#include <stddef.h> // intptr_t
#else
@@ -599,6 +634,7 @@
static ImFont* GetDefaultFont();
static void SetCurrentFont(ImFont* font);
static void SetCurrentWindow(ImGuiWindow* window);
+static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x);
static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y);
static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond);
static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond);
@@ -714,9 +750,9 @@
colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.30f); // Background of checkbox, radio button, plot, slider, text input
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.90f, 0.80f, 0.80f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f);
- colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f);
+ colors[ImGuiCol_TitleBg] = ImVec4(0.24f, 0.24f, 0.50f, 0.83f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f);
- colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f);
+ colors[ImGuiCol_TitleBgActive] = ImVec4(0.33f, 0.33f, 0.65f, 0.87f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f);
@@ -747,6 +783,8 @@
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
+ colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered];
+ colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.12f);
}
ImGuiIO::ImGuiIO()
@@ -766,6 +804,7 @@
KeyMap[i] = -1;
KeyRepeatDelay = 0.250f;
KeyRepeatRate = 0.050f;
+ NavMovesMouse = false;
UserData = NULL;
Fonts = &GImDefaultFontAtlas;
@@ -786,11 +825,12 @@
ImeWindowHandle = NULL;
// Input (NB: we already have memset zero the entire structure)
- MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
- MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
+ MousePos = ImVec2(-FLT_MAX,-FLT_MAX);
+ MousePosPrev = ImVec2(-FLT_MAX,-FLT_MAX);
MouseDragThreshold = 6.0f;
for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
- for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
+ for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
+ for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
// Set OS X style defaults based on __APPLE__ compile time flag
#ifdef __APPLE__
@@ -1787,6 +1827,7 @@
SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f);
WindowPadding = ImVec2(0.0f, 0.0f);
MoveId = GetID("#MOVE");
+ ChildId = 0;
Scroll = ImVec2(0.0f, 0.0f);
ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
@@ -1796,10 +1837,12 @@
Active = WasActive = false;
Accessed = false;
Collapsed = false;
+ CollapseToggleWanted = false;
SkipItems = false;
Appearing = false;
BeginCount = 0;
PopupId = 0;
+ NavLastId = 0;
AutoFitFramesX = AutoFitFramesY = -1;
AutoFitOnlyGrows = false;
AutoFitChildAxises = 0x00;
@@ -1818,6 +1861,7 @@
ParentWindow = NULL;
RootWindow = NULL;
RootNonPopupWindow = NULL;
+ RootNavWindow = NULL;
FocusIdxAllCounter = FocusIdxTabCounter = -1;
FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
@@ -1879,9 +1923,38 @@
ImGuiContext& g = *GImGui;
g.ActiveIdIsJustActivated = (g.ActiveId != id);
g.ActiveId = id;
+ g.ActiveIdAllowNavDirFlags = 0;
g.ActiveIdAllowOverlap = false;
- g.ActiveIdIsAlive |= (id != 0);
g.ActiveIdWindow = window;
+ if (id)
+ {
+ g.ActiveIdIsAlive = true;
+ g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavTabbedId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
+ if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ g.NavDisableMouseHover = true;
+ else
+ g.NavDisableHighlight = true;
+ g.NavId = id;
+ if (window)
+ g.NavLayer = window->DC.NavLayerCurrent;
+ if (window && window->DC.NavLayerCurrent == 0) // (Assume that id correspond to the current NavLayer, which should be the case)
+ window->NavLastId = id;
+ }
+}
+
+void ImGui::SetActiveIDNoNav(ImGuiID id, ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ g.ActiveIdIsJustActivated = (g.ActiveId != id);
+ g.ActiveId = id;
+ g.ActiveIdAllowNavDirFlags = 0;
+ g.ActiveIdAllowOverlap = false;
+ g.ActiveIdWindow = window;
+ if (id)
+ {
+ g.ActiveIdIsAlive = true;
+ g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
+ }
}
void ImGui::ClearActiveID()
@@ -1931,17 +2004,209 @@
ItemSize(bb.GetSize(), text_offset_y);
}
+static ImGuiDir NavScoreItemGetQuadrant(float dx, float dy)
+{
+ if (fabsf(dx) > fabsf(dy))
+ return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
+ return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
+}
+
+static float NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
+{
+ if (a1 < b0) return a1 - b0;
+ if (b1 < a0) return a0 - b1;
+ return 0.0f;
+}
+
+// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
+// FIXME-NAVIGATION: Pretty rough. Also may want to handle the degenerate case that we have commented out.
+static bool NavScoreItem(ImRect cand)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.NavWindow;
+ if (g.NavLayer != window->DC.NavLayerCurrent)
+ return false;
+
+ 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 lots of items with varied width)
+
+ // We perform scoring on items bounding box clipped by their parent window on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
+ if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
+ {
+ cand.Min.y = ImClamp(cand.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y);
+ cand.Max.y = ImClamp(cand.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y);
+ }
+ else
+ {
+ cand.Min.x = ImClamp(cand.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
+ cand.Max.x = ImClamp(cand.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
+ }
+
+ // Compute distance between boxes
+ // FIXME-NAVIGATION: Introducing various biases toward typical imgui uses cases, but we don't have any rigorous proof of their side-effect..
+ float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
+ float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
+ if (dby && dbx)
+ dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
+ float dist_box = fabsf(dbx) + fabsf(dby);
+
+ // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter)
+ float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
+ float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
+ float dist_center = fabsf(dcx) + fabsf(dcy); // L1 metric (need this for our connectedness guarantee)
+
+ // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
+ ImGuiDir quadrant;
+ float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
+ if (dbx || dby)
+ {
+ // For non-overlapping boxes, use distance between boxes
+ dax = dbx;
+ day = dby;
+ dist_axial = dist_box;
+ quadrant = NavScoreItemGetQuadrant(dbx, dby);
+ }
+ else if (dcx || dcy)
+ {
+ // For overlapping boxes with different centers, use distance between centers
+ dax = dcx;
+ day = dcy;
+ dist_axial = dist_center;
+ quadrant = NavScoreItemGetQuadrant(dcx, dcy);
+ }
+ else
+ {
+ // Degenerate case: two overlapping buttons with same center, break ties using order
+ quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
+ }
+
+#if 0 // [DEBUG]
+ if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
+ {
+ char buf[128];
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "db (%.0f,%.0f->%.5f) dc (%.0f,%.0f->%.5f) da (%.0f,%.0f->%.5f) quad %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[quadrant]);
+ g.OverlayDrawList.AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
+ g.OverlayDrawList.AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150));
+ g.OverlayDrawList.AddText(cand.Max, ~0U, buf);
+ }
+ #endif
+
+ // Is it in the quadrant we're interesting in moving to?
+ bool new_best = false;
+ if (quadrant == g.NavMoveDir)
+ {
+ // Does it beat the current best candidate?
+ if (dist_box < g.NavMoveResultDistBox)
+ {
+ g.NavMoveResultDistBox = dist_box;
+ g.NavMoveResultDistCenter = dist_center;
+ return true;
+ }
+ if (dist_box == g.NavMoveResultDistBox)
+ {
+ // Try using distance between center points to break ties
+ if (dist_center < g.NavMoveResultDistCenter)
+ {
+ g.NavMoveResultDistCenter = dist_center;
+ new_best = true;
+ }
+ else if (dist_center == g.NavMoveResultDistCenter)
+ {
+ // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" buttons
+ // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index),
+ // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis.
+ if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
+ new_best = true;
+ }
+ }
+ }
+
+ // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches
+ // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
+ // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too.
+ if (g.NavMoveResultDistBox == FLT_MAX)
+ if (dist_axial < g.NavMoveResultDistAxial) // Check axial match
+ if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f))
+ g.NavMoveResultDistAxial = dist_axial, new_best = true;
+
+ return new_best;
+}
+
+void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id)
+{
+ ImGuiContext& g = *GImGui;
+ if (id != g.NavId || g.NavDisableHighlight)
+ return;
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+
+ const float THICKNESS = 2.0f;
+ const float DISTANCE = 3.0f + THICKNESS * 0.5f;
+ ImRect display_rect(bb.Min - ImVec2(DISTANCE,DISTANCE), bb.Max + ImVec2(DISTANCE,DISTANCE));
+ if (!window->ClipRect.Contains(display_rect))
+ window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
+ window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), g.Style.FrameRounding, 0x0F, THICKNESS);
+ //window->DrawList->AddRect(g.NavRefRectScreen.Min, g.NavRefRectScreen.Max, IM_COL32(255,0,0,255));
+ if (!window->ClipRect.Contains(display_rect))
+ window->DrawList->PopClipRect();
+}
+
// Declare item bounding box for clipping and interaction.
// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
// declares their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd().
-bool ImGui::ItemAdd(const ImRect& bb, const ImGuiID* id)
+bool ImGui::ItemAdd(const ImRect& bb, const ImGuiID* id, const ImRect* nav_bb_arg)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
window->DC.LastItemId = id ? *id : 0;
window->DC.LastItemRect = bb;
window->DC.LastItemHoveredAndUsable = window->DC.LastItemHoveredRect = false;
- if (IsClippedEx(bb, id, false))
+ const bool is_clipped = IsClippedEx(bb, id, false);
+ if (id != NULL)
+ window->DC.NavLayerActiveFlagsNext |= (1 << window->DC.NavLayerCurrent);
+
+ // Navigation processing runs prior to clipping early-out
+ // (a) So that NavInitDefaultRequest can be honored, for newly opened windows to select a default widget
+ // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window.
+ // it may not scale very well for windows with ten of thousands of item, but at least the NavRequest is only performed on user interaction, aka maximum once a frame.
+ // We could early out with `if (is_clipped && !g.NavInitDefaultRequest) return false;` but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick)
+ // A more pragmatic solution for handling long lists is relying on the fact that they are likely evenly spread items (so that clipper can be used) and we could Nav at higher-level (apply index, etc.)
+ // So eventually we would like to provide the user will the primitives to be able to implement that customized/efficient navigation handling whenever necessary.
+ if (id != NULL && g.IO.NavUsable && g.NavWindow == window->RootNavWindow)
+ {
+ const ImRect& nav_bb = nav_bb_arg ? *nav_bb_arg : bb;
+ const ImRect nav_bb_rel(nav_bb.Min - g.NavWindow->Pos, nav_bb.Max - g.NavWindow->Pos);
+ if (g.NavInitDefaultRequest && g.NavLayer == window->DC.NavLayerCurrent)
+ {
+ // Even if 'ImGuiItemFlags_AllowNavDefaultFocus' is off (typically collapse/close button) we record the first ResultId so they can be used as fallback
+ if (window->DC.ItemFlags & ImGuiItemFlags_AllowNavDefaultFocus)
+ g.NavInitDefaultRequest = g.NavInitDefaultResultExplicit = false; // Found a match, clear request
+ if (g.NavInitDefaultResultId == 0 || (window->DC.ItemFlags & ImGuiItemFlags_AllowNavDefaultFocus))
+ {
+ g.NavInitDefaultResultId = *id;
+ g.NavInitDefaultResultRectRel = nav_bb_rel;
+ }
+ }
+
+ //const bool DEBUG_NAV = false; // [DEBUG] Enable to test scoring on all items.
+ if ((g.NavMoveRequest /*|| DEBUG_NAV*/) && g.NavId != *id)
+ {
+ //if (DEBUG_NAV && !g.NavMoveRequest) g.NavMoveDir = ImGuiDir_N;
+ if (NavScoreItem(nav_bb)) //if (!DEBUG || g.NavMoveRequest)
+ {
+ g.NavMoveResultId = *id;
+ g.NavMoveResultRectRel = nav_bb_rel;
+ }
+ }
+
+ // Update window-relative bounding box of navigated item
+ if (g.NavId == *id)
+ {
+ g.NavRefRectRel = nav_bb_rel;
+ g.NavIdIsAlive = true;
+ g.NavIdTabCounter = window->FocusIdxTabCounter;
+ }
+ }
+
+ if (is_clipped)
return false;
// Setting LastItemHoveredAndUsable for IsItemHovered(). This is a sensible default, but widgets are free to override it.
@@ -1952,7 +2217,7 @@
window->DC.LastItemHoveredRect = true;
if (g.HoveredRootWindow == window->RootWindow)
if (g.ActiveId == 0 || (id && g.ActiveId == *id) || g.ActiveIdAllowOverlap || (g.ActiveId == window->MoveId))
- if (IsWindowContentHoverable(window))
+ if (!g.NavDisableMouseHover && IsWindowContentHoverable(window))
window->DC.LastItemHoveredAndUsable = true;
}
@@ -1979,7 +2244,7 @@
ImGuiWindow* window = GetCurrentWindowRead();
if (g.HoveredWindow == window || (flatten_childs && g.HoveredRootWindow == window->RootWindow))
if ((g.ActiveId == 0 || g.ActiveId == id || g.ActiveIdAllowOverlap) && IsMouseHoveringRect(bb.Min, bb.Max))
- if (IsWindowContentHoverable(g.HoveredRootWindow))
+ if (!g.NavDisableMouseHover && IsWindowContentHoverable(g.HoveredRootWindow))
return true;
}
return false;
@@ -2001,10 +2266,11 @@
if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent)
return true;
-
- if (allow_keyboard_focus)
- if (window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
- return true;
+ if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
+ {
+ g.NavTabbedId = id;
+ return true;
+ }
return false;
}
@@ -2133,6 +2399,401 @@
return GImGui->FrameCount;
}
+static void SetNavId(ImGuiID id)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.NavWindow);
+ g.NavId = id;
+ if (g.NavLayer == 0)
+ g.NavWindow->NavLastId = g.NavId;
+}
+
+static void SetNavIdAndMoveMouse(ImGuiID id, const ImRect& rect_rel)
+{
+ ImGuiContext& g = *GImGui;
+ SetNavId(id);
+ g.NavRefRectRel = rect_rel;
+ g.NavMousePosDirty = true;
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+}
+
+// This needs to be called before we submit any widget (aka in or before Begin)
+static void NavInitWindow(ImGuiWindow* window, bool force_reinit)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(window == g.NavWindow);
+ if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastId == 0) || force_reinit)
+ {
+ SetNavId(0);
+ g.NavInitDefaultRequest = true;
+ g.NavInitDefaultResultId = 0;
+ g.NavInitDefaultResultExplicit = false;
+ g.NavInitDefaultResultRectRel = ImRect();
+ }
+ else
+ {
+ g.NavId = window->NavLastId;
+ }
+}
+
+static ImVec2 NavCalcPreferredMousePos()
+{
+ ImGuiContext& g = *GImGui;
+ if (!g.NavWindow)
+ return g.IO.MousePos;
+ ImVec2 p = g.NavWindow->Pos + ImVec2(g.NavRefRectRel.Min.x + ImMin(g.Style.FramePadding.x*4, g.NavRefRectRel.GetWidth()), g.NavRefRectRel.Max.y - ImMin(g.Style.FramePadding.y, g.NavRefRectRel.GetHeight()));
+ ImRect r = GetVisibleRect();
+ return ImFloor(ImClamp(p, r.Min, r.Max)); // ImFloor() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta.
+}
+
+static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
+{
+ ImGuiContext& g = *GImGui;
+ for (int i = g.Windows.Size-1; i >= 0; i--)
+ if (g.Windows[i] == window)
+ return i;
+ return -1;
+}
+
+static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
+{
+ ImGuiContext& g = *GImGui;
+ for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
+ {
+ ImGuiWindow* window = g.Windows[i];
+ if (window->Active && window == window->RootNonPopupWindow && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow))
+ return window;
+ }
+ return NULL;
+}
+
+enum ImGuiNavReadMode
+{
+ ImGuiNavReadMode_Down,
+ ImGuiNavReadMode_Pressed,
+ ImGuiNavReadMode_Repeat,
+ ImGuiNavReadMode_RepeatSlow,
+ ImGuiNavReadMode_RepeatFast
+};
+
+// FIXME-NAVIGATION: Expose navigation repeat delay/rate
+static float GetNavInputAmount(ImGuiNavInput n, ImGuiNavReadMode mode)
+{
+ ImGuiContext& g = *GImGui;
+ if (mode == ImGuiNavReadMode_Down)
+ return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
+ const float t = g.IO.NavInputsDownDuration[n]; // Duration pressed
+ if (mode == ImGuiNavReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input (we don't need it for Pressed logic)
+ return (t == 0.0f) ? 1.0f : 0.0f;
+ if (mode == ImGuiNavReadMode_Repeat)
+ return (float)ImGui::CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
+ if (mode == ImGuiNavReadMode_RepeatSlow)
+ return (float)ImGui::CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
+ if (mode == ImGuiNavReadMode_RepeatFast)
+ return (float)ImGui::CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
+ return 0.0f;
+}
+
+// Equivalent of IsKeyDown() for NavInputs[]
+static bool IsNavInputDown(ImGuiNavInput n)
+{
+ return GImGui->IO.NavInputs[n] > 0.0f;
+}
+
+// Equivalent of IsKeyPressed() for NavInputs[]
+static bool IsNavInputPressed(ImGuiNavInput n, ImGuiNavReadMode mode)// = ImGuiNavReadMode_Re)
+{
+ return GetNavInputAmount(n, mode) > 0.0f;
+}
+
+static ImVec2 GetNavInputAmount2d(int stick_no, ImGuiNavReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f)
+{
+ IM_ASSERT(ImGuiNavInput_PadScrollUp == ImGuiNavInput_PadUp + 4);
+ IM_ASSERT(stick_no >= 0 && stick_no < 2);
+
+ ImVec2 delta;
+ delta.x = GetNavInputAmount(ImGuiNavInput_PadRight + stick_no*4, mode) - GetNavInputAmount(ImGuiNavInput_PadLeft + stick_no*4, mode);
+ delta.y = GetNavInputAmount(ImGuiNavInput_PadDown + stick_no*4, mode) - GetNavInputAmount(ImGuiNavInput_PadUp + stick_no*4, mode);
+ if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_PadTweakSlow))
+ delta *= slow_factor;
+ if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_PadTweakFast))
+ delta *= fast_factor;
+ return delta;
+}
+
+static void NavUpdate()
+{
+ ImGuiContext& g = *GImGui;
+ g.IO.WantMoveMouse = false;
+
+ // Process navigation init request (select first/default focus)
+ if (g.NavInitDefaultResultId != 0 && (!g.NavDisableHighlight || g.NavInitDefaultResultExplicit))
+ {
+ // Apply result from previous navigation init request (typically select the first item, unless SetItemDefaultFocus() has been called)
+ IM_ASSERT(g.NavWindow);
+ SetNavId(g.NavInitDefaultResultId);
+ g.NavRefRectRel = g.NavInitDefaultResultRectRel;
+ if (g.NavDisableMouseHover)
+ g.NavMousePosDirty = true;
+ }
+ g.NavInitDefaultRequest = false;
+ g.NavInitDefaultResultExplicit = false;
+ g.NavInitDefaultResultId = 0;
+
+ // Process navigation move request
+ if (g.NavMoveRequest && g.NavMoveResultId != 0)
+ {
+ IM_ASSERT(g.NavWindow);
+
+ // Scroll to keep newly navigated item fully into view
+ ImRect window_rect_rel(g.NavWindow->InnerRect.Min - g.NavWindow->Pos - ImVec2(1,1), g.NavWindow->InnerRect.Max - g.NavWindow->Pos + ImVec2(1,1));
+ //g.OverlayDrawList.AddRect(g.NavWindow->Pos + window_rect_rel.Min, g.NavWindow->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG]
+ if (g.NavLayer == 0 && !window_rect_rel.Contains(g.NavMoveResultRectRel))
+ {
+ if (g.NavWindow->ScrollbarX && g.NavMoveResultRectRel.Min.x < window_rect_rel.Min.x)
+ {
+ g.NavWindow->ScrollTarget.x = g.NavMoveResultRectRel.Min.x + g.NavWindow->Scroll.x - g.Style.ItemSpacing.x;
+ g.NavWindow->ScrollTargetCenterRatio.x = 0.0f;
+ }
+ else if (g.NavWindow->ScrollbarX && g.NavMoveResultRectRel.Max.x >= window_rect_rel.Max.x)
+ {
+ g.NavWindow->ScrollTarget.x = g.NavMoveResultRectRel.Max.x + g.NavWindow->Scroll.x + g.Style.ItemSpacing.x;
+ g.NavWindow->ScrollTargetCenterRatio.x = 1.0f;
+ }
+ if (g.NavMoveResultRectRel.Min.y < window_rect_rel.Min.y)
+ {
+ g.NavWindow->ScrollTarget.y = g.NavMoveResultRectRel.Min.y + g.NavWindow->Scroll.y - g.Style.ItemSpacing.y;
+ g.NavWindow->ScrollTargetCenterRatio.y = 0.0f;
+ }
+ else if (g.NavMoveResultRectRel.Max.y >= window_rect_rel.Max.y)
+ {
+ g.NavWindow->ScrollTarget.y = g.NavMoveResultRectRel.Max.y + g.NavWindow->Scroll.y + g.Style.ItemSpacing.y;
+ g.NavWindow->ScrollTargetCenterRatio.y = 1.0f;
+ }
+
+ // Estimate upcoming scroll so we can offset our relative mouse position so mouse position can be applied immediately (under this block)
+ ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(g.NavWindow);
+ g.NavMoveResultRectRel.Translate(g.NavWindow->Scroll - next_scroll);
+ }
+
+ // Apply result from previous frame navigation directional move request
+ ImGui::ClearActiveID();
+ SetNavIdAndMoveMouse(g.NavMoveResultId, g.NavMoveResultRectRel);
+ g.NavMoveFromClampedRefRect = false;
+ }
+
+ // Apply application mouse position movement, after we had a chance to process move request result.
+ if (g.NavMousePosDirty && g.NavIdIsAlive)
+ {
+ // Set mouse position given our knowledge of the nav widget position from last frame
+ if (g.IO.NavMovesMouse)
+ {
+ g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredMousePos();
+ g.IO.WantMoveMouse = true;
+ }
+ g.NavMousePosDirty = false;
+ }
+ g.NavIdIsAlive = false;
+ g.NavTabbedId = 0;
+
+ // Navigation windowing mode (change focus, move/resize window)
+ if (!g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_PadMenu, ImGuiNavReadMode_Pressed))
+ {
+ ImGuiWindow* window = g.NavWindow;
+ if (!window)
+ window = FindWindowNavigable(g.Windows.Size-1, -1, -1);
+ if (window)
+ {
+ g.NavWindowingTarget = window->RootNonPopupWindow;
+ g.NavWindowingDisplayAlpha = 0.0f;
+ g.NavWindowingToggleLayer = true;
+ }
+ }
+ if (g.NavWindowingTarget)
+ {
+ // Visuals only appears after a brief time holding the button, so that a fast tap (to toggle NavLayer) doesn't add visual noise
+ const float pressed_duration = g.IO.NavInputsDownDuration[ImGuiNavInput_PadMenu];
+ g.NavWindowingDisplayAlpha = ImMax(g.NavWindowingDisplayAlpha, ImSaturate((pressed_duration - 0.20f) / 0.05f));
+ g.NavWindowingToggleLayer &= (g.NavWindowingDisplayAlpha < 1.0f); // Once button is held long enough we don't consider it a tag-to-toggle-layer press anymore.
+
+ // Select window to focus
+ const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_PadFocusPrev, ImGuiNavReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_PadFocusNext, ImGuiNavReadMode_RepeatSlow);
+ if (focus_change_dir != 0 && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal))
+ {
+ const int i_current = FindWindowIndex(g.NavWindowingTarget);
+ ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -1, focus_change_dir);
+ if (!window_target)
+ window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size-1) : 0, i_current, focus_change_dir);
+ g.NavWindowingTarget = window_target;
+ g.NavWindowingToggleLayer = false;
+ g.NavWindowingDisplayAlpha = 1.0f;
+ }
+
+ // Move window
+ if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
+ {
+ const ImVec2 move_delta = GetNavInputAmount2d(1, ImGuiNavReadMode_Down);
+ if (move_delta.x != 0.0f || move_delta.y != 0.0f)
+ {
+ const float move_speed = ImFloor(600 * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
+ g.NavWindowingTarget->PosFloat += move_delta * move_speed;
+ g.NavDisableMouseHover = true;
+ MarkIniSettingsDirty(g.NavWindowingTarget);
+ }
+ }
+
+ if (!IsNavInputDown(ImGuiNavInput_PadMenu))
+ {
+ // Apply actual focus only when releasing the NavMenu button (until then the window was merely rendered front-most)
+ if (g.NavWindowingTarget && !g.NavWindowingToggleLayer && (!g.NavWindow || g.NavWindowingTarget != g.NavWindow->RootNonPopupWindow))
+ {
+ ImGui::FocusWindow(g.NavWindowingTarget);
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+ if (g.NavWindowingTarget->NavLastId == 0)
+ NavInitWindow(g.NavWindowingTarget, false);
+ }
+
+ // Single press toggles NavLayer
+ if (g.NavWindowingToggleLayer && g.NavWindow)
+ {
+ if ((g.NavWindow->DC.NavLayerActiveFlags & (1<<1)) == 0 && (g.NavWindow->RootWindow->DC.NavLayerActiveFlags & (1<<1)) != 0)
+ ImGui::FocusWindow(g.NavWindow->RootWindow);
+ g.NavLayer = (g.NavWindow->DC.NavLayerActiveFlags & (1<<1)) ? (g.NavLayer ^ 1) : 0;
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+ if (g.NavLayer == 0 && g.NavWindow->NavLastId)
+ SetNavIdAndMoveMouse(g.NavWindow->NavLastId, ImRect());
+ else
+ NavInitWindow(g.NavWindow, true);
+ }
+ g.NavWindowingTarget = NULL;
+ }
+ }
+
+ // Set output flags for user application
+ g.IO.NavUsable = g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
+ g.IO.NavActive = (g.IO.NavUsable && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitDefaultRequest;
+
+ // Process NavCancel input (to close a popup, get back to parent, clear focus)
+ if (IsNavInputPressed(ImGuiNavInput_PadCancel, ImGuiNavReadMode_Pressed))
+ {
+ if (g.ActiveId != 0)
+ {
+ ImGui::ClearActiveID();
+ }
+ else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
+ {
+ // Exit child window
+ ImGuiWindow* child_window = g.NavWindow;
+ ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
+ ImGui::FocusWindow(parent_window);
+ IM_ASSERT(child_window->ChildId != 0);
+ SetNavId(child_window->ChildId);
+ g.NavIdIsAlive = false;
+ if (g.NavDisableMouseHover)
+ g.NavMousePosDirty = true;
+ }
+ else if (g.OpenPopupStack.Size > 0)
+ {
+ // Close open popup/menu
+ ClosePopupToLevel(g.OpenPopupStack.Size - 1);
+ }
+ else if (g.NavLayer != 0)
+ {
+ // Leave the "menu" layer
+ g.NavLayer = 0;
+ if (g.NavWindow->NavLastId)
+ SetNavIdAndMoveMouse(g.NavWindow->NavLastId, ImRect());
+ else
+ NavInitWindow(g.NavWindow, true);
+ }
+ else
+ {
+ // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
+ if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
+ g.NavWindow->NavLastId = 0;
+ g.NavId = 0;
+ }
+ }
+
+ g.NavActivateId = (g.NavId && !g.NavDisableHighlight && !g.NavWindowingTarget && g.ActiveId == 0 && IsNavInputPressed(ImGuiNavInput_PadActivate, ImGuiNavReadMode_Pressed)) ? g.NavId : 0;
+ g.NavInputId = (g.NavId && !g.NavDisableHighlight && !g.NavWindowingTarget && g.ActiveId == 0 && IsNavInputPressed(ImGuiNavInput_PadInput, ImGuiNavReadMode_Pressed)) ? g.NavId : 0;
+ if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+ {
+ g.NavActivateId = g.NavInputId = 0;
+ g.NavDisableHighlight = true;
+ }
+ g.NavMoveRequest = false;
+
+ // Initiate directional inputs request
+ const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
+ g.NavMoveDir = ImGuiDir_None;
+ if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+ {
+ if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressed(ImGuiNavInput_PadLeft, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
+ if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressed(ImGuiNavInput_PadRight, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
+ if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressed(ImGuiNavInput_PadUp, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
+ if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressed(ImGuiNavInput_PadDown, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
+ }
+ if (g.NavMoveDir != ImGuiDir_None)
+ g.NavMoveRequest = true;
+
+ // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match
+ if (g.NavMoveRequest && g.NavId == 0)
+ {
+ g.NavInitDefaultRequest = g.NavInitDefaultResultExplicit = true;
+ g.NavInitDefaultResultId = 0;
+ g.NavDisableHighlight = false;
+ }
+
+ // Scrolling
+ if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
+ {
+ // Fallback manual-scroll with NavUp/NavDown when window has no navigable item
+ const float scroll_speed = ImFloor(g.NavWindow->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
+ if (!g.NavWindow->DC.NavLayerActiveFlags && g.NavWindow->DC.NavHasScroll && g.NavMoveRequest && (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down))
+ SetWindowScrollY(g.NavWindow, ImFloor(g.NavWindow->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
+
+ // Manual scroll with NavScrollXXX keys
+ ImVec2 scroll_dir = GetNavInputAmount2d(1, ImGuiNavReadMode_Down, 1.0f/10.0f, 10.0f);
+ if (scroll_dir.x != 0.0f && g.NavWindow->ScrollbarX)
+ {
+ SetWindowScrollX(g.NavWindow, ImFloor(g.NavWindow->Scroll.x + scroll_dir.x * scroll_speed));
+ g.NavMoveFromClampedRefRect = true;
+ }
+ if (scroll_dir.y != 0.0f)
+ {
+ SetWindowScrollY(g.NavWindow, ImFloor(g.NavWindow->Scroll.y + scroll_dir.y * scroll_speed));
+ g.NavMoveFromClampedRefRect = true;
+ }
+ }
+
+ // Reset search
+ g.NavMoveResultId = 0;
+ g.NavMoveResultDistAxial = g.NavMoveResultDistBox = g.NavMoveResultDistCenter = FLT_MAX;
+ if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
+ {
+ // When we have manually scrolled and NavId is out of bounds, we clamp its bounding box (used for search) to the visible area to restart navigation within visible items
+ ImRect window_rect_rel(g.NavWindow->InnerRect.Min - g.NavWindow->Pos - ImVec2(1,1), g.NavWindow->InnerRect.Max - g.NavWindow->Pos + ImVec2(1,1));
+ if (!window_rect_rel.Contains(g.NavRefRectRel))
+ {
+ float pad = g.NavWindow->CalcFontSize() * 0.5f;
+ window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intend of starting navigation from first fully visible item
+ g.NavRefRectRel.ClipWith(window_rect_rel);
+ g.NavId = 0;
+ }
+ g.NavMoveFromClampedRefRect = false;
+ }
+
+ // 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)
+ g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + g.NavRefRectRel.Min, g.NavWindow->Pos + g.NavRefRectRel.Max) : ImRect();
+ g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
+ g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
+ //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
+}
+
void ImGui::NewFrame()
{
ImGuiContext& g = *GImGui;
@@ -2180,6 +2841,12 @@
memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f;
+ memcpy(g.IO.NavInputsPrev, g.IO.NavInputs, sizeof(g.IO.NavInputs));
+ for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
+ g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f;
+
+ // Update directional navigation which may override MousePos if 'NavMovesMouse=true'
+ NavUpdate();
// Update mouse input state
// If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta
@@ -2187,6 +2854,9 @@
g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
else
g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
+ if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
+ g.NavDisableMouseHover = false;
+
g.IO.MousePosPrev = g.IO.MousePos;
for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
{
@@ -2214,6 +2884,8 @@
{
g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]));
}
+ if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
+ g.NavDisableMouseHover = false;
}
// Calculate frame-rate for the user, as a purely luxurious feature
@@ -2223,7 +2895,7 @@
g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame));
// Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows.
- if (g.MovedWindowMoveId && g.MovedWindowMoveId == g.ActiveId)
+ if (g.MovedWindowMoveId && g.MovedWindowMoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse)
{
KeepAliveID(g.MovedWindowMoveId);
IM_ASSERT(g.MovedWindow && g.MovedWindow->RootWindow);
@@ -2334,9 +3006,15 @@
}
// Pressing TAB activate widget focus
- // NB: Don't discard FocusedWindow if it isn't active, so that a window that go on/off programatically won't lose its keyboard focus.
- if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && IsKeyPressedMap(ImGuiKey_Tab, false))
- g.NavWindow->FocusIdxTabRequestNext = 0;
+ //// NB: Don't discard FocusedWindow if it isn't active, so that a window that go on/off programatically won't lose its keyboard focus. // [2016/07/17] That comment was made invalid by 19d02becef94e8e0f1d432a8bd55cd783876583c
+ if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false))
+ {
+ if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
+ g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1);
+ else
+ g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0;
+ }
+ g.NavIdTabCounter = INT_MAX;
// Mark all windows as not visible
for (int i = 0; i != g.Windows.Size; i++)
@@ -2698,11 +3376,16 @@
if (g.HoveredRootWindow != NULL)
{
FocusWindow(g.HoveredWindow);
+ if (g.NavWindow != g.HoveredWindow)
+ {
+ g.NavRefRectRel = ImRect(g.IO.MousePos - g.HoveredWindow->Pos, g.IO.MousePos - g.HoveredWindow->Pos); //ImRect(0,0,0,0);
+ g.NavDisableHighlight = true;
+ }
if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove))
{
g.MovedWindow = g.HoveredWindow;
g.MovedWindowMoveId = g.HoveredRootWindow->MoveId;
- SetActiveID(g.MovedWindowMoveId, g.HoveredRootWindow);
+ SetActiveIDNoNav(g.MovedWindowMoveId, g.HoveredRootWindow);
}
}
else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL)
@@ -2755,9 +3438,11 @@
for (int i = 0; i != g.Windows.Size; i++)
{
ImGuiWindow* window = g.Windows[i];
- if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0)
+ if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0 && window != g.NavWindowingTarget)
AddWindowToRenderListSelectLayer(window);
}
+ if (g.NavWindowingTarget && g.NavWindowingTarget->Active && g.NavWindowingTarget->HiddenFrames <= 0) // NavWindowing target is always displayed front-most
+ AddWindowToRenderListSelectLayer(g.NavWindowingTarget);
// Flatten layers
int n = g.RenderDrawLists[0].Size;
@@ -3095,6 +3780,11 @@
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);
+ if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Up) // When performing a navigation request, ensure we have one item extra in the direction we are moving to
+ start--;
+ if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down)
+ end++;
+
start = ImClamp(start, 0, items_count);
end = ImClamp(end + 1, start, items_count);
*out_items_display_start = start;
@@ -3148,6 +3838,12 @@
return g.HoveredWindow != NULL;
}
+bool ImGui::IsAnyWindowFocused()
+{
+ ImGuiContext& g = *GImGui;
+ return g.NavWindow != NULL;
+}
+
static bool IsKeyPressedMap(ImGuiKey key, bool repeat)
{
const int key_index = GImGui->IO.KeyMap[key];
@@ -3326,6 +4022,9 @@
bool ImGui::IsItemHovered()
{
ImGuiWindow* window = GetCurrentWindowRead();
+ ImGuiContext& g = *GImGui;
+ if (g.NavDisableMouseHover)
+ return IsItemFocused();
return window->DC.LastItemHoveredAndUsable;
}
@@ -3346,6 +4045,12 @@
return false;
}
+bool ImGui::IsItemFocused()
+{
+ ImGuiContext& g = *GImGui;
+ return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId;
+}
+
bool ImGui::IsItemClicked(int mouse_button)
{
return IsMouseClicked(mouse_button) && IsItemHovered();
@@ -3361,6 +4066,11 @@
return GImGui->ActiveId != 0;
}
+bool ImGui::IsAnyItemFocused()
+{
+ return GImGui->NavId != 0 && !GImGui->NavDisableHighlight;
+}
+
bool ImGui::IsItemVisible()
{
ImGuiWindow* window = GetCurrentWindowRead();
@@ -3377,6 +4087,20 @@
g.ActiveIdAllowOverlap = true;
}
+void ImGui::SetItemDefaultFocus()
+{
+ ImGuiContext& g = *GImGui;
+ if (g.NavWindow == g.CurrentWindow->RootNavWindow && (g.NavInitDefaultRequest || g.NavInitDefaultResultId != 0))
+ {
+ g.NavInitDefaultRequest = false;
+ g.NavInitDefaultResultExplicit = true;
+ g.NavInitDefaultResultId = g.NavWindow->DC.LastItemId;
+ g.NavInitDefaultResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
+ if (!IsItemVisible())
+ SetScrollHere();
+ }
+}
+
ImVec2 ImGui::GetItemRectMin()
{
ImGuiWindow* window = GetCurrentWindowRead();
@@ -3425,7 +4149,7 @@
window->HiddenFrames = 1;
ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip%02d", ++g.TooltipOverrideCount);
}
- ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize;
+ ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNavFocus;
ImGui::Begin(window_name, NULL, flags | extra_flags);
}
@@ -3464,7 +4188,9 @@
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
int current_stack_size = g.CurrentPopupStack.Size;
- ImGuiPopupRef popup_ref = ImGuiPopupRef(id, window, window->GetID("##menus"), g.IO.MousePos); // Tagged as new ref because constructor sets Window to NULL (we are passing the ParentWindow info here)
+ ImVec2 mouse_pos = g.IO.MousePos;
+ ImVec2 popup_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : mouse_pos;
+ ImGuiPopupRef popup_ref = ImGuiPopupRef(id, window, window->GetID("##menus"), popup_pos, mouse_pos); // Tagged as new ref because constructor sets Window to NULL (we are passing the ParentWindow info here)
if (g.OpenPopupStack.Size < current_stack_size + 1)
g.OpenPopupStack.push_back(popup_ref);
else if (reopen_existing || g.OpenPopupStack[current_stack_size].PopupId != id)
@@ -3702,10 +4428,20 @@
bool ret = ImGui::Begin(title, NULL, size, -1.0f, flags);
ImGuiWindow* child_window = ImGui::GetCurrentWindow();
+ child_window->ChildId = id;
child_window->AutoFitChildAxises = auto_fit_axises;
if (!(parent_window->Flags & ImGuiWindowFlags_ShowBorders))
child_window->Flags &= ~ImGuiWindowFlags_ShowBorders;
+ // Process navigation-in immediately so NavInit can run on first frame
+ if (/*!(flags & ImGuiWindowFlags_NavFlattened) &&*/ (child_window->DC.NavLayerActiveFlags != 0 || child_window->DC.NavHasScroll) && GImGui->NavActivateId == id)
+ {
+ ImGui::FocusWindow(child_window);
+ NavInitWindow(child_window, false);
+ ImGui::SetActiveIDNoNav(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
+ GImGui->ActiveIdSource = ImGuiInputSource_Nav;
+ }
+
return ret;
}
@@ -3742,7 +4478,15 @@
ImGuiWindow* parent_window = GetCurrentWindow();
ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
ItemSize(sz);
- ItemAdd(bb, NULL);
+ if (/*!(window->Flags & ImGuiWindowFlags_NavFlattened) &&*/ (window->DC.NavLayerActiveFlags != 0 || window->DC.NavHasScroll))
+ {
+ ItemAdd(bb, &window->ChildId);
+ RenderNavHighlight(bb, window->ChildId);
+ }
+ else
+ {
+ ItemAdd(bb, NULL);
+ }
}
}
@@ -3952,6 +4696,9 @@
if (flags & ImGuiWindowFlags_NoInputs)
flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
+ //if (flags & ImGuiWindowFlags_NavFlattened)
+ // IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
+
// Find or create
bool window_is_new = false;
ImGuiWindow* window = FindWindowByName(name);
@@ -3987,6 +4734,8 @@
}
const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1);
+ if (window_just_appearing_after_hidden_for_resize)
+ window->NavLastId = 0;
window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize);
// Process SetNextWindow***() calls
@@ -4048,6 +4797,9 @@
window->ParentWindow = parent_window;
window->RootWindow = g.CurrentWindowStack[root_idx];
window->RootNonPopupWindow = g.CurrentWindowStack[root_non_popup_idx]; // Used to display TitleBgActive color and for selecting which window to use for NavWindowing
+ window->RootNavWindow = window;
+ //while (window->RootNavWindow->Flags & ImGuiWindowFlags_NavFlattened)
+ // window->RootNavWindow = window->RootNavWindow->ParentWindow;
// When reusing window again multiple times a frame, just append content (don't need to setup again)
if (first_begin_of_the_frame)
@@ -4073,7 +4825,7 @@
// Popup first latch mouse position, will position itself when it appears next frame
window->AutoPosLastDirection = -1;
if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api)
- window->PosFloat = g.IO.MousePos;
+ window->PosFloat = g.CurrentPopupStack.back().PopupPosOnOpen;
}
// Collapse window by double-clicking on title bar
@@ -4081,7 +4833,7 @@
if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))
{
ImRect title_bar_rect = window->TitleBarRect();
- if (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])
+ if (window->CollapseToggleWanted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]))
{
window->Collapsed = !window->Collapsed;
MarkIniSettingsDirty(window);
@@ -4092,6 +4844,7 @@
{
window->Collapsed = false;
}
+ window->CollapseToggleWanted = false;
// SIZE
@@ -4195,7 +4948,7 @@
IM_ASSERT(window_pos_set_by_api);
float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value).
ImRect rect_to_avoid;
- if (parent_window->DC.MenuBarAppending)
+ if (parent_window && parent_window->DC.MenuBarAppending)
rect_to_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight());
else
rect_to_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX);
@@ -4210,7 +4963,7 @@
// Position tooltip (always follows mouse)
if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api)
{
- ImVec2 ref_pos = g.IO.MousePos;
+ ImVec2 ref_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos;
ImRect rect_to_avoid(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24, ref_pos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead?
window->PosFloat = FindBestPopupWindowPos(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
if (window->AutoPosLastDirection == -1)
@@ -4249,13 +5002,23 @@
if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow())
window->DrawList->AddRectFilled(fullscreen_rect.Min, fullscreen_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio));
+ // Navigation windowing (via ImGuiKey_NavWindowing key) shows whole window selected
+ if (g.NavWindowingTarget == window)
+ {
+ ImRect bb = window->Rect();
+ bb.Expand(g.FontSize);
+ window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingDisplayAlpha), g.Style.WindowRounding);
+ window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_HeaderHovered, g.NavWindowingDisplayAlpha), g.Style.WindowRounding);
+ }
+
// Draw window + handle manual resize
ImRect title_bar_rect = window->TitleBarRect();
const float window_rounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildWindowRounding : style.WindowRounding;
if (window->Collapsed)
{
// Title bar only
- RenderFrame(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding);
+ const bool is_focused = g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow && !g.NavDisableHighlight;
+ RenderFrame(title_bar_rect.GetTL(), title_bar_rect.GetBR(), GetColorU32(is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed), true, window_rounding);
}
else
{
@@ -4268,11 +5031,22 @@
const ImRect resize_rect(br - ImVec2(resize_corner_size * 0.75f, resize_corner_size * 0.75f), br);
const ImGuiID resize_id = window->GetID("#RESIZE");
bool hovered, held;
- ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds);
- resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
+ ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds | ImGuiButtonFlags_NoNavOverride);
if (hovered || held)
g.MouseCursor = ImGuiMouseCursor_ResizeNWSE;
+ ImVec2 nav_resize_delta(0.0f, 0.0f);
+ if (g.NavWindowingTarget == window)
+ {
+ nav_resize_delta = GetNavInputAmount2d(0, ImGuiNavReadMode_Down);
+ if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
+ {
+ nav_resize_delta *= ImFloor(600 * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
+ g.NavDisableMouseHover = true;
+ held = true; // For coloring
+ }
+ }
+
ImVec2 size_target(FLT_MAX,FLT_MAX);
if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0])
{
@@ -4280,6 +5054,12 @@
size_target = size_auto_fit;
ClearActiveID();
}
+ else if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
+ {
+ // FIXME-NAVIGATION: Should store and accumulate into a separate size buffer to handle sizing constraints properly
+ g.NavWindowingToggleLayer = false;
+ size_target = window->SizeFull + nav_resize_delta;
+ }
else if (held)
{
// We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
@@ -4291,6 +5071,8 @@
ApplySizeFullWithConstraint(window, size_target);
MarkIniSettingsDirty(window);
}
+
+ resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
window->Size = window->SizeFull;
title_bar_rect = window->TitleBarRect();
}
@@ -4375,6 +5157,9 @@
window->DC.CursorMaxPos = window->DC.CursorStartPos;
window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f;
window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
+ window->DC.NavLayerActiveFlags = window->DC.NavLayerActiveFlagsNext;
+ window->DC.NavLayerActiveFlagsNext = 0x00;
+ window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f);
window->DC.MenuBarAppending = false;
window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x);
window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
@@ -4410,11 +5195,32 @@
// New windows appears in front (we need to do that AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
if (!(flags & (ImGuiWindowFlags_ChildWindow|ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
+ {
FocusWindow(window);
+ NavInitWindow(window, false);
+ }
// Title bar
if (!(flags & ImGuiWindowFlags_NoTitleBar))
{
+ // Close & collapse button are on layer 1 (same as menus) and don't default focus
+ const ImGuiItemFlags backup_item_options = window->DC.ItemFlags;
+ window->DC.ItemFlags &= ~ImGuiItemFlags_AllowNavDefaultFocus;
+ window->DC.NavLayerCurrent++;
+
+ // Collapse button
+ const ImVec2 text_size = CalcTextSize(name, NULL, true);
+ if (!(flags & ImGuiWindowFlags_NoCollapse))
+ {
+ ImGuiID id = window->GetID("#COLLAPSE");
+ ImRect bb(window->Pos + style.FramePadding + ImVec2(1,1), window->Pos + style.FramePadding + ImVec2(g.FontSize,g.FontSize) - ImVec2(1,1));
+ ItemAdd(bb, &id); // To allow navigation
+ if (ButtonBehavior(bb, id, NULL, NULL))
+ window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function
+ RenderNavHighlight(bb, id);
+ RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f);
+ }
+
// Close button
if (p_open != NULL)
{
@@ -4424,10 +5230,10 @@
*p_open = false;
}
- const ImVec2 text_size = CalcTextSize(name, NULL, true);
- if (!(flags & ImGuiWindowFlags_NoCollapse))
- RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f);
+ window->DC.NavLayerCurrent--;
+ window->DC.ItemFlags = backup_item_options;
+ // Title text (FIXME: refactor text alignment facilities along with RenderText helpers)
ImVec2 text_min = window->Pos;
ImVec2 text_max = window->Pos + ImVec2(window->Size.x, style.FramePadding.y*2 + text_size.y);
ImRect clip_rect;
@@ -4455,17 +5261,23 @@
*/
}
- // Inner clipping rectangle
+ // Inner rectangle and inner clipping rectangle
// We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame
// Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior.
const ImRect title_bar_rect = window->TitleBarRect();
const float border_size = window->BorderSize;
- // Force round to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
+ window->InnerRect.Min.x = title_bar_rect.Min.x;
+ window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight();
+ window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x;
+ window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y;
+ //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE);
+
+ // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
ImRect clip_rect;
- clip_rect.Min.x = ImFloor(0.5f + title_bar_rect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f)));
- clip_rect.Min.y = ImFloor(0.5f + title_bar_rect.Max.y + window->MenuBarHeight() + border_size);
- clip_rect.Max.x = ImFloor(0.5f + window->Pos.x + window->Size.x - window->ScrollbarSizes.x - ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f)));
- clip_rect.Max.y = ImFloor(0.5f + window->Pos.y + window->Size.y - window->ScrollbarSizes.y - border_size);
+ clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f)));
+ clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + border_size);
+ clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(border_size, ImFloor(window->WindowPadding.x*0.5f)));
+ clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - border_size);
PushClipRect(clip_rect.Min, clip_rect.Max, true);
// Clear 'accessed' flag last thing
@@ -4569,7 +5381,7 @@
bool held = false;
bool hovered = false;
const bool previously_held = (g.ActiveId == id);
- ImGui::ButtonBehavior(bb, id, &hovered, &held);
+ ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavOverride);
float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
float scroll_ratio = ImSaturate(scroll_v / scroll_max);
@@ -4630,8 +5442,16 @@
{
ImGuiContext& g = *GImGui;
- // Always mark the window we passed as focused. This is used for keyboard interactions such as tabbing.
- g.NavWindow = window;
+ if (g.NavWindow != window)
+ {
+ g.NavId = window ? window->NavLastId : 0; // Restore NavId
+ g.NavIdIsAlive = false;
+ g.NavLayer = 0;
+ if (window && g.NavDisableMouseHover)
+ g.NavMousePosDirty = true;
+ g.NavRefRectRel.Min = g.NavRefRectRel.Max = window ? (window->DC.CursorStartPos - window->Pos) : ImVec2(0,0);
+ g.NavWindow = window;
+ }
// Passing NULL allow to disable keyboard focus
if (!window)
@@ -4939,6 +5759,8 @@
case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening";
+ case ImGuiCol_NavHighlight: return "NavHighlight";
+ case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
}
IM_ASSERT(0);
return "Unknown";
@@ -4999,6 +5821,13 @@
return window->Pos;
}
+static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x)
+{
+ window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it.
+ window->Scroll.x = new_scroll_x;
+ window->DC.CursorMaxPos.x -= window->Scroll.x;
+}
+
static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y)
{
window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it.
@@ -5674,9 +6503,13 @@
// PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
// PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
// PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
+ // FIXME-NAVIGATION: We don't honor those different behaviors.
if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
{
- SetActiveID(id, window); // Hold on ID
+ if (flags & ImGuiButtonFlags_NoNavOverride)
+ SetActiveIDNoNav(id, window);
+ else
+ SetActiveID(id, window); // Hold on ID
FocusWindow(window);
g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
}
@@ -5698,10 +6531,29 @@
if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
pressed = true;
}
+
+ if (pressed)
+ g.NavDisableHighlight = true;
+ }
+
+ // Gamepad/Keyboard navigation
+ if (g.NavId == id && !g.NavDisableHighlight && (g.ActiveId == 0 || g.ActiveId == id))
+ {
+ // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse
+ hovered = true;
+ if (!g.NavWindowingTarget && IsNavInputDown(ImGuiNavInput_PadActivate))
+ {
+ // Set active id so it can be queried by user via IsItemActive(), etc. but don't react to it ourselves
+ g.NavActivateId = id;
+ SetActiveID(id, window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+ if (IsNavInputPressed(ImGuiNavInput_PadActivate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiNavReadMode_Repeat : ImGuiNavReadMode_Pressed))
+ pressed = true;
+ }
}
bool held = false;
- if (g.ActiveId == id)
+ if (g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Mouse)
{
if (g.IO.MouseDown[0])
{
@@ -5714,7 +6566,12 @@
pressed = true;
ClearActiveID();
}
+ if (!(flags & ImGuiButtonFlags_NoNavOverride))
+ g.NavDisableHighlight = true;
}
+ if (g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Nav)
+ if (!IsNavInputDown(ImGuiNavInput_PadActivate))
+ ClearActiveID();
// AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
if (hovered && (flags & ImGuiButtonFlags_AllowOverlapMode) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
@@ -5753,6 +6610,7 @@
// Render
const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ RenderNavHighlight(bb, id);
RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
@@ -5806,6 +6664,8 @@
ImGuiWindow* window = GetCurrentWindow();
const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
+ if (!ItemAdd(bb, &id)) // To allow navigation
+ return false;
bool hovered, held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
@@ -5880,6 +6740,7 @@
// Render
const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ RenderNavHighlight(bb, id);
RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
if (bg_col.w > 0.0f)
window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
@@ -6092,9 +6953,9 @@
bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
if (pressed && !(flags & ImGuiTreeNodeFlags_Leaf))
{
- bool toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick));
+ bool toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
- toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y));
+ toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
toggled |= g.IO.MouseDoubleClicked[0];
if (toggled)
@@ -6504,7 +7365,8 @@
// Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
// On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
- SetActiveID(g.ScalarAsInputTextId, window);
+ SetActiveIDNoNav(g.ScalarAsInputTextId, window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
SetHoveredID(0);
FocusableItemUnregister(window);
@@ -6601,7 +7463,9 @@
const ImGuiStyle& style = g.Style;
// Draw frame
- RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+ const ImU32 frame_col = GetColorU32((g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Nav) ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBg);
+ RenderNavHighlight(frame_bb, id);
+ RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f);
const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
@@ -6638,7 +7502,7 @@
{
bool set_new_value = false;
float clicked_t = 0.0f;
- if (g.IO.MouseDown[0])
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse && g.IO.MouseDown[0])
{
const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
@@ -6646,6 +7510,31 @@
clicked_t = 1.0f - clicked_t;
set_new_value = true;
}
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav && IsNavInputDown(ImGuiNavInput_PadActivate))
+ {
+ const ImVec2 delta2 = GetNavInputAmount2d(0, ImGuiNavReadMode_RepeatFast, 0.0f, 0.0f);
+ if (float delta = is_horizontal ? delta2.x : -delta2.y)
+ {
+ clicked_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos);
+ if (decimal_precision == 0 && !is_non_linear)
+ {
+ if (fabsf(v_max - v_min) <= 100.0f || IsNavInputDown(ImGuiNavInput_PadTweakSlow))
+ delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (v_max - v_min); // Gamepad/keyboard tweak speeds in integer steps
+ else
+ delta /= 100.0f;
+ }
+ else
+ {
+ delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
+ if (IsNavInputDown(ImGuiNavInput_PadTweakSlow))
+ delta /= 10.0f;
+ }
+ if (IsNavInputDown(ImGuiNavInput_PadTweakFast))
+ delta *= 10.0f;
+ clicked_t = ImSaturate(clicked_t + delta); // FIXME-NAVIGATION: todo: cancel adjustment if current value already past edge and we are moving in edge direction, to avoid clamping value to edge.
+ set_new_value = true;
+ }
+ }
else
{
ClearActiveID();
@@ -6728,7 +7617,7 @@
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
// NB- we don't call ItemSize() yet because we may turn into a text edit box below
- if (!ItemAdd(total_bb, &id))
+ if (!ItemAdd(total_bb, &id, &frame_bb))
{
ItemSize(total_bb, style.FramePadding.y);
return false;
@@ -6745,11 +7634,11 @@
// Tabbing or CTRL-clicking on Slider turns it into an input box
bool start_text_input = false;
const bool tab_focus_requested = FocusableItemRegister(window, id);
- if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]))
+ if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
{
SetActiveID(id, window);
FocusWindow(window);
- if (tab_focus_requested || g.IO.KeyCtrl)
+ if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
{
start_text_input = true;
g.ScalarAsInputTextId = 0;
@@ -6788,7 +7677,7 @@
const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
ItemSize(bb, style.FramePadding.y);
- if (!ItemAdd(frame_bb, &id))
+ if (!ItemAdd(frame_bb, &id, &frame_bb))
return false;
const bool hovered = IsHovered(frame_bb, id);
@@ -6799,7 +7688,7 @@
display_format = "%.3f";
int decimal_precision = ParseFormatPrecision(display_format, 3);
- if (hovered && g.IO.MouseClicked[0])
+ if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
{
SetActiveID(id, window);
FocusWindow(window);
@@ -6939,6 +7828,7 @@
// Draw frame
const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+ RenderNavHighlight(frame_bb, id);
RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
bool value_changed = false;
@@ -6946,7 +7836,7 @@
// Process clicking on the drag
if (g.ActiveId == id)
{
- if (g.IO.MouseDown[0])
+ if (g.IO.MouseDown[0] || IsNavInputDown(ImGuiNavInput_PadActivate))
{
if (g.ActiveIdIsJustActivated)
{
@@ -6957,10 +7847,11 @@
if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX)
v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio;
+
float v_cur = g.DragCurrentValue;
const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f);
float adjust_delta = 0.0f;
- //if (g.ActiveIdSource == ImGuiInputSource_Mouse)
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse)
{
adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x;
if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f)
@@ -6968,6 +7859,11 @@
if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f)
adjust_delta *= g.DragSpeedScaleSlow;
}
+ if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ {
+ adjust_delta = GetNavInputAmount2d(0, ImGuiNavReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x;
+ v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
+ }
adjust_delta *= v_speed;
g.DragLastMouseDelta.x = mouse_drag_delta.x;
@@ -7028,7 +7924,7 @@
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
// NB- we don't call ItemSize() yet because we may turn into a text edit box below
- if (!ItemAdd(total_bb, &id))
+ if (!ItemAdd(total_bb, &id, &frame_bb))
{
ItemSize(total_bb, style.FramePadding.y);
return false;
@@ -7045,11 +7941,11 @@
// Tabbing or CTRL-clicking on Drag turns it into an input box
bool start_text_input = false;
const bool tab_focus_requested = FocusableItemRegister(window, id);
- if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])))
+ if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || g.NavInputId == id)
{
SetActiveID(id, window);
FocusWindow(window);
- if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0])
+ if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
{
start_text_input = true;
g.ScalarAsInputTextId = 0;
@@ -7237,7 +8133,7 @@
const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, NULL))
+ if (!ItemAdd(total_bb, NULL, &frame_bb))
return;
// Determine scale from values if not specified
@@ -7431,6 +8327,7 @@
if (pressed)
*v = !(*v);
+ RenderNavHighlight(total_bb, id);
RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
if (*v)
{
@@ -7497,6 +8394,7 @@
bool hovered, held;
bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
+ RenderNavHighlight(total_bb, id);
window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
if (active)
{
@@ -7824,7 +8722,7 @@
else
{
ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, &id))
+ if (!ItemAdd(total_bb, &id, &frame_bb))
return false;
}
@@ -7863,8 +8761,8 @@
bool clear_active_id = false;
- bool select_all = (g.ActiveId != id) && (flags & ImGuiInputTextFlags_AutoSelectAll) != 0;
- if (focus_requested || user_clicked || user_scrolled)
+ bool select_all = (g.ActiveId != id) && (((flags & ImGuiInputTextFlags_AutoSelectAll) != 0) || (g.NavInputId == id));
+ if (focus_requested || user_clicked || user_scrolled || g.NavInputId == id)
{
if (g.ActiveId != id)
{
@@ -7903,6 +8801,8 @@
select_all = true;
}
SetActiveID(id, window);
+ if (!is_multiline)
+ g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
FocusWindow(window);
}
else if (io.MouseClicked[0])
@@ -8196,6 +9096,7 @@
// Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL;
+ RenderNavHighlight(frame_bb, id);
if (!is_multiline)
RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
@@ -8612,16 +9513,18 @@
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, &id))
+ if (!ItemAdd(total_bb, &id, &frame_bb))
return false;
const float arrow_size = SmallSquareSize();
const bool hovered = IsHovered(frame_bb, id);
+ const bool navigated = g.NavId == id;
bool popup_open = IsPopupOpen(id);
const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
+ RenderNavHighlight(frame_bb, id);
RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
- RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING
+ RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered || navigated ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING
RenderCollapseTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), true);
if (preview_value != NULL)
@@ -8640,6 +9543,8 @@
popup_toggled = true;
}
}
+ if (g.NavActivateId == id)
+ popup_toggled = true;
if (popup_toggled)
{
if (popup_open)
@@ -8648,6 +9553,8 @@
}
else
{
+ if (window->DC.NavLayerCurrent == 0)
+ window->NavLastId = id;
FocusWindow(window);
OpenPopupEx(id, false);
}
@@ -8721,7 +9628,7 @@
*current_item = i;
}
if (item_selected && IsWindowAppearing())
- SetScrollHere();
+ SetItemDefaultFocus(); //SetScrollHere();
PopID();
}
@@ -8769,7 +9676,7 @@
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, &id))
+ if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? NULL : &id))
{
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsCount > 1)
PushColumnClipRect();
@@ -8778,7 +9685,7 @@
ImGuiButtonFlags button_flags = 0;
if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick;
- if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnClick|ImGuiButtonFlags_PressedOnRelease;
+ if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease;
if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
bool hovered, held;
@@ -8786,6 +9693,13 @@
if (flags & ImGuiSelectableFlags_Disabled)
selected = false;
+ // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
+ if (hovered && !g.NavDisableMouseHover && g.NavWindow == window && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f))
+ {
+ g.NavDisableHighlight = true;
+ SetNavId(id);
+ }
+
// Render
if (hovered || selected)
{
@@ -8804,7 +9718,7 @@
if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
// Automatically close popups
- if (pressed && !(flags & ImGuiSelectableFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
+ if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !((window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)))
CloseCurrentPopup();
return pressed;
}
@@ -8906,6 +9820,8 @@
*current_item = i;
value_changed = true;
}
+ if (item_selected)
+ SetItemDefaultFocus();
PopID();
}
ListBoxFooter();
@@ -8990,6 +9906,7 @@
PushClipRect(ImVec2(ImFloor(rect.Min.x+0.5f), ImFloor(rect.Min.y + window->BorderSize + 0.5f)), ImVec2(ImFloor(rect.Max.x+0.5f), ImFloor(rect.Max.y+0.5f)), false);
window->DC.CursorPos = ImVec2(rect.Min.x + window->DC.MenuBarOffsetX, rect.Min.y);// + g.Style.FramePadding.y);
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ window->DC.NavLayerCurrent++;
window->DC.MenuBarAppending = true;
AlignFirstTextHeightToWidgets();
return true;
@@ -9009,6 +9926,7 @@
window->DC.GroupStack.back().AdvanceCursor = false;
EndGroup();
window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.NavLayerCurrent--;
window->DC.MenuBarAppending = false;
}
@@ -9055,7 +9973,7 @@
if (!enabled) PopStyleColor();
}
- bool hovered = enabled && IsHovered(window->DC.LastItemRect, id);
+ bool hovered = enabled && IsHovered(window->DC.LastItemRect, id); // FIXME: Why not using window->DC.LastItemHoveredAndUsable / IsItemHovered() ?
if (menuset_is_open)
g.NavWindow = backed_nav_window;
@@ -9065,7 +9983,7 @@
{
// Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
bool moving_within_opened_triangle = false;
- if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window)
+ if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
{
if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
{
@@ -9084,16 +10002,36 @@
want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
+
+ if (g.NavActivateId == id)
+ {
+ want_close = menu_is_open;
+ want_open = !menu_is_open;
+ }
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
+ {
+ want_open = true;
+ g.NavMoveRequest = false;
+ }
+ if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && IsPopupOpen(id)) // Nav-Left to close
+ {
+ want_close = true;
+ g.NavMoveRequest = false;
+ }
}
else if (menu_is_open && pressed && menuset_is_open) // Menu bar: click an open menu again to close it
{
want_close = true;
want_open = menu_is_open = false;
}
- else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // menu-bar: first click to open, then hover to open others
+ else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // Menu bar: first click to open, then hover to open others
{
want_open = true;
}
+ else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Menu bar: Nav-Down to open
+ {
+ g.NavMoveRequest = false;
+ }
if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
want_close = true;
@@ -9251,12 +10189,13 @@
{
RenderColorRectWithAlphaCheckerboard(bb.Min, bb.Max, GetColorU32((flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha), grid_step, ImVec2(0,0), rounding);
}
+ RenderNavHighlight(bb, id);
if (window->Flags & ImGuiWindowFlags_ShowBorders)
RenderFrameBorder(bb.Min, bb.Max, rounding);
else
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
- if (hovered && !(flags & ImGuiColorEditFlags_NoTooltip))
+ if (!(flags & ImGuiColorEditFlags_NoTooltip) && IsItemHovered()) // FIXME-NAVIGATION: 'hovered' ?
ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
return pressed;
@@ -10509,6 +11448,7 @@
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3);
ImGui::Text("%d allocations", ImGui::GetIO().MetricsAllocs);
+ ImGui::Text("sizeof(ImGuiContext) = %u, sizeof(ImGuiWindow) = %u", (int)sizeof(ImGuiContext), (int)sizeof(ImGuiWindow));
static bool show_clip_rects = true;
ImGui::Checkbox("Show clipping rectangles when hovering an ImDrawCmd", &show_clip_rects);
ImGui::Separator();
@@ -10591,6 +11531,7 @@
ImGui::BulletText("Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y);
ImGui::BulletText("Scroll: (%.2f,%.2f)", window->Scroll.x, window->Scroll.y);
ImGui::BulletText("Active: %d, Accessed: %d", window->Active, window->Accessed);
+ ImGui::BulletText("NavLastId: 0x%08x, NavLayerActiveFlags: %02X", window->NavLastId, window->DC.NavLayerActiveFlags);
if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair));
@@ -10617,12 +11558,17 @@
}
if (ImGui::TreeNode("Basic state"))
{
+ const char* input_source_names[] = { "None", "Mouse", "Nav" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_Count_);
ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
ImGui::Text("HoveredId: 0x%08X/0x%08X", g.HoveredId, g.HoveredIdPreviousFrame); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not
- ImGui::Text("ActiveId: 0x%08X/0x%08X", g.ActiveId, g.ActiveIdPreviousFrame);
+ ImGui::Text("ActiveId: 0x%08X/0x%08X, ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, input_source_names[g.ActiveIdSource]);
ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
- ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
+ ImGui::Text("NavWindow: '%s', NavId: 0x%08X, NavLayer: %d", g.NavWindow ? g.NavWindow->Name : "NULL", g.NavId, g.NavLayer);
+ ImGui::Text("NavRefRectRel: (%.1f,%.1f)(%.1f,%.1f)", g.NavRefRectRel.Min.x, g.NavRefRectRel.Min.y, g.NavRefRectRel.Max.x, g.NavRefRectRel.Max.y);
+ ImGui::Text("NavUsable: %d, NavActive: %d", g.IO.NavUsable, g.IO.NavActive);
+ ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
+ ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
ImGui::TreePop();
}
}
diff --git a/imgui.h b/imgui.h
index 238ff89..2463077 100644
--- a/imgui.h
+++ b/imgui.h
@@ -72,6 +72,7 @@
typedef int ImGuiCol; // a color identifier for styling // enum ImGuiCol_
typedef int ImGuiStyleVar; // a variable identifier for styling // enum ImGuiStyleVar_
typedef int ImGuiKey; // a key identifier (ImGui-side enum) // enum ImGuiKey_
+typedef int ImGuiNavInput; // an input identifier for gamepad nav // enum ImGuiNavInput_
typedef int ImGuiColorEditFlags; // color edit flags for Color*() // enum ImGuiColorEditFlags_
typedef int ImGuiMouseCursor; // a mouse cursor identifier // enum ImGuiMouseCursor_
typedef int ImGuiWindowFlags; // window flags for Begin*() // enum ImGuiWindowFlags_
@@ -177,7 +178,7 @@
IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()]
IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom.
IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions.
- IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use negative 'offset' to access previous widgets.
+ IMGUI_API void SetKeyboardFocusHere(int offset = 0); // FIXME-NAVIGATION // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use negative 'offset' to access previous widgets.
IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it)
IMGUI_API ImGuiStorage* GetStateStorage();
@@ -293,6 +294,7 @@
// Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds)
// For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x
+ // Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision).
IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound
IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f);
IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f);
@@ -416,23 +418,27 @@
IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL);
// Utilities
- IMGUI_API bool IsItemHovered(); // is the last item hovered by mouse (and usable)?
+ IMGUI_API bool IsItemHovered(); // is the last item hovered by mouse (and usable)? or we are currently using Nav and the item is focused.
IMGUI_API bool IsItemRectHovered(); // is the last item hovered by mouse? even if another item is active or window is blocked by popup while we are hovering this
IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false)
+ IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation?
IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on)
IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.)
IMGUI_API bool IsAnyItemHovered();
IMGUI_API bool IsAnyItemActive();
+ IMGUI_API bool IsAnyItemFocused();
IMGUI_API ImVec2 GetItemRectMin(); // get bounding rect of last item in screen space
IMGUI_API ImVec2 GetItemRectMax(); // "
IMGUI_API ImVec2 GetItemRectSize(); // "
IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area.
+ IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window
IMGUI_API bool IsWindowFocused(); // is current window focused
IMGUI_API bool IsWindowHovered(); // is current window hovered and hoverable (not blocked by a popup) (differentiate child windows from each others)
IMGUI_API bool IsWindowRectHovered(); // is current window rectangle hovered, disregarding of any consideration of being blocked by a popup. (unlike IsWindowHovered() this will return true even if the window is blocked because of a popup)
IMGUI_API bool IsRootWindowFocused(); // is current root window focused (root = top-most parent of a child, otherwise self)
IMGUI_API bool IsRootWindowOrAnyChildFocused(); // is current root window or any of its child (including current window) focused
IMGUI_API bool IsRootWindowOrAnyChildHovered(); // is current root window or any of its child (including current window) hovered and hoverable (not blocked by a popup)
+ IMGUI_API bool IsAnyWindowFocused();
IMGUI_API bool IsAnyWindowHovered(); // is mouse hovering any visible window
IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped.
IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side.
@@ -522,6 +528,9 @@
ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y)
ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x)
ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient)
+ ImGuiWindowFlags_NoNavFocus = 1 << 17, // No focusing of this window with gamepad/keyboard navigation
+ ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window
+ //ImGuiWindowFlags_NavFlattened = 1 << 19, // Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!)
// [Internal]
ImGuiWindowFlags_ChildWindow = 1 << 22, // Don't use! For internal use by BeginChild()
ImGuiWindowFlags_ComboBox = 1 << 23, // Don't use! For internal use by ComboBox()
@@ -607,6 +616,32 @@
ImGuiKey_COUNT
};
+// [BETA] Gamepad/Keyboard directional navigation
+// Fill ImGuiIO.NavInputs[] float array every frame to feed gamepad/keyboard navigation inputs.
+// 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
+// ImGui uses a simple >0.0f for activation testing, and won't attempt to test for a dead-zone.
+// Your code passing analog gamepad values is likely to want to transform your raw inputs, using a dead-zone and maybe a power curve.
+enum ImGuiNavInput_
+{
+ ImGuiNavInput_PadActivate, // press button, tweak value // e.g. Circle button
+ ImGuiNavInput_PadCancel, // close menu/popup/child, lose selection // e.g. Cross button
+ ImGuiNavInput_PadInput, // text input // e.g. Triangle button
+ ImGuiNavInput_PadMenu, // access menu, focus, move, resize // e.g. Square button
+ ImGuiNavInput_PadUp, // move up, resize window (with PadMenu held) // e.g. D-pad up/down/left/right, analog
+ ImGuiNavInput_PadDown, // move down
+ ImGuiNavInput_PadLeft, // move left
+ ImGuiNavInput_PadRight, // move right
+ ImGuiNavInput_PadScrollUp, // scroll up, move window (with PadMenu held) // e.g. right stick up/down/left/right, analog
+ ImGuiNavInput_PadScrollDown, // "
+ ImGuiNavInput_PadScrollLeft, //
+ ImGuiNavInput_PadScrollRight, //
+ ImGuiNavInput_PadFocusPrev, // next window (with PadMenu held) // e.g. L-trigger
+ ImGuiNavInput_PadFocusNext, // prev window (with PadMenu held) // e.g. R-trigger
+ ImGuiNavInput_PadTweakSlow, // slower tweaks // e.g. L-trigger, analog
+ ImGuiNavInput_PadTweakFast, // faster tweaks // e.g. R-trigger, analog
+ ImGuiNavInput_COUNT,
+};
+
// Enumeration for PushStyleColor() / PopStyleColor()
enum ImGuiCol_
{
@@ -653,6 +688,8 @@
ImGuiCol_PlotHistogramHovered,
ImGuiCol_TextSelectedBg,
ImGuiCol_ModalWindowDarkening, // darken entire screen when a modal window is active
+ ImGuiCol_NavHighlight, // gamepad/keyboard: current highlighted item
+ ImGuiCol_NavWindowingHighlight, // gamepad/keyboard: when holding NavMenu to focus/move/resize windows
ImGuiCol_COUNT
// Obsolete names (will be removed)
@@ -790,6 +827,7 @@
int KeyMap[ImGuiKey_COUNT]; // <unset> // Map of indices into the KeysDown[512] entries array
float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.).
float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds.
+ bool NavMovesMouse; // = false // Directional navigation can move the mouse cursor. Updates MousePos and set WantMoveMouse=true. If enabled you MUST honor those requests in your binding, otherwise ImGui will react as if mouse is jumping around.
void* UserData; // = NULL // Store your own data for retrieval by callbacks.
ImFontAtlas* Fonts; // <auto> // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array.
@@ -842,6 +880,7 @@
bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows
bool KeysDown[512]; // Keyboard keys that are pressed (in whatever storage order you naturally have access to keyboard data)
ImWchar InputCharacters[16+1]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper.
+ float NavInputs[ImGuiNavInput_COUNT];
// Functions
IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[]
@@ -855,7 +894,9 @@
bool WantCaptureMouse; // Mouse is hovering a window or widget is active (= ImGui will use your mouse input). Use to hide mouse from the rest of your application
bool WantCaptureKeyboard; // Widget is active (= ImGui will use your keyboard input). Use to hide keyboard from the rest of your application
bool WantTextInput; // Some text input widget is active, which will read input characters from the InputCharacters array. Use to activate on screen keyboard if your system needs one
- bool WantMoveMouse; // [BETA-NAV] MousePos has been altered. back-end should reposition mouse on next frame. used only if 'NavMovesMouse=true'.
+ bool WantMoveMouse; // MousePos has been altered. back-end should reposition mouse on next frame. used only if 'NavMovesMouse=true'.
+ bool NavUsable; // Directional navigation is currently allowed (ImGuiKey_NavXXX events).
+ bool NavActive; // Directional navigation is active/visible and currently allowed (ImGuiKey_NavXXX events).
float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames
int MetricsAllocs; // Number of active memory allocations
int MetricsRenderVertices; // Vertices output during last call to Render()
@@ -879,6 +920,8 @@
float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the click point
float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed)
float KeysDownDurationPrev[512]; // Previous duration the key has been down
+ float NavInputsDownDuration[ImGuiNavInput_COUNT];
+ float NavInputsPrev[ImGuiNavInput_COUNT];
IMGUI_API ImGuiIO();
};
diff --git a/imgui_demo.cpp b/imgui_demo.cpp
index 543172b..61ffc8c 100644
--- a/imgui_demo.cpp
+++ b/imgui_demo.cpp
@@ -171,6 +171,7 @@
static bool no_scrollbar = false;
static bool no_collapse = false;
static bool no_menu = false;
+ static bool no_nav = false;
// Demonstrate the various window flags. Typically you would just use the default.
ImGuiWindowFlags window_flags = 0;
@@ -181,6 +182,7 @@
if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar;
if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse;
if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar;
+ if (no_nav) window_flags |= ImGuiWindowFlags_NoNavInputs;
ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("ImGui Demo", p_open, window_flags))
{
@@ -242,7 +244,8 @@
ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150);
ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300);
ImGui::Checkbox("No collapse", &no_collapse);
- ImGui::Checkbox("No menu", &no_menu);
+ ImGui::Checkbox("No menu", &no_menu); ImGui::SameLine(150);
+ ImGui::Checkbox("No nav", &no_nav);
if (ImGui::TreeNode("Style"))
{
@@ -1446,6 +1449,7 @@
ImGui::PopStyleVar();
if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); }
+ ImGui::SetItemDefaultFocus();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); }
ImGui::EndPopup();
@@ -1463,7 +1467,7 @@
ImGui::OpenPopup("Stacked 2");
if (ImGui::BeginPopupModal("Stacked 2"))
{
- ImGui::Text("Hello from Stacked The Second");
+ ImGui::Text("Hello from Stacked The Second!");
if (ImGui::Button("Close"))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
@@ -1667,9 +1671,12 @@
ImGui::BulletText("%s", lines[i]);
}
- if (ImGui::CollapsingHeader("Inputs & Focus"))
+ if (ImGui::CollapsingHeader("Inputs, Navigation & Focus"))
{
ImGuiIO& io = ImGui::GetIO();
+ ImGui::Checkbox("io.NavMovesMouse", &io.NavMovesMouse);
+ ImGui::SameLine(); ShowHelpMarker("Request ImGui to move your move cursor when using gamepad/keyboard navigation. NewFrame() will change io.MousePos and set the io.WantMoveMouse flag, your backend will need to apply the new mouse position.");
+
ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor);
ImGui::SameLine(); ShowHelpMarker("Request ImGui to render a mouse cursor for you in software. Note that a mouse cursor rendered via regular GPU rendering will feel more laggy than hardware cursor, but will be more in sync with your other visuals.");
@@ -1678,7 +1685,7 @@
ImGui::Text("WantTextInput: %d", io.WantTextInput);
ImGui::Text("WantMoveMouse: %d", io.WantMoveMouse);
- if (ImGui::TreeNode("Keyboard & Mouse State"))
+ if (ImGui::TreeNode("Keyboard, Mouse & Navigation State"))
{
ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y);
ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); }
@@ -1692,6 +1699,10 @@
ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); }
ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : "");
+ ImGui::Text("NavUsable: %d, NavActive: %d", io.NavUsable, io.NavActive);
+ ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); }
+ ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); }
+ ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); }
ImGui::Button("Hovering me sets the\nkeyboard capture flag");
if (ImGui::IsItemHovered())
@@ -1776,7 +1787,7 @@
char label[32];
sprintf(label, "Mouse cursor %d", i);
ImGui::Bullet(); ImGui::Selectable(label, false);
- if (ImGui::IsItemHovered())
+ if (ImGui::IsItemHovered() || ImGui::IsItemFocused())
ImGui::SetMouseCursor(i);
}
ImGui::TreePop();
@@ -2119,7 +2130,7 @@
{
ImGui::SetNextWindowPos(ImVec2(10,10));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
- if (!ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings))
+ if (!ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoFocusOnAppearing|ImGuiWindowFlags_NoNavFocus|ImGuiWindowFlags_NoNavInputs))
{
ImGui::End();
return;
@@ -2384,6 +2395,7 @@
ImGui::Separator();
// Command-line
+ bool reclaim_focus = false;
if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this))
{
char* input_end = InputBuf+strlen(InputBuf);
@@ -2391,10 +2403,12 @@
if (InputBuf[0])
ExecCommand(InputBuf);
strcpy(InputBuf, "");
+ reclaim_focus = true;
}
- // Demonstrate keeping auto focus on the input box
- if (ImGui::IsItemHovered() || (ImGui::IsRootWindowOrAnyChildFocused() && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)))
+ // Demonstrate keeping focus on the input box
+ ImGui::SetItemDefaultFocus();
+ if (ImGui::IsItemHovered() || reclaim_focus)
ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
ImGui::End();
diff --git a/imgui_internal.h b/imgui_internal.h
index fcced1a..71d9002 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -15,6 +15,7 @@
#include <stdio.h> // FILE*
#include <math.h> // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf
+#include <limits.h> // INT_MIN, INT_MAX
#ifdef _MSC_VER
#pragma warning (push)
@@ -177,7 +178,8 @@
ImGuiButtonFlags_Disabled = 1 << 7, // disable interaction
ImGuiButtonFlags_AlignTextBaseLine = 1 << 8, // vertically align button to match text baseline - ButtonEx() only
ImGuiButtonFlags_NoKeyModifiers = 1 << 9, // disable interaction if a key modifier is held
- ImGuiButtonFlags_AllowOverlapMode = 1 << 10 // require previous frame HoveredId to either match id or be null before being usable
+ ImGuiButtonFlags_AllowOverlapMode = 1 << 10, // require previous frame HoveredId to either match id or be null before being usable
+ ImGuiButtonFlags_NoNavOverride = 1 << 11 // don't override navigation id when activated
};
enum ImGuiSliderFlags_
@@ -223,6 +225,14 @@
ImGuiDataType_Float2
};
+enum ImGuiInputSource
+{
+ ImGuiInputSource_None = 0,
+ ImGuiInputSource_Mouse,
+ ImGuiInputSource_Nav,
+ ImGuiInputSource_Count_,
+};
+
enum ImGuiDir
{
ImGuiDir_None = -1,
@@ -387,9 +397,10 @@
ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup()
ImGuiWindow* ParentWindow; // Set on OpenPopup()
ImGuiID ParentMenuSet; // Set on OpenPopup()
+ ImVec2 PopupPosOnOpen; // Preferred popup position (typically == MousePosOnOpen when using mouse)
ImVec2 MousePosOnOpen; // Copy of mouse position at the time of opening popup
- ImGuiPopupRef(ImGuiID id, ImGuiWindow* parent_window, ImGuiID parent_menu_set, const ImVec2& mouse_pos) { PopupId = id; Window = NULL; ParentWindow = parent_window; ParentMenuSet = parent_menu_set; MousePosOnOpen = mouse_pos; }
+ ImGuiPopupRef(ImGuiID id, ImGuiWindow* parent_window, ImGuiID parent_menu_set, const ImVec2& popup_pos, const ImVec2& mouse_pos) { PopupId = id; Window = NULL; ParentWindow = parent_window; ParentMenuSet = parent_menu_set; PopupPosOnOpen = popup_pos; MousePosOnOpen = mouse_pos; }
};
// Main state for ImGui
@@ -411,7 +422,6 @@
ImVector<ImGuiWindow*> WindowsSortBuffer;
ImVector<ImGuiWindow*> CurrentWindowStack;
ImGuiWindow* CurrentWindow; // Being drawn into
- ImGuiWindow* NavWindow; // Nav/focused window for navigation
ImGuiWindow* HoveredWindow; // Will catch mouse inputs
ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only)
ImGuiID HoveredId; // Hovered widget
@@ -422,10 +432,11 @@
bool ActiveIdIsAlive; // Active widget has been seen this frame
bool ActiveIdIsJustActivated; // Set at the time of activation for one frame
bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always)
-
+ int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and move away from it)
ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior)
ImGuiWindow* ActiveIdWindow;
- ImGuiWindow* MovedWindow; // Track the child window we clicked on to move a window.
+ ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard)
+ ImGuiWindow* MovedWindow; // Track the child window we clicked on to move a window (only apply to moving with mouse)
ImGuiID MovedWindowMoveId; // == MovedWindow->RootWindow->MoveId
ImVector<ImGuiIniData> Settings; // .ini Settings
float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero
@@ -435,6 +446,34 @@
ImVector<ImGuiPopupRef> OpenPopupStack; // Which popups are open (persistent)
ImVector<ImGuiPopupRef> CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame)
+ // Navigation data (for gamepad/keyboard)
+ ImGuiWindow* NavWindow; // Nav/focused window for navigation
+ ImGuiID NavId; // Nav/focused item for navigation
+ ImGuiID NavActivateId, NavInputId; // ~~ IsKeyPressedMap(ImGuiKey_NavActive) ? NavId : 0, etc. (to make widget code terser)
+ ImGuiID NavTabbedId; //
+ ImRect NavRefRectRel, NavScoringRectScreen;// Reference rectangle, in window space. Modified rectangle for directional navigation scoring, in screen space.
+ ImGuiWindow* NavWindowingTarget;
+ float NavWindowingDisplayAlpha;
+ bool NavWindowingToggleLayer;
+ int NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later.
+ int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing
+ bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRefRectRel is valid
+ bool NavMousePosDirty;
+ bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (nb: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover)
+ bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again.
+ bool NavInitDefaultRequest; // Init request for appearing window to select first item
+ ImGuiID NavInitDefaultResultId;
+ ImRect NavInitDefaultResultRectRel;
+ bool NavInitDefaultResultExplicit; // Whether the result was explicitly requested with SetItemDefaultFocus()
+ bool NavMoveRequest; // Move request for this frame
+ bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items
+ ImGuiDir NavMoveDir; // West/East/North/South
+ ImGuiID NavMoveResultId; // Best move request candidate
+ float NavMoveResultDistBox; // Best move request candidate box distance to current NavId
+ float NavMoveResultDistCenter; // Best move request candidate center distance to current NavId
+ float NavMoveResultDistAxial;
+ ImRect NavMoveResultRectRel; // Best move request candidate bounding box in window relative space
+
// Storage for SetNexWindow** and SetNextTreeNode*** functions
ImVec2 SetNextWindowPosVal;
ImVec2 SetNextWindowSizeVal;
@@ -503,7 +542,6 @@
FrameCount = 0;
FrameCountEnded = FrameCountRendered = -1;
CurrentWindow = NULL;
- NavWindow = NULL;
HoveredWindow = NULL;
HoveredRootWindow = NULL;
HoveredId = 0;
@@ -516,10 +554,30 @@
ActiveIdAllowOverlap = false;
ActiveIdClickOffset = ImVec2(-1,-1);
ActiveIdWindow = NULL;
+ ActiveIdSource = ImGuiInputSource_None;
MovedWindow = NULL;
MovedWindowMoveId = 0;
SettingsDirtyTimer = 0.0f;
+ NavWindow = NULL;
+ NavId = NavActivateId = NavInputId = NavTabbedId = 0;
+ NavRefRectRel = NavScoringRectScreen = ImRect();
+ NavWindowingTarget = NULL;
+ NavWindowingDisplayAlpha = 0.0f;
+ NavWindowingToggleLayer = false;
+ NavIdTabCounter = INT_MAX;
+ NavIdIsAlive = false;
+ NavMousePosDirty = false;
+ NavDisableHighlight = true;
+ NavDisableMouseHover = false;
+ NavInitDefaultRequest = false;
+ NavInitDefaultResultId = 0;
+ NavInitDefaultResultExplicit = false;
+ NavMoveRequest = false;
+ NavMoveDir = ImGuiDir_None;
+ NavMoveResultId = 0;
+ NavMoveResultDistBox = NavMoveResultDistCenter = NavMoveResultDistAxial = 0.0f;
+
SetNextWindowPosVal = ImVec2(0.0f, 0.0f);
SetNextWindowSizeVal = ImVec2(0.0f, 0.0f);
SetNextWindowCollapsedVal = false;
@@ -571,9 +629,9 @@
ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true
ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings.
//ImGuiItemFlags_Disabled = 1 << 2, // false // All widgets appears are disabled
- //ImGuiItemFlags_AllowNavDefaultFocus = 1 << 3, // true
- //ImGuiItemFlags_SelectableDontClosePopup = 1 << 4, // false // MenuItem/Selectable() automatically closes current Popup window
- ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus
+ ImGuiItemFlags_AllowNavDefaultFocus = 1 << 3, // true
+ ImGuiItemFlags_SelectableDontClosePopup = 1 << 4, // false // MenuItem/Selectable() automatically closes current Popup window
+ ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus|ImGuiItemFlags_AllowNavDefaultFocus
};
// Transient per-window data, reset at the beginning of the frame
@@ -592,8 +650,11 @@
int TreeDepth;
ImGuiID LastItemId;
ImRect LastItemRect;
- bool LastItemHoveredAndUsable; // Item rectangle is hovered, and its window is currently interactable with (not blocked by a popup preventing access to the window)
- bool LastItemHoveredRect; // Item rectangle is hovered, but its window may or not be currently interactable with (might be blocked by a popup preventing access to the window)
+ bool LastItemHoveredAndUsable; // Item rectangle is hovered, and its window is currently interactable with (not blocked by a popup preventing access to the window)
+ bool LastItemHoveredRect; // Item rectangle is hovered, but its window may or not be currently interactable with (might be blocked by a popup preventing access to the window)
+ bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f)
+ int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1)
+ int NavLayerActiveFlags, NavLayerActiveFlagsNext; // Which layer have been written to.
bool MenuBarAppending;
float MenuBarOffsetX;
ImVector<ImGuiWindow*> ChildWindows;
@@ -635,6 +696,9 @@
LastItemId = 0;
LastItemRect = ImRect(0.0f,0.0f,0.0f,0.0f);
LastItemHoveredAndUsable = LastItemHoveredRect = false;
+ NavHasScroll = false;
+ NavLayerActiveFlags = NavLayerActiveFlagsNext = 0x00;
+ NavLayerCurrent = 0;
MenuBarAppending = false;
MenuBarOffsetX = 0.0f;
StateStorage = NULL;
@@ -674,6 +738,7 @@
ImRect ContentsRegionRect; // Maximum visible content position in window coordinates. ~~ (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis
ImVec2 WindowPadding; // Window padding at the time of begin. We need to lock it, in particular manipulation of the ShowBorder would have an effect
ImGuiID MoveId; // == window->GetID("#MOVE")
+ ImGuiID ChildId; // Id of corresponding item in parent window (for child windows)
ImVec2 Scroll;
ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change)
ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered
@@ -684,10 +749,12 @@
bool WasActive;
bool Accessed; // Set to true when any widget access the current window
bool Collapsed; // Set when collapsing window to become only title-bar
+ bool CollapseToggleWanted;
bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed)
bool Appearing; // Set during the frame where the window is appearing (or re-appearing)
int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs)
ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling)
+ ImGuiID NavLastId; // Last known NavId for this window, for nav layer 0 only.
int AutoFitFramesX, AutoFitFramesY;
bool AutoFitOnlyGrows;
int AutoFitChildAxises;
@@ -702,6 +769,7 @@
ImVector<ImGuiID> IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack
ImRect ClipRect; // = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2.
ImRect WindowRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window.
+ ImRect InnerRect;
int LastFrameActive;
float ItemWidthDefault;
ImGuiSimpleColumns MenuColumns; // Simplified columns storage for menu items
@@ -711,8 +779,10 @@
ImGuiWindow* ParentWindow; // Immediate parent in the window stack *regardless* of whether this window is a child window or not)
ImGuiWindow* RootWindow; // Generally point to ourself. If we are a child window, this is pointing to the first non-child parent window.
ImGuiWindow* RootNonPopupWindow; // Generally point to ourself. Used to display TitleBgActive color and for selecting which window to use for NavWindowing
+ ImGuiWindow* RootNavWindow; // Generally point to ourself. If we are a child window with the ImGuiWindowFlags_NavFlattenedChild flag, point to parent. Used to display TitleBgActive color and for selecting which window to use for NavWindowing.
// Navigation / Focus
+ // FIXME-NAVIGATION: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext
int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister()
int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through)
int FocusIdxAllRequestCurrent; // Item being requested for focus
@@ -757,13 +827,14 @@
IMGUI_API void EndFrame(); // Ends the ImGui frame. Automatically called by Render()! you most likely don't need to ever call that yourself directly. If you don't need to render you can call EndFrame() but you'll have wasted CPU already. If you don't need to render, don't create any windows instead!
IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window);
+ IMGUI_API void SetActiveIDNoNav(ImGuiID id, ImGuiWindow* window);
IMGUI_API void ClearActiveID();
IMGUI_API void SetHoveredID(ImGuiID id);
IMGUI_API void KeepAliveID(ImGuiID id);
IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f);
IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f);
- IMGUI_API bool ItemAdd(const ImRect& bb, const ImGuiID* id);
+ IMGUI_API bool ItemAdd(const ImRect& bb, const ImGuiID* id, const ImRect* nav_bb = NULL);
IMGUI_API bool IsClippedEx(const ImRect& bb, const ImGuiID* id, bool clip_even_when_logged);
IMGUI_API bool IsHovered(const ImRect& bb, ImGuiID id, bool flatten_childs = false);
IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested
@@ -798,6 +869,7 @@
IMGUI_API void RenderCollapseTriangle(ImVec2 pos, bool is_open, float scale = 1.0f);
IMGUI_API void RenderBullet(ImVec2 pos);
IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col);
+ IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id); // Navigation highlight
IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding);
IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text.