mirror of
https://github.com/ocornut/imgui.git
synced 2024-11-28 09:30:56 +01:00
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:
parent
314e6443c9
commit
5a2b1e8482
@ -39,6 +39,11 @@ Breaking Changes:
|
|||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
|
- InputText: Fixed a tricky edge case, ensuring value is always written back on the
|
||||||
|
frame where IsItemDeactivated() returns true, in order to allow usage without user
|
||||||
|
retaining underlying data. While we don't really want to encourage user not retaining
|
||||||
|
underlying data, in the absence of a "late commit" behavior/flag we understand it may
|
||||||
|
be desirable to take advantage of this trick. (#4714)
|
||||||
- Backends: OpenGL3: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530)
|
- Backends: OpenGL3: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530)
|
||||||
- Backends: GLFW: Fixed key modifiers handling on secondary viewports. (#6248, #6034) [@aiekick]
|
- Backends: GLFW: Fixed key modifiers handling on secondary viewports. (#6248, #6034) [@aiekick]
|
||||||
- Examples: Windows: Added 'misc/debuggers/imgui.natstepfilter' file to all Visual Studio projects,
|
- Examples: Windows: Added 'misc/debuggers/imgui.natstepfilter' file to all Visual Studio projects,
|
||||||
|
27
imgui.cpp
27
imgui.cpp
@ -3587,6 +3587,7 @@ void ImGui::Shutdown()
|
|||||||
g.ClipboardHandlerData.clear();
|
g.ClipboardHandlerData.clear();
|
||||||
g.MenusIdSubmittedThisFrame.clear();
|
g.MenusIdSubmittedThisFrame.clear();
|
||||||
g.InputTextState.ClearFreeMemory();
|
g.InputTextState.ClearFreeMemory();
|
||||||
|
g.InputTextDeactivatedState.ClearFreeMemory();
|
||||||
|
|
||||||
g.SettingsWindows.clear();
|
g.SettingsWindows.clear();
|
||||||
g.SettingsHandlers.clear();
|
g.SettingsHandlers.clear();
|
||||||
@ -3759,8 +3760,11 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
|
|||||||
{
|
{
|
||||||
ImGuiContext& g = *GImGui;
|
ImGuiContext& g = *GImGui;
|
||||||
|
|
||||||
|
// Clear previous active id
|
||||||
|
if (g.ActiveId != 0)
|
||||||
|
{
|
||||||
// While most behaved code would make an effort to not steal active id during window move/drag operations,
|
// While most behaved code would make an effort to not steal active id during window move/drag operations,
|
||||||
// we at least need to be resilient to it. Cancelling the move is rather aggressive and users of 'master' branch
|
// we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch
|
||||||
// may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.
|
// may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.
|
||||||
if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId)
|
if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId)
|
||||||
{
|
{
|
||||||
@ -3768,6 +3772,13 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
|
|||||||
g.MovingWindow = NULL;
|
g.MovingWindow = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This could be written in a more general way (e.g associate a hook to ActiveId),
|
||||||
|
// but since this is currently quite an exception we'll leave it as is.
|
||||||
|
// One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveId()
|
||||||
|
if (g.InputTextState.ID == g.ActiveId)
|
||||||
|
InputTextDeactivateHook(g.ActiveId);
|
||||||
|
}
|
||||||
|
|
||||||
// Set active id
|
// Set active id
|
||||||
g.ActiveIdIsJustActivated = (g.ActiveId != id);
|
g.ActiveIdIsJustActivated = (g.ActiveId != id);
|
||||||
if (g.ActiveIdIsJustActivated)
|
if (g.ActiveIdIsJustActivated)
|
||||||
@ -3840,11 +3851,17 @@ void ImGui::MarkItemEdited(ImGuiID id)
|
|||||||
// This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
|
// This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
|
||||||
// ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.
|
// ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.
|
||||||
ImGuiContext& g = *GImGui;
|
ImGuiContext& g = *GImGui;
|
||||||
IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive);
|
if (g.ActiveId == id || g.ActiveId == 0)
|
||||||
IM_UNUSED(id); // Avoid unused variable warnings when asserts are compiled out.
|
{
|
||||||
//IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
|
|
||||||
g.ActiveIdHasBeenEditedThisFrame = true;
|
g.ActiveIdHasBeenEditedThisFrame = true;
|
||||||
g.ActiveIdHasBeenEditedBefore = true;
|
g.ActiveIdHasBeenEditedBefore = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343)
|
||||||
|
// We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714)
|
||||||
|
IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id);
|
||||||
|
|
||||||
|
//IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
|
||||||
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9293,7 +9310,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu
|
|||||||
// return false;
|
// return false;
|
||||||
const bool is_rect_visible = bb.Overlaps(window->ClipRect);
|
const bool is_rect_visible = bb.Overlaps(window->ClipRect);
|
||||||
if (!is_rect_visible)
|
if (!is_rect_visible)
|
||||||
if (id == 0 || (id != g.ActiveId && id != g.NavId))
|
if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId))
|
||||||
if (!g.LogEnabled)
|
if (!g.LogEnabled)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
2
imgui.h
2
imgui.h
@ -23,7 +23,7 @@
|
|||||||
// Library Version
|
// Library Version
|
||||||
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345')
|
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345')
|
||||||
#define IMGUI_VERSION "1.89.5 WIP"
|
#define IMGUI_VERSION "1.89.5 WIP"
|
||||||
#define IMGUI_VERSION_NUM 18941
|
#define IMGUI_VERSION_NUM 18942
|
||||||
#define IMGUI_HAS_TABLE
|
#define IMGUI_HAS_TABLE
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -130,6 +130,7 @@ struct ImGuiDataVarInfo; // Variable information (e.g. to avoid style
|
|||||||
struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum
|
struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum
|
||||||
struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup()
|
struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup()
|
||||||
struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box
|
struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box
|
||||||
|
struct ImGuiInputTextDeactivateData;// Short term storage to backup text of a deactivating InputText() while another is stealing active id
|
||||||
struct ImGuiLastItemData; // Status storage for last submitted items
|
struct ImGuiLastItemData; // Status storage for last submitted items
|
||||||
struct ImGuiLocEntry; // A localization entry.
|
struct ImGuiLocEntry; // A localization entry.
|
||||||
struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only
|
struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only
|
||||||
@ -1048,6 +1049,15 @@ struct IMGUI_API ImGuiMenuColumns
|
|||||||
void CalcNextTotalWidth(bool update_offsets);
|
void CalcNextTotalWidth(bool update_offsets);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Internal temporary state for deactivating InputText() instances.
|
||||||
|
struct IMGUI_API ImGuiInputTextDeactivatedState
|
||||||
|
{
|
||||||
|
ImGuiID ID; // widget id owning the text state (which just got deactivated)
|
||||||
|
ImVector<char> TextA; // text buffer
|
||||||
|
|
||||||
|
ImGuiInputTextDeactivatedState() { memset(this, 0, sizeof(*this)); }
|
||||||
|
void ClearFreeMemory() { ID = 0; TextA.clear(); }
|
||||||
|
};
|
||||||
// Internal state of the currently focused/edited text input box
|
// Internal state of the currently focused/edited text input box
|
||||||
// For a given item ID, access with ImGui::GetInputTextState()
|
// For a given item ID, access with ImGui::GetInputTextState()
|
||||||
struct IMGUI_API ImGuiInputTextState
|
struct IMGUI_API ImGuiInputTextState
|
||||||
@ -1935,6 +1945,7 @@ struct ImGuiContext
|
|||||||
// Widget state
|
// Widget state
|
||||||
ImVec2 MouseLastValidPos;
|
ImVec2 MouseLastValidPos;
|
||||||
ImGuiInputTextState InputTextState;
|
ImGuiInputTextState InputTextState;
|
||||||
|
ImGuiInputTextDeactivatedState InputTextDeactivatedState;
|
||||||
ImFont InputTextPasswordFont;
|
ImFont InputTextPasswordFont;
|
||||||
ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc.
|
ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc.
|
||||||
ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets
|
ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets
|
||||||
@ -3131,6 +3142,7 @@ namespace ImGui
|
|||||||
|
|
||||||
// InputText
|
// InputText
|
||||||
IMGUI_API bool InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
IMGUI_API bool InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||||
|
IMGUI_API void InputTextDeactivateHook(ImGuiID id);
|
||||||
IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags);
|
IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags);
|
||||||
IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL);
|
IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL);
|
||||||
inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); }
|
inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); }
|
||||||
|
@ -3999,6 +3999,21 @@ static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* st
|
|||||||
p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i);
|
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
|
// Edit a string of text
|
||||||
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
|
// - 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
|
// 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 = &g.InputTextState;
|
||||||
state->CursorAnimReset();
|
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)
|
// 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 ignoring the content of 'buf' (unless we are in read-only mode)
|
||||||
const int buf_len = (int)strlen(buf);
|
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
|
// Push records into the undo stack so we can CTRL+Z the revert operation itself
|
||||||
apply_new_text = state->InitialTextA.Data;
|
apply_new_text = state->InitialTextA.Data;
|
||||||
apply_new_text_length = state->InitialTextA.Size - 1;
|
apply_new_text_length = state->InitialTextA.Size - 1;
|
||||||
|
value_changed = true;
|
||||||
ImVector<ImWchar> w_text;
|
ImVector<ImWchar> w_text;
|
||||||
if (apply_new_text_length > 0)
|
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 = state->TextA.Data;
|
||||||
apply_new_text_length = state->CurLenA;
|
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)
|
// Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
|
||||||
if (apply_new_text != NULL)
|
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.
|
// 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));
|
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)
|
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
|
||||||
|
Loading…
Reference in New Issue
Block a user