diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index bfccaabca..4436d2c63 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -44,9 +44,15 @@ Breaking changes: Other changes: - Error Handling: Enabled/improved error recovery systems. (#1651, #5654) + - Error recovery is provided as a way to facilitate: + - Recovery after a programming error. Native code or scripting language (the later + tends to facilitate iterating on code while running). + - Recovery after running an exception handler or any error processing which may skip code + after an error has been detected. - Error recovery is not perfect nor guaranteed! It is a feature to ease development. + You not are not supposed to rely on it in the course of a normal application run. - Functions that support error recovery are using IM_ASSERT_USER_ERROR() instead of IM_ASSERT(). - - You not are not supposed to rely on it in the course of a normal application run. + - By design, we do not allow error recovery to be 100% silent. One of the options needs to be enabled! - Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers. Surface errors to programmers in less agressive ways. - Always ensure that on programmers seats you have at minimum Asserts or Tooltips enabled @@ -65,6 +71,12 @@ Other changes: - Scrollbar: Shift+Click scroll to clicked location (pre-1.90.8 default). (#8002, #7328) - Scrollbar: added io.ConfigScrollbarScrollByPage setting (default to true). (#8002, #7328) Set io.ConfigScrollbarScrollByPage=false to enforce always scrolling to clicked location. +- Tooltips, Drag and Drop: Fixed an issue where the fallback drag and drop payload tooltip + appeared during drag and drop release. +- Tooltips, Drag and Drop: Stabilized name of drag and drop tooltip window so that + transitioning from an item tooltip to a drag tooltip doesn't leak window auto-sizing + info from one to the other. (#8036) +- Tooltips: Tooltips triggered from touch inputs are positionned above the item. (#8036) - Backends: SDL3: Update for API changes: SDL_bool removal. SDL_INIT_TIMER removal. - Backends: WebGPU: Fixed DAWN api change using WGPUStringView in WGPUShaderSourceWGSL. (#8009, #8010) [@blitz-research] diff --git a/imgui.cpp b/imgui.cpp index 542c27e4e..0cb2379f3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1147,7 +1147,9 @@ static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduc static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. // Tooltip offset -static const ImVec2 TOOLTIP_DEFAULT_OFFSET = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale +static const ImVec2 TOOLTIP_DEFAULT_OFFSET_MOUSE = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale +static const ImVec2 TOOLTIP_DEFAULT_OFFSET_TOUCH = ImVec2(0, -20); // Multiplied by g.Style.MouseCursorScale +static const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f); // Multiplied by g.Style.MouseCursorScale // Docking static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport. @@ -4042,6 +4044,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) DisabledStackSize = 0; LockMarkEdited = 0; TooltipOverrideCount = 0; + TooltipPreviousWindow = NULL; PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission @@ -5334,6 +5337,7 @@ void ImGui::NewFrame() g.DragDropWithinSource = false; g.DragDropWithinTarget = false; g.DragDropHoldJustPressedId = 0; + g.TooltipPreviousWindow = NULL; // Close popups on focus lost (currently wip/opt-in) //if (g.IO.AppFocusLost) @@ -5767,7 +5771,7 @@ void ImGui::EndFrame() // in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot // (e.g. end of your item loop, or before EndFrame) by reading payload data. // In the typical case, the contents of drag tooltip should be possible to infer solely from payload data. - if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + if (g.DragDropActive && g.DragDropSourceFrameCount + 1 < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) { g.DragDropWithinSource = true; SetTooltip("..."); @@ -8061,6 +8065,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Clear hit test shape every frame window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0; + if (flags & ImGuiWindowFlags_Tooltip) + g.TooltipPreviousWindow = window; + // Pressing CTRL+C while holding on a window copy its content to the clipboard // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. // Maybe we can support CTRL+C on every element? @@ -11192,7 +11199,7 @@ void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryStat { ImGuiContext& g = *GImGui; - while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) + while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) //-V1044 { IM_ASSERT_USER_ERROR(0, "Missing EndTable()"); EndTable(); @@ -11206,7 +11213,7 @@ void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryStat IM_ASSERT_USER_ERROR(0, "Missing EndTabBar()"); EndTabBar(); } - while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window) + while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window) //-V1044 { IM_ASSERT_USER_ERROR(0, "Missing EndMultiSelect()"); EndMultiSelect(); @@ -11239,7 +11246,7 @@ void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryStat } } IM_ASSERT(g.DisabledStackSize == state_in->SizeOfDisabledStack); - while (g.ColorStack.Size > state_in->SizeOfColorStack) + while (g.ColorStack.Size > state_in->SizeOfColorStack) //-V1044 { IM_ASSERT_USER_ERROR(0, "Missing PopStyleColor()"); PopStyleColor(); @@ -12181,7 +12188,8 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext { ImGuiContext& g = *GImGui; - if (g.DragDropWithinSource || g.DragDropWithinTarget) + const bool is_dragdrop_tooltip = g.DragDropWithinSource || g.DragDropWithinTarget; + if (is_dragdrop_tooltip) { // Drag and Drop tooltips are positioning differently than other tooltips: // - offset visibility to increase visibility around mouse. @@ -12189,24 +12197,30 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext // We call SetNextWindowPos() to enforce position and disable clamping. // See FindBestWindowPosForPopup() for positionning logic of other tooltips (not drag and drop ones). //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; - ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET * g.Style.MouseCursorScale; + const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen); if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) - SetNextWindowPos(tooltip_pos); + { + ImVec2 tooltip_pos = is_touchscreen ? (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale) : (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale); + ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f); + SetNextWindowPos(tooltip_pos, ImGuiCond_None, tooltip_pivot); + } + SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( tooltip_flags |= ImGuiTooltipFlags_OverridePrevious; } - char window_name[16]; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); - if (tooltip_flags & ImGuiTooltipFlags_OverridePrevious) - if (ImGuiWindow* window = FindWindowByName(window_name)) - if (window->Active) - { - // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. - SetWindowHiddenAndSkipItemsForCurrentFrame(window); - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); - } + const char* window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d"; + char window_name[32]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount); + if ((tooltip_flags & ImGuiTooltipFlags_OverridePrevious) && g.TooltipPreviousWindow != NULL && g.TooltipPreviousWindow->Active) + { + // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. + //IMGUI_DEBUG_LOG("[tooltip] '%s' already active, using +1 for this frame\n", window_name); + SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow); + ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking; Begin(window_name, NULL, flags | extra_window_flags); // 2023-03-09: Added bool return value to the API, but currently always returning true. @@ -12808,18 +12822,30 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) if (window->Flags & ImGuiWindowFlags_Tooltip) { // Position tooltip (always follows mouse + clamp within outer boundaries) - // Note that drag and drop tooltips are NOT using this path: BeginTooltipEx() manually sets their position. - // In theory we could handle both cases in same location, but requires a bit of shuffling as drag and drop tooltips are calling SetWindowPos() leading to 'window_pos_set_by_api' being set in Begin() + // FIXME: + // - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested position (so touch screen path doesn't use it by default). + // - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position. + // - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of shuffling + // as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin(). IM_ASSERT(g.CurrentWindow == window); const float scale = g.Style.MouseCursorScale; const ImVec2 ref_pos = NavCalcPreferredRefPos(); - const ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET * scale; + + if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen) + { + ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size); + if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size))) + return tooltip_pos; + } + + ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale; ImRect r_avoid; if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); else r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255)); + return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); } IM_ASSERT(0); @@ -20896,7 +20922,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugBreakClearData(); // Basic info - Text("Dear ImGui %s", GetVersion()); + Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); if (g.ContextName[0] != 0) { SameLine(); diff --git a/imgui.h b/imgui.h index 6c6219463..33051d6c4 100644 --- a/imgui.h +++ b/imgui.h @@ -29,7 +29,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.91.3 WIP" -#define IMGUI_VERSION_NUM 19123 +#define IMGUI_VERSION_NUM 19124 #define IMGUI_HAS_TABLE #define IMGUI_HAS_VIEWPORT // Viewport WIP branch #define IMGUI_HAS_DOCK // Docking WIP branch @@ -2355,18 +2355,20 @@ struct ImGuiIO // Debug options //------------------------------------------------------------------ - // Options to configure how we handle recoverable errors [EXPERIMENTAL] + // Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL] + // - Error recovery is provided as a way to facilitate: + // - Recovery after a programming error (native code or scripting language - the later tends to facilitate iterating on code while running). + // - Recovery after running an exception handler or any error processing which may skip code after an error has been detected. // - Error recovery is not perfect nor guaranteed! It is a feature to ease development. + // You not are not supposed to rely on it in the course of a normal application run. // - Functions that support error recovery are using IM_ASSERT_USER_ERROR() instead of IM_ASSERT(). - // - You not are not supposed to rely on it in the course of a normal application run. - // - Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers. - // - Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API calls! + // - By design, we do NOT allow error recovery to be 100% silent. One of the three options needs to be checked! + // - Always ensure that on programmers seats you have at minimum Asserts or Tooltips enabled when making direct imgui API calls! // Otherwise it would severely hinder your ability to catch and correct mistakes! - // Read https://github.com/ocornut/imgui/wiki/Error-Handling for details about typical usage scenarios: + // Read https://github.com/ocornut/imgui/wiki/Error-Handling for details. // - Programmer seats: keep asserts (default), or disable asserts and keep error tooltips (new and nice!) - // - Non-programmer seats: maybe disable asserts, but make sure errors are resurfaced (visible log entries, use callback etc.) - // - Recovery after error from scripting language: record stack sizes before running script, disable assert, trigger breakpoint from ErrorCallback, recover with ErrorRecoveryTryToRecoverState(), restore settings. - // - Recovery after an exception handler: record stack sizes before try {} block, disable assert, set log callback, recover with ErrorRecoveryTryToRecoverState(), restore settings. + // - Non-programmer seats: maybe disable asserts, but make sure errors are resurfaced (tooltips, visible log entries, use callback etc.) + // - Recovery after error/exception: record stack sizes with ErrorRecoveryStoreState(), disable assert, set log callback (to e.g. trigger high-level breakpoint), recover with ErrorRecoveryTryToRecoverState(), restore settings. bool ConfigErrorRecovery; // = true // Enable error recovery support. Some errors won't be detected and lead to direct crashes if recovery is disabled. bool ConfigErrorRecoveryEnableAssert; // = true // Enable asserts on recoverable error. By default call IM_ASSERT() when returning from a failing IM_ASSERT_USER_ERROR() bool ConfigErrorRecoveryEnableDebugLog; // = true // Enable debug log output on recoverable errors. diff --git a/imgui_internal.h b/imgui_internal.h index 62b1f746f..0541032a3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2486,6 +2486,7 @@ struct ImGuiContext short DisabledStackSize; short LockMarkEdited; short TooltipOverrideCount; + ImGuiWindow* TooltipPreviousWindow; // Window of last tooltip submitted during the frame ImVector ClipboardHandlerData; // If no custom clipboard handler is defined ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest() @@ -3615,6 +3616,7 @@ namespace ImGui IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name); IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset); IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d162f442e..5a65c691a 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9682,6 +9682,13 @@ void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) tab_bar->NextSelectedTabId = tab->ID; } +void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name) +{ + IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars + ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL); + tab_bar->NextSelectedTabId = tab_id; +} + void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset) { IM_ASSERT(offset != 0);