diff --git a/backends/imgui_impl_allegro5.cpp b/backends/imgui_impl_allegro5.cpp index 2b784424..93d84f1b 100644 --- a/backends/imgui_impl_allegro5.cpp +++ b/backends/imgui_impl_allegro5.cpp @@ -179,7 +179,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) // Apply scissor/clipping rectangle, Draw ALLEGRO_BITMAP* texture = (ALLEGRO_BITMAP*)pcmd->GetTexID(); - al_set_clipping_rectangle(clip_min.x, clip_min.y, clip_max.x, clip_max.y); + al_set_clipping_rectangle(clip_min.x, clip_min.y, clip_max.x - clip_min.x, clip_max.y - clip_min.y); al_draw_prim(&vertices[0], bd->VertexDecl, texture, idx_offset, idx_offset + pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST); } idx_offset += pcmd->ElemCount; diff --git a/backends/imgui_impl_opengl3_loader.h b/backends/imgui_impl_opengl3_loader.h index e70e7a32..9313deda 100644 --- a/backends/imgui_impl_opengl3_loader.h +++ b/backends/imgui_impl_opengl3_loader.h @@ -1,3 +1,22 @@ +//----------------------------------------------------------------------------- +// About imgui_impl_opengl3_loader.h: +// +// We embed our own OpenGL loader to not require user to provide their own or to have to use ours, +// which proved to be endless problems for users. +// Our loader is custom-generated, based on gl3w but automatically filtered to only include +// enums/functions that we use in our imgui_impl_opengl3.cpp source file in order to be small. +// +// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY imgui_impl_opengl3.cpp ONLY. +// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE. +// +// Regenerate with: +// python gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt +// +// More info: +// https://github.com/dearimgui/gl3w_stripped +// https://github.com/ocornut/imgui/issues/4445 +//----------------------------------------------------------------------------- + /* * This file was generated with gl3w_gen.py, part of imgl3w * (hosted at https://github.com/dearimgui/gl3w_stripped) @@ -26,14 +45,11 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -// We embed our own OpenGL loader to not require user to provide their own or to have to use ours, which proved to be endless problems for users. -// Our loader is custom-generated, based on gl3w but automatically filtered to only include enums/functions that we use in this source file. -// Regenerate with: python gl3w_gen.py --imgui-dir /path/to/imgui/ -// see https://github.com/dearimgui/gl3w_stripped for more info. #ifndef __gl3w_h_ #define __gl3w_h_ // Adapted from KHR/khrplatform.h to avoid including entire file. +#ifndef __khrplatform_h_ typedef float khronos_float_t; typedef signed char khronos_int8_t; typedef unsigned char khronos_uint8_t; @@ -58,6 +74,7 @@ typedef uint64_t khronos_uint64_t; typedef signed long long khronos_int64_t; typedef unsigned long long khronos_uint64_t; #endif +#endif // __khrplatform_h_ #ifndef __gl_glcorearb_h_ #define __gl_glcorearb_h_ 1 @@ -162,6 +179,7 @@ typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap); typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap); typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void); typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data); typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name); typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap); @@ -177,6 +195,7 @@ GLAPI void APIENTRY glDisable (GLenum cap); GLAPI void APIENTRY glEnable (GLenum cap); GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param); GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GLAPI GLenum APIENTRY glGetError (void); GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data); GLAPI const GLubyte *APIENTRY glGetString (GLenum name); GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap); @@ -416,7 +435,7 @@ GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc); /* gl3w internal state */ union GL3WProcs { - GL3WglProc ptr[52]; + GL3WglProc ptr[53]; struct { PFNGLACTIVETEXTUREPROC ActiveTexture; PFNGLATTACHSHADERPROC AttachShader; @@ -448,6 +467,7 @@ union GL3WProcs { PFNGLGENTEXTURESPROC GenTextures; PFNGLGENVERTEXARRAYSPROC GenVertexArrays; PFNGLGETATTRIBLOCATIONPROC GetAttribLocation; + PFNGLGETERRORPROC GetError; PFNGLGETINTEGERVPROC GetIntegerv; PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog; PFNGLGETPROGRAMIVPROC GetProgramiv; @@ -506,6 +526,7 @@ GL3W_API extern union GL3WProcs imgl3wProcs; #define glGenTextures imgl3wProcs.gl.GenTextures #define glGenVertexArrays imgl3wProcs.gl.GenVertexArrays #define glGetAttribLocation imgl3wProcs.gl.GetAttribLocation +#define glGetError imgl3wProcs.gl.GetError #define glGetIntegerv imgl3wProcs.gl.GetIntegerv #define glGetProgramInfoLog imgl3wProcs.gl.GetProgramInfoLog #define glGetProgramiv imgl3wProcs.gl.GetProgramiv @@ -691,6 +712,7 @@ static const char *proc_names[] = { "glGenTextures", "glGenVertexArrays", "glGetAttribLocation", + "glGetError", "glGetIntegerv", "glGetProgramInfoLog", "glGetProgramiv", diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ca7b1007..69525b86 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -42,18 +42,25 @@ Breaking Changes: Other Changes: -- Windows: fixed background order of overlapping childs submitted sequentially. (#4493) +- Windows: Fixed background order of overlapping childs submitted sequentially. (#4493) - InputTextMultiline: Fixed label size not being included into window contents rect unless the whole widget is clipped. +- InputText: Allow cancelling/validating input with gamepad nav events to facilitate undoing + an accidental press on NavInput (Triangle button on PS4/PS5) without a wired keyboard. (#2321) - TextUnformatted: Accept null ranges including (NULL,NULL) without asserting, in order to conform to common idioms (e.g. passing .data(), .data() + .size() from a null string). (#3615) +- Nav: Fixed toggling menu layer with Alt or exiting menu layer with Esc not moving mouse when + the NavEnableSetMousePos config flag is set. +- Menus: adjust closing logic to accomodate for varying font size and dpi. +- Drag and Drop: Fixed using BeginDragDropSource() inside a BeginChild() that returned false. (#4515) +- PlotHistogram: Fixed zero-line position when manually specifying min<0 and max>0. (#4349) [@filippocrocchini] - IO: Added 'io.WantCaptureMouseUnlessPopupClose' alternative to `io.WantCaptureMouse'. (#4480) This allows apps to receive the click on void when that click is used to close popup (by default, clicking on a void when a popup is open will close the popup but not release io.WantCaptureMouse). - Fonts: imgui_freetype: Fixed crash when FT_Render_Glyph() fails to render a glyph and returns NULL (which apparently happens with Freetype 2.11). (#4394, #4145?). - Fonts: Fixed ImFontAtlas::ClearInputData() marking atlas as not built. (#4455, #3487) -- Backends: OpenGL3: Fixed our new GL loader conflicting with user using GL3W. (#4445) +- Backends: OpenGL3: Fixed our custom GL loader conflicting with user using GL3W. (#4445) [@rokups] - Backends: WebGPU: Fixed for latest specs. (#4472) [@Kangz] - Backends: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted via a direct unclipped PushClipRect() call. (#4464) diff --git a/docs/TODO.txt b/docs/TODO.txt index bc6151f4..b38bac9d 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -362,9 +362,10 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - nav: patterns to make it possible for arrows key to update selection (see JustMovedTo in range_select branch) - 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: NavFlattened: init requests don't work properly on flattened siblings. + - nav: NavFlattened: pageup/pagedown/home/end don't work properly on flattened siblings. - 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: init request doesn't select items that are part of a NavFlattened child - nav: NavFlattened: cannot access menu-bar of a flattened child window with Alt/menu key (not a very common use case..). - nav: simulate right-click or context activation? (SHIFT+F10) - nav/tabbing: refactor old tabbing system and turn into navigation, should pass through all widgets (in submission order?). diff --git a/imgui.cpp b/imgui.cpp index 3728c37b..bb5f7d85 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -923,15 +923,14 @@ namespace ImGui static void NavUpdate(); static void NavUpdateWindowing(); static void NavUpdateWindowingOverlay(); -static void NavUpdateInitResult(); static void NavUpdateCancelRequest(); static void NavUpdateCreateMoveRequest(); static float NavUpdatePageUpPageDown(); static inline void NavUpdateAnyRequestFlag(); static void NavEndFrame(); -static bool NavScoreItem(ImGuiNavItemData* result, ImRect cand); -static void NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel); -static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id); +static bool NavScoreItem(ImGuiNavItemData* result); +static void NavApplyItemToResult(ImGuiNavItemData* result); +static void NavProcessItem(); static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); @@ -2294,21 +2293,21 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items return; } - // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect + // We create the union of the ClipRect and the scoring rect which at worst should be 1 page away from ClipRect ImRect unclipped_rect = window->ClipRect; - if (g.NavMoveRequest) + if (g.NavMoveScoringItems) unclipped_rect.Add(g.NavScoringRect); if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId) - unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max)); + unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max)); // Could store and use NavJustMovedToRectRel const ImVec2 pos = window->DC.CursorPos; int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); int end = (int)((unclipped_rect.Max.y - pos.y) / items_height); // When performing a navigation request, ensure we have one item extra in the direction we are moving to - if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up) + if (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Up) start--; - if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down) + if (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Down) end++; start = ImClamp(start, 0, items_count); @@ -3154,7 +3153,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) if (id) { g.ActiveIdIsAlive = id; - g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; + g.ActiveIdSource = (g.NavActivateId == id || g.NavActivateInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; } // Clear declaration of inputs claimed by the widget @@ -3361,7 +3360,8 @@ void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemS // Called by ItemAdd() // Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out. -void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id) +// [WIP] This will eventually be refactored and moved into NavProcessItem() +void ImGui::ItemInputable(ImGuiWindow* window, ImGuiID id) { ImGuiContext& g = *GImGui; IM_ASSERT(id != 0 && id == g.LastItemData.ID); @@ -3395,7 +3395,7 @@ void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id) } if (is_tab_stop && window->DC.FocusCounterTabStop == g.TabFocusRequestCurrCounterTabStop) { - g.NavJustTabbedId = id; + g.NavJustTabbedId = id; // FIXME-NAV: aim to eventually set in NavUpdate() once we finish the refactor g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing; return; } @@ -3821,13 +3821,15 @@ static void ImGui::UpdateMouseInputs() // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) if (IsMousePosValid(&g.IO.MousePos)) - g.IO.MousePos = g.LastValidMousePos = ImFloor(g.IO.MousePos); + g.IO.MousePos = g.MouseLastValidPos = ImFloor(g.IO.MousePos); // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; else g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + + // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) g.NavDisableMouseHover = false; @@ -7066,7 +7068,7 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.NavFocusScopeId = 0; g.NavIdIsAlive = false; g.NavLayer = ImGuiNavLayer_Main; - g.NavInitRequest = g.NavMoveRequest = false; + g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; NavUpdateAnyRequestFlag(); //IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL); } @@ -7656,6 +7658,7 @@ void ImGui::ActivateItem(ImGuiID id) { ImGuiContext& g = *GImGui; g.NavNextActivateId = id; + g.NavNextActivateFlags = ImGuiActivateFlags_None; } void ImGui::PushFocusScope(ImGuiID id) @@ -8124,15 +8127,17 @@ void ImGui::ItemSize(const ImRect& bb, float text_baseline_y) // Declare item bounding box for clipping and interaction. // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. -bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemAddFlags flags) +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; // Set item data + // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set) g.LastItemData.ID = id; g.LastItemData.Rect = bb; - g.LastItemData.InFlags = g.CurrentItemFlags; + g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; + g.LastItemData.InFlags = g.CurrentItemFlags | extra_flags; g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; // Directional navigation processing @@ -8151,7 +8156,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu if (g.NavId == id || g.NavAnyRequest) if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) - NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); + NavProcessItem(); // [DEBUG] Item Picker tool, when enabling the "extended" version we perform the check in ItemAdd() #ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX @@ -8175,10 +8180,10 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu return false; //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] - // Tab stop handling (previously was using internal ItemFocusable() api) - // FIXME-NAV: We would now want to move this above the clipping test, but this would require being able to scroll and currently this would mean an extra frame. (#4079, #343) - if (flags & ImGuiItemAddFlags_Focusable) - ItemFocusable(window, id); + // [WIP] Tab stop handling (previously was using internal FocusableItemRegister() api) + // FIXME-NAV: We would now want to move this before the clipping test, but this would require being able to scroll and currently this would mean an extra frame. (#4079, #343) + if (extra_flags & ImGuiItemFlags_Inputable) + ItemInputable(window, id); // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (IsMouseHoveringRect(bb.Min, bb.Max)) @@ -8444,6 +8449,7 @@ ImVec2 ImGui::GetWindowContentRegionMax() // Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) // Groups are currently a mishmash of functionalities which should perhaps be clarified and separated. +// FIXME-OPT: Could we safely early out on ->SkipItems? void ImGui::BeginGroup() { ImGuiContext& g = *GImGui; @@ -9324,6 +9330,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) //----------------------------------------------------------------------------- // FIXME-NAV: The existence of SetNavID vs SetFocusID properly needs to be clarified/reworked. +// In our terminology those should be interchangeable. Those two functions are merely a legacy artifact, so at minimum naming should be clarified. void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel) { ImGuiContext& g = *GImGui; @@ -9354,7 +9361,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) - window->NavRectRel[nav_layer] = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos); + window->NavRectRel[nav_layer] = ImRect(g.LastItemData.NavRect.Min - window->Pos, g.LastItemData.NavRect.Max - window->Pos); if (g.ActiveIdSource == ImGuiInputSource_Nav) g.NavDisableMouseHover = true; @@ -9385,7 +9392,7 @@ static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); } - else + else // FIXME: PageUp/PageDown are leaving move_dir == None { r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); @@ -9393,15 +9400,17 @@ static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect } // Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057 -static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) +static bool ImGui::NavScoreItem(ImGuiNavItemData* result) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (g.NavLayer != window->DC.NavLayerCurrent) return false; - const ImRect& curr = g.NavScoringRect; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) - g.NavScoringCount++; + // FIXME: Those are not good variables names + ImRect cand = g.LastItemData.NavRect; // Current item nav rectangle + const ImRect curr = g.NavScoringRect; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringDebugCount++; // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring if (window->ParentWindow == g.NavWindow) @@ -9463,24 +9472,24 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100)); draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40,0,0,150)); - draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + draw_list->AddText(cand.Max, ~0U, buf); } else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. { - if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } if (quadrant == g.NavMoveDir) { ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); ImDrawList* draw_list = GetForegroundDrawList(window); draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); - draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + draw_list->AddText(cand.Min, IM_COL32(255, 255, 255, 255), buf); } } #endif // Is it in the quadrant we're interesting in moving to? bool new_best = false; - if (quadrant == g.NavMoveDir) + const ImGuiDir move_dir = g.NavMoveDir; + if (quadrant == move_dir) { // Does it beat the current best candidate? if (dist_box < result->DistBox) @@ -9502,7 +9511,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. - if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + if (((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance new_best = true; } } @@ -9515,7 +9524,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) - if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + if ((move_dir == ImGuiDir_Left && dax < 0.0f) || (move_dir == ImGuiDir_Right && dax > 0.0f) || (move_dir == ImGuiDir_Up && day < 0.0f) || (move_dir == ImGuiDir_Down && day > 0.0f)) { result->DistAxial = dist_axial; new_best = true; @@ -9524,23 +9533,25 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) return new_best; } -static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel) +static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; result->Window = window; - result->ID = id; + result->ID = g.LastItemData.ID; result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; - result->RectRel = nav_bb_rel; + result->RectRel = ImRect(g.LastItemData.NavRect.Min - window->Pos, g.LastItemData.NavRect.Max - window->Pos); } // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) -static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +// This is called after LastItemData is set. +static void ImGui::NavProcessItem() { ImGuiContext& g = *GImGui; - //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. - // return; - + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = g.LastItemData.ID; + const ImRect nav_bb = g.LastItemData.NavRect; const ImGuiItemFlags item_flags = g.LastItemData.InFlags; - const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); // Process Init Request if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) @@ -9550,7 +9561,7 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con if (candidate_for_nav_default_focus || g.NavInitResultId == 0) { g.NavInitResultId = id; - g.NavInitResultRectRel = nav_bb_rel; + g.NavInitResultRectRel = ImRect(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); } if (candidate_for_nav_default_focus) { @@ -9560,27 +9571,22 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con } // Process Move Request (scoring for navigation) - // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) - if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav))) + // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped according to current wrapping policy) + if (g.NavMoveScoringItems) { - ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; -#if IMGUI_DEBUG_NAV_SCORING - // [DEBUG] Score all items in NavWindow at all times - if (!g.NavMoveRequest) - g.NavMoveDir = g.NavMoveDirLast; - bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; -#else - bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); -#endif - if (new_best) - NavApplyItemToResult(result, window, id, nav_bb_rel); + if ((g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav))) + { + ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + if (NavScoreItem(result)) + NavApplyItemToResult(result); - // 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)) - NavApplyItemToResult(&g.NavMoveResultLocalVisibleSet, window, id, 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.NavMoveFlags & 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.NavMoveResultLocalVisible)) + NavApplyItemToResult(&g.NavMoveResultLocalVisible); + } } // Update window-relative bounding box of navigated item @@ -9590,20 +9596,38 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con g.NavLayer = window->DC.NavLayerCurrent; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavIdIsAlive = true; - window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + window->NavRectRel[window->DC.NavLayerCurrent] = ImRect(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); // Store item bounding box (relative to window position) } } bool ImGui::NavMoveRequestButNoResultYet() { ImGuiContext& g = *GImGui; - return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; + return g.NavMoveScoringItems && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +// FIXME: ScoringRect is not set +void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindow != NULL); + g.NavMoveSubmitted = g.NavMoveScoringItems = true; + g.NavMoveDir = move_dir; + g.NavMoveDirForDebug = move_dir; + g.NavMoveClipDir = clip_dir; + g.NavMoveFlags = move_flags; + g.NavMoveForwardToNextFrame = false; + g.NavMoveKeyMods = g.IO.KeyMods; + g.NavMoveResultLocal.Clear(); + g.NavMoveResultLocalVisible.Clear(); + g.NavMoveResultOther.Clear(); + NavUpdateAnyRequestFlag(); } void ImGui::NavMoveRequestCancel() { ImGuiContext& g = *GImGui; - g.NavMoveRequest = false; + g.NavMoveSubmitted = g.NavMoveScoringItems = false; NavUpdateAnyRequestFlag(); } @@ -9611,12 +9635,12 @@ void ImGui::NavMoveRequestCancel() void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags) { ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavMoveRequestForwardToNextFrame == false); + IM_ASSERT(g.NavMoveForwardToNextFrame == false); NavMoveRequestCancel(); - g.NavMoveRequestForwardToNextFrame = true; + g.NavMoveForwardToNextFrame = true; g.NavMoveDir = move_dir; g.NavMoveClipDir = clip_dir; - g.NavMoveRequestFlags = move_flags | ImGuiNavMoveFlags_Forwarded; + g.NavMoveFlags = move_flags | ImGuiNavMoveFlags_Forwarded; } // Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire @@ -9625,8 +9649,9 @@ void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags wra { ImGuiContext& g = *GImGui; IM_ASSERT(wrap_flags != 0); // Call with _WrapX, _WrapY, _LoopX, _LoopY - if (g.NavWindow == window && g.NavMoveRequest && g.NavLayer == ImGuiNavLayer_Main) - g.NavMoveRequestFlags |= wrap_flags; + // In theory we should test for NavMoveRequestButNoResultYet() but there's no point doing it, NavEndFrame() will do the same test + if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == ImGuiNavLayer_Main) + g.NavMoveFlags |= wrap_flags; } // FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer == 0). @@ -9661,20 +9686,20 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer) if (window->NavLastIds[layer] != 0) { SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); - g.NavDisableHighlight = false; - g.NavDisableMouseHover = g.NavMousePosDirty = true; } else { g.NavLayer = layer; NavInitWindow(window, true); } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; } static inline void ImGui::NavUpdateAnyRequestFlag() { ImGuiContext& g = *GImGui; - g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); + g.NavAnyRequest = g.NavMoveScoringItems || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); if (g.NavAnyRequest) IM_ASSERT(g.NavWindow != NULL); } @@ -9720,7 +9745,7 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() // Mouse (we need a fallback in case the mouse becomes invalid after being used) if (IsMousePosValid(&g.IO.MousePos)) return g.IO.MousePos; - return g.LastValidMousePos; + return g.MouseLastValidPos; } else { @@ -9776,9 +9801,7 @@ static void ImGui::NavUpdate() ImGuiIO& io = g.IO; io.WantSetMousePos = false; -#if 0 - if (g.NavScoringCount > 0) IMGUI_DEBUG_LOG("NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); -#endif + //if (g.NavScoringDebugCount > 0) IMGUI_DEBUG_LOG("NavScoringDebugCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.NavScoringDebugCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); // Set input source as Gamepad when buttons are pressed (as some features differs when used with Gamepad vs Keyboard) // (do it before we map Keyboard input!) @@ -9814,16 +9837,16 @@ static void ImGui::NavUpdate() // Process navigation init request (select first/default focus) if (g.NavInitResultId != 0) - NavUpdateInitResult(); + NavInitRequestApplyResult(); g.NavInitRequest = false; g.NavInitRequestFromMove = false; g.NavInitResultId = 0; g.NavJustMovedToId = 0; // Process navigation move request - if (g.NavMoveRequest) + if (g.NavMoveSubmitted) NavMoveRequestApplyResult(); - g.NavMoveRequest = false; + g.NavMoveSubmitted = g.NavMoveScoringItems = false; // Apply application mouse position movement, after we had a chance to process move request result. if (g.NavMousePosDirty && g.NavIdIsAlive) @@ -9834,6 +9857,7 @@ static void ImGui::NavUpdate() { io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos(); io.WantSetMousePos = true; + //IMGUI_DEBUG_LOG("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); } g.NavMousePosDirty = false; } @@ -9858,19 +9882,28 @@ static void ImGui::NavUpdate() NavUpdateCancelRequest(); // Process manual activation request - g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavActivateInputId = 0; + g.NavActivateFlags = ImGuiActivateFlags_None; if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool input_down = IsNavInputDown(ImGuiNavInput_Input); bool activate_pressed = activate_down && IsNavInputTest(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + bool input_pressed = input_down && IsNavInputTest(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed); if (g.ActiveId == 0 && activate_pressed) + { g.NavActivateId = g.NavId; + g.NavActivateFlags = ImGuiActivateFlags_PreferTweak; + } + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed) + { + g.NavActivateInputId = g.NavId; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; + } if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) g.NavActivateDownId = g.NavId; if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) g.NavActivatePressedId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputTest(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) - g.NavInputId = g.NavId; } if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) g.NavDisableHighlight = true; @@ -9878,8 +9911,15 @@ static void ImGui::NavUpdate() IM_ASSERT(g.NavActivateDownId == g.NavActivateId); // Process programmatic activation request + // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others) if (g.NavNextActivateId != 0) - g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + { + if (g.NavNextActivateFlags & ImGuiActivateFlags_PreferInput) + g.NavActivateInputId = g.NavNextActivateId; + else + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavNextActivateId; + g.NavActivateFlags = g.NavNextActivateFlags; + } g.NavNextActivateId = 0; // Process move requests @@ -9892,12 +9932,13 @@ static void ImGui::NavUpdate() // *Fallback* manual-scroll with Nav directional keys when window has no navigable item ImGuiWindow* window = g.NavWindow; const float scroll_speed = IM_ROUND(window->CalcFontSize() * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. - if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) //-V560 + const ImGuiDir move_dir = g.NavMoveDir; + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll && move_dir != ImGuiDir_None) { - if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) - SetScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); - if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) - SetScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) + SetScrollX(window, ImFloor(window->Scroll.x + ((move_dir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) + SetScrollY(window, ImFloor(window->Scroll.y + ((move_dir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); } // *Normal* Manual scroll with NavScrollXXX keys @@ -9909,8 +9950,15 @@ static void ImGui::NavUpdate() SetScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); } + // Always prioritize mouse highlight if navigation is disabled + if (!nav_keyboard_active && !nav_gamepad_active) + { + g.NavDisableHighlight = true; + g.NavDisableMouseHover = g.NavMousePosDirty = false; + } + // [DEBUG] - g.NavScoringCount = 0; + g.NavScoringDebugCount = 0; #if IMGUI_DEBUG_NAV_RECTS if (g.NavWindow) { @@ -9921,7 +9969,7 @@ static void ImGui::NavUpdate() #endif } -static void ImGui::NavUpdateInitResult() +void ImGui::NavInitRequestApplyResult() { // In very rare cases g.NavWindow may be null (e.g. clearing focus after requesting an init request, which does happen when releasing Alt while clicking on void) ImGuiContext& g = *GImGui; @@ -9932,6 +9980,7 @@ static void ImGui::NavUpdateInitResult() // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently. IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", g.NavInitResultId, g.NavLayer, g.NavWindow->Name); SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); + g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result if (g.NavInitRequestFromMove) { g.NavDisableHighlight = false; @@ -9945,20 +9994,19 @@ void ImGui::NavUpdateCreateMoveRequest() ImGuiIO& io = g.IO; ImGuiWindow* window = g.NavWindow; - if (g.NavMoveRequestForwardToNextFrame) + if (g.NavMoveForwardToNextFrame && window != NULL) { // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window) // (preserve most state, which were already set by the NavMoveRequestForward() function) IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None); - IM_ASSERT(g.NavMoveRequestFlags & ImGuiNavMoveFlags_Forwarded); + IM_ASSERT(g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded); IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestForward %d\n", g.NavMoveDir); - g.NavMoveRequestForwardToNextFrame = false; } else { // Initiate directional inputs request g.NavMoveDir = ImGuiDir_None; - g.NavMoveRequestFlags = ImGuiNavMoveFlags_None; + g.NavMoveFlags = ImGuiNavMoveFlags_None; if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs)) { const ImGuiInputReadMode read_mode = ImGuiInputReadMode_Repeat; @@ -9973,24 +10021,28 @@ void ImGui::NavUpdateCreateMoveRequest() // Update PageUp/PageDown/Home/End scroll // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag? const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - float nav_scoring_rect_offset_y = 0.0f; - if (nav_keyboard_active) - nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(); + float scoring_rect_offset_y = 0.0f; + if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active) + scoring_rect_offset_y = NavUpdatePageUpPageDown(); - // If we initiate a movement request and have no current NavId, we initiate a InitDefaultRequest that will be used as a fallback if the direction fails to find a match - if (g.NavMoveDir != ImGuiDir_None) + // [DEBUG] Always send a request +#if IMGUI_DEBUG_NAV_SCORING + if (io.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3); + if (io.KeyCtrl && g.NavMoveDir == ImGuiDir_None) { - IM_ASSERT(window != NULL); - g.NavMoveRequest = true; - g.NavMoveRequestKeyMods = io.KeyMods; - g.NavMoveDirLast = g.NavMoveDir; - g.NavMoveResultLocal.Clear(); - g.NavMoveResultLocalVisibleSet.Clear(); - g.NavMoveResultOther.Clear(); + g.NavMoveDir = g.NavMoveDirForDebug; + g.NavMoveFlags |= ImGuiNavMoveFlags_DebugNoResult; } +#endif - // Moving with no reference triggers a init request - if (g.NavMoveRequest && g.NavId == 0) + // Submit + g.NavMoveForwardToNextFrame = false; + if (g.NavMoveDir != ImGuiDir_None) + NavMoveRequestSubmit(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags); + + // Moving with no reference triggers a init request (will be used as a fallback if the direction fails to find a match) + if (g.NavMoveSubmitted && g.NavId == 0) { IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", g.NavWindow->Name, g.NavLayer); g.NavInitRequest = g.NavInitRequestFromMove = true; @@ -10001,7 +10053,7 @@ void ImGui::NavUpdateCreateMoveRequest() // When using gamepad, we project the reference nav bounding box into window visible area. // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with gamepad every movements are relative // (can't focus a visible object like we can with the mouse). - if (g.NavMoveRequest && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main && window != NULL) + if (g.NavMoveSubmitted && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main && window != NULL) { ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) @@ -10015,27 +10067,35 @@ void ImGui::NavUpdateCreateMoveRequest() } // 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.NavScoringRect = ImRect(); - if (window) + ImRect scoring_rect; + if (window != NULL) { ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0); - g.NavScoringRect = ImRect(window->Pos + nav_rect_rel.Min, window->Pos + nav_rect_rel.Max); - g.NavScoringRect.TranslateY(nav_scoring_rect_offset_y); - g.NavScoringRect.Min.x = ImMin(g.NavScoringRect.Min.x + 1.0f, g.NavScoringRect.Max.x); - g.NavScoringRect.Max.x = g.NavScoringRect.Min.x; - IM_ASSERT(!g.NavScoringRect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). - //GetForegroundDrawList()->AddRect(g.NavScoringRect.Min, g.NavScoringRect.Max, IM_COL32(255,200,0,255)); // [DEBUG] + scoring_rect = ImRect(window->Pos + nav_rect_rel.Min, window->Pos + nav_rect_rel.Max); + scoring_rect.TranslateY(scoring_rect_offset_y); + scoring_rect.Min.x = ImMin(scoring_rect.Min.x + 1.0f, scoring_rect.Max.x); + scoring_rect.Max.x = scoring_rect.Min.x; + IM_ASSERT(!scoring_rect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). + //GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG] } + g.NavScoringRect = scoring_rect; } // Apply result from previous frame navigation directional move request. Always called from NavUpdate() void ImGui::NavMoveRequestApplyResult() { ImGuiContext& g = *GImGui; +#if IMGUI_DEBUG_NAV_SCORING + if (g.NavMoveFlags & ImGuiNavMoveFlags_DebugNoResult) // [DEBUG] Scoring all items in NavWindow at all times + return; +#endif - if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + // Select which result to use + ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : (g.NavMoveResultOther.ID != 0) ? &g.NavMoveResultOther : NULL; + + // In a situation when there is no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result) + if (result == NULL) { - // In a situation when there is no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result) if (g.NavId != 0) { g.NavDisableHighlight = false; @@ -10044,13 +10104,10 @@ void ImGui::NavMoveRequestApplyResult() return; } - // Select which result to use - ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; - // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. - if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) - if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId) - result = &g.NavMoveResultLocalVisibleSet; + if (g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) + if (g.NavMoveResultLocalVisible.ID != 0 && g.NavMoveResultLocalVisible.ID != g.NavId) + result = &g.NavMoveResultLocalVisible; // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) @@ -10062,7 +10119,7 @@ void ImGui::NavMoveRequestApplyResult() if (g.NavLayer == ImGuiNavLayer_Main) { ImVec2 delta_scroll; - if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_ScrollToEdge) + if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdge) { float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; delta_scroll.y = result->Window->Scroll.y - scroll_target; @@ -10086,10 +10143,14 @@ void ImGui::NavMoveRequestApplyResult() // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId) g.NavJustMovedToId = result->ID; g.NavJustMovedToFocusScopeId = result->FocusScopeId; - g.NavJustMovedToKeyMods = g.NavMoveRequestKeyMods; + g.NavJustMovedToKeyMods = g.NavMoveKeyMods; } + + // Focus IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer, g.NavWindow->Name); SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); + + // Enable nav highlight g.NavDisableHighlight = false; g.NavDisableMouseHover = g.NavMousePosDirty = true; } @@ -10141,18 +10202,18 @@ static void ImGui::NavUpdateCancelRequest() } // Handle PageUp/PageDown/Home/End keys +// Called from NavUpdateCreateMoveRequest() which will use our output to create a move request +// FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference // FIXME-NAV: how to get Home/End to aim at the beginning/end of a 2D grid? static float ImGui::NavUpdatePageUpPageDown() { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; - if (g.NavMoveDir != ImGuiDir_None || g.NavWindow == NULL) - return 0.0f; - if ((g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL || g.NavLayer != ImGuiNavLayer_Main) + ImGuiWindow* window = g.NavWindow; + if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL || g.NavLayer != ImGuiNavLayer_Main) return 0.0f; - ImGuiWindow* window = g.NavWindow; const bool page_up_held = IsKeyDown(io.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp); const bool page_down_held = IsKeyDown(io.KeyMap[ImGuiKey_PageDown]) && !IsActiveIdUsingKey(ImGuiKey_PageDown); const bool home_pressed = IsKeyPressed(io.KeyMap[ImGuiKey_Home]) && !IsActiveIdUsingKey(ImGuiKey_Home); @@ -10182,14 +10243,14 @@ static float ImGui::NavUpdatePageUpPageDown() nav_scoring_rect_offset_y = -page_offset_y; 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; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; } else if (IsKeyPressed(io.KeyMap[ImGuiKey_PageDown], true)) { nav_scoring_rect_offset_y = +page_offset_y; 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; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; } else if (home_pressed) { @@ -10200,7 +10261,8 @@ static float ImGui::NavUpdatePageUpPageDown() 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; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge; + // FIXME-NAV: MoveClipDir left to _None, intentional? } else if (end_pressed) { @@ -10208,7 +10270,8 @@ static float ImGui::NavUpdatePageUpPageDown() 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; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge; + // FIXME-NAV: MoveClipDir left to _None, intentional? } return nav_scoring_rect_offset_y; } @@ -10226,9 +10289,9 @@ static void ImGui::NavEndFrame() // Perform wrap-around in menus // FIXME-NAV: Wrap (not Loop) support could be handled by the scoring function and then WrapX would function without an extra frame. ImGuiWindow* window = g.NavWindow; - const ImGuiNavMoveFlags move_flags = g.NavMoveRequestFlags; + const ImGuiNavMoveFlags move_flags = g.NavMoveFlags; const ImGuiNavMoveFlags wanted_flags = ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY; - if (window && NavMoveRequestButNoResultYet() && (g.NavMoveRequestFlags & wanted_flags) && (g.NavMoveRequestFlags & ImGuiNavMoveFlags_Forwarded) == 0) + if (window && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & wanted_flags) && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) { bool do_forward = false; ImRect bb_rel = window->NavRectRel[g.NavLayer]; @@ -10484,15 +10547,17 @@ static void ImGui::NavUpdateWindowing() FocusWindow(new_nav_window); new_nav_window->NavLastChildNavWindow = old_nav_window; } - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; - // Reinitialize navigation when entering menu bar with the Alt key. + // Toggle layer const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main; - const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL); - if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id) - g.NavWindow->NavLastIds[new_nav_layer] = 0; - NavRestoreLayer(new_nav_layer); + if (new_nav_layer != g.NavLayer) + { + // Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the layer?) + const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL); + if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id) + g.NavWindow->NavLastIds[new_nav_layer] = 0; + NavRestoreLayer(new_nav_layer); + } } } @@ -10587,14 +10652,16 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) return false; if (g.ActiveIdMouseButton != -1) mouse_button = g.ActiveIdMouseButton; - if (g.IO.MouseDown[mouse_button] == false) + if (g.IO.MouseDown[mouse_button] == false || window->SkipItems) return false; g.ActiveIdAllowOverlap = false; } else { // Uncommon path: items without ID - if (g.IO.MouseDown[mouse_button] == false) + if (g.IO.MouseDown[mouse_button] == false || window->SkipItems) + return false; + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) return false; // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: @@ -10605,10 +10672,6 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) return false; } - // Early out - if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) - return false; - // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() // We build a throwaway ID based on current ID stack + relative AABB of items in window. // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. @@ -10774,7 +10837,7 @@ bool ImGui::BeginDragDropTarget() if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)) return false; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; - if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree) + if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree || window->SkipItems) return false; const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect; @@ -16716,7 +16779,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); Text("NavInputSource: %s", input_source_names[g.NavInputSource]); Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); - Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); + Text("NavActivateId/DownId/PressedId/InputId: %08X/%08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId, g.NavActivateInputId); + Text("NavActivateFlags: %04X", g.NavActivateFlags); Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); diff --git a/imgui.h b/imgui.h index b7487d0a..1dfc4e88 100644 --- a/imgui.h +++ b/imgui.h @@ -65,7 +65,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.85 WIP" -#define IMGUI_VERSION_NUM 18410 +#define IMGUI_VERSION_NUM 18412 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE #define IMGUI_HAS_VIEWPORT // Viewport WIP branch diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 63a3b2bb..b48f11f7 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1613,16 +1613,17 @@ static void ShowDemoWindowWidgets() } // Plot/Graph widgets are not very good. - // Consider writing your own, or using a third-party one, see: - // - ImPlot https://github.com/epezent/implot - // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions + // Consider using a third-party library such as ImPlot: https://github.com/epezent/implot + // (see others https://github.com/ocornut/imgui/wiki/Useful-Extensions) if (ImGui::TreeNode("Plots Widgets")) { static bool animate = true; ImGui::Checkbox("Animate", &animate); + // Plot as lines and plot as histogram static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); // Fill an array of contiguous float values to plot // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float @@ -1652,7 +1653,6 @@ static void ShowDemoWindowWidgets() sprintf(overlay, "avg %f", average); ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); } - ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); // Use functions to generate output // FIXME: This is rather awkward because current plot API only pass in indices. diff --git a/imgui_internal.h b/imgui_internal.h index 60f0780f..52ff9874 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -148,8 +148,8 @@ struct ImGuiWindowSettings; // Storage for a window .ini settings (we ke // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. typedef int ImGuiDataAuthority; // -> enum ImGuiDataAuthority_ // Enum: for storing the source authority (dock node vs window) of a field typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical +typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() -typedef int ImGuiItemAddFlags; // -> enum ImGuiItemAddFlags_ // Flags: for ItemAdd() typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() @@ -754,15 +754,8 @@ enum ImGuiItemFlags_ ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false // Disable item being a candidate for default focus (e.g. used by title bar items) ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) - ImGuiItemFlags_ReadOnly = 1 << 7 // false // [ALPHA] Allow hovering interactions but underlying value is not changed. -}; - -// Flags for ItemAdd() -// FIXME-NAV: _Focusable is _ALMOST_ what you would expect to be called '_TabStop' but because SetKeyboardFocusHere() works on items with no TabStop we distinguish Focusable from TabStop. -enum ImGuiItemAddFlags_ -{ - ImGuiItemAddFlags_None = 0, - ImGuiItemAddFlags_Focusable = 1 << 0 // FIXME-NAV: In current/legacy scheme, Focusable+TabStop support are opt-in by widgets. We will transition it toward being opt-out, so this flag is expected to eventually disappear. + ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. + ImGuiItemFlags_Inputable = 1 << 8 // false // [WIP] Auto-activate item when focused. Currently only used and supported by a few items before it becomes a generic feature. }; // Storage for LastItem data @@ -770,7 +763,7 @@ enum ImGuiItemStatusFlags_ { ImGuiItemStatusFlags_None = 0, ImGuiItemStatusFlags_HoveredRect = 1 << 0, // Mouse position is within item rectangle (does NOT mean that the window is in correct z-order and can be hovered!, this is only one part of the most-common IsItemHovered test) - ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, // window->DC.LastItemDisplayRect is valid + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, // g.LastItemData.DisplayRect is valid ImGuiItemStatusFlags_Edited = 1 << 2, // Value exposed by item was edited in the current frame (should match the bool return value of most widgets) ImGuiItemStatusFlags_ToggledSelection = 1 << 3, // Set when Selectable(), TreeNode() reports toggling a selection. We can't report "Selected", only state changes, in order to easily handle clipping with less issues. ImGuiItemStatusFlags_ToggledOpen = 1 << 4, // Set when TreeNode() reports toggling their open state. @@ -1143,8 +1136,9 @@ struct ImGuiLastItemData ImGuiID ID; ImGuiItemFlags InFlags; // See ImGuiItemFlags_ ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ - ImRect Rect; - ImRect DisplayRect; + ImRect Rect; // Full rectangle + ImRect NavRect; // Navigation scoring rectangle (not displayed) + ImRect DisplayRect; // Display rectangle (only if ImGuiItemStatusFlags_HasDisplayRect is set) ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } }; @@ -1175,6 +1169,14 @@ struct ImGuiPtrOrIndex // [SECTION] Navigation support //----------------------------------------------------------------------------- +enum ImGuiActivateFlags_ +{ + ImGuiActivateFlags_None = 0, + ImGuiActivateFlags_PreferInput = 1 << 0, // Favor activation that requires keyboard text input (e.g. for Slider/Drag). Default if keyboard is available. + ImGuiActivateFlags_PreferTweak = 1 << 1, // Favor activation for tweaking with arrows or gamepad (e.g. for Slider/Drag). Default if keyboard is not available. + ImGuiActivateFlags_TryToPreserveState = 1 << 2 // Request widget to preserve state if it can (e.g. InputText will try to preserve cursor/selection) +}; + enum ImGuiNavHighlightFlags_ { ImGuiNavHighlightFlags_None = 0, @@ -1200,9 +1202,10 @@ 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 but 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 (used by PageUp/PageDown) + ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5, // Store alternate result in NavMoveResultLocalVisible that only comprise elements that are already fully visible (used by PageUp/PageDown) ImGuiNavMoveFlags_ScrollToEdge = 1 << 6, - ImGuiNavMoveFlags_Forwarded = 1 << 7 + ImGuiNavMoveFlags_Forwarded = 1 << 7, + ImGuiNavMoveFlags_DebugNoResult = 1 << 8 }; enum ImGuiNavLayer @@ -1686,34 +1689,40 @@ struct ImGuiContext 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 NavActivateInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0; ImGuiActivateFlags_PreferInput will be set and NavActivateId will be 0. + ImGuiActivateFlags NavActivateFlags; ImGuiID NavJustTabbedId; // Just tabbed to this id. ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). ImGuiKeyModFlags NavJustMovedToKeyMods; ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS WILL ONLY BE None or NavGamepad or NavKeyboard. - ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring. - int NavScoringCount; // Metrics for debugging ImGuiNavLayer 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 ~~ NavRectRel is valid bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default) bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + + // Navigation: Init & Move Requests bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd() bool NavInitRequest; // Init request for appearing window to select first item bool NavInitRequestFromMove; ImGuiID NavInitResultId; // Init request result (first item of the window, or one for which SetItemDefaultFocus() was called) ImRect NavInitResultRectRel; // Init request result rectangle (relative to parent window) - bool NavMoveRequest; // Move request for this frame - bool NavMoveRequestForwardToNextFrame; - ImGuiNavMoveFlags NavMoveRequestFlags; - ImGuiKeyModFlags NavMoveRequestKeyMods; - ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request + bool NavMoveSubmitted; // Move request submitted, will process result on next NewFrame() + bool NavMoveScoringItems; // Move request submitted, still scoring incoming items + bool NavMoveForwardToNextFrame; + ImGuiNavMoveFlags NavMoveFlags; + ImGuiKeyModFlags NavMoveKeyMods; + ImGuiDir NavMoveDir; // Direction of the move request (left/right/up/down) + ImGuiDir NavMoveDirForDebug; ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? + ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring. + int NavScoringDebugCount; // Metrics for debugging ImGuiNavItemData NavMoveResultLocal; // Best move request candidate within NavWindow - ImGuiNavItemData NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) + ImGuiNavItemData NavMoveResultLocalVisible; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) ImGuiNavItemData NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) @@ -1771,7 +1780,7 @@ struct ImGuiContext ImVector ShrinkWidthBuffer; // Widget state - ImVec2 LastValidMousePos; + ImVec2 MouseLastValidPos; ImGuiInputTextState InputTextState; ImFont InputTextPasswordFont; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. @@ -1905,12 +1914,11 @@ struct ImGuiContext ViewportFrontMostStampCount = 0; NavWindow = NULL; - NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavActivateInputId = 0; NavJustTabbedId = NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; + NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; NavJustMovedToKeyMods = ImGuiKeyModFlags_None; NavInputSource = ImGuiInputSource_None; - NavScoringRect = ImRect(); - NavScoringCount = 0; NavLayer = ImGuiNavLayer_Main; NavIdTabCounter = INT_MAX; NavIdIsAlive = false; @@ -1921,11 +1929,13 @@ struct ImGuiContext NavInitRequest = false; NavInitRequestFromMove = false; NavInitResultId = 0; - NavMoveRequest = false; - NavMoveRequestForwardToNextFrame = false; - NavMoveRequestFlags = ImGuiNavMoveFlags_None; - NavMoveRequestKeyMods = ImGuiKeyModFlags_None; - NavMoveDir = NavMoveDirLast = NavMoveClipDir = ImGuiDir_None; + NavMoveSubmitted = false; + NavMoveScoringItems = false; + NavMoveForwardToNextFrame = false; + NavMoveFlags = ImGuiNavMoveFlags_None; + NavMoveKeyMods = ImGuiKeyModFlags_None; + NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None; + NavScoringDebugCount = 0; NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; @@ -1955,7 +1965,6 @@ struct ImGuiContext CurrentTableStackIdx = -1; CurrentTabBar = NULL; - LastValidMousePos = ImVec2(0.0f, 0.0f); TempInputId = 0; ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; ColorEditLastHue = ColorEditLastSat = 0.0f; @@ -2041,7 +2050,7 @@ struct IMGUI_API ImGuiWindowTempData int CurrentTableIdx; // Current table index (into g.Tables) ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() - int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase as assigned via FocusableItemRegister() (FIXME-NAV: Needs redesign) + int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase when ImGuiItemFlags_Inputable (FIXME-NAV: Needs redesign) int FocusCounterTabStop; // (Legacy Focus/Tabbing system) Same, but only count widgets which you can Tab through. // Local parameters stacks @@ -2627,9 +2636,9 @@ namespace ImGui // Basic Helpers for widget code IMGUI_API void ItemSize(const ImVec2& size, float text_baseline_y = -1.0f); IMGUI_API void ItemSize(const ImRect& bb, float text_baseline_y = -1.0f); - IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL, ImGuiItemAddFlags flags = 0); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL, ImGuiItemFlags extra_flags = 0); IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); - IMGUI_API void ItemFocusable(ImGuiWindow* window, ImGuiID id); + IMGUI_API void ItemInputable(ImGuiWindow* window, ImGuiID id); IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); @@ -2644,11 +2653,13 @@ namespace ImGui IMGUI_API void PopItemFlag(); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Currently refactoring focus/nav/tabbing system // If you have old/custom copy-and-pasted widgets that used FocusableItemRegister(): // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool focused = FocusableItemRegister(...)' - // (New) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' + // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' + // (New) IMGUI_VERSION_NUM >= 18411: using 'ItemAdd(..., ImGuiItemAddFlags_Inputable)' and 'bool focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' // Widget code are simplified as there's no need to call FocusableItemUnregister() while managing the transition from regular widget to TempInputText() - inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Focusable flag to ItemAdd() + inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() inline void FocusableItemUnregister(ImGuiWindow* window) { IM_ASSERT(0); IM_UNUSED(window); } // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem #endif @@ -2683,7 +2694,9 @@ namespace ImGui // Gamepad/Keyboard Navigation IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); - IMGUI_API bool NavMoveRequestButNoResultYet(); // Should be called ~NavMoveRequestIsActiveButNoResultYet() + IMGUI_API void NavInitRequestApplyResult(); + IMGUI_API bool NavMoveRequestButNoResultYet(); + IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags); IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e70a4dea..b60676a6 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1479,6 +1479,7 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b // Ensure frozen columns are ordered in their section. We still allow multiple frozen columns to be reordered. + // FIXME-TABLE: This work for preserving 2143 into 21|43. How about 4321 turning into 21|43? (preserve relative order in each section) for (int column_n = 0; column_n < table->FreezeColumnsRequest; column_n++) { int order_n = table->DisplayOrderToIndex[column_n]; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index cb835f2a..26a5b5ec 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -273,6 +273,7 @@ void ImGui::TextV(const char* fmt, va_list args) if (window->SkipItems) return; + // FIXME-OPT: Handle the %s shortcut? ImGuiContext& g = *GImGui; const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); @@ -604,13 +605,12 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool bool nav_activated_by_code = (g.NavActivateId == id); bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); if (nav_activated_by_code || nav_activated_by_inputs) - pressed = true; - if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) { // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. - g.NavActivateId = id; // This is so SetActiveId assign a Nav source + pressed = true; SetActiveID(id, window); - if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus)) + g.ActiveIdSource = ImGuiInputSource_Nav; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) SetFocusID(id, window); } } @@ -1587,7 +1587,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF bool pressed = ButtonBehavior(bb, id, &hovered, &held); const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id); bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None); - if ((pressed || g.NavActivateId == id) && !popup_open) + if (pressed && !popup_open) { OpenPopupEx(popup_id, ImGuiPopupFlags_None); popup_open = true; @@ -2395,7 +2395,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0)) + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) return false; // Default format string when passing NULL @@ -2412,21 +2412,23 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0; const bool clicked = (hovered && g.IO.MouseClicked[0]); const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]); - if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id) + if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavActivateInputId == id) { SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); - if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id)) - temp_input_is_active = true; + if (temp_input_allowed) + if (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavActivateInputId == id) + temp_input_is_active = true; } + // Experimental: simple click (without moving) turns Drag into an InputText - // FIXME: Currently polling ImGuiConfigFlags_IsTouchScreen, may either poll an hypothetical ImGuiBackendFlags_HasKeyboard and/or an explicit drag settings. if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active) if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - g.NavInputId = id; + g.NavActivateId = g.NavActivateInputId = id; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; temp_input_is_active = true; } } @@ -3011,7 +3013,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0)) + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) return false; // Default format string when passing NULL @@ -3027,13 +3029,13 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat { const bool focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0; const bool clicked = (hovered && g.IO.MouseClicked[0]); - if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id) + if (focus_requested || clicked || g.NavActivateId == id || g.NavActivateInputId == id) { SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); - if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id)) + if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id)) temp_input_is_active = true; } } @@ -3185,7 +3187,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d format = PatchFormatStringFloatToInt(format); const bool hovered = ItemHoverable(frame_bb, id); - if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id) { SetActiveID(id, window); SetFocusID(id, window); @@ -3449,7 +3451,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data style.FramePadding.x = style.FramePadding.y; ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; if (flags & ImGuiInputTextFlags_ReadOnly) - BeginDisabled(true); + BeginDisabled(); SameLine(0, style.ItemInnerSpacing.x); if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) { @@ -3981,7 +3983,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { ImVec2 backup_pos = window->DC.CursorPos; ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) { EndGroup(); return false; @@ -4012,7 +4014,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd) ItemSize(total_bb, style.FramePadding.y); if (!(flags & ImGuiInputTextFlags_MergedItem)) - if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) return false; item_status_flags = g.LastItemData.StatusFlags; } @@ -4027,7 +4029,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool focus_requested_by_tabbing = (item_status_flags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; const bool user_clicked = hovered && io.MouseClicked[0]; - const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard)); + const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavActivateInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard)); const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); @@ -4254,6 +4256,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable); const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable; + // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. + const bool is_validate = IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter) || IsNavInputTest(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed) || IsNavInputTest(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed); + const bool is_cancel = IsKeyPressedMap(ImGuiKey_Escape) || IsNavInputTest(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed); + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } @@ -4274,7 +4280,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter)) + else if (is_validate) { bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) @@ -4288,7 +4294,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->OnKeyPressed((int)c); } } - else if (IsKeyPressedMap(ImGuiKey_Escape)) + else if (is_cancel) { clear_active_id = cancel_edit = true; } @@ -6153,20 +6159,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl window->ClipRect.Max.x = window->ParentWorkRect.Max.x; } - bool item_add; const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; - if (disabled_item) - { - ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; - g.CurrentItemFlags |= ImGuiItemFlags_Disabled; - item_add = ItemAdd(bb, id); - g.CurrentItemFlags = backup_item_flags; - } - else - { - item_add = ItemAdd(bb, id); - } - + const bool item_add = ItemAdd(bb, id, NULL, disabled_item ? ImGuiItemFlags_Disabled : ImGuiItemFlags_None); if (span_all_columns) { window->ClipRect.Min.x = backup_clip_rect_min_x; @@ -6178,7 +6172,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; if (disabled_item && !disabled_global) // Only testing this as an optimization - BeginDisabled(true); + BeginDisabled(); // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, // which would be advantageous since most selectable are not selected. @@ -6215,7 +6209,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { - SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); // (bb == NavRect) g.NavDisableHighlight = true; } } @@ -6480,7 +6474,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get float v0 = values_getter(data, (0 + values_offset) % values_count); float t0 = 0.0f; ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle - float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); @@ -6696,10 +6690,11 @@ void ImGui::EndMenuBar() // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) { + // Try to find out if the request is for one of our child menu ImGuiWindow* nav_earliest_child = g.NavWindow; while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) nav_earliest_child = nav_earliest_child->ParentWindow; - if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveRequestFlags & ImGuiNavMoveFlags_Forwarded) == 0) + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) { // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth bothering) @@ -6709,7 +6704,7 @@ void ImGui::EndMenuBar() SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. g.NavDisableMouseHover = g.NavMousePosDirty = true; - NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveRequestFlags); // Repeat + NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags); // Repeat } } @@ -6894,37 +6889,29 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. bool moving_toward_other_child_menu = false; - ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL; if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) { - // FIXME-DPI: Values should be derived from a master "scale" factor. + float ref_unit = g.FontSize; // FIXME-DPI ImRect next_window_rect = child_menu_window->Rect(); - ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta); ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); - float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. - ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues - tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? - tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // add a bit of extra slack. + ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues (FIXME: ??) + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +ref_unit * 8.0f); moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); - //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_other_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] } - - // FIXME: Hovering a disabled BeginMenu or MenuItem won't close us if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) want_close = true; - if (!menu_is_open && hovered && pressed) // Click to open + // Open + if (!menu_is_open && pressed) // Click/activate to open want_open = true; else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open want_open = true; - - if (g.NavActivateId == id) - { - want_close = menu_is_open; - want_open = !menu_is_open; - } if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open { want_open = true; @@ -7019,7 +7006,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut bool pressed; PushID(label); if (!enabled) - BeginDisabled(true); + BeginDisabled(); const ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover; const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)