diff --git a/CHANGELOG.txt b/CHANGELOG.txt index e1b9c38c..c471d45d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -63,6 +63,7 @@ Other Changes: - Each example still has its own main.cpp which you may refer you to understand how to initialize and glue everything together. - Some frameworks (such as the Allegro, Marmalade) handle both the "platform" and "rendering" part, and your custom engine may as well. - Read examples/README.txt for details. + - Added IsItemDeactivated() to query if the last item was active previously but isn't anymore. Useful for Undo/Redo patterns. (#820, #956, #1875) - Nav: Added support for PageUp/PageDown (explorer-style: first aim at bottom/top most item, when scroll a page worth of contents). (#787) - Nav: To keep the navigated item in view we also attempt to scroll the parent window as well as the current window. (#787) - TreeNode: Fixed nodes with ImGuiTreeNodeFlags_Leaf flag always returning true which was meaningless. diff --git a/imgui.cpp b/imgui.cpp index e57cdbaa..825a63d1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2251,6 +2251,8 @@ void ImGui::KeepAliveID(ImGuiID id) ImGuiContext& g = *GImGui; if (g.ActiveId == id) g.ActiveIdIsAlive = true; + if (g.ActiveIdPreviousFrame == id) + g.ActiveIdPreviousFrameIsAlive = true; } static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) @@ -3719,7 +3721,8 @@ void ImGui::NewFrame() g.ActiveIdTimer += g.IO.DeltaTime; g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; - g.ActiveIdIsAlive = false; + g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; + g.ActiveIdIsAlive = g.ActiveIdPreviousFrameIsAlive = false; g.ActiveIdIsJustActivated = false; if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) g.ScalarAsInputTextId = 0; @@ -3940,7 +3943,7 @@ void ImGui::Shutdown(ImGuiContext* context) g.NavWindow = NULL; g.HoveredWindow = NULL; g.HoveredRootWindow = NULL; - g.ActiveIdWindow = NULL; + g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; g.MovingWindow = NULL; g.ColorModifiers.clear(); g.StyleModifiers.clear(); @@ -4978,6 +4981,13 @@ bool ImGui::IsItemActive() return false; } +bool ImGui::IsItemDeactivated() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); +} + bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; @@ -12804,6 +12814,7 @@ void ImGui::BeginGroup() group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; group_data.BackupLogLinePosY = window->DC.LogLinePosY; group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; + group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; group_data.AdvanceCursor = true; window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX; @@ -12839,11 +12850,13 @@ void ImGui::EndGroup() ItemAdd(group_bb, 0); } - // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group. - // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context. - const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); - if (active_id_within_group) + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but put a little more burden on individual widgets. + // (and if you grep for LastItemId you'll notice it is only used in that context. + if (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow) window->DC.LastItemId = g.ActiveId; + else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow) + window->DC.LastItemId = g.ActiveIdPreviousFrame; window->DC.LastItemRect = group_bb; window->DC.GroupStack.pop_back(); diff --git a/imgui.h b/imgui.h index 233e0f65..e2dfa1a3 100644 --- a/imgui.h +++ b/imgui.h @@ -504,6 +504,7 @@ namespace ImGui IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) == IsMouseClicked(0) && IsItemHovered() IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) + IMGUI_API bool IsItemDeactivated(); // is the last item just made inactive (item was previously active), useful for Undo/Redo patterns. IMGUI_API bool IsAnyItemHovered(); IMGUI_API bool IsAnyItemActive(); IMGUI_API bool IsAnyItemFocused(); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 53367a08..34e3e154 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2028,7 +2028,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TreePop(); } - if (ImGui::TreeNode("Focused & Hovered Test")) + if (ImGui::TreeNode("Active, Focused & Hovered Test")) { static bool embed_all_inside_a_child_window = false; ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); @@ -2068,16 +2068,22 @@ void ImGui::ShowDemoWindow(bool* p_open) // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) ImGui::Button("ITEM"); ImGui::BulletText( + "IsItemFocused() = %d\n" "IsItemHovered() = %d\n" "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" "IsItemHovered(_AllowWhenOverlapped) = %d\n" - "IsItemhovered(_RectOnly) = %d\n", + "IsItemHovered(_RectOnly) = %d\n" + "IsItemActive() = %d\n" + "IsItemDeactivated() = %d\n", + ImGui::IsItemFocused(), ImGui::IsItemHovered(), ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), - ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), + ImGui::IsItemActive(), + ImGui::IsItemDeactivated()); ImGui::BeginChild("child", ImVec2(0,50), true); ImGui::Text("This is another child window for testing IsWindowHovered() flags."); diff --git a/imgui_internal.h b/imgui_internal.h index 1ef9ce9d..f6e4e057 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -394,6 +394,7 @@ struct ImGuiGroupData float BackupCurrentLineTextBaseOffset; float BackupLogLinePosY; bool BackupActiveIdIsAlive; + bool BackupActiveIdPreviousFrameIsAlive; bool AdvanceCursor; }; @@ -624,9 +625,11 @@ struct ImGuiContext bool ActiveIdIsAlive; // Active widget has been seen this frame bool ActiveIdIsJustActivated; // Set at the time of activation for one frame bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + bool ActiveIdPreviousFrameIsAlive; int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and move away from it) ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; + ImGuiWindow* ActiveIdPreviousFrameWindow; ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. @@ -762,9 +765,10 @@ struct ImGuiContext ActiveIdIsAlive = false; ActiveIdIsJustActivated = false; ActiveIdAllowOverlap = false; + ActiveIdPreviousFrameIsAlive = false; ActiveIdAllowNavDirFlags = 0; ActiveIdClickOffset = ImVec2(-1,-1); - ActiveIdWindow = NULL; + ActiveIdWindow = ActiveIdPreviousFrameWindow = NULL; ActiveIdSource = ImGuiInputSource_None; LastActiveId = 0; LastActiveIdTimer = 0.0f;