From f2d530040893d9ac91fcd629aa62285351f5c99d Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 25 Jan 2018 19:03:47 +0100 Subject: [PATCH] Nav: Keyboard: Added CTRL+TAB (and CTRL+Shift+TAB) style window selection. (#787) --- TODO.txt | 1 - imgui.cpp | 119 +++++++++++++++++++++++++++++------------------ imgui.h | 4 +- imgui_internal.h | 7 ++- 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/TODO.txt b/TODO.txt index a9bc641e..c21d68bb 100644 --- a/TODO.txt +++ b/TODO.txt @@ -237,7 +237,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - focus: SetKeyboardFocusHere() on with >= 0 offset could be done on same frame (else latch and modulate on beginning of next frame) - focus: unable to use SetKeyboardFocusHere() on clipped widgets. (#787) - inputs: rework IO system to be able to pass actual ordered/timestamped events. use an event queue? (~#335, #71) - - inputs: allow to pass explicit double-clicks if that's the only thing the user's backend can get them. (e.g. for windows by the CS_DBLCLKS style). - inputs: support track pad style scrolling & slider edit. - misc: idle refresh: expose cursor blink animation timer for backend to be able to lower framerate. diff --git a/imgui.cpp b/imgui.cpp index 8d73ded9..9a58a65e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2700,42 +2700,52 @@ ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInput return delta; } +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = FindWindowIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, 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; +} + // Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) static void ImGui::NavUpdateWindowing() { ImGuiContext& g = *GImGui; - bool toggle_layer = false; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; - if (!g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_PadMenu, ImGuiInputReadMode_Pressed)) - { - ImGuiWindow* window = g.NavWindow; - if (!window) - window = FindWindowNavigable(g.Windows.Size - 1, -1, -1); - if (window) + bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_PadMenu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard); + if (start_windowing_with_gamepad || start_windowing_with_keyboard) + if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1)) { g.NavWindowingTarget = window->RootNonPopupWindow; - g.NavWindowingDisplayAlpha = 0.0f; - g.NavWindowingToggleLayer = true; + g.NavWindowingHighlightTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavWindowingIsKeyboardMode = start_windowing_with_keyboard; } - } - if (g.NavWindowingTarget) + + // Gamepad update + g.NavWindowingHighlightTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && !g.NavWindowingIsKeyboardMode) { - // 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. + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.20f) / 0.05f)); // Select window to focus const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_PadFocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_PadFocusNext, ImGuiInputReadMode_RepeatSlow); - if (focus_change_dir != 0 && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)) + if (focus_change_dir != 0) { - 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; + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; } // Move window @@ -2751,34 +2761,51 @@ static void ImGui::NavUpdateWindowing() } } + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) 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 + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. if (g.NavWindowingToggleLayer && g.NavWindow) - toggle_layer = true; + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; g.NavWindowingTarget = NULL; } } - - // Keyboard: Press and release ALT to toggle menu - if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu, ImGuiInputReadMode_Released)) - toggle_layer = true; - if (toggle_layer && g.NavWindow) + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavWindowingIsKeyboardMode) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.15f) / 0.04f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and release ALT to toggle menu layer + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu, ImGuiInputReadMode_Released)) + apply_toggle_layer = true; + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootNonPopupWindow)) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window only has a menu layer, select it directly + if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) + g.NavLayer = 1; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) { if ((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) == 0 && (g.NavWindow->RootWindow->DC.NavLayerActiveMask & (1 << 1)) != 0) FocusWindow(g.NavWindow->RootWindow); @@ -5710,8 +5737,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { ImRect bb = window->Rect(); bb.Expand(g.FontSize); - window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingDisplayAlpha * 0.25f), g.Style.WindowRounding); - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingDisplayAlpha), g.Style.WindowRounding, ~0, 3.0f); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), g.Style.WindowRounding, ~0, 3.0f); } // Draw window + handle manual resize diff --git a/imgui.h b/imgui.h index 0f6b832d..9d3a5690 100644 --- a/imgui.h +++ b/imgui.h @@ -730,8 +730,8 @@ enum ImGuiNavInput_ // [BETA] Gamepad/Keyboard directional navigation options enum ImGuiNavFlags_ { - ImGuiNavFlags_EnableGamepad = 1 << 0, // Master gamepad navigation enable flag. This is to instruct your imgui binding whether to fill in gamepad navigation inputs. imgui itself won't use this flag. - ImGuiNavFlags_EnableKeyboard = 1 << 1, // Master keyboard navigation enable flag. This is to instruct your imgui binding whether to fill in keyboard navigation inputs. imgui itself won't use this flag. + ImGuiNavFlags_EnableGamepad = 1 << 0, // Master gamepad navigation enable flag. This is mostly to instruct your imgui binding whether to fill in gamepad navigation inputs. + ImGuiNavFlags_EnableKeyboard = 1 << 1, // Master keyboard navigation enable flag. This is mostly to instruct your imgui binding whether to fill in keyboard navigation inputs. ImGuiNavFlags_MoveMouse = 1 << 2 // Request navigation to allow move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantMoveMouse=true. If enabled you MUST honor io.WantMoveMouse requests in your binding, otherwise ImGui will react as if the mouse is jumping around back and forth. }; diff --git a/imgui_internal.h b/imgui_internal.h index a3def317..2d5248e4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -591,8 +591,10 @@ struct ImGuiContext 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; // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) this window is temporarily displayed front-most. - float NavWindowingDisplayAlpha; + float NavWindowingHighlightTimer; + float NavWindowingHighlightAlpha; bool NavWindowingToggleLayer; + bool NavWindowingIsKeyboardMode; // Gamepad or keyboard mode 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 @@ -710,8 +712,9 @@ struct ImGuiContext NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; NavScoringRectScreen = ImRect(); NavWindowingTarget = NULL; - NavWindowingDisplayAlpha = 0.0f; + NavWindowingHighlightTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; + NavWindowingIsKeyboardMode = false; NavLayer = 0; NavIdTabCounter = INT_MAX; NavIdIsAlive = false;