Merge branch 'master' into docking

# Conflicts:
#	.github/workflows/build.yml
#	backends/imgui_impl_opengl2.cpp
#	backends/imgui_impl_opengl3.cpp
#	backends/imgui_impl_sdl2.cpp
#	examples/example_apple_metal4/main.mm
#	imgui.cpp
This commit is contained in:
ocornut
2026-07-16 15:56:36 +02:00
13 changed files with 358 additions and 100 deletions

View File

@@ -1332,7 +1332,7 @@ bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
if (!all_on && any_on)
{
ImGuiContext& g = *GImGui;
g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_MixedValue;
pressed = Checkbox(label, &all_on);
}
else
@@ -3729,8 +3729,9 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
const bool init = (g.TempInputId != id);
if (init)
const bool is_deactivated = (g.InputTextDeactivatedState.ID == id);
const bool is_active = (g.TempInputId == id);
if (!is_active && !is_deactivated)
ClearActiveID();
ImVec2 backup_pos = window->DC.CursorPos;
@@ -3738,13 +3739,13 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; // Using ImGuiInputTextFlags_MergedItem above will skip ItemAdd() so we poke here.
bool value_changed = InputTextEx(label, NULL, buf, (int)buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_TempInput | ImGuiInputTextFlags_AutoSelectAll, callback, user_data);
KeepAliveID(id); // Not done because of ImGuiInputTextFlags_TempInput
if (init)
if (!is_active && !is_deactivated)
{
// First frame we started displaying the InputText widget, we expect it to take the active id.
IM_ASSERT(g.ActiveId == id);
g.TempInputId = g.ActiveId;
}
if (g.ActiveId != id)
if (is_active && g.ActiveId != id)
g.TempInputId = 0;
window->DC.CursorPos = backup_pos;
return value_changed;
@@ -3769,6 +3770,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.
g.LastItemData.ItemFlags = (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (g.LastItemData.ItemFlags | ImGuiItemFlags_LiveEditOnInputText) : (g.LastItemData.ItemFlags & ~ImGuiItemFlags_LiveEditOnInputText);
if (!TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags))
return false;
@@ -3826,7 +3828,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
// Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.
// We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;
g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoMarkEdited;
flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
const bool has_step_buttons = (p_step != NULL);
@@ -3848,8 +3850,23 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
}
// Apply
bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false;
bool value_changed = false;
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar)
{
bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
if (input_edited)
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
}
else
{
//g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_Edited;
if (g.DeactivatedItemData.ID == g.LastItemData.ID)
{
//g.DeactivatedItemData.HasBeenEditedBefore = false; // Will be set below by MarkItemEdited()
//if (IsItemDeactivated()) // Should be unnecessary
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
}
}
// Step buttons
if (has_step_buttons)
@@ -4591,10 +4608,13 @@ void ImGui::InputTextDeactivateHook(ImGuiID id)
{
ImGuiContext& g = *GImGui;
ImGuiInputTextState* state = &g.InputTextState;
if (id == 0 || state->ID != id)
if (id == 0 || state->ID != id || g.ActiveId != id)
return;
if (!state->EditedBefore)
return;
//IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id);
g.InputTextDeactivatedState.ID = state->ID;
g.InputTextDeactivatedState.ElapseFrame = g.FrameCount + 1;
if (state->Flags & ImGuiInputTextFlags_ReadOnly)
{
g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.
@@ -4824,7 +4844,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
const bool user_clicked = hovered && io.MouseClicked[0];
const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id);
const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive
const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput));
const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput) && g.InputTextDeactivatedState.ID != id);
const ImGuiID scrollbar_id = (is_multiline && state != NULL) ? GetWindowScrollbarID(draw_window, ImGuiAxis_Y) : 0;
const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == scrollbar_id;
const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == scrollbar_id;
@@ -4856,7 +4876,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
state->CursorAnimReset();
// 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);
if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) //-V560
InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below.
// Take a copy of the initial buffer value.
// From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
@@ -5180,7 +5201,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (!is_new_line)
{
validated = clear_active_id = true;
if (io.ConfigInputTextEnterKeepActive && !is_multiline)
if (io.ConfigInputTextEnterKeepActive && !is_multiline && !is_ctrl_enter && !is_shift_enter)
{
// Queue reactivation, so that e.g. IsItemDeactivatedAfterEdit() will work. (#9001)
state->SelectAll(); // No need to scroll
@@ -5387,18 +5408,33 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
state->EditedBefore = state->EditedThisFrame = true;
state->CursorAnimReset();
}
}
}
// Will copy result string if modified.
// Write back result string if modified.
// FIXME-OPT: Could mark dirty state from the stb_textedit callbacks
if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
if (!is_readonly)
{
apply_new_text = state->TextSrc;
apply_new_text_length = state->TextLen;
value_changed = true;
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText)
{
// Apply when modified
if (strcmp(state->TextSrc, buf) != 0)
{
apply_new_text = state->TextSrc;
apply_new_text_length = state->TextLen;
value_changed = true;
}
}
else
{
// Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert)
value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0);
apply_new_text = value_changed ? state->TextSrc : NULL;
apply_new_text_length = value_changed ? state->TextLen : 0;
}
}
}
@@ -5406,13 +5442,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer.
if (g.InputTextDeactivatedState.ID == id)
{
if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
{
apply_new_text = g.InputTextDeactivatedState.TextA.Data;
apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
value_changed = true;
//IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
}
// The state only exists after an Edit. IsItemDeactivatedAfterEdit() is not valid in every code path (see "widgets_inputtext_status_noliveedit" test).
if ((g.ActiveId != id && IsItemDeactivated()) || (g.ActiveId == id && (flags & ImGuiInputTextFlags_TempInput)))
if (!is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
{
apply_new_text = g.InputTextDeactivatedState.TextA.Data;
apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
value_changed = true;
//IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
}
g.InputTextDeactivatedState.ID = 0;
}
@@ -5452,7 +5490,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
// Otherwise request text input ahead for next frame.
if (g.ActiveId == id && clear_active_id)
{
state->ID = 0; // To avoid InputTextDeactivateHook() unnecessarily running, which wouldn't be harmful but wasteful.
ClearActiveID();
state->ID = id;
}
// Render frame
if (!is_multiline)
@@ -5673,7 +5715,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
// For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...
Dummy(ImVec2(0.0f, text_size_y + style.FramePadding.y));
g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
g.NextItemData.ItemFlagsSet |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
EndChild();
item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
@@ -6087,7 +6129,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
ImGuiIO& io = g.IO;
const float width = CalcItemWidth();
const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
const bool is_readonly = ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
g.NextItemData.ClearFlags();
PushID(label);
@@ -8230,7 +8272,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d
if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
{
// Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)
g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
if (ms->IO.RangeSrcItem == selection_user_data)
ms->RangeSrcPassedBy = true;
//ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select.
@@ -8238,7 +8280,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d
}
else
{
g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_HasSelectionUserData;
}
}
@@ -9398,7 +9440,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
// This is only done for items for the menu set and not the full parent window.
const bool menuset_is_open = IsRootOfOpenMenuSet();
if (menuset_is_open)
g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck;
g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoWindowHoverableCheck;
// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
// However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
@@ -9623,7 +9665,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
// See BeginMenuEx() for comments about this.
const bool menuset_is_open = IsRootOfOpenMenuSet();
if (menuset_is_open)
g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck;
g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoWindowHoverableCheck;
// We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
// but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.