update Dear ImGui to 1.89.9

this is part of an upgrade process to the latest version of Dear ImGui
it will take a while as I am stepping through individual versions due
to the heavy modifications we've made for Furnace
This commit is contained in:
tildearrow 2025-08-09 20:45:33 -05:00
parent 0d1acc41bf
commit b3d60e512c
19 changed files with 596 additions and 409 deletions

View file

@ -1,4 +1,4 @@
// dear imgui, v1.89.8
// dear imgui, v1.89.9
// (widgets code)
/*
@ -120,7 +120,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
//-------------------------------------------------------------------------
// For InputTextEx()
static bool InputTextFilterCharacter(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, 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, const bool stop_on_new_line = false, const float wrap_width = 0.0f, const bool keep_trailing_blanks = false);
static ImVec2 FindCharPosition(const ImWchar* text_begin, const ImWchar* char_position, const ImWchar* text_end, const float wrap_width);
@ -675,7 +675,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
// Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags);
if (flags & ImGuiButtonFlags_AllowOverlap)
item_flags |= ImGuiItemflags_AllowOverlap;
item_flags |= ImGuiItemFlags_AllowOverlap;
if (flags & ImGuiButtonFlags_Repeat)
item_flags |= ImGuiItemFlags_ButtonRepeat;
@ -990,14 +990,14 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
// Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825)
// This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible?
const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
ImRect bb_interact = bb;
const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
if (area_to_visible_ratio < 1.5f)
bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f));
// Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
// (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
// (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer).
bool is_clipped = !ItemAdd(bb_interact, id);
bool hovered, held;
@ -1027,22 +1027,24 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
ItemAdd(bb, id);
ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
bool is_clipped = !ItemAdd(bb, id);
bool hovered, held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
if (is_clipped)
return pressed;
// Render
//bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
ImU32 text_col = GetColorU32(ImGuiCol_Text);
if (hovered || held)
window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, bg_col);
window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, bg_col);
if (dock_node)
RenderArrowDockMenu(window->DrawList, bb.Min + g.Style.FramePadding, g.FontSize, text_col);
RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col);
else
RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
// Switch to moving the window after mouse is moved beyond the initial drag threshold
if (IsItemActive() && IsMouseDragging(0))
@ -2967,14 +2969,14 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
const SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it.
// Calculate bounds
const float grab_padding = 2.0f; // FIXME: Should be part of style.
const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
float grab_sz = style.GrabMinSize;
if (!is_floating_point && v_range >= 0) // v_range < 0 may happen on integer overflows
grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
if (!is_floating_point && v_range_f >= 0.0f) // v_range_f < 0 may happen on integer overflows
grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
grab_sz = ImMin(grab_sz, slider_sz);
const float slider_usable_sz = slider_sz - grab_sz;
const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
@ -3043,8 +3045,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
}
else
{
if ((v_range >= -100.0f && v_range <= 100.0f) || tweak_slow)
input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)
input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Gamepad/keyboard tweak speeds in integer steps
else
input_delta /= 100.0f;
}
@ -3091,6 +3093,10 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
}
}
if (set_new_value)
if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
set_new_value = false;
if (set_new_value)
{
TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
@ -3136,11 +3142,6 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type
// Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
// Those are the things we can do easily outside the SliderBehaviorT<> template, saves code generation.
ImGuiContext& g = *GImGui;
if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
return false;
switch (data_type)
{
case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
@ -4352,7 +4353,7 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
}
// Return false to discard a character.
static bool InputTextFilterCharacter(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, ImGuiInputSource input_source)
{
IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard);
unsigned int c = *p_char;
@ -4391,10 +4392,13 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
// The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
// We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
// Change the default decimal_point with:
// ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
// ImGui::GetIO()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
// 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 = *GImGui;
const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint;
ImGuiContext& g = *ctx;
const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint;
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific))
if (c == '.' || c == ',')
c = c_decimal_point;
// Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
// While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
@ -4847,7 +4851,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if ((flags & ImGuiInputTextFlags_AllowTabInput) && Shortcut(ImGuiKey_Tab, id) && !is_readonly)
{
unsigned int c = '\t'; // Insert TAB
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
state->OnKeyPressed((int)c);
}
@ -4863,7 +4867,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(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
state->OnKeyPressed((int)c);
}
@ -4946,7 +4950,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(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
state->OnKeyPressed((int)c);
}
}
@ -5013,7 +5017,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
unsigned int c;
s += ImTextCharFromUtf8(&c, s, NULL);
if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard))
continue;
clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
}
@ -6342,6 +6346,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
return;
ImGuiContext& g = *GImGui;
g.LockMarkEdited++;
ImGuiColorEditFlags opts = g.ColorEditOptions;
if (allow_opt_inputs)
{
@ -6384,6 +6389,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
g.ColorEditOptions = opts;
EndPopup();
g.LockMarkEdited--;
}
void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
@ -6393,6 +6399,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl
if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
return;
ImGuiContext& g = *GImGui;
g.LockMarkEdited++;
if (allow_opt_picker)
{
ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
@ -6422,6 +6429,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl
CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
}
EndPopup();
g.LockMarkEdited--;
}
//-------------------------------------------------------------------------
@ -6618,18 +6626,29 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0)
interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f;
// Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
// Compute open and multi-select states before ItemAdd() as it clear NextItem data.
bool is_open = TreeNodeUpdateNextOpen(id, flags);
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
bool item_add = ItemAdd(interact_bb, id);
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
g.LastItemData.DisplayRect = frame_bb;
// If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled:
// Store data for the current depth to allow returning to this node from any child item.
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
{
g.NavTreeNodeStack.resize(g.NavTreeNodeStack.Size + 1);
ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back();
nav_tree_node_data->ID = id;
nav_tree_node_data->InFlags = g.LastItemData.InFlags;
nav_tree_node_data->NavRect = g.LastItemData.NavRect;
window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
}
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
if (!item_add)
{
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
@ -6639,7 +6658,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
}
ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemflags_AllowOverlap))
if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap))
button_flags |= ImGuiButtonFlags_AllowOverlap;
if (!is_leaf)
button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
@ -6799,12 +6818,14 @@ void ImGui::TreePop()
ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
// Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask))
{
SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect());
NavMoveRequestCancel();
}
if (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask) // Only set during request
{
ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back();
IM_ASSERT(nav_tree_node_data->ID == window->IDStack.back());
if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, nav_tree_node_data);
g.NavTreeNodeStack.pop_back();
}
window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1;
IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
@ -6866,8 +6887,8 @@ bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFl
ImGuiContext& g = *GImGui;
ImGuiLastItemData last_item_backup = g.LastItemData;
float button_size = g.FontSize;
float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
float button_y = g.LastItemData.Rect.Min.y;
float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);
float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
*p_visible = false;
@ -6979,7 +7000,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemflags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
const bool was_selected = selected;
bool hovered, held;
@ -7018,7 +7039,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : (hovered && !(g.IO.ConfigFlags&ImGuiConfigFlags_NoHoverColors)) ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
}
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
if (g.NavId == id)
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
if (span_all_columns && window->DC.CurrentColumns)
PopColumnsBackground();
@ -9185,7 +9207,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
}
const float button_sz = g.FontSize;
const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x * 2.0f - button_sz), bb.Min.y);
const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);
// Close Button & Unsaved Marker
// We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
@ -9203,10 +9225,8 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
if (close_button_visible)
{
ImGuiLastItemData last_item_backup = g.LastItemData;
PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding);
if (CloseButton(close_button_id, button_pos))
close_button_pressed = true;
PopStyleVar();
g.LastItemData = last_item_backup;
// Close with middle mouse button