From ab9ce2a927ab0688c97f7fefadb49d84e41df346 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Oct 2024 18:28:27 +0200 Subject: [PATCH] Nav: added io.ConfigNavCursorVisibleAuto, io.ConfigNavCursorVisibleAlways. (#1074, #2048, #7237, #8059, #3200, #787) Note: the NavCursorHideFrames addition is to support 88a354585 even though ConfigNavCursorVisibleAlways is set. --- docs/CHANGELOG.txt | 34 ++++++++++++++++++++-------------- docs/TODO.txt | 1 - imgui.cpp | 21 +++++++++++++++++---- imgui.h | 2 ++ imgui_demo.cpp | 6 +++++- imgui_internal.h | 1 + imgui_widgets.cpp | 14 ++++++++++---- 7 files changed, 55 insertions(+), 24 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ca0c2a3742d0..3279969a2bbe 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -69,20 +69,26 @@ Other changes: state to draw callbacks. (#6969, #5834, #7468, #3590) - IO: WantCaptureKeyboard is never set when ImGuiConfigFlags_NoKeyboard is enabled. (#4921) - Error Handling: turned a few more functions into recoverable errors. (#1651) -- Nav: added NavSetCursorVisible(bool visible) to manipulate visibility of keyboard/gamepad - navigation cursor. (#1074, #2048, #7237, #8059) -- Nav: added io.ConfigNavEscapeClearFocusItem and io.ConfigNavEscapeClearFocusWindow to change - how pressing Escape affects navigation. (#8059, #2048, #1074, #3200) - - Set io.ConfigNavEscapeClearFocusItem = true (default) to clear focused item and highlight. - - Set io.ConfigNavEscapeClearFocusItem = false for Escape to not have a specific effect. - - Set io.ConfigNavEscapeClearFocusWindow = true to completely unfocus the dear imgui window, - is for some reason your app relies on imgui focus to take other decisions. -- Nav: pressing escape to hide nav highlight doesn't clear location from when ctrl+tabbing - back into same window later. -- Nav: fixed Ctrl+Tab so when starting with no focused window it starts from the top-most - window. (#3200) -- Nav: rectangle highlight not rendered for items with ImGuiItemFlags_NoNav. Can be relevant - when e.g activating the item with mouse, then Ctrl+Tabbing back and forth. +- Nav (Keyboard/Gamepad navigation): + - Nav: added io.ConfigNavCursorVisibleAuto and io.ConfigNavCursorVisibleAlways to configure + visibility of navigation cursor. (#1074, #2048, #7237, #8059, #3200, #787) + - Set io.ConfigNavCursorVisibleAuto = true (default) to enable automatic toggling + of cursor visibility (mouse click hide the cursor, arrow keys makes it visible). + - Set io.ConfigNavCursorVisibleAlways to keep cursor always visible. + - Nav: added NavSetCursorVisible(bool visible) function to manipulate visibility of + navigation cursor (e.g. set default state, or after some actions). (#1074, #2048, #7237, #8059) + - Nav: added io.ConfigNavEscapeClearFocusItem and io.ConfigNavEscapeClearFocusWindow to change + how pressing Escape affects navigation. (#8059, #2048, #1074, #3200) + - Set io.ConfigNavEscapeClearFocusItem = true (default) to clear focused item and highlight. + - Set io.ConfigNavEscapeClearFocusItem = false for Escape to not have a specific effect. + - Set io.ConfigNavEscapeClearFocusWindow = true to completely unfocus the dear imgui window, + is for some reason your app relies on imgui focus to take other decisions. + - Nav: pressing escape to hide nav highlight doesn't clear location from when Ctrl+Tabbing + back into same window later. + - Nav: fixed Ctrl+Tab so when starting with no focused window it starts from the top-most + window. (#3200) + - Nav: rectangle highlight not rendered for items with ImGuiItemFlags_NoNav. Can be relevant + when e.g activating the item with mouse, then Ctrl+Tabbing back and forth. - Disabled: clicking a disabled item focuses parent window. (#8064) - InvisibleButton, Nav: fixed an issue when InvisibleButton() would be navigable into but not display navigation highlight. Properly navigation on it by default. (#8057) diff --git a/docs/TODO.txt b/docs/TODO.txt index dd98d18b17d3..2a42874cd3fe 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -274,7 +274,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - font/opt: Glyph currently 40 bytes (2+9*4). Consider storing UV as 16-bits integer? (->32 bytes). X0/Y0/X1/Y1 as 16 fixed-point integers? Or X0/Y0 as float and X1/Y1 as fixed8_8? - nav: some features such as PageUp/Down/Home/End should probably work without ImGuiConfigFlags_NavEnableKeyboard? (where do we draw the line? how about CTRL+Tab) - ! nav: never clear NavId on some setup (e.g. gamepad centric) - nav: Home/End behavior when navigable item is not fully visible at the edge of scrolling? should be backtrack to keep item into view? - nav: NavScrollToBringItemIntoView() with item bigger than view should focus top-right? Repro: using Nav in "About Window" - nav: expose wrap around flags/logic to allow e.g. grid based layout (pressing NavRight on the right-most element would go to the next row, etc.). see internal's NavMoveRequestTryWrapping(). diff --git a/imgui.cpp b/imgui.cpp index c4388cedd4c2..0ebd61dc5537 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1406,6 +1406,8 @@ ImGuiIO::ImGuiIO() ConfigNavCaptureKeyboard = true; ConfigNavEscapeClearFocusItem = true; ConfigNavEscapeClearFocusWindow = false; + ConfigNavCursorVisibleAuto = true; + ConfigNavCursorVisibleAlways = false; // Miscellaneous options MouseDrawCursor = false; @@ -3916,6 +3918,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavHighlightActivatedTimer = 0.0f; NavInputSource = ImGuiInputSource_Keyboard; NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; + NavCursorHideFrames = 0; NavAnyRequest = false; NavInitRequest = false; @@ -4828,7 +4831,8 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) ImGuiContext& g = *GImGui; FocusWindow(window); SetActiveID(window->MoveId, window); - g.NavCursorVisible = false; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos; g.ActiveIdNoClearOnFocusLoss = true; SetActiveIdUsingAllKeyboardKeys(); @@ -12216,6 +12220,8 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) void ImGui::SetNavCursorVisible(bool visible) { ImGuiContext& g = *GImGui; + if (g.IO.ConfigNavCursorVisibleAlways) + visible = true; g.NavCursorVisible = visible; } @@ -12223,7 +12229,8 @@ void ImGui::SetNavCursorVisible(bool visible) void ImGui::SetNavCursorVisibleAfterMove() { ImGuiContext& g = *GImGui; - g.NavCursorVisible = true; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = true; g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; } @@ -12289,7 +12296,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) g.NavHighlightItemUnderNav = true; - else + else if (g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = false; // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it) @@ -12904,6 +12911,9 @@ static void ImGui::NavUpdate() NavMoveRequestApplyResult(); g.NavTabbingCounter = 0; g.NavMoveSubmitted = g.NavMoveScoringItems = false; + if (g.NavCursorHideFrames > 0) + if (--g.NavCursorHideFrames == 0) + g.NavCursorVisible = true; // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling) bool set_mouse_pos = false; @@ -12958,6 +12968,8 @@ static void ImGui::NavUpdate() } if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) g.NavCursorVisible = false; + else if (g.IO.ConfigNavCursorVisibleAlways && g.NavCursorHideFrames == 0) + g.NavCursorVisible = true; if (g.NavActivateId != 0) IM_ASSERT(g.NavActivateDownId == g.NavActivateId); @@ -13158,7 +13170,8 @@ void ImGui::NavUpdateCreateMoveRequest() IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "", g.NavLayer); g.NavInitRequest = g.NavInitRequestFromMove = true; g.NavInitResult.ID = 0; - g.NavCursorVisible = true; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = true; } // When using gamepad, we project the reference nav bounding box into window visible area. diff --git a/imgui.h b/imgui.h index 60bcbee6c548..73705cd367d7 100644 --- a/imgui.h +++ b/imgui.h @@ -2252,6 +2252,8 @@ struct ImGuiIO bool ConfigNavCaptureKeyboard; // = true // Sets io.WantCaptureKeyboard when io.NavActive is set. bool ConfigNavEscapeClearFocusItem; // = true // Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on. bool ConfigNavEscapeClearFocusWindow;// = false // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem). + bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. + bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c484dd48c79e..668c5c0eedad 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -527,7 +527,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. 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)."); - ImGui::SeparatorText("Navigation"); + ImGui::SeparatorText("Keyboard/Gamepad Navigation"); ImGui::Checkbox("io.ConfigNavSwapGamepadButtons", &io.ConfigNavSwapGamepadButtons); ImGui::Checkbox("io.ConfigNavMoveSetMousePos", &io.ConfigNavMoveSetMousePos); ImGui::SameLine(); HelpMarker("Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult"); @@ -536,6 +536,10 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Pressing Escape clears focused item."); ImGui::Checkbox("io.ConfigNavEscapeClearFocusWindow", &io.ConfigNavEscapeClearFocusWindow); ImGui::SameLine(); HelpMarker("Pressing Escape clears focused window."); + ImGui::Checkbox("io.ConfigNavCursorVisibleAuto", &io.ConfigNavCursorVisibleAuto); + ImGui::SameLine(); HelpMarker("Using directional navigation key makes the cursor visible. Mouse click hides the cursor."); + ImGui::Checkbox("io.ConfigNavCursorVisibleAlways", &io.ConfigNavCursorVisibleAlways); + ImGui::SameLine(); HelpMarker("Navigation cursor is always visible."); ImGui::SeparatorText("Widgets"); ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); diff --git a/imgui_internal.h b/imgui_internal.h index bca327cdf888..97a51a3559d4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2154,6 +2154,7 @@ struct ImGuiContext ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse ImGuiSelectionUserData NavLastValidSelectionUserData; // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. + ImS8 NavCursorHideFrames; // Navigation: Init & Move Requests bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd() diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e0bc7f7476bf..307e11d5b780 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -619,7 +619,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool pressed = true; } - if (pressed) + if (pressed && g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = false; } @@ -688,7 +688,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } ClearActiveID(); } - if (!(flags & ImGuiButtonFlags_NoNavFocus)) + if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = false; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) @@ -7001,7 +7001,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) - g.NavCursorVisible = false; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } } if (pressed) @@ -8624,7 +8625,12 @@ void ImGui::EndMenuBar() IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary) FocusWindow(window); SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); - g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. + // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto? + if (g.NavCursorVisible) + { + g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again + g.NavCursorHideFrames = 2; + } g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat }