diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c471d45d..3db9e271 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -63,7 +63,9 @@ 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) + - Added IsItemDeactivated() to query if the last item was active previously and isn't anymore. Useful for Undo/Redo patterns. (#820, #956, #1875) + - Added IsItemDeactivatedAfterChange() if the last item was active previously, isn't anymore, and during its active state modified a value. + Note that you may still get false positive (e.g. drag value and while holding return on the same value). (#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 296280c0..fce6cf8c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2182,6 +2182,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) if (g.ActiveIdIsJustActivated) { g.ActiveIdTimer = 0.0f; + g.ActiveIdValueChanged = false; if (id != 0) { g.LastActiveId = id; @@ -2249,6 +2250,15 @@ void ImGui::KeepAliveID(ImGuiID id) g.ActiveIdPreviousFrameIsAlive = true; } +void ImGui::MarkItemValueChanged(ImGuiID id) +{ + // This marking is solely to be able to provide info for IsItemDeactivatedAfterChange(). + // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need need to fill the data. + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId == id || g.ActiveId == 0); + g.ActiveIdValueChanged = true; +} + static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) { // An active popup disable hovering on other windows (apart from its own children) @@ -3716,6 +3726,7 @@ void ImGui::NewFrame() g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; + g.ActiveIdPreviousFrameValueChanged = g.ActiveIdValueChanged; g.ActiveIdIsAlive = g.ActiveIdPreviousFrameIsAlive = false; g.ActiveIdIsJustActivated = false; if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) @@ -4982,6 +4993,12 @@ bool ImGui::IsItemDeactivated() return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); } +bool ImGui::IsItemDeactivatedAfterChange() +{ + ImGuiContext& g = *GImGui; + return IsItemDeactivated() && (g.ActiveIdPreviousFrameValueChanged || (g.ActiveId == 0 && g.ActiveIdValueChanged)); +} + bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; @@ -8022,6 +8039,8 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags |= ImGuiButtonFlags_Repeat; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + if (pressed) + MarkItemValueChanged(id); // Render const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); @@ -9327,6 +9346,8 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, co // Actual slider behavior + render grab ItemSize(total_bb, style.FramePadding.y); const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power); + if (value_changed) + MarkItemValueChanged(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; @@ -9380,7 +9401,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d } // Actual slider behavior + render grab - bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical); + const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical); + if (value_changed) + MarkItemValueChanged(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding @@ -9657,6 +9680,8 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, floa // Actual drag behavior ItemSize(total_bb, style.FramePadding.y); const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power); + if (value_changed) + MarkItemValueChanged(id); // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); @@ -10005,7 +10030,10 @@ bool ImGui::Checkbox(const char* label, bool* v) bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); if (pressed) + { *v = !(*v); + MarkItemValueChanged(id); + } RenderNavHighlight(total_bb, id); RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); @@ -10073,6 +10101,8 @@ bool ImGui::RadioButton(const char* label, bool active) bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + MarkItemValueChanged(id); RenderNavHighlight(total_bb, id); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); @@ -10969,6 +10999,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + if (value_changed) + MarkItemValueChanged(id); + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) return enter_pressed; else @@ -11433,6 +11466,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl g.NavDisableHighlight = true; SetNavID(id, window->DC.NavLayerCurrent); } + if (pressed) + MarkItemValueChanged(id); // Render if (hovered || selected) @@ -12025,6 +12060,9 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + if (pressed) + MarkItemValueChanged(id); + return pressed; } @@ -12319,6 +12357,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) window->DC.LastItemId = g.ActiveId; + if (value_changed) + MarkItemValueChanged(window->DC.LastItemId); + return value_changed; } @@ -12642,9 +12683,15 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } EndGroup(); + + if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) + value_changed = false; + if (value_changed) + MarkItemValueChanged(window->DC.LastItemId); + PopID(); - return value_changed && memcmp(backup_initial_col, col, components * sizeof(float)); + return value_changed; } // Horizontal separating line. @@ -12752,6 +12799,8 @@ bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float *size1 += mouse_delta; *size2 -= mouse_delta; bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + + MarkItemValueChanged(id); } // Render diff --git a/imgui.h b/imgui.h index 1dbb34e6..f6b952ac 100644 --- a/imgui.h +++ b/imgui.h @@ -506,7 +506,8 @@ 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(mouse_button) && IsItemHovered() IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) - IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active), useful for Undo/Redo patterns. + IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that requires continuous editing. + IMGUI_API bool IsItemDeactivatedAfterChange(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that requires continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). IMGUI_API bool IsAnyItemHovered(); IMGUI_API bool IsAnyItemActive(); IMGUI_API bool IsAnyItemFocused(); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 46fbbf66..9b3271aa 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1178,18 +1178,21 @@ void ImGui::ShowDemoWindow(bool* p_open) // Display the value of IsItemHovered() and other common item state functions. Note that the flags can be combined. // (because BulletText is an item itself and that would affect the output of IsItemHovered() we pass all state in a single call to simplify the code). static int item_type = 1; + static bool b = false; static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f }; ImGui::RadioButton("Text", &item_type, 0); ImGui::SameLine(); ImGui::RadioButton("Button", &item_type, 1); ImGui::SameLine(); - ImGui::RadioButton("SliderFloat", &item_type, 2); ImGui::SameLine(); - ImGui::RadioButton("ColorEdit4", &item_type, 3); ImGui::SameLine(); - ImGui::RadioButton("ListBox", &item_type, 4); + ImGui::RadioButton("CheckBox", &item_type, 2); ImGui::SameLine(); + ImGui::RadioButton("SliderFloat", &item_type, 3); ImGui::SameLine(); + ImGui::RadioButton("ColorEdit4", &item_type, 4); ImGui::SameLine(); + ImGui::RadioButton("ListBox", &item_type, 5); bool ret = false; if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button - if (item_type == 2) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item - if (item_type == 3) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) - if (item_type == 4) { const char* items[] = { "Apple", "Banana", "Cherry" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } + if (item_type == 2) { ret = ImGui::Checkbox("ITEM: CheckBox", &b); } // Testing checkbox + if (item_type == 3) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item + if (item_type == 4) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) + if (item_type == 5) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } ImGui::BulletText( "Return value = %d\n" "IsItemFocused() = %d\n" @@ -1200,6 +1203,7 @@ void ImGui::ShowDemoWindow(bool* p_open) "IsItemHovered(_RectOnly) = %d\n" "IsItemActive() = %d\n" "IsItemDeactivated() = %d\n" + "IsItemDeactivatedAfterChange() = %d\n" "IsItemVisible() = %d\n", ret, ImGui::IsItemFocused(), @@ -1210,6 +1214,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), ImGui::IsItemActive(), ImGui::IsItemDeactivated(), + ImGui::IsItemDeactivatedAfterChange(), ImGui::IsItemVisible() ); diff --git a/imgui_internal.h b/imgui_internal.h index 99718db1..cc9d16c9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -625,7 +625,9 @@ 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 ActiveIdValueChanged; bool ActiveIdPreviousFrameIsAlive; + bool ActiveIdPreviousFrameValueChanged; 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; @@ -765,7 +767,9 @@ struct ImGuiContext ActiveIdIsAlive = false; ActiveIdIsJustActivated = false; ActiveIdAllowOverlap = false; + ActiveIdValueChanged = false; ActiveIdPreviousFrameIsAlive = false; + ActiveIdPreviousFrameValueChanged = false; ActiveIdAllowNavDirFlags = 0; ActiveIdClickOffset = ImVec2(-1,-1); ActiveIdWindow = ActiveIdPreviousFrameWindow = NULL; @@ -1082,6 +1086,7 @@ namespace ImGui IMGUI_API ImGuiID GetHoveredID(); IMGUI_API void SetHoveredID(ImGuiID id); IMGUI_API void KeepAliveID(ImGuiID id); + IMGUI_API void MarkItemValueChanged(ImGuiID id); IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f);