From b05f6f6f502752586bb82afb01b942475953677e Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 9 Sep 2019 18:09:41 +0900 Subject: [PATCH] Nav, Scrolling: Added support for Home/End key. (#787) --- docs/CHANGELOG.txt | 1 + docs/TODO.txt | 7 +++-- imgui.cpp | 73 ++++++++++++++++++++++++++++++++++------------ imgui_internal.h | 5 ++-- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index cf03be732..5bc49ac7e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -34,6 +34,7 @@ HOW TO UPDATE? ----------------------------------------------------------------------- Other Changes: +- Nav, Scrolling: Added support for Home/End key. (#787) - ColorPicker: Made rendering aware of global style alpha of the picker can be faded out. (#2711) Note that some elements won't accurately fade down with the same intensity, and the color wheel when enabled will have small overlap glitches with (style.Alpha < 1.0). diff --git a/docs/TODO.txt b/docs/TODO.txt index f289ebab7..70c64c40f 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -300,12 +300,14 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - font/opt: Considering storing standalone AdvanceX table as 16-bit fixed point integer? - 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?) + - nav: configuration flag to disable global shortcuts (currently only CTRL-Tab) ? + - 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: wrap around 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(). - nav: patterns to make it possible for arrows key to update selection - - nav: restore/find nearest navid when current one disappear (e.g. pressed a button that disappear, or perhaps auto restoring when current button change name) + - nav: restore/find nearest NavId when current one disappear (e.g. pressed a button that disappear, or perhaps auto restoring when current button change name) - nav: SetItemDefaultFocus() level of priority, so widget like Selectable when inside a popup could claim a low-priority default focus on the first selected iem - - nav: allow input system to be be more tolerant of io.DeltaTime=0.0f - nav: ESC within a menu of a child window seems to exit the child window. - nav: NavFlattened: ESC on a flattened child should select something. - nav: NavFlattened: broken: in typical usage scenario, the items of a fully clipped child are currently not considered to enter into a NavFlattened child. @@ -323,7 +325,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - nav: esc/enter default behavior for popups, e.g. be able to mark an "ok" or "cancel" button that would get triggered by those keys. - nav: when activating a button that changes label (without a static ID) or disappear, can we somehow automatically recover into a nearest highlight item? - nav: there's currently no way to completely clear focus with the keyboard. depending on patterns used by the application to dispatch inputs, it may be desirable. - - nav: configuration flag to disable global shortcuts (currently only CTRL-tab) ? - focus: preserve ActiveId/focus stack state, e.g. when opening a menu and close it, previously selected InputText() focus gets restored (#622) - 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) diff --git a/imgui.cpp b/imgui.cpp index 89541b2f7..1dc4862a7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1078,7 +1078,7 @@ static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& // Navigation static void NavUpdate(); static void NavUpdateWindowing(); -static void NavUpdateWindowingList(); +static void NavUpdateWindowingOverlay(); static void NavUpdateMoveResult(); static float NavUpdatePageUpPageDown(int allowed_dir_flags); static inline void NavUpdateAnyRequestFlag(); @@ -4184,8 +4184,8 @@ void ImGui::EndFrame() End(); // Show CTRL+TAB list window - if (g.NavWindowingTarget) - NavUpdateWindowingList(); + if (g.NavWindowingTarget != NULL) + NavUpdateWindowingOverlay(); // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) if (g.DragDropActive) @@ -8074,7 +8074,7 @@ void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const Im { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); - ImGui::NavMoveRequestCancel(); + NavMoveRequestCancel(); g.NavMoveDir = move_dir; g.NavMoveClipDir = clip_dir; g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; @@ -8410,10 +8410,10 @@ static void ImGui::NavUpdate() g.NavMoveRequestFlags = ImGuiNavMoveFlags_None; if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { - if ((allowed_dir_flags & (1<RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); - ImVec2 delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs); + ImVec2 delta_scroll; + if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_ScrollToEdge) + { + float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; + delta_scroll.y = result->Window->Scroll.y - scroll_target; + SetScrollY(result->Window, scroll_target); + } + else + { + ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); + delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs); + } // Offset our result position so mouse position can be applied immediately after in NavUpdate() result->RectRel.TranslateX(-delta_scroll.x); @@ -8565,6 +8576,7 @@ static void ImGui::NavUpdateMoveResult() g.NavMoveFromClampedRefRect = false; } +// Handle PageUp/PageDown/Home/End keys static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) { ImGuiContext& g = *GImGui; @@ -8574,9 +8586,11 @@ static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) return 0.0f; ImGuiWindow* window = g.NavWindow; - bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); - bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); - if (page_up_held != page_down_held) // If either (not both) are pressed + const bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); + const bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); + const bool home_pressed = IsKeyPressed(g.IO.KeyMap[ImGuiKey_Home]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); + const bool end_pressed = IsKeyPressed(g.IO.KeyMap[ImGuiKey_End]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); + if (page_up_held != page_down_held || home_pressed != end_pressed) // If either (not both) are pressed { if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll) { @@ -8585,26 +8599,49 @@ static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); + else if (home_pressed) + SetScrollY(window, 0.0f); + else if (end_pressed) + SetScrollY(window, window->ScrollMax.y); } else { - const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; + ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); float nav_scoring_rect_offset_y = 0.0f; if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) { nav_scoring_rect_offset_y = -page_offset_y; - g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) + g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction (so we can always land on the last item) g.NavMoveClipDir = ImGuiDir_Up; g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; } else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) { nav_scoring_rect_offset_y = +page_offset_y; - g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) + g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so we can always land on the last item) g.NavMoveClipDir = ImGuiDir_Down; g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; } + else if (home_pressed) + { + // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y + // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdge flag, we don't scroll immediately to avoid scrolling happening before nav result. + // Preserve current horizontal position if we have any. + nav_rect_rel.Min.y = nav_rect_rel.Max.y = -window->Scroll.y; + if (nav_rect_rel.IsInverted()) + nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; + g.NavMoveDir = ImGuiDir_Down; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge; + } + else if (end_pressed) + { + nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ScrollMax.y + window->SizeFull.y - window->Scroll.y; + if (nav_rect_rel.IsInverted()) + nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; + g.NavMoveDir = ImGuiDir_Up; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge; + } return nav_scoring_rect_offset_y; } } @@ -8800,7 +8837,7 @@ static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) } // Overlay displayed when using CTRL+TAB. Called by EndFrame(). -void ImGui::NavUpdateWindowingList() +void ImGui::NavUpdateWindowingOverlay() { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindowingTarget != NULL); diff --git a/imgui_internal.h b/imgui_internal.h index f13aeedc7..9c87d2a14 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -497,7 +497,8 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_WrapX = 1 << 2, // On failed request, request from opposite side one line down (when NavDir==right) or one line up (when NavDir==left) ImGuiNavMoveFlags_WrapY = 1 << 3, // This is not super useful for provided for completeness ImGuiNavMoveFlags_AllowCurrentNavId = 1 << 4, // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place) - ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5 // Store alternate result in NavMoveResultLocalVisibleSet that only comprise elements that are already fully visible. + ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5, // Store alternate result in NavMoveResultLocalVisibleSet that only comprise elements that are already fully visible. + ImGuiNavMoveFlags_ScrollToEdge = 1 << 6 }; enum ImGuiNavForward @@ -960,7 +961,7 @@ struct ImGuiContext ImGuiNavMoveFlags NavMoveRequestFlags; ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request - ImGuiDir NavMoveClipDir; + ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow ImGuiNavMoveResult NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag)