From 2ebe08be40da88eada47a8c2a5ba6f6c93e91acb Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 14 Jan 2020 19:23:50 +0100 Subject: [PATCH] Focus, Nav: Merged bits from RangeSelect features to enable early manipulation of focus scope for styling purpose. FocusScopeId is tracked by nav scoring/request and stored in result. It's all rather WIP and we should reorganize the SetNavIDXXX functions fiasco at some point (soon?). Didn't separate FocusScope from SelectionScope for now, will re-investigate this later, this is the minimum commit to be able to do some styling. --- imgui.cpp | 51 ++++++++++++++++++++++++++++------------------- imgui_internal.h | 45 ++++++++++++++++++++--------------------- imgui_widgets.cpp | 8 ++++---- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7fcaea294..c7e7a3c60 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2752,19 +2752,21 @@ void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window) window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0; } -void ImGui::SetNavID(ImGuiID id, int nav_layer) +// FIXME-NAV: Refactor those functions into a single, more explicit one. +void ImGui::SetNavID(ImGuiID id, int nav_layer, int focus_scope_id) { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindow); IM_ASSERT(nav_layer == 0 || nav_layer == 1); g.NavId = id; + g.NavFocusScopeId = focus_scope_id; g.NavWindow->NavLastIds[nav_layer] = id; } -void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel) +void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, int focus_scope_id, const ImRect& rect_rel) { ImGuiContext& g = *GImGui; - SetNavID(id, nav_layer); + SetNavID(id, nav_layer, focus_scope_id); g.NavWindow->NavRectRel[nav_layer] = rect_rel; g.NavMousePosDirty = true; g.NavDisableHighlight = false; @@ -2809,13 +2811,15 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) ImGuiContext& g = *GImGui; IM_ASSERT(id != 0); - // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it. + // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and window->DC.NavFocusScopeIdCurrent are valid. + // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; if (g.NavWindow != window) g.NavInitRequest = false; - g.NavId = id; g.NavWindow = window; + g.NavId = id; g.NavLayer = nav_layer; + g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; 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); @@ -5845,6 +5849,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main); window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; window->DC.NavLayerActiveMaskNext = 0x00; + window->DC.NavFocusScopeIdCurrent = 0; window->DC.NavHideHighlightOneFrame = false; window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f); @@ -6047,6 +6052,7 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.NavMousePosDirty = true; g.NavInitRequest = false; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavFocusScopeId = 0; g.NavIdIsAlive = false; g.NavLayer = ImGuiNavLayer_Main; //IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL); @@ -8039,21 +8045,22 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con #endif if (new_best) { - result->ID = id; - result->SelectScopeId = g.MultiSelectScopeId; result->Window = window; + result->ID = id; + result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; result->RectRel = nav_bb_rel; } + // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. const float VISIBLE_RATIO = 0.70f; if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) { result = &g.NavMoveResultLocalVisibleSet; - result->ID = id; - result->SelectScopeId = g.MultiSelectScopeId; result->Window = window; + result->ID = id; + result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; result->RectRel = nav_bb_rel; } } @@ -8063,6 +8070,7 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con { g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. g.NavLayer = window->DC.NavLayerCurrent; + g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavIdIsAlive = true; g.NavIdTabCounter = window->DC.FocusCounterTabStop; window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) @@ -8153,10 +8161,11 @@ static void NavRestoreLayer(ImGuiNavLayer layer) g.NavLayer = layer; if (layer == 0) g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow); - if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) - ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); + ImGuiWindow* window = g.NavWindow; + if (layer == 0 && window->NavLastIds[0] != 0) + ImGui::SetNavIDWithRectRel(window->NavLastIds[0], layer, 0, window->NavRectRel[0]); else - ImGui::NavInitWindow(g.NavWindow, true); + ImGui::NavInitWindow(window, true); } static inline void ImGui::NavUpdateAnyRequestFlag() @@ -8179,7 +8188,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) //IMGUI_DEBUG_LOG("[Nav] NavInitWindow() init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer); if (init_for_nav) { - SetNavID(0, g.NavLayer); + SetNavID(0, g.NavLayer, 0); g.NavInitRequest = true; g.NavInitRequestFromMove = false; g.NavInitResultId = 0; @@ -8189,6 +8198,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) else { g.NavId = window->NavLastIds[0]; + g.NavFocusScopeId = 0; } } @@ -8295,9 +8305,9 @@ static void ImGui::NavUpdate() // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) //IMGUI_DEBUG_LOG("[Nav] Apply NavInitRequest result: 0x%08X Layer %d in \"%s\"\n", g.NavInitResultId, g.NavLayer, g.NavWindow->Name); if (g.NavInitRequestFromMove) - SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); + SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); else - SetNavID(g.NavInitResultId, g.NavLayer); + SetNavID(g.NavInitResultId, g.NavLayer, 0); g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; } g.NavInitRequest = false; @@ -8364,7 +8374,7 @@ static void ImGui::NavUpdate() ImGuiWindow* parent_window = g.NavWindow->ParentWindow; IM_ASSERT(child_window->ChildId != 0); FocusWindow(parent_window); - SetNavID(child_window->ChildId, 0); + SetNavID(child_window->ChildId, 0, 0); // Reassigning with same value, we're being explicit here. g.NavIdIsAlive = false; // -V1048 if (g.NavDisableMouseHover) @@ -8386,7 +8396,7 @@ static void ImGui::NavUpdate() // 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.NavId = g.NavFocusScopeId = 0; } } @@ -8506,7 +8516,7 @@ static void ImGui::NavUpdate() 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.NavId = g.NavFocusScopeId = 0; } g.NavMoveFromClampedRefRect = false; } @@ -8586,9 +8596,10 @@ static void ImGui::NavUpdateMoveResult() { // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId) g.NavJustMovedToId = result->ID; - g.NavJustMovedToMultiSelectScopeId = result->SelectScopeId; + g.NavJustMovedToFocusScopeId = result->FocusScopeId; + } - SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel); + SetNavIDWithRectRel(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); g.NavMoveFromClampedRefRect = false; } diff --git a/imgui_internal.h b/imgui_internal.h index d3cfabe7d..894519b28 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -877,16 +877,16 @@ struct ImDrawDataBuilder struct ImGuiNavMoveResult { - ImGuiID ID; // Best candidate - ImGuiID SelectScopeId;// Best candidate window current selectable group ID - ImGuiWindow* Window; // Best candidate window - float DistBox; // Best candidate box distance to current NavId - float DistCenter; // Best candidate center distance to current NavId - float DistAxial; - ImRect RectRel; // Best candidate bounding box in window relative space + ImGuiWindow* Window; // Best candidate window + ImGuiID ID; // Best candidate ID + ImGuiID FocusScopeId; // Best candidate focus scope ID + float DistBox; // Best candidate box distance to current NavId + float DistCenter; // Best candidate center distance to current NavId + float DistAxial; + ImRect RectRel; // Best candidate bounding box in window relative space ImGuiNavMoveResult() { Clear(); } - void Clear() { ID = SelectScopeId = 0; Window = NULL; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } + void Clear() { Window = NULL; ID = FocusScopeId = 0; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } }; enum ImGuiNextWindowDataFlags_ @@ -920,7 +920,7 @@ struct ImGuiNextWindowData ImVec2 MenuBarOffsetMinVal; // *Always on* This is not exposed publicly, so we don't clear it. ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } - inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } // Also cleared by ItemAdd() + inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } }; enum ImGuiNextItemDataFlags_ @@ -933,12 +933,13 @@ enum ImGuiNextItemDataFlags_ struct ImGuiNextItemData { ImGuiNextItemDataFlags Flags; - float Width; // Set by SetNextItemWidth(). - bool OpenVal; // Set by SetNextItemOpen() function. + float Width; // Set by SetNextItemWidth() + ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't use Flags for this because they are cleared too early. This is mostly used for debugging) ImGuiCond OpenCond; + bool OpenVal; // Set by SetNextItemOpen() ImGuiNextItemData() { memset(this, 0, sizeof(*this)); } - inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; } + inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; } // Also cleared manually by ItemAdd()! }; //----------------------------------------------------------------------------- @@ -1038,13 +1039,15 @@ struct ImGuiContext // Gamepad/keyboard Navigation ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' ImGuiID NavId; // Focused item for navigation + ImGuiID NavFocusScopeId; ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem() ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 ImGuiID NavJustTabbedId; // Just tabbed to this id. ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). - ImGuiID NavJustMovedToMultiSelectScopeId; // Just navigated to this select scope id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS WILL ONLY BE None or NavGamepad or NavKeyboard. ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring. @@ -1085,10 +1088,6 @@ struct ImGuiContext int FocusRequestNextCounterTabStop; // " bool FocusTabPressed; // - // Range-Select/Multi-Select - // [This is unused in this branch, but left here to facilitate merging/syncing multiple branches] - ImGuiID MultiSelectScopeId; - // Render ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user ImDrawDataBuilder DrawDataBuilder; @@ -1218,8 +1217,8 @@ struct ImGuiContext LastActiveIdTimer = 0.0f; NavWindow = NULL; - NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; - NavJustTabbedId = NavJustMovedToId = NavJustMovedToMultiSelectScopeId = NavNextActivateId = 0; + NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavJustTabbedId = NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; NavInputSource = ImGuiInputSource_None; NavScoringRectScreen = ImRect(); NavScoringCount = 0; @@ -1247,8 +1246,6 @@ struct ImGuiContext FocusRequestNextCounterRegular = FocusRequestNextCounterTabStop = INT_MAX; FocusTabPressed = false; - MultiSelectScopeId = 0; - DimBgRatio = 0.0f; BackgroundDrawList._OwnerName = "##Background"; // Give it a name for debugging ForegroundDrawList._OwnerName = "##Foreground"; // Give it a name for debugging @@ -1334,6 +1331,7 @@ struct IMGUI_API ImGuiWindowTempData 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) + ImGuiID NavFocusScopeIdCurrent; // Current focus scope ID while appending bool NavHideHighlightOneFrame; bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) @@ -1378,6 +1376,7 @@ struct IMGUI_API ImGuiWindowTempData NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; NavLayerCurrent = ImGuiNavLayer_Main; NavLayerCurrentMask = (1 << ImGuiNavLayer_Main); + NavFocusScopeIdCurrent = 0; NavHideHighlightOneFrame = false; NavHasScroll = false; @@ -1701,8 +1700,8 @@ namespace ImGui IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate); 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 void SetNavID(ImGuiID id, int nav_layer); - IMGUI_API void SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel); + IMGUI_API void SetNavID(ImGuiID id, int nav_layer, int focus_scope_id); + IMGUI_API void SetNavIDWithRectRel(ImGuiID id, int nav_layer, int focus_scope_id, const ImRect& rect_rel); // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index a6d1dfd21..95483e2f8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5312,9 +5312,9 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l bool hovered, held; bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + bool toggled = false; if (!is_leaf) { - bool toggled = false; if (pressed) { if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id)) @@ -5444,7 +5444,7 @@ void ImGui::TreePop() if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask)) { - SetNavID(window->IDStack.back(), g.NavLayer); + SetNavID(window->IDStack.back(), g.NavLayer, 0); NavMoveRequestCancel(); } window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1; @@ -5600,7 +5600,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { g.NavDisableHighlight = true; - SetNavID(id, window->DC.NavLayerCurrent); + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent); } } if (pressed) @@ -6077,7 +6077,7 @@ void ImGui::EndMenuBar() const ImGuiNavLayer layer = ImGuiNavLayer_Menu; IM_ASSERT(window->DC.NavLayerActiveMaskNext & (1 << layer)); // Sanity check FocusWindow(window); - SetNavIDWithRectRel(window->NavLastIds[layer], layer, window->NavRectRel[layer]); + SetNavIDWithRectRel(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); g.NavLayer = layer; g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;