Merge commit '5dd98340099b3f44e06208a3124182bcf818a77e' into refact_and_update_sdl_imgui

This commit is contained in:
2024-04-15 18:19:01 +02:00
37 changed files with 2390 additions and 1087 deletions

View File

@@ -1,4 +1,4 @@
// dear imgui, v1.90.1
// dear imgui, v1.90.6 WIP
// (widgets code)
/*
@@ -75,6 +75,7 @@ Index of this file:
#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
@@ -122,9 +123,9 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
//-------------------------------------------------------------------------
// For InputTextEx()
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source);
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
//-------------------------------------------------------------------------
// [SECTION] Widgets: Text, etc.
@@ -477,6 +478,9 @@ void ImGui::BulletTextV(const char* fmt, va_list args)
// Frame N + RepeatDelay + RepeatRate*N true true - true
//-------------------------------------------------------------------------------------------------------------------------------------------------
// FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.
// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
{
ImGuiContext& g = *GImGui;
@@ -597,9 +601,9 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
g.NavDisableHighlight = true;
}
// Gamepad/Keyboard navigation
// We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
// Gamepad/Keyboard handling
// We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover)
if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
hovered = true;
if (g.NavActivateDownId == id)
@@ -621,8 +625,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
pressed = true;
SetActiveID(id, window);
g.ActiveIdSource = g.NavInputSource;
if (!(flags & ImGuiButtonFlags_NoNavFocus))
if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
SetFocusID(id, window);
if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
g.ActiveIdFromShortcut = true;
}
}
@@ -666,13 +672,19 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
{
// When activated using Nav, we hold on the ActiveID until activation button is released
if (g.NavActivateDownId != id)
if (g.NavActivateDownId == id)
held = true; // hovered == true not true as we are already likely hovered on direct activation.
else
ClearActiveID();
}
if (pressed)
g.ActiveIdHasBeenPressedBefore = true;
}
// Activation highlight (this may be a remote activation)
if (g.NavHighlightActivatedId == id)
hovered = true;
if (out_hovered) *out_hovered = hovered;
if (out_held) *out_held = held;
@@ -966,7 +978,6 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
// Click position in scrollbar normalized space (0.0f->1.0f)
const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
SetHoveredID(id);
bool seek_absolute = false;
if (g.ActiveIdIsJustActivated)
@@ -1278,12 +1289,15 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over
if (!ItemAdd(bb, 0))
return;
// Render
// Out of courtesy we accept a NaN fraction without crashing
fraction = ImSaturate(fraction);
const float fraction_not_nan = (fraction == fraction) ? fraction : 0.0f;
// Render
RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction_not_nan), bb.Max.y);
RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction_not_nan, style.FrameRounding);
// Default displaying the fraction as percentage string, but user can override it
char overlay_buf[32];
@@ -1774,7 +1788,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
// This is essentially a specialized version of BeginPopupEx()
char name[16];
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
// Set position given a custom constraint (peak into expected window size so we can position it)
// FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
@@ -1801,12 +1815,15 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
return false;
}
g.BeginComboDepth++;
return true;
}
void ImGui::EndCombo()
{
ImGuiContext& g = *GImGui;
EndPopup();
g.BeginComboDepth--;
}
// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
@@ -1982,7 +1999,7 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*
// - DataTypeGetInfo()
// - DataTypeFormatString()
// - DataTypeApplyOp()
// - DataTypeApplyOpFromText()
// - DataTypeApplyFromText()
// - DataTypeCompare()
// - DataTypeClamp()
// - GetMinimumStepAtDecimalPrecision
@@ -3413,7 +3430,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
ImStrTrimBlanks(data_buf);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited;
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
bool value_changed = false;
if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
@@ -3458,6 +3475,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
flags |= (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
bool value_changed = false;
if (p_step == NULL)
@@ -3903,9 +3921,8 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
}
// Return false to discard a character.
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source)
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
{
IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard);
unsigned int c = *p_char;
// Filter non-printable (NB: isprint is unreliable! see #2467)
@@ -3920,7 +3937,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im
apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
}
if (input_source != ImGuiInputSource_Clipboard)
if (input_source_is_clipboard == false)
{
// We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
if (c == 127)
@@ -3936,7 +3953,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im
return false;
// Generic named filters
if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)))
if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
{
// The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.
// The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
@@ -3946,7 +3963,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im
// Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.
ImGuiContext& g = *ctx;
const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint;
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific))
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
if (c == '.' || c == ',')
c = c_decimal_point;
@@ -4175,27 +4192,32 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); // state != NULL means its our state.
const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
const bool init_state = (init_make_active || user_scroll_active);
if ((init_state && g.ActiveId != id) || init_changed_specs)
if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
{
// Access state even if we don't own it yet.
state = &g.InputTextState;
state->CursorAnimReset();
state->ReloadUserBuf = false;
// Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
InputTextDeactivateHook(state->ID);
// Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
// From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
// From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
const int buf_len = (int)strlen(buf);
state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->InitialTextA.Data, buf, buf_len + 1);
if (!init_reload_from_user_buf)
{
// Take a copy of the initial buffer value.
state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->InitialTextA.Data, buf, buf_len + 1);
}
// Preserve cursor position and undo/redo stack if we come back to same widget
// FIXME: Since we reworked this on 2022/06, may want to differenciate recycle_cursor vs recycle_undostate?
bool recycle_state = (state->ID == id && !init_changed_specs);
// FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0)))
recycle_state = false;
@@ -4220,7 +4242,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
stb_textedit_initialize_state(&state->Stb, !is_multiline);
}
if (!is_multiline)
if (init_reload_from_user_buf)
{
state->Stb.select_start = state->ReloadSelectionStart;
state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd;
state->CursorClamp();
}
else if (!is_multiline)
{
if (flags & ImGuiInputTextFlags_AutoSelectAll)
select_all = true;
@@ -4250,6 +4278,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
SetKeyOwner(ImGuiKey_Enter, id);
SetKeyOwner(ImGuiKey_KeypadEnter, id);
SetKeyOwner(ImGuiKey_Home, id);
SetKeyOwner(ImGuiKey_End, id);
if (is_multiline)
@@ -4259,8 +4289,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
if (is_osx)
SetKeyOwner(ImGuiMod_Alt, id);
if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character.
SetShortcutRouting(ImGuiKey_Tab, id);
}
// We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
@@ -4389,11 +4417,20 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
// (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)
if ((flags & ImGuiInputTextFlags_AllowTabInput) && Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat) && !is_readonly)
if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
{
unsigned int c = '\t'; // Insert TAB
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
state->OnKeyPressed((int)c);
if (Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat))
{
unsigned int c = '\t'; // Insert TAB
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
state->OnKeyPressed((int)c);
}
// FIXME: Implement Shift+Tab
/*
if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, id, ImGuiInputFlags_Repeat))
{
}
*/
}
// Process regular text input (before we check for Return because using some IME will effectively send a Return?)
@@ -4408,7 +4445,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
unsigned int c = (unsigned int)io.InputQueueCharacters[n];
if (c == '\t') // Skip Tab, see above.
continue;
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
state->OnKeyPressed((int)c);
}
@@ -4491,7 +4528,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
else if (!is_readonly)
{
unsigned int c = '\n'; // Insert new line
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
state->OnKeyPressed((int)c);
}
}
@@ -4558,7 +4595,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
unsigned int c;
s += ImTextCharFromUtf8(&c, s, NULL);
if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
continue;
clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
}
@@ -6308,7 +6345,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
// Render
const ImU32 text_col = GetColorU32(ImGuiCol_Text);
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact;
if (display_frame)
{
// Framed type
@@ -6611,7 +6648,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
}
if (g.NavId == id)
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding);
if (span_all_columns)
{
@@ -6895,6 +6932,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
RenderText(label_pos, label);
window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
AlignTextToFramePadding();
}
BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
@@ -7491,6 +7529,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
PopItemFlag();
bool want_open = false;
bool want_open_nav_init = false;
bool want_close = false;
if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
{
@@ -7533,8 +7572,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
want_open = true;
if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
{
want_open = true;
want_open = want_open_nav_init = true;
NavMoveRequestCancel();
NavRestoreHighlightAfterMove();
}
}
else
@@ -7566,13 +7606,13 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
{
// Don't reopen/recycle same menu level in the same frame, first close the other menu and yield for a frame.
// Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.
OpenPopup(label);
}
else if (want_open)
{
menu_is_open = true;
OpenPopup(label);
OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
}
if (menu_is_open)
@@ -7584,6 +7624,14 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
PopStyleVar();
if (menu_is_open)
{
// Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
// Perform an init request in the case the popup was already open (via a previous mouse hover)
if (want_open && want_open_nav_init && !g.NavInitRequest)
{
FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
NavInitWindow(g.CurrentWindow, false);
}
// Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
// (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
g.LastItemData = last_item_in_parent;