InputText: Fixed a tricky edge case, ensuring value is always written back on the frame where IsItemDeactivated() returns true (#4714)

Altered ItemAdd() clipping rule to keep previous-frame ActiveId unclipped to support that late commit.

Also, MarkItemEdited() may in theory need to do:
if (g.ActiveIdPreviousFrame == id)
        g.ActiveIdPreviousFrameHasBeenEditedBefore = true;
But this should already be set so not adding now.
This commit is contained in:
ocornut
2021-12-08 20:54:04 +01:00
parent 314e6443c9
commit 5a2b1e8482
5 changed files with 79 additions and 13 deletions

View File

@ -3999,6 +3999,21 @@ static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* st
p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i);
}
// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)
// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)
// It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick,
// but that more likely be attractive when we do have _NoLiveEdit flag available.
void ImGui::InputTextDeactivateHook(ImGuiID id)
{
ImGuiContext& g = *GImGui;
ImGuiInputTextState* state = &g.InputTextState;
if (id == 0 || state->ID != id)
return;
g.InputTextDeactivatedState.ID = state->ID;
g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1);
memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1);
}
// Edit a string of text
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
@ -4113,6 +4128,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
state = &g.InputTextState;
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);
// 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)
const int buf_len = (int)strlen(buf);
@ -4525,6 +4543,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// Push records into the undo stack so we can CTRL+Z the revert operation itself
apply_new_text = state->InitialTextA.Data;
apply_new_text_length = state->InitialTextA.Size - 1;
value_changed = true;
ImVector<ImWchar> w_text;
if (apply_new_text_length > 0)
{
@ -4638,10 +4657,24 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
apply_new_text = state->TextA.Data;
apply_new_text_length = state->CurLenA;
value_changed = true;
}
}
}
// Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
if (g.InputTextDeactivatedState.ID == id)
{
if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly)
{
apply_new_text = g.InputTextDeactivatedState.TextA.Data;
apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
value_changed |= (strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0);
//IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
}
g.InputTextDeactivatedState.ID = 0;
}
// Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
if (apply_new_text != NULL)
{
@ -4669,7 +4702,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
value_changed = true;
}
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)