Merge branch 'master' into navigation
diff --git a/imgui.cpp b/imgui.cpp
index 6173f71..f48eaa9 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -1,6 +1,9 @@
// dear imgui, v1.53 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
@@ -76,6 +80,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
@@ -204,6 +209,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
@@ -591,13 +627,14 @@
#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
#include <stdint.h> // intptr_t
#endif
+#define IMGUI_DEBUG_NAV 0
+
#ifdef _MSC_VER
#pragma warning (disable: 4127) // condition expression is constant
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
@@ -634,6 +671,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);
@@ -673,6 +711,9 @@
namespace ImGui
{
+static void NavUpdate();
+static void NavUpdateWindowing();
+static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id);
static void FocusPreviousWindow();
}
@@ -782,6 +823,7 @@
KeyMap[i] = -1;
KeyRepeatDelay = 0.250f;
KeyRepeatRate = 0.050f;
+ NavMovesMouse = false;
UserData = NULL;
Fonts = &GImDefaultFontAtlas;
@@ -814,7 +856,8 @@
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;
}
// Pass in translated ASCII characters for text input.
@@ -1832,6 +1875,7 @@
WindowRounding = 0.0f;
WindowBorderSize = 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);
@@ -1840,6 +1884,7 @@
Active = WasActive = false;
WriteAccessed = false;
Collapsed = false;
+ CollapseToggleWanted = false;
SkipItems = false;
Appearing = false;
CloseButton = false;
@@ -1855,6 +1900,9 @@
SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
+ NavLastIds[0] = NavLastIds[1] = 0;
+ NavRectRel[0] = NavRectRel[1] = ImRect();
+
LastFrameActive = -1;
ItemWidthDefault = 0.0f;
FontWindowScale = 1.0f;
@@ -1864,6 +1912,7 @@
ParentWindow = NULL;
RootWindow = NULL;
RootNonPopupWindow = NULL;
+ RootNavWindow = NULL;
FocusIdxAllCounter = FocusIdxTabCounter = -1;
FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
@@ -1920,6 +1969,25 @@
g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
}
+static void SetNavID(ImGuiID id, int nav_layer)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.NavWindow);
+ IM_ASSERT(nav_layer == 0 || nav_layer == 1);
+ g.NavId = id;
+ g.NavWindow->NavLastIds[nav_layer] = g.NavId;
+}
+
+static void SetNavIDAndMoveMouse(ImGuiID id, int nav_layer, const ImRect& rect_rel)
+{
+ ImGuiContext& g = *GImGui;
+ SetNavID(id, nav_layer);
+ g.NavWindow->NavRectRel[nav_layer] = rect_rel;
+ g.NavMousePosDirty = true;
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+}
+
void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
@@ -1927,9 +1995,40 @@
if (g.ActiveIdIsJustActivated)
g.ActiveIdTimer = 0.0f;
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.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
+ }
+}
+
+ImGuiID ImGui::GetActiveID()
+{
+ ImGuiContext& g = *GImGui;
+ return g.ActiveId;
+}
+
+void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
+{
+ IM_ASSERT(id != 0);
+ ImGuiContext& g = *GImGui;
+
+ // Assume that SetActiveID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it.
+ const int nav_layer = window->DC.NavLayerCurrent;
+ g.NavId = id;
+ g.NavWindow = window;
+ g.NavLayer = nav_layer;
+ window->NavLastIds[nav_layer] = id;
+ if (window->DC.LastItemId == id)
+ window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos);
+
+ if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ g.NavDisableMouseHover = true;
+ else
+ g.NavDisableHighlight = true;
}
void ImGui::ClearActiveID()
@@ -1945,6 +2044,12 @@
g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f;
}
+ImGuiID ImGui::GetHoveredID()
+{
+ ImGuiContext& g = *GImGui;
+ return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
+}
+
void ImGui::KeepAliveID(ImGuiID id)
{
ImGuiContext& g = *GImGui;
@@ -2004,19 +2109,232 @@
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
+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 varied item 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 biases for vertical navigation, needs to be removed.
+ 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 arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter)
+ quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
+ }
+
+#if IMGUI_DEBUG_NAV
+ if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
+ {
+ char buf[128];
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f) dcen (%.2f,%.2f->%.4f) d (%.2f,%.2f->%.4f) nav %c, quad %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "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" items
+ // (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.
+ // 2017/09/29: FIXME: This now currently only enabled inside menubars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward.
+ // Disabling it may however lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
+ if (g.NavMoveResultDistBox == FLT_MAX && dist_axial < g.NavMoveResultDistAxial) // Check axial match
+ if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ 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;
+}
+
+static inline void NavUpdateAnyRequestFlag()
+{
+ ImGuiContext& g = *GImGui;
+ g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || IMGUI_DEBUG_NAV;
+}
+
+static void NavMoveRequestCancel()
+{
+ ImGuiContext& g = *GImGui;
+ g.NavMoveRequest = false;
+ NavUpdateAnyRequestFlag();
+}
+
+// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
+static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id)
+{
+ ImGuiContext& g = *GImGui;
+ //if (!g.IO.NavUsable) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag.
+ // return;
+
+ const ImGuiItemFlags item_flags = window->DC.ItemFlags;
+ const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
+ if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
+ {
+ // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
+ if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
+ {
+ g.NavInitRequest = g.NavInitResultExplicit = false; // Found a match, clear request
+ NavUpdateAnyRequestFlag();
+ }
+ if (g.NavInitResultId == 0 || !(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
+ {
+ g.NavInitResultId = id;
+ g.NavInitResultRectRel = nav_bb_rel;
+ }
+ }
+
+ // Scoring for navigation
+ if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav))
+ {
+#if IMGUI_DEBUG_NAV
+ // [DEBUG] Score all items in NavWindow at all times
+ if (!g.NavMoveRequest)
+ g.NavMoveDir = g.NavMoveDirLast;
+ bool new_best = NavScoreItem(nav_bb) && g.NavMoveRequest;
+#else
+ bool new_best = g.NavMoveRequest && NavScoreItem(nav_bb);
+#endif
+ if (new_best)
+ {
+ g.NavMoveResultId = id;
+ g.NavMoveResultParentId = window->IDStack.back();
+ g.NavMoveResultRectRel = nav_bb_rel;
+ }
+ }
+
+ // Update window-relative bounding box of navigated item
+ if (g.NavId == id)
+ {
+ g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
+ g.NavLayer = window->DC.NavLayerCurrent;
+ g.NavIdIsAlive = true;
+ g.NavIdTabCounter = window->FocusIdxTabCounter;
+ window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
+ }
+}
+
// 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, ImGuiID id)
+// declare 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, ImGuiID id, const ImRect* nav_bb_arg)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
- const bool is_clipped = IsClippedEx(bb, id, false);
+
+ if (id != 0)
+ {
+ // Navigation processing runs prior to clipping early-out
+ // (a) So that NavInitRequest 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 NavMoveRequest is only set on user interaction, aka maximum once a frame.
+ // We could early out with "if (is_clipped && !g.NavInitRequest) 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)
+ window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask;
+ if (g.NavWindow == window->RootNavWindow)
+ if (g.NavId == id || g.NavAnyRequest)
+ NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
+ }
+
window->DC.LastItemId = id;
window->DC.LastItemRect = bb;
- window->DC.LastItemRectHoveredRect = false;
+
+ // Clipping test
+ const bool is_clipped = IsClippedEx(bb, id, false);
if (is_clipped)
+ {
+ window->DC.LastItemRectHoveredRect = false;
return false;
+ }
//if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
// We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
@@ -2031,6 +2349,8 @@
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
+ if (g.NavDisableMouseHover)
+ return IsItemFocused();
// Test for bounding box overlap, as updated as ItemAdd()
if (!window->DC.LastItemRectHoveredRect)
@@ -2078,7 +2398,7 @@
return false;
if (!IsMouseHoveringRect(bb.Min, bb.Max))
return false;
- if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_Default))
+ if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_Default))
return false;
if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
return false;
@@ -2114,10 +2434,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.NavJustTabbedId = id;
+ return true;
+ }
return false;
}
@@ -2256,6 +2577,472 @@
return &GImGui->DrawListSharedData;
}
+// This needs to be called before we submit any widget (aka in or before Begin)
+void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(window == g.NavWindow);
+ bool init_for_nav = false;
+ if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
+ if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
+ init_for_nav = true;
+ if (init_for_nav)
+ {
+ SetNavID(0, g.NavLayer);
+ g.NavInitRequest = true;
+ g.NavInitResultId = 0;
+ g.NavInitResultExplicit = false;
+ g.NavInitResultRectRel = ImRect();
+ NavUpdateAnyRequestFlag();
+ }
+ else
+ {
+ g.NavId = window->NavLastIds[0];
+ }
+}
+
+static ImVec2 NavCalcPreferredMousePos()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.NavWindow;
+ if (!window)
+ return g.IO.MousePos;
+ const ImRect& rect_rel = window->NavRectRel[g.NavLayer];
+ ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight()));
+ ImRect visible_rect = GetVisibleRect();
+ return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.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_Released,
+ 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_Released) // Return 1.0f when just release, no repeat, ignore analog input (we don't need it for Pressed logic)
+ return (t < 0.0f && g.IO.NavInputsDownDurationPrev[n] >= 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;
+}
+
+// Window management mode (change focus, move/resize window, toggle menu layer)
+static void ImGui::NavUpdateWindowing()
+{
+ ImGuiContext& g = *GImGui;
+ bool toggle_layer = false;
+
+ 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))
+ {
+ FocusWindow(g.NavWindowingTarget);
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+ if (g.NavWindowingTarget->NavLastIds[0] == 0)
+ NavInitWindow(g.NavWindowingTarget, false);
+
+ // If the window only has a menu layer, select it directly
+ if (g.NavWindowingTarget->DC.NavLayerActiveMask == 0x02)
+ g.NavLayer = 1;
+ }
+
+ // Single press toggles NavLayer
+ if (g.NavWindowingToggleLayer && g.NavWindow)
+ toggle_layer = true;
+ g.NavWindowingTarget = NULL;
+ }
+ }
+
+ // Keyboard: Press and release ALT to toggle menu
+ if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu, ImGuiNavReadMode_Released))
+ toggle_layer = true;
+
+ if (toggle_layer && g.NavWindow)
+ {
+ if ((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) == 0 && (g.NavWindow->RootWindow->DC.NavLayerActiveMask & (1 << 1)) != 0)
+ FocusWindow(g.NavWindow->RootWindow);
+ g.NavLayer = (g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0;
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+ if (g.NavLayer == 0 && g.NavWindow->NavLastIds[0] != 0)
+ SetNavIDAndMoveMouse(g.NavWindow->NavLastIds[0], g.NavLayer, ImRect());
+ else
+ NavInitWindow(g.NavWindow, true);
+ }
+}
+
+// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
+static void NavScrollToBringItemIntoView(ImGuiWindow* window, ImRect& item_rect_rel)
+{
+ // Scroll to keep newly navigated item fully into view
+ ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1));
+ //g.OverlayDrawList.AddRect(window->Pos + window_rect_rel.Min, window->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG]
+ if (window_rect_rel.Contains(item_rect_rel))
+ return;
+
+ ImGuiContext& g = *GImGui;
+ if (window->ScrollbarX && item_rect_rel.Min.x < window_rect_rel.Min.x)
+ {
+ window->ScrollTarget.x = item_rect_rel.Min.x + window->Scroll.x - g.Style.ItemSpacing.x;
+ window->ScrollTargetCenterRatio.x = 0.0f;
+ }
+ else if (window->ScrollbarX && item_rect_rel.Max.x >= window_rect_rel.Max.x)
+ {
+ window->ScrollTarget.x = item_rect_rel.Max.x + window->Scroll.x + g.Style.ItemSpacing.x;
+ window->ScrollTargetCenterRatio.x = 1.0f;
+ }
+ if (item_rect_rel.Min.y < window_rect_rel.Min.y)
+ {
+ window->ScrollTarget.y = item_rect_rel.Min.y + window->Scroll.y - g.Style.ItemSpacing.y;
+ window->ScrollTargetCenterRatio.y = 0.0f;
+ }
+ else if (item_rect_rel.Max.y >= window_rect_rel.Max.y)
+ {
+ window->ScrollTarget.y = item_rect_rel.Max.y + window->Scroll.y + g.Style.ItemSpacing.y;
+ window->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(window);
+ item_rect_rel.Translate(window->Scroll - next_scroll);
+}
+
+static void ImGui::NavUpdate()
+{
+ ImGuiContext& g = *GImGui;
+ g.IO.WantMoveMouse = false;
+
+ // Process navigation init request (select first/default focus)
+ if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitResultExplicit))
+ {
+ // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
+ IM_ASSERT(g.NavWindow);
+ SetNavID(g.NavInitResultId, g.NavLayer);
+ g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
+ if (g.NavDisableMouseHover)
+ g.NavMousePosDirty = true;
+ }
+ g.NavInitRequest = false;
+ g.NavInitResultExplicit = false;
+ g.NavInitResultId = 0;
+ g.NavJustMovedToId = 0;
+
+ // Process navigation move request
+ if (g.NavMoveRequest && g.NavMoveResultId != 0)
+ {
+ // Scroll to keep newly navigated item fully into view
+ IM_ASSERT(g.NavWindow);
+ if (g.NavLayer == 0)
+ NavScrollToBringItemIntoView(g.NavWindow, g.NavMoveResultRectRel);
+
+ // Apply result from previous frame navigation directional move request
+ ClearActiveID();
+ SetNavIDAndMoveMouse(g.NavMoveResultId, g.NavLayer, g.NavMoveResultRectRel);
+ g.NavJustMovedToId = g.NavMoveResultId;
+ g.NavMoveFromClampedRefRect = false;
+ }
+
+ // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
+ if (g.NavMoveRequestForwardStep == 2)
+ {
+ IM_ASSERT(g.NavMoveRequest);
+ if (g.NavMoveResultId == 0)
+ g.NavDisableHighlight = false;
+ g.NavMoveRequestForwardStep = 0;
+ }
+
+ // 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.NavJustTabbedId = 0;
+ IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
+
+ NavUpdateWindowing();
+
+ // 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.NavInitRequest;
+
+ // Process NavCancel input (to close a popup, get back to parent, clear focus)
+ if (IsNavInputPressed(ImGuiNavInput_PadCancel, ImGuiNavReadMode_Pressed))
+ {
+ if (g.ActiveId != 0)
+ {
+ 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;
+ FocusWindow(parent_window);
+ IM_ASSERT(child_window->ChildId != 0);
+ SetNavID(child_window->ChildId, g.NavLayer); // FIXME-NAV: Layer not necessarily correct
+ 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->NavLastIds[0])
+ SetNavIDAndMoveMouse(g.NavWindow->NavLastIds[0], g.NavLayer, 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->NavLastIds[0] = 0;
+ g.NavId = 0;
+ }
+ }
+
+ g.NavActivateId = g.NavActivateDownId = g.NavInputId = 0;
+ if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget)
+ {
+ if (g.ActiveId == 0 && IsNavInputPressed(ImGuiNavInput_PadActivate, ImGuiNavReadMode_Pressed))
+ g.NavActivateId = g.NavId;
+ if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputDown(ImGuiNavInput_PadActivate))
+ g.NavActivateDownId = g.NavId;
+ if (g.ActiveId == 0 && IsNavInputPressed(ImGuiNavInput_PadInput, ImGuiNavReadMode_Pressed))
+ g.NavInputId = g.NavId;
+ }
+ if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+ {
+ g.NavActivateId = g.NavActivateDownId = g.NavInputId = 0;
+ g.NavDisableHighlight = true;
+ }
+ if (g.NavActivateId != 0)
+ IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
+ g.NavMoveRequest = false;
+
+ // Process explicit activation request
+ if (g.NavNextActivateId != 0)
+ g.NavActivateId = g.NavActivateDownId = g.NavInputId = g.NavNextActivateId;
+ g.NavNextActivateId = 0;
+
+ // Initiate directional inputs request
+ const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
+ if (g.NavMoveRequestForwardStep == 0)
+ {
+ 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;
+ }
+ }
+ else
+ {
+ IM_ASSERT(g.NavMoveDir != ImGuiDir_None);
+ IM_ASSERT(g.NavMoveRequestForwardStep == 1);
+ g.NavMoveRequestForwardStep = 2;
+ }
+
+ if (g.NavMoveDir != ImGuiDir_None)
+ {
+ g.NavMoveRequest = true;
+ g.NavMoveDirLast = g.NavMoveDir;
+ }
+
+ // 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.NavInitRequest = g.NavInitResultExplicit = true;
+ g.NavInitResultId = 0;
+ g.NavDisableHighlight = false;
+ }
+
+ NavUpdateAnyRequestFlag();
+
+ // Scrolling
+ if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
+ {
+ // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item
+ ImGuiWindow* window = g.NavWindow;
+ const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
+ if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
+ {
+ if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
+ SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
+ if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
+ SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
+ }
+
+ // *Normal* Manual scroll with NavScrollXXX keys
+ // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
+ ImVec2 scroll_dir = GetNavInputAmount2d(1, ImGuiNavReadMode_Down, 1.0f/10.0f, 10.0f);
+ if (scroll_dir.x != 0.0f && window->ScrollbarX)
+ {
+ SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
+ g.NavMoveFromClampedRefRect = true;
+ }
+ if (scroll_dir.y != 0.0f)
+ {
+ SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
+ g.NavMoveFromClampedRefRect = true;
+ }
+ }
+
+ // Reset search
+ g.NavMoveResultId = 0;
+ g.NavMoveResultParentId = 0;
+ g.NavMoveResultDistAxial = g.NavMoveResultDistBox = g.NavMoveResultDistCenter = FLT_MAX;
+
+ // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
+ if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
+ {
+ ImGuiWindow* window = g.NavWindow;
+ ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1,1), window->InnerRect.Max - window->Pos + ImVec2(1,1));
+ if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
+ {
+ float pad = window->CalcFontSize() * 0.5f;
+ window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item
+ window->NavRectRel[g.NavLayer].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.NavWindow->NavRectRel[g.NavLayer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[g.NavLayer].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]
+ //if (g.NavWindow) for (int layer = 0; layer < 2; layer++) g.OverlayDrawList.AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); // [DEBUG]
+}
+
void ImGui::NewFrame()
{
ImGuiContext& g = *GImGui;
@@ -2324,6 +3111,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.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
+ 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
@@ -2331,6 +3124,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++)
{
@@ -2362,6 +3158,8 @@
g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y);
g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta));
}
+ 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
@@ -2371,7 +3169,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.MovingWindowMoveId && g.MovingWindowMoveId == g.ActiveId)
+ if (g.MovingWindowMoveId && g.MovingWindowMoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse)
{
KeepAliveID(g.MovingWindowMoveId);
IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
@@ -2491,8 +3289,14 @@
}
// Pressing TAB activate widget focus
- if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && IsKeyPressedMap(ImGuiKey_Tab, false))
- g.NavWindow->FocusIdxTabRequestNext = 0;
+ 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++)
@@ -2924,6 +3728,14 @@
if (g.HoveredRootWindow != NULL)
{
FocusWindow(g.HoveredWindow);
+ // FIXME-NAV: This never execute because of the FocusWindow call above, however we may might this behavior?
+ /*
+ 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.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove))
{
g.MovingWindow = g.HoveredWindow;
@@ -3003,9 +3815,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;
@@ -3311,6 +4125,35 @@
window->DrawList->PathStroke(col, false, thickness);
}
+void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
+{
+ ImGuiContext& g = *GImGui;
+ if (id != g.NavId)
+ return;
+ if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
+ return;
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+
+ ImRect display_rect = bb;
+ display_rect.ClipWith(window->ClipRect);
+ if (flags & ImGuiNavHighlightFlags_TypeDefault)
+ {
+ const float THICKNESS = 2.0f;
+ const float DISTANCE = 3.0f + THICKNESS * 0.5f;
+ display_rect.Expand(ImVec2(DISTANCE,DISTANCE));
+ bool fully_visible = window->ClipRect.Contains(display_rect);
+ if (!fully_visible)
+ 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);
+ if (!fully_visible)
+ window->DrawList->PopClipRect();
+ }
+ if (flags & ImGuiNavHighlightFlags_TypeThin)
+ {
+ window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), g.Style.FrameRounding, ~0, 1.0f);
+ }
+}
+
// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize)
ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
@@ -3362,6 +4205,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;
@@ -3605,6 +4453,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(ImGuiHoveredFlags_Default);
@@ -3618,7 +4472,14 @@
bool ImGui::IsAnyItemActive()
{
- return GImGui->ActiveId != 0;
+ ImGuiContext& g = *GImGui;
+ return g.ActiveId != 0;
+}
+
+bool ImGui::IsAnyItemFocused()
+{
+ ImGuiContext& g = *GImGui;
+ return g.NavId != 0 && !g.NavDisableHighlight;
}
bool ImGui::IsItemVisible()
@@ -3685,7 +4546,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;
Begin(window_name, NULL, flags | extra_flags);
}
@@ -3724,7 +4585,9 @@
ImGuiContext& g = *GImGui;
ImGuiWindow* parent_window = g.CurrentWindow;
int current_stack_size = g.CurrentPopupStack.Size;
- ImGuiPopupRef popup_ref = ImGuiPopupRef(id, parent_window, parent_window->GetID("##Menus"), g.IO.MousePos); // Tagged as new ref because constructor sets Window to NULL.
+ ImVec2 mouse_pos = g.IO.MousePos;
+ ImVec2 popup_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : mouse_pos;
+ ImGuiPopupRef popup_ref = ImGuiPopupRef(id, parent_window, parent_window->GetID("##Menus"), popup_pos, mouse_pos); // Tagged as new ref because constructor sets Window to NULL.
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)
@@ -3898,11 +4761,27 @@
return is_open;
}
+static void NavProcessMoveRequestWrapAround(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.NavMoveRequest && g.NavWindow == window && g.NavMoveResultId == 0)
+ if ((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) && g.NavMoveRequestForwardStep == 0 && g.NavLayer == 0)
+ {
+ g.NavMoveRequestForwardStep = 1;
+ NavMoveRequestCancel();
+ g.NavWindow->NavRectRel[0].Min.y = g.NavWindow->NavRectRel[0].Max.y = (g.NavMoveDir == ImGuiDir_Up) ? window->SizeFull.y : 0.0f;
+ }
+}
+
void ImGui::EndPopup()
{
ImGuiContext& g = *GImGui; (void)g;
IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
IM_ASSERT(g.CurrentPopupStack.Size > 0);
+
+ // Make all menus and popups wrap around for now, may need to expose that policy.
+ NavProcessMoveRequestWrapAround(g.CurrentWindow);
+
End();
}
@@ -3984,9 +4863,19 @@
ImGui::SetNextWindowSize(size);
bool ret = ImGui::Begin(title, NULL, flags);
ImGuiWindow* child_window = ImGui::GetCurrentWindow();
+ child_window->ChildId = id;
child_window->AutoFitChildAxises = auto_fit_axises;
g.Style.ChildBorderSize = backup_border_size;
+ // Process navigation-in immediately so NavInit can run on first frame
+ if (/*!(flags & ImGuiWindowFlags_NavFlattened) &&*/ (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && g.NavActivateId == id)
+ {
+ ImGui::FocusWindow(child_window);
+ ImGui::NavInitWindow(child_window, false);
+ ImGui::SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
+ g.ActiveIdSource = ImGuiInputSource_Nav;
+ }
+
return ret;
}
@@ -4023,7 +4912,16 @@
ImGuiWindow* parent_window = GetCurrentWindow();
ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
ItemSize(sz);
- ItemAdd(bb, 0);
+ if (/*!(window->Flags & ImGuiWindowFlags_NavFlattened) &&*/ (window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll))
+ {
+ ItemAdd(bb, window->ChildId);
+ RenderNavHighlight(bb, window->ChildId);
+ }
+ else
+ {
+ // Not navigable into
+ ItemAdd(bb, 0);
+ }
}
}
@@ -4349,6 +5247,9 @@
if (flags & ImGuiWindowFlags_NoInputs)
flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
+ //if (flags & ImGuiWindowFlags_NavFlattened)
+ // IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
+
// Find or create
ImGuiWindow* window = FindWindowByName(name);
if (!window)
@@ -4395,6 +5296,9 @@
window->PopupId = popup_ref.PopupId;
}
+ if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow))
+ window->NavLastIds[0] = 0;
+
// Process SetNextWindow***() calls
bool window_pos_set_by_api = false;
bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
@@ -4456,7 +5360,7 @@
window->RootWindow = parent_window->RootWindow;
if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
window->RootNonPopupWindow = parent_window->RootNonPopupWindow;
- //window->RootNavWindow = window;
+ window->RootNavWindow = window;
//while (window->RootNavWindow->Flags & ImGuiWindowFlags_NavFlattened)
// window->RootNavWindow = window->RootNavWindow->ParentWindow;
@@ -4483,7 +5387,7 @@
// Popup first latch mouse position, will position itself when it appears next frame
window->AutoPosLastDirection = ImGuiDir_None;
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
@@ -4491,7 +5395,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);
@@ -4502,6 +5406,7 @@
{
window->Collapsed = false;
}
+ window->CollapseToggleWanted = false;
// SIZE
@@ -4620,7 +5525,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. Store boxes in mouse cursor data? Scale? Center on cursor hit-point?
window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
if (window->AutoPosLastDirection == ImGuiDir_None)
@@ -4661,18 +5566,28 @@
if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
want_focus = true;
+ // Draw navigation windowing rectangle (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 modal window background (darkens what is behind them)
if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow())
window->DrawList->AddRectFilled(fullscreen_rect.Min, fullscreen_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio));
// Draw window + handle manual resize
ImRect title_bar_rect = window->TitleBarRect();
+ const bool window_is_focused = want_focus || (g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow);
if (window->Collapsed)
{
// Title bar only
float backup_border_size = style.FrameBorderSize;
g.Style.FrameBorderSize = window->WindowBorderSize;
- RenderFrame(title_bar_rect.Min, title_bar_rect.Max, GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding);
+ RenderFrame(title_bar_rect.Min, title_bar_rect.Max, GetColorU32((window_is_focused && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed), true, window_rounding);
g.Style.FrameBorderSize = backup_border_size;
}
else
@@ -4701,7 +5616,7 @@
ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size);
resize_rect.FixInverted();
bool hovered, held;
- ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
+ ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
if (hovered || held)
g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
@@ -4727,7 +5642,7 @@
const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise
bool hovered, held;
ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE);
- ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n+4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
+ ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n+4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held)
{
g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
@@ -4746,6 +5661,25 @@
}
PopID();
+ // Navigation/gamepad resize
+ if (g.NavWindowingTarget == window)
+ {
+ ImVec2 nav_resize_delta = GetNavInputAmount2d(0, ImGuiNavReadMode_Down);
+ if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
+ {
+ const float GAMEPAD_RESIZE_SPEED = 600.0f;
+ nav_resize_delta *= ImFloor(GAMEPAD_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
+ g.NavDisableMouseHover = true;
+ resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
+ 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, right now a constraint will make us stuck.
+ g.NavWindowingToggleLayer = false;
+ size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
+ }
+ }
+ }
+
// Apply back modified position/size to window
if (size_target.x != FLT_MAX)
{
@@ -4767,7 +5701,6 @@
window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot);
// Title bar
- const bool window_is_focused = want_focus || (g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow);
if (!(flags & ImGuiWindowFlags_NoTitleBar))
window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImDrawCornerFlags_Top);
@@ -4833,11 +5766,15 @@
window->DC.CursorMaxPos = window->DC.CursorStartPos;
window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f;
window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
+ window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext;
+ window->DC.NavLayerActiveMaskNext = 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;
window->DC.ChildWindows.resize(0);
window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
window->DC.ItemFlags = ImGuiItemFlags_Default_;
window->DC.ItemWidth = window->ItemWidthDefault;
window->DC.TextWrapPos = -1.0f; // disabled
@@ -4863,14 +5800,29 @@
// Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
if (want_focus)
+ {
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 item_flags_backup = window->DC.ItemFlags;
+ window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
+ window->DC.NavLayerCurrent++;
+ window->DC.NavLayerCurrentMask <<= 1;
+
// Collapse button
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);
RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
}
@@ -4883,6 +5835,10 @@
*p_open = false;
}
+ window->DC.NavLayerCurrent--;
+ window->DC.NavLayerCurrentMask >>= 1;
+ window->DC.ItemFlags = item_flags_backup;
+
// Title text (FIXME: refactor text alignment facilities along with RenderText helpers)
const ImVec2 text_size = CalcTextSize(name, NULL, true);
ImVec2 text_min = window->Pos;
@@ -5066,7 +6022,7 @@
bool held = false;
bool hovered = false;
const bool previously_held = (g.ActiveId == id);
- ButtonBehavior(bb, id, &hovered, &held);
+ ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
float scroll_ratio = ImSaturate(scroll_v / scroll_max);
@@ -5157,8 +6113,17 @@
{
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->NavLastIds[0] : 0; // Restore NavId
+ g.NavIdIsAlive = false;
+ g.NavLayer = 0;
+ if (window && g.NavDisableMouseHover)
+ g.NavMousePosDirty = true;
+ if (window)
+ window->NavRectRel[0].Min = window->NavRectRel[0].Max = window->DC.CursorStartPos - window->Pos;
+ g.NavWindow = window;
+ }
// Passing NULL allow to disable keyboard focus
if (!window)
@@ -5182,7 +6147,7 @@
{
ImGuiContext& g = *GImGui;
for (int i = g.Windows.Size - 1; i >= 0; i--)
- if (g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow))
+ if (g.Windows[i] != g.NavWindow && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow))
{
FocusWindow(g.Windows[i]);
return;
@@ -5479,6 +6444,8 @@
case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening";
case ImGuiCol_DragDropTarget: return "DragDropTarget";
+ case ImGuiCol_NavHighlight: return "NavHighlight";
+ case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
}
IM_ASSERT(0);
return "Unknown";
@@ -5566,6 +6533,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.
@@ -5955,13 +6929,16 @@
SetScrollFromPosY(target_y, center_y_ratio);
}
-// FIXME-NAV: This function is a placeholder for the upcoming Navigation branch + Focusing features.
-// In the current branch this function will only set the scrolling, in the navigation branch it will also set your navigation cursor.
-// Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable.
-void ImGui::SetItemDefaultFocus()
+void ImGui::ActivateItem(ImGuiID id)
{
- if (IsWindowAppearing())
- SetScrollHere();
+ ImGuiContext& g = *GImGui;
+ g.NavNextActivateId = id;
+}
+
+ImGuiID ImGui::GetItemID()
+{
+ ImGuiContext& g = *GImGui;
+ return g.CurrentWindow->DC.LastItemId;
}
void ImGui::SetKeyboardFocusHere(int offset)
@@ -5972,6 +6949,24 @@
window->FocusIdxTabRequestNext = INT_MAX;
}
+void ImGui::SetItemDefaultFocus()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ if (!window->Appearing)
+ return;
+ if (g.NavWindow == window->RootNavWindow && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent)
+ {
+ g.NavInitRequest = false;
+ g.NavInitResultExplicit = true;
+ g.NavInitResultId = g.NavWindow->DC.LastItemId;
+ g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
+ NavUpdateAnyRequestFlag();
+ if (!IsItemVisible())
+ SetScrollHere();
+ }
+}
+
void ImGui::SetStateStorage(ImGuiStorage* tree)
{
ImGuiWindow* window = GetCurrentWindow();
@@ -6253,9 +7248,12 @@
// 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
+ SetActiveID(id, window);
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
+ SetFocusID(id, window);
FocusWindow(window);
g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
}
@@ -6285,22 +7283,57 @@
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
+ // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
+ if (g.NavId == id && !g.NavDisableHighlight && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
+ hovered = true;
+
+ if (g.NavActivateDownId == id)
+ {
+ bool nav_activated_by_code = (g.NavActivateId == id);
+ bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_PadActivate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiNavReadMode_Repeat : ImGuiNavReadMode_Pressed);
+ if (nav_activated_by_code || nav_activated_by_inputs)
+ pressed = true;
+ if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
+ {
+ // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
+ g.NavActivateId = id; // This is so SetActiveId assign a Nav source
+ SetActiveID(id, window);
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
+ SetFocusID(id, window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+ }
}
bool held = false;
if (g.ActiveId == id)
{
- if (g.IO.MouseDown[0])
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse)
{
- held = true;
+ if (g.IO.MouseDown[0])
+ {
+ held = true;
+ }
+ else
+ {
+ if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
+ if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
+ if (!g.DragDropActive)
+ pressed = true;
+ ClearActiveID();
+ }
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
+ g.NavDisableHighlight = true;
}
- else
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav)
{
- if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
- if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
- if (!g.DragDropActive)
- pressed = true;
- ClearActiveID();
+ if (g.NavActivateDownId != id)
+ ClearActiveID();
}
}
@@ -6331,12 +7364,14 @@
if (!ItemAdd(bb, id))
return false;
- if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat;
+ if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
+ flags |= ImGuiButtonFlags_Repeat;
bool hovered, held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
// 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);
@@ -6390,6 +7425,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);
@@ -6492,6 +7529,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));
@@ -6704,15 +7742,31 @@
button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
- if (pressed && !(flags & ImGuiTreeNodeFlags_Leaf))
+ if (!(flags & ImGuiTreeNodeFlags_Leaf))
{
- bool toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick));
- if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
- toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y));
- if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
- toggled |= g.IO.MouseDoubleClicked[0];
- if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
- toggled = false;
+ bool toggled = false;
+ if (pressed)
+ {
+ 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)) && (!g.NavDisableMouseHover);
+ if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
+ toggled |= g.IO.MouseDoubleClicked[0];
+ if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
+ toggled = false;
+ }
+
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
+ {
+ toggled = true;
+ g.NavMoveRequest = false;
+ }
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
+ {
+ toggled = true;
+ g.NavMoveRequest = false;
+ }
+
if (toggled)
{
is_open = !is_open;
@@ -6729,6 +7783,7 @@
{
// Framed type
RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
+ RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin);
RenderTriangle(bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
if (g.LogEnabled)
{
@@ -6748,8 +7803,10 @@
{
// Unframed typed for tree nodes
if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
+ {
RenderFrame(bb.Min, bb.Max, col, false);
-
+ RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin);
+ }
if (flags & ImGuiTreeNodeFlags_Bullet)
RenderBullet(bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
else if (!(flags & ImGuiTreeNodeFlags_Leaf))
@@ -7125,6 +8182,7 @@
// 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);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
SetHoveredID(0);
FocusableItemUnregister(window);
@@ -7221,7 +8279,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;
@@ -7258,7 +8318,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;
@@ -7266,6 +8326,31 @@
clicked_t = 1.0f - clicked_t;
set_new_value = true;
}
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivateDownId == id)
+ {
+ 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();
@@ -7348,7 +8433,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;
@@ -7362,11 +8447,12 @@
// 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);
+ SetFocusID(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;
@@ -7405,7 +8491,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 = ItemHoverable(frame_bb, id);
@@ -7413,9 +8499,10 @@
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);
+ SetFocusID(id, window);
FocusWindow(window);
}
@@ -7553,6 +8640,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;
@@ -7560,7 +8648,7 @@
// Process clicking on the drag
if (g.ActiveId == id)
{
- if (g.IO.MouseDown[0])
+ if (g.IO.MouseDown[0] || g.NavActivateDownId == id)
{
if (g.ActiveIdIsJustActivated)
{
@@ -7575,7 +8663,7 @@
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)
@@ -7583,6 +8671,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;
@@ -7643,7 +8736,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;
@@ -7657,11 +8750,12 @@
// 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);
+ SetFocusID(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;
@@ -7849,7 +8943,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, 0))
+ if (!ItemAdd(total_bb, 0, &frame_bb))
return;
const bool hovered = ItemHoverable(inner_bb, 0);
@@ -8044,6 +9138,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)
{
@@ -8110,6 +9205,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)
{
@@ -8426,6 +9522,7 @@
ImGuiWindow* draw_window = window;
if (is_multiline)
{
+ ItemAdd(total_bb, id, &frame_bb);
if (!BeginChildFrame(id, frame_bb.GetSize()))
{
EndChildFrame();
@@ -8438,7 +9535,7 @@
else
{
ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, id))
+ if (!ItemAdd(total_bb, id, &frame_bb))
return false;
}
const bool hovered = ItemHoverable(frame_bb, id);
@@ -8474,8 +9571,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)) && (!is_multiline);
+ if (focus_requested || user_clicked || user_scrolled || g.NavInputId == id)
{
if (g.ActiveId != id)
{
@@ -8514,7 +9611,10 @@
select_all = true;
}
SetActiveID(id, window);
+ SetFocusID(id, window);
FocusWindow(window);
+ if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
+ g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
}
else if (io.MouseClicked[0])
{
@@ -8807,6 +9907,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);
@@ -9178,7 +10279,7 @@
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;
bool hovered, held;
@@ -9187,6 +10288,7 @@
const float arrow_size = GetFrameHeight();
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
RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
@@ -9195,8 +10297,10 @@
if (label_size.x > 0)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
- if (pressed && !popup_open)
+ if ((pressed || g.NavActivateId == id) && !popup_open)
{
+ if (window->DC.NavLayerCurrent == 0)
+ window->NavLastIds[0] = id;
OpenPopupEx(id, false);
popup_open = true;
}
@@ -9390,7 +10494,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) ? 0 : id))
{
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
PushColumnClipRect();
@@ -9407,11 +10511,20 @@
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 (pressed || hovered)// && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f))
+ if (!g.NavDisableMouseHover && g.NavWindow == window)
+ {
+ g.NavDisableHighlight = true;
+ SetNavID(id, window->DC.NavLayerCurrent);
+ }
+
// Render
if (hovered || selected)
{
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f);
+ RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin);
}
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
@@ -9527,6 +10640,8 @@
*current_item = i;
value_changed = true;
}
+ if (item_selected)
+ SetItemDefaultFocus();
PopID();
}
ListBoxFooter();
@@ -9607,6 +10722,12 @@
void ImGui::EndMainMenuBar()
{
EndMenuBar();
+
+ // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
+ ImGuiContext& g = *GImGui;
+ if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
+ FocusPreviousWindow();
+
End();
PopStyleVar(2);
}
@@ -9632,6 +10753,8 @@
window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y);// + g.Style.FramePadding.y);
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ window->DC.NavLayerCurrent++;
+ window->DC.NavLayerCurrentMask <<= 1;
window->DC.MenuBarAppending = true;
AlignTextToFramePadding();
return true;
@@ -9642,6 +10765,27 @@
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return;
+ ImGuiContext& g = *GImGui;
+
+ // When a move request within one of our child menu failed, capture the request to navigate among our siblings.
+ if (g.NavMoveRequest && g.NavMoveResultId == 0 && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ {
+ ImGuiWindow* nav_earliest_child = g.NavWindow;
+ while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ nav_earliest_child = nav_earliest_child->ParentWindow;
+ if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForwardStep == 0)
+ {
+ // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
+ // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
+ IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
+ FocusWindow(window);
+ SetNavIDAndMoveMouse(window->NavLastIds[1], 1, window->NavRectRel[1]);
+ g.NavLayer = 1;
+ g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
+ g.NavMoveRequestForwardStep = 1;
+ NavMoveRequestCancel();
+ }
+ }
IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
IM_ASSERT(window->DC.MenuBarAppending);
@@ -9651,6 +10795,8 @@
window->DC.GroupStack.back().AdvanceCursor = false;
EndGroup();
window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.NavLayerCurrent--;
+ window->DC.NavLayerCurrentMask >>= 1;
window->DC.MenuBarAppending = false;
}
@@ -9709,7 +10855,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)
{
@@ -9728,6 +10874,22 @@
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
{
@@ -9741,6 +10903,11 @@
{
want_open = true;
}
+ else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
+ {
+ g.NavMoveRequest = false;
+ want_open = true;
+ }
}
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.. }'
@@ -9907,6 +11074,7 @@
else
window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
}
+ RenderNavHighlight(bb, id);
if (g.Style.FrameBorderSize > 0.0f)
RenderFrameBorder(bb.Min, bb.Max, rounding);
else
@@ -10315,6 +11483,7 @@
bool value_changed = false, value_changed_h = false, value_changed_sv = false;
+ PushItemFlag(ImGuiItemFlags_NoNav, true);
if (flags & ImGuiColorEditFlags_PickerHueWheel)
{
// Hue wheel + SV triangle logic
@@ -10384,6 +11553,7 @@
value_changed = true;
}
}
+ PopItemFlag(); // ImGuiItemFlags_NoNav
if (!(flags & ImGuiColorEditFlags_NoSidePreview))
{
@@ -10404,6 +11574,7 @@
if (!(flags & ImGuiColorEditFlags_NoSidePreview))
{
+ PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
if ((flags & ImGuiColorEditFlags_NoLabel))
Text("Current");
@@ -10418,6 +11589,7 @@
value_changed = true;
}
}
+ PopItemFlag();
EndGroup();
}
@@ -11595,6 +12767,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();
@@ -11686,6 +12859,8 @@
GImGui->OverlayDrawList.AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255,255,0,255));
ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window));
ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed);
+ ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask);
+ ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y);
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));
@@ -11713,12 +12888,16 @@
}
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 (%.2f sec)", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer); // 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 (%.2f sec)", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer);
+ ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, 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("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 de9177c..3f5e056 100644
--- a/imgui.h
+++ b/imgui.h
@@ -17,6 +17,7 @@
#include <string.h> // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp
#define IMGUI_VERSION "1.53 WIP"
+#define IMGUI_HAS_NAV // navigation branch
// Define attributes of all API symbols declarations, e.g. for DLL under Windows.
#ifndef IMGUI_API
@@ -76,6 +77,7 @@
typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_
typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_
typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_
+typedef int ImGuiNavInput; // enum: an input identifier for navigation // enum ImGuiNavInput_
typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_
typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_
typedef int ImDrawCornerFlags; // flags: for ImDrawList::AddRect*() etc. // enum ImDrawCornerFlags_
@@ -308,6 +310,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);
@@ -442,19 +445,21 @@
IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL);
IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL);
- // Focus
- // (FIXME: Those functions will be reworked after we merge the navigation branch + have a pass at focusing/tabbing features.)
- // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged)
- IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window (WIP navigation branch only). Pleaase use instead of SetScrollHere().
+ // Focus, Activation
+ IMGUI_API void ActivateItem(ImGuiID id); // remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again.
+ IMGUI_API ImGuiID GetItemID(); // get id of previous item, generally ~GetID(label)
+ IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. Please use instead of "if (IsWindowAppearing()) SetScrollHere()" to signify "default item".
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 -1 to access previous widget.
// Utilities
IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options.
IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- 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 rectangle of last item, in screen space
IMGUI_API ImVec2 GetItemRectMax(); // "
IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space
@@ -541,6 +546,9 @@
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_ResizeFromAnySide = 1 << 17, // (WIP) Enable resize from any corners and borders. Your back-end needs to honor the different values of io.MouseCursor set by imgui.
+ ImGuiWindowFlags_NoNavFocus = 1 << 18, // No focusing of this window with gamepad/keyboard navigation
+ ImGuiWindowFlags_NoNavInputs = 1 << 19, // No gamepad/keyboard navigation within the window
+ //ImGuiWindowFlags_NavFlattened = 1 << 20, // Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!)
// [Internal]
ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild()
@@ -683,6 +691,33 @@
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_KeyMenu, // access menu // e.g. ALT
+ ImGuiNavInput_COUNT,
+};
+
// Enumeration for PushStyleColor() / PopStyleColor()
enum ImGuiCol_
{
@@ -729,6 +764,8 @@
ImGuiCol_TextSelectedBg,
ImGuiCol_ModalWindowDarkening, // darken entire screen when a modal window is active
ImGuiCol_DragDropTarget,
+ 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)
@@ -883,6 +920,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.
@@ -936,6 +974,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[]
@@ -949,7 +988,9 @@
bool WantCaptureMouse; // When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. This is set by ImGui when it wants to use your mouse (e.g. unclicked mouse is hovering a window, or a widget is active).
bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. This is set by ImGui when it wants to use your keyboard inputs.
bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active).
- bool WantMoveMouse; // [BETA-NAV] MousePos has been altered, back-end should reposition mouse on next frame. Set only when 'NavMovesMouse=true'.
+ bool WantMoveMouse; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when 'NavMovesMouse=true'.
+ bool NavUsable; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events).
+ bool NavActive; // Directional navigation is active/visible and currently allowed (will handle 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()
@@ -974,6 +1015,8 @@
float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking 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 NavInputsDownDurationPrev[ImGuiNavInput_COUNT];
IMGUI_API ImGuiIO();
};
diff --git a/imgui_demo.cpp b/imgui_demo.cpp
index 0624bdd..fc017e7 100644
--- a/imgui_demo.cpp
+++ b/imgui_demo.cpp
@@ -171,6 +171,7 @@
static bool no_resize = false;
static bool no_collapse = false;
static bool no_close = false;
+ static bool no_nav = false;
// Demonstrate the various window flags. Typically you would just use the default.
ImGuiWindowFlags window_flags = 0;
@@ -180,6 +181,7 @@
if (no_move) window_flags |= ImGuiWindowFlags_NoMove;
if (no_resize) window_flags |= ImGuiWindowFlags_NoResize;
if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse;
+ if (no_nav) window_flags |= ImGuiWindowFlags_NoNavInputs;
if (no_close) p_open = NULL; // Don't pass our bool* to Begin
ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver);
@@ -244,7 +246,8 @@
ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150);
ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300);
ImGui::Checkbox("No collapse", &no_collapse);
- ImGui::Checkbox("No close", &no_close);
+ ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150);
+ ImGui::Checkbox("No nav", &no_nav);
if (ImGui::TreeNode("Style"))
{
@@ -1474,6 +1477,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();
@@ -1493,7 +1497,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();
@@ -1722,9 +1726,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 your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something).");
@@ -1733,7 +1740,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"))
{
if (ImGui::IsMousePosValid())
ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y);
@@ -1750,6 +1757,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())
@@ -1798,14 +1809,41 @@
ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf));
if (ImGui::IsItemActive()) has_focus = 3;
ImGui::PopAllowKeyboardFocus();
+
if (has_focus)
ImGui::Text("Item with focus: %d", has_focus);
else
ImGui::Text("Item with focus: <none>");
- ImGui::TextWrapped("Cursor & selection are preserved when refocusing last used item in code.");
+
+ // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item
+ static float f3[3] = { 0.0f, 0.0f, 0.0f };
+ int focus_ahead = -1;
+ if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine();
+ if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine();
+ if (ImGui::Button("Focus on Z")) focus_ahead = 2;
+ if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead);
+ ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f);
+
+ ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code.");
ImGui::TreePop();
}
+#if 0
+ if (ImGui::TreeNode("Remote Activation"))
+ {
+ static char label[256];
+ ImGui::InputText("Label", label, IM_ARRAYSIZE(label));
+ ImGui::PopID(); // We don't yet have an easy way compute ID at other levels of the ID stack so we pop it manually for now (e.g. we'd like something like GetID("../label"))
+ ImGuiID id = ImGui::GetID(label);
+ ImGui::PushID("Remote Activation");
+ if (ImGui::SmallButton("Activate"))
+ ImGui::ActivateItem(id);
+ ImGui::SameLine();
+ ImGui::Text("ID = 0x%08X", id);
+ ImGui::TreePop();
+ }
+#endif
+
if (ImGui::TreeNode("Focused & Hovered Test"))
{
static bool embed_all_inside_a_child_window = false;
@@ -1901,7 +1939,7 @@
char label[32];
sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]);
ImGui::Bullet(); ImGui::Selectable(label, false);
- if (ImGui::IsItemHovered())
+ if (ImGui::IsItemHovered() || ImGui::IsItemFocused())
ImGui::SetMouseCursor(i);
}
ImGui::TreePop();
@@ -2331,7 +2369,7 @@
ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); // Transparent background
- if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings))
+ if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoFocusOnAppearing|ImGuiWindowFlags_NoNavFocus|ImGuiWindowFlags_NoNavInputs))
{
ImGui::Text("Simple overlay\nin the corner of the screen.\n(right-click to change position)");
ImGui::Separator();
@@ -2615,6 +2653,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);
@@ -2622,10 +2661,12 @@
if (InputBuf[0])
ExecCommand(InputBuf);
strcpy(InputBuf, "");
+ reclaim_focus = true;
}
- // Demonstrate keeping auto focus on the input box
- if (ImGui::IsItemHovered() || (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)))
+ // Demonstrate keeping focus on the input box
+ ImGui::SetItemDefaultFocus();
+ if (reclaim_focus) //|| ImGui::IsItemHovered())
ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
ImGui::End();
diff --git a/imgui_draw.cpp b/imgui_draw.cpp
index 2ffdf4b..be824b6 100644
--- a/imgui_draw.cpp
+++ b/imgui_draw.cpp
@@ -173,6 +173,8 @@
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_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
+ colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered];
+ colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.12f);
}
void ImGui::StyleColorsDark(ImGuiStyle* dst)
@@ -223,6 +225,8 @@
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
+ colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+ colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.12f);
}
void ImGui::StyleColorsLight(ImGuiStyle* dst)
@@ -275,6 +279,8 @@
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
+ colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered];
+ colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.40f, 0.40f, 0.40f, 0.12f);
}
//-----------------------------------------------------------------------------
diff --git a/imgui_internal.h b/imgui_internal.h
index c08f508..4bc2a78 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -14,6 +14,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)
@@ -46,6 +47,7 @@
typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_
typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_
typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_
+typedef int ImGuiNavHighlightFlags; // flags: for RenderNavHighlight() // enum ImGuiNavHighlightFlags_
typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_
typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_
@@ -190,7 +192,8 @@
ImGuiButtonFlags_AlignTextBaseLine = 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine
ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held
ImGuiButtonFlags_NoHoldingActiveID = 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only)
- ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12 // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers)
+ ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12, // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers)
+ ImGuiButtonFlags_NoNavFocus = 1 << 13 // don't override navigation focus when activated
};
enum ImGuiSliderFlags_
@@ -250,6 +253,14 @@
ImGuiDataType_Float2
};
+enum ImGuiInputSource
+{
+ ImGuiInputSource_None = 0,
+ ImGuiInputSource_Mouse,
+ ImGuiInputSource_Nav,
+ ImGuiInputSource_Count_,
+};
+
enum ImGuiDir
{
ImGuiDir_None = -1,
@@ -260,6 +271,13 @@
ImGuiDir_Count_
};
+enum ImGuiNavHighlightFlags_
+{
+ ImGuiNavHighlightFlags_TypeDefault = 1 << 0,
+ ImGuiNavHighlightFlags_TypeThin = 1 << 1,
+ ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2
+};
+
// 2D axis aligned bounding-box
// NB: we can't rely on ImVec2 math operators being available here
struct IMGUI_API ImRect
@@ -411,9 +429,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; }
};
struct ImGuiColumnData
@@ -493,7 +512,6 @@
ImGuiStorage WindowsById;
int WindowsActiveCount;
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
@@ -506,8 +524,10 @@
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;
+ ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard)
ImGuiWindow* MovingWindow; // Track the child window we clicked on to move a window.
ImGuiID MovingWindowMoveId; // == MovingWindow->MoveId
ImVector<ImGuiColMod> ColorModifiers; // Stack for PushStyleColor()/PopStyleColor()
@@ -516,6 +536,42 @@
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; // Focused window for navigation. Could be called 'FocusWindow'
+ ImGuiID NavId; // Focused item for navigation
+ ImGuiID NavActivateId; // ~~ IsNavInputPressed(ImGuiNavInput_PadActivate) ? NavId : 0, also set when calling ActivateItem()
+ ImGuiID NavActivateDownId; // ~~ IsNavInputPressed(ImGuiNavInput_PadActivate) ? NavId : 0
+ ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_PadInput) ? NavId : 0
+ ImGuiID NavJustTabbedId; // Just tabbed to this id.
+ ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame
+ ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest)
+ ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring.
+ 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 NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest
+ bool NavInitRequest; // Init request for appearing window to select first item
+ ImGuiID NavInitResultId;
+ ImRect NavInitResultRectRel;
+ bool NavInitResultExplicit; // Whether the result was explicitly requested with SetItemDefaultFocus()
+ bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items
+ bool NavMoveRequest; // Move request for this frame
+ int NavMoveRequestForwardStep; // 0: no forward, 1: forward request, 2: forward result (this is used to navigate sibling parent menus from a child menu)
+ ImGuiDir NavMoveDir; // Direction of the move request (left/right/up/down)
+ ImGuiDir NavMoveDirLast; // Direction of the previous move request
+ ImGuiID NavMoveResultId; // Best move request candidate
+ ImGuiID NavMoveResultParentId; //
+ 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 SetNextWindowPosPivot;
@@ -604,7 +660,6 @@
FrameCountEnded = FrameCountRendered = -1;
WindowsActiveCount = 0;
CurrentWindow = NULL;
- NavWindow = NULL;
HoveredWindow = NULL;
HoveredRootWindow = NULL;
HoveredId = 0;
@@ -617,11 +672,38 @@
ActiveIdIsAlive = false;
ActiveIdIsJustActivated = false;
ActiveIdAllowOverlap = false;
+ ActiveIdAllowNavDirFlags = 0;
ActiveIdClickOffset = ImVec2(-1,-1);
ActiveIdWindow = NULL;
+ ActiveIdSource = ImGuiInputSource_None;
MovingWindow = NULL;
MovingWindowMoveId = 0;
+ NavWindow = NULL;
+ NavId = NavActivateId = NavActivateDownId = NavInputId = 0;
+ NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0;
+ NavScoringRectScreen = ImRect();
+ NavWindowingTarget = NULL;
+ NavWindowingDisplayAlpha = 0.0f;
+ NavWindowingToggleLayer = false;
+ NavLayer = 0;
+ NavIdTabCounter = INT_MAX;
+ NavIdIsAlive = false;
+ NavMousePosDirty = false;
+ NavDisableHighlight = true;
+ NavDisableMouseHover = false;
+ NavAnyRequest = false;
+ NavInitRequest = false;
+ NavInitResultId = 0;
+ NavInitResultExplicit = false;
+ NavMoveFromClampedRefRect = false;
+ NavMoveRequest = false;
+ NavMoveRequestForwardStep = 0;
+ NavMoveDir = NavMoveDirLast = ImGuiDir_None;
+ NavMoveResultId = 0;
+ NavMoveResultParentId = 0;
+ NavMoveResultDistBox = NavMoveResultDistCenter = NavMoveResultDistAxial = 0.0f;
+
SetNextWindowPosVal = ImVec2(0.0f, 0.0f);
SetNextWindowSizeVal = ImVec2(0.0f, 0.0f);
SetNextWindowCollapsedVal = false;
@@ -684,8 +766,8 @@
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 // FIXME-WIP: Disable interactions but doesn't affect visuals. Should be: grey out and disable interactions with widgets that affect data + view widgets (WIP)
- //ImGuiItemFlags_NoNav = 1 << 3, // false
- //ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false
+ ImGuiItemFlags_NoNav = 1 << 3, // false
+ ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false
ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window
ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus
};
@@ -707,11 +789,17 @@
ImGuiID LastItemId;
ImRect LastItemRect;
bool LastItemRectHoveredRect;
- bool MenuBarAppending;
+ bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f)
+ int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1)
+ int NavLayerCurrentMask; // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping.
+ int NavLayerActiveMask; // Which layer have been written to (result from previous frame)
+ int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame)
+ bool MenuBarAppending; // FIXME: Remove this
float MenuBarOffsetX;
ImVector<ImGuiWindow*> ChildWindows;
ImGuiStorage* StateStorage;
ImGuiLayoutType LayoutType;
+ ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin()
// We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings.
ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default]
@@ -738,10 +826,14 @@
LastItemId = 0;
LastItemRect = ImRect();
LastItemRectHoveredRect = false;
+ NavHasScroll = false;
+ NavLayerActiveMask = NavLayerActiveMaskNext = 0x00;
+ NavLayerCurrent = 0;
+ NavLayerCurrentMask = 1 << 0;
MenuBarAppending = false;
MenuBarOffsetX = 0.0f;
StateStorage = NULL;
- LayoutType = ImGuiLayoutType_Vertical;
+ LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical;
ItemWidth = 0.0f;
ItemFlags = ImGuiItemFlags_Default_;
TextWrapPos = -1.0f;
@@ -772,6 +864,7 @@
float WindowRounding; // Window rounding at the time of begin.
float WindowBorderSize; // Window border size at the time of begin.
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
@@ -781,6 +874,7 @@
bool WasActive;
bool WriteAccessed; // 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)
bool CloseButton; // Set when the window has a close button (p_open != NULL)
@@ -799,6 +893,9 @@
ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size)
ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right.
+ ImGuiID NavLastIds[2]; // Last known NavId for this window, per layer (0/1)
+ ImRect NavRectRel[2]; // Reference rectangle, in window space
+
ImGuiDrawContext DC; // Temporary per-window data, reset at the beginning of the frame
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.
@@ -814,8 +911,10 @@
ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL.
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
@@ -879,13 +978,16 @@
IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id);
IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window);
+ IMGUI_API ImGuiID GetActiveID();
+ IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window);
IMGUI_API void ClearActiveID();
IMGUI_API void SetHoveredID(ImGuiID id);
+ IMGUI_API ImGuiID GetHoveredID();
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, ImGuiID id);
+ IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL);
IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id);
IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged);
IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested
@@ -902,6 +1004,8 @@
IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags);
IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true);
+ IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit);
+
IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate);
IMGUI_API void Scrollbar(ImGuiLayoutType direction);
@@ -928,6 +1032,7 @@
IMGUI_API void RenderTriangle(ImVec2 pos, ImGuiDir dir, float scale = 1.0f);
IMGUI_API void RenderBullet(ImVec2 pos);
IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz);
+ IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // 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.