From 04157da2919b657aa77a6b07863ae2007ceefe5f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 30 Jul 2016 17:18:34 +0200 Subject: [PATCH] Nav: first committed pass for manual moving and manual scrolling (after a bunch of attempts) (#323) --- imgui.cpp | 92 ++++++++++++++++++++++++++++++++++++++++-------- imgui.h | 5 +++ imgui_demo.cpp | 2 +- imgui_internal.h | 3 +- 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8bee3569..45b27881 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -662,6 +662,7 @@ static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); static void SetCurrentFont(ImFont* font); static void SetCurrentWindow(ImGuiWindow* window); +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiSetCond cond); static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiSetCond cond); @@ -2364,8 +2365,7 @@ static void NavUpdate() IM_ASSERT(g.NavWindow); // Scroll to keep newly navigated item fully into view - ImRect window_rect_rel(g.NavWindow->InnerRect.Min - g.NavWindow->Pos, g.NavWindow->InnerRect.Max - g.NavWindow->Pos); - window_rect_rel.Expand(1.0f); + ImRect window_rect_rel(g.NavWindow->InnerRect.Min - g.NavWindow->Pos - ImVec2(1,1), g.NavWindow->InnerRect.Max - g.NavWindow->Pos + ImVec2(1,1)); //g.OverlayDrawList.AddRect(g.NavWindow->Pos + window_rect_rel.Min, g.NavWindow->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG] if (g.NavLayer == 0 && !window_rect_rel.Contains(g.NavMoveResultRectRel)) { @@ -2394,6 +2394,7 @@ static void NavUpdate() // Apply result from previous navigation directional move request ImGui::SetActiveID(0); SetNavIdMoveMouse(g.NavMoveResultId, g.NavMoveResultRectRel); + g.NavMoveFromClampedRefRect = false; } // Navigation windowing mode (change focus, move/resize window) @@ -2433,6 +2434,19 @@ static void NavUpdate() g.NavWindowingDisplayAlpha = 1.0f; } + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + const ImVec2 move_delta = ImGui::NavGetMovingDir(1); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float move_speed = ImFloor(600 * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingTarget->PosFloat += move_delta * move_speed; + if (!(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoSavedSettings)) + MarkSettingsDirty(); + } + } + if (!IsKeyDownMap(ImGuiKey_NavMenu)) { // Apply actual focus only when releasing the NavMenu button (until then the window was merely rendered front-most) @@ -2537,16 +2551,44 @@ static void NavUpdate() g.NavWindow = g.FocusedWindow; } - // Fallback manual-scroll with NavUp/NavDown when window has no navigable item - if (g.FocusedWindow && !g.FocusedWindow->DC.NavLayerActiveFlags && g.FocusedWindow->DC.NavHasScroll && !(g.FocusedWindow->Flags & ImGuiWindowFlags_NoNav) && g.NavMoveRequest && (g.NavMoveDir == ImGuiNavDir_Up || g.NavMoveDir == ImGuiNavDir_Down)) + // Scrolling + if (g.FocusedWindow && !(g.FocusedWindow->Flags & ImGuiWindowFlags_NoNav)) { - float scroll_speed = ImFloor(g.FocusedWindow->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. - SetWindowScrollY(g.FocusedWindow, ImFloor(g.FocusedWindow->Scroll.y + ((g.NavMoveDir == ImGuiNavDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + // Fallback manual-scroll with NavUp/NavDown when window has no navigable item + const float scroll_speed = ImFloor(g.FocusedWindow->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (!g.FocusedWindow->DC.NavLayerActiveFlags && g.FocusedWindow->DC.NavHasScroll && g.NavMoveRequest && (g.NavMoveDir == ImGuiNavDir_Up || g.NavMoveDir == ImGuiNavDir_Down)) + SetWindowScrollY(g.FocusedWindow, ImFloor(g.FocusedWindow->Scroll.y + ((g.NavMoveDir == ImGuiNavDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + + // Manual scroll with NavScrollXXX keys + ImVec2 scroll_dir = ImGui::NavGetMovingDir(1, 1.0f/10.0f, 10.0f); + if (scroll_dir.x != 0.0f && g.NavWindow->ScrollbarX) + { + SetWindowScrollX(g.FocusedWindow, ImFloor(g.FocusedWindow->Scroll.x + scroll_dir.x * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + if (scroll_dir.y != 0.0f) + { + SetWindowScrollY(g.FocusedWindow, ImFloor(g.FocusedWindow->Scroll.y + scroll_dir.y * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } } // Reset search g.NavMoveResultId = 0; g.NavMoveResultDistAxial = g.NavMoveResultDistBox = g.NavMoveResultDistCenter = FLT_MAX; + if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) + { + // When we have manually scrolled and NavId is out of bounds, we clamp its bounding box (used for search) to the visible area to restart navigation within visible items + ImRect window_rect_rel(g.NavWindow->InnerRect.Min - g.NavWindow->Pos - ImVec2(1,1), g.NavWindow->InnerRect.Max - g.NavWindow->Pos + ImVec2(1,1)); + if (!window_rect_rel.Contains(g.NavRefRectRel)) + { + float pad = g.NavWindow->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intend of starting navigation from first fully visible item + window_rect_rel.Clip(g.NavRefRectRel); + g.NavId = 0; + } + g.NavMoveFromClampedRefRect = false; + } // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + g.NavRefRectRel.Min, g.NavWindow->Pos + g.NavRefRectRel.Max) : ImRect(); @@ -4737,7 +4779,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us if (g.NavWindowingTarget == window) { const float resize_speed = ImFloor(600 * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); - nav_resize_delta = NavGetMovingDir() * resize_speed; + nav_resize_delta = NavGetMovingDir(0) * resize_speed; held |= (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f); // For coloring } @@ -5469,6 +5511,13 @@ ImVec2 ImGui::GetWindowPos() return window->Pos; } +static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x) +{ + window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.x = new_scroll_x; + window->DC.CursorMaxPos.x -= window->Scroll.x; +} + static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) { window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. @@ -7043,13 +7092,28 @@ int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) return precision; } -ImVec2 ImGui::NavGetMovingDir() +ImVec2 ImGui::NavGetMovingDir(int stick_no, float slow_factor, float fast_factor) { + IM_ASSERT(stick_no >= 0 && stick_no < 2); ImVec2 dir(0.0f, 0.0f); - if (IsKeyDownMap(ImGuiKey_NavLeft)) dir.x -= 1.0f; - if (IsKeyDownMap(ImGuiKey_NavRight)) dir.x += 1.0f; - if (IsKeyDownMap(ImGuiKey_NavUp)) dir.y -= 1.0f; - if (IsKeyDownMap(ImGuiKey_NavDown)) dir.y += 1.0f; + if (stick_no == 0) + { + if (IsKeyDownMap(ImGuiKey_NavLeft)) dir.x -= 1.0f; + if (IsKeyDownMap(ImGuiKey_NavRight)) dir.x += 1.0f; + if (IsKeyDownMap(ImGuiKey_NavUp)) dir.y -= 1.0f; + if (IsKeyDownMap(ImGuiKey_NavDown)) dir.y += 1.0f; + } + if (stick_no == 1) + { + if (IsKeyDownMap(ImGuiKey_NavScrollLeft)) dir.x -= 1.0f; + if (IsKeyDownMap(ImGuiKey_NavScrollRight)) dir.x += 1.0f; + if (IsKeyDownMap(ImGuiKey_NavScrollUp)) dir.y -= 1.0f; + if (IsKeyDownMap(ImGuiKey_NavScrollDown)) dir.y += 1.0f; + } + if (slow_factor != 0.0f && IsKeyDownMap(ImGuiKey_NavTweakSlower)) + dir *= slow_factor; + if (fast_factor != 0.0f && IsKeyDownMap(ImGuiKey_NavTweakFaster)) + dir *= fast_factor; return dir; } @@ -10458,7 +10522,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %-4d %s vtx, tex = %p, clip_rect = (%.0f,%.0f)..(%.0f,%.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); - if (show_clip_rects && ImGui::IsItemHovered()) + if (show_clip_rects && (ImGui::IsItemHovered() || ImGui::IsItemFocused())) { ImRect clip_rect = pcmd->ClipRect; ImRect vtxs_rect; @@ -10482,7 +10546,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) buf_p += sprintf(buf_p, "%s %04d { pos = (%8.2f,%8.2f), uv = (%.6f,%.6f), col = %08X }\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); } ImGui::Selectable(buf, false); - if (ImGui::IsItemHovered()) + if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f, false); // Add triangle without AA, more readable for large-thin triangle } ImGui::TreePop(); diff --git a/imgui.h b/imgui.h index a0e6ea8c..19650ec2 100644 --- a/imgui.h +++ b/imgui.h @@ -604,8 +604,13 @@ enum ImGuiKey_ ImGuiKey_NavRight, // e.g. Right arrow, D-Pad right ImGuiKey_NavUp, // e.g. Up arrow, D-Pad up ImGuiKey_NavDown, // e.g. Down arrow, D-Pad down + ImGuiKey_NavScrollLeft, // e.g. Analog left + ImGuiKey_NavScrollRight,// e.g. Analog right + ImGuiKey_NavScrollUp, // e.g. Analog up + ImGuiKey_NavScrollDown, // e.g. Analog down ImGuiKey_NavTweakFaster,// e.g. Shift key, R-trigger ImGuiKey_NavTweakSlower,// e.g. Alt key, L-trigger + ImGuiKey_NavLast_, ImGuiKey_COUNT }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c17537a0..db58ac08 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1904,7 +1904,7 @@ static void ShowExampleAppConstrainedResize(bool* p_open) static void ShowExampleAppFixedOverlay(bool* p_open) { ImGui::SetNextWindowPos(ImVec2(10,10)); - if (!ImGui::Begin("Example: Fixed Overlay", p_open, ImVec2(0,0), 0.3f, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) + if (!ImGui::Begin("Example: Fixed Overlay", p_open, ImVec2(0,0), 0.3f, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoNav)) { ImGui::End(); return; diff --git a/imgui_internal.h b/imgui_internal.h index b78494c5..1f08a176 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -423,6 +423,7 @@ struct ImGuiContext ImRect NavInitDefaultResultRectRel; bool NavInitDefaultResultExplicit; // Whether the result was explicitly requested with SetItemDefaultFocus() bool NavMoveRequest; // Move request for this frame + bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items ImGuiNavDir NavMoveDir; // West/East/North/South ImGuiID NavMoveResultId; // Best move request candidate float NavMoveResultDistBox; // Best move request candidate box distance to current NavId @@ -784,7 +785,7 @@ namespace ImGui IMGUI_API void OpenPopupEx(const char* str_id, bool reopen_existing); IMGUI_API ImVec2 NavGetTweakDelta(); - IMGUI_API ImVec2 NavGetMovingDir(); + IMGUI_API ImVec2 NavGetMovingDir(int stick_no, float slow_factor = 0.0f, float fast_factor = 0.0f); inline IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul) { ImVec4 c = GImGui->Style.Colors[idx]; c.w *= GImGui->Style.Alpha * alpha_mul; return ImGui::ColorConvertFloat4ToU32(c); } inline IMGUI_API ImU32 GetColorU32(const ImVec4& col) { ImVec4 c = col; c.w *= GImGui->Style.Alpha; return ImGui::ColorConvertFloat4ToU32(c); }