Nav, Scrolling: Added support for Home/End key. (#787)

This commit is contained in:
omar 2019-09-09 18:09:41 +09:00
parent 3cf519c9cb
commit b05f6f6f50
4 changed files with 63 additions and 23 deletions

View File

@ -34,6 +34,7 @@ HOW TO UPDATE?
----------------------------------------------------------------------- -----------------------------------------------------------------------
Other Changes: 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) - 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 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). when enabled will have small overlap glitches with (style.Alpha < 1.0).

View File

@ -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: 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? - 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: 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: 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: 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: 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: 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: 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. - 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: 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: 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: 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: 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: 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) - focus: unable to use SetKeyboardFocusHere() on clipped widgets. (#787)

View File

@ -1078,7 +1078,7 @@ static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2&
// Navigation // Navigation
static void NavUpdate(); static void NavUpdate();
static void NavUpdateWindowing(); static void NavUpdateWindowing();
static void NavUpdateWindowingList(); static void NavUpdateWindowingOverlay();
static void NavUpdateMoveResult(); static void NavUpdateMoveResult();
static float NavUpdatePageUpPageDown(int allowed_dir_flags); static float NavUpdatePageUpPageDown(int allowed_dir_flags);
static inline void NavUpdateAnyRequestFlag(); static inline void NavUpdateAnyRequestFlag();
@ -4184,8 +4184,8 @@ void ImGui::EndFrame()
End(); End();
// Show CTRL+TAB list window // Show CTRL+TAB list window
if (g.NavWindowingTarget) if (g.NavWindowingTarget != NULL)
NavUpdateWindowingList(); NavUpdateWindowingOverlay();
// Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
if (g.DragDropActive) if (g.DragDropActive)
@ -8074,7 +8074,7 @@ void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const Im
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None);
ImGui::NavMoveRequestCancel(); NavMoveRequestCancel();
g.NavMoveDir = move_dir; g.NavMoveDir = move_dir;
g.NavMoveClipDir = clip_dir; g.NavMoveClipDir = clip_dir;
g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
@ -8410,10 +8410,10 @@ static void ImGui::NavUpdate()
g.NavMoveRequestFlags = ImGuiNavMoveFlags_None; g.NavMoveRequestFlags = ImGuiNavMoveFlags_None;
if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
{ {
if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left; if ((allowed_dir_flags & (1 << ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) { g.NavMoveDir = ImGuiDir_Left; }
if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right; if ((allowed_dir_flags & (1 << ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) { g.NavMoveDir = ImGuiDir_Right; }
if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up; if ((allowed_dir_flags & (1 << ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) { g.NavMoveDir = ImGuiDir_Up; }
if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down; if ((allowed_dir_flags & (1 << ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) { g.NavMoveDir = ImGuiDir_Down; }
} }
g.NavMoveClipDir = g.NavMoveDir; g.NavMoveClipDir = g.NavMoveDir;
} }
@ -8426,7 +8426,8 @@ static void ImGui::NavUpdate()
g.NavMoveRequestForward = ImGuiNavForward_ForwardActive; g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
} }
// Update PageUp/PageDown scroll // Update PageUp/PageDown/Home/End scroll
// FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag?
float nav_scoring_rect_offset_y = 0.0f; float nav_scoring_rect_offset_y = 0.0f;
if (nav_keyboard_active) if (nav_keyboard_active)
nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(allowed_dir_flags); nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(allowed_dir_flags);
@ -8544,9 +8545,19 @@ static void ImGui::NavUpdateMoveResult()
// Scroll to keep newly navigated item fully into view. // Scroll to keep newly navigated item fully into view.
if (g.NavLayer == 0) if (g.NavLayer == 0)
{
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); ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
ImVec2 delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs); delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs);
}
// Offset our result position so mouse position can be applied immediately after in NavUpdate() // Offset our result position so mouse position can be applied immediately after in NavUpdate()
result->RectRel.TranslateX(-delta_scroll.x); result->RectRel.TranslateX(-delta_scroll.x);
@ -8565,6 +8576,7 @@ static void ImGui::NavUpdateMoveResult()
g.NavMoveFromClampedRefRect = false; g.NavMoveFromClampedRefRect = false;
} }
// Handle PageUp/PageDown/Home/End keys
static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
@ -8574,9 +8586,11 @@ static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags)
return 0.0f; return 0.0f;
ImGuiWindow* window = g.NavWindow; ImGuiWindow* window = g.NavWindow;
bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); const 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)); const 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 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) 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()); SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight());
else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); 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 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()); 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; float nav_scoring_rect_offset_y = 0.0f;
if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
{ {
nav_scoring_rect_offset_y = -page_offset_y; 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.NavMoveClipDir = ImGuiDir_Up;
g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
} }
else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
{ {
nav_scoring_rect_offset_y = +page_offset_y; 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.NavMoveClipDir = ImGuiDir_Down;
g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; 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; 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(). // Overlay displayed when using CTRL+TAB. Called by EndFrame().
void ImGui::NavUpdateWindowingList() void ImGui::NavUpdateWindowingOverlay()
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
IM_ASSERT(g.NavWindowingTarget != NULL); IM_ASSERT(g.NavWindowingTarget != NULL);

View File

@ -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_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_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_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 enum ImGuiNavForward
@ -960,7 +961,7 @@ struct ImGuiContext
ImGuiNavMoveFlags NavMoveRequestFlags; ImGuiNavMoveFlags NavMoveRequestFlags;
ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) 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 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 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 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) ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag)