-
-
Notifications
You must be signed in to change notification settings - Fork 10.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strange behavior with InputXXX widgets editing stack variables #4714
Comments
With Dear ImGui you're responsible for maintaining the backing store for values being edited. In your code you're constantly overwriting You need to be more picky about when you set If If The reason it seems to work at all is (The reason for why it seems to work sometimes and not others is more complicated. I started to explain it but it was turning into a wall of text. The short version is |
So I guess I have to duplicate every single edited value. My use case is a property grid. Where I need to only change the actual value once the editing operation is complete or else my undo/redo stack explodes. Just so I understand, I basically need to build a retained mode wrapper on top of imgui to solve this issue? Cache a value per property edited, have the imgui widgets modify that, then reflect that back to actual edited type? |
Yeah that sounds like a solid plan. Another alternative (if the undo/redo stack issue is your main concern) is to have your undo/redo stack only actually commit on |
This looks like a bug with Solving it is not exactly straightforward. If we wanted to patch code as it is now, we would have to introduce a 1 frame lag, because when previous widget gets activated it should realize that another Not all is lost though. There is a rework of |
Hello,
I heard you say variation of that half a dozen time already in other places, it might generally be more constructive to think in term of fixing bugs or implement features on library side, given that the library purpose is to minimize work for the user. It might also be misleading to use blanket term "retained mode wrapper" as it can mean so many things. e.g. There are many situation where you only need storage for 1 element (e.g. there can be only one active item), which tend to be very simple to implement. There's mixture of things here. (1) We definitively want the equivalent of _NoLiveEdit flag for inputs (whatever the final name is). It may generally a good default for numerical value (#701). The friction against implementing the general purpose version (which include
I don't think there is, it can be confirmed with a test like: static ImVec4 w;
static int activated_frame = -1;
static int activated_field = 0;
static int deactivated_frame = -1;
static int deactivated_field = 0;
ImGui::InputFloat("w.x", &w.x, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 0; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 0; }
ImGui::InputFloat("w.y", &w.y, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 1; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 1; }
ImGui::InputFloat("w.z", &w.z, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 2; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 2; }
ImGui::InputFloat("w.w", &w.w, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 3; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 3; }
ImGui::Text("w %.3f %.3f %.3f %.3f", w.x, w.y, w.z, w.w);
ImGui::Text("Activated frame %d, field %d", activated_frame, activated_field);
ImGui::Text("Deactivated frame %d, field %d", deactivated_frame, deactivated_field); The problem is indeed that the behavior of that backing copy of the data in InputText() described in @PathogenDavid is a quite ill-defined. People have been relying on it for some idioms but it has issues and here you are stumbling on one.
Here I think we should fix this on both side:
Either would solve the problem but I think both are ultimately desirable.
On my last game the undo stack was stored in term of only storing the new value.
It would store:
Which isn't as nice** but I guess that's one of the reason I side-stepped the _NoLiveEdit thing for a while. My bad. |
We would absolutely avoid a frame of lag here primarily because it would likely have side-effect on other logic.
We can possibly implement this even before the larger merge (I guess I am going to try and if it's too messy I'll postpone to when we merge the InputText change). |
Brain fart here: the _NoLiveEdit would facilitate use without a stack/temp variable but would rely on us performing a last write on the Deactivated frame. So first item is 100% required first. |
My bad, I assumed from the first reply that the issue was on my side. So I started to work around it by providing a fixed value for imgui to work on and copying it back to the actual type when I received the signal the edit operation was complete. Ideally if I didn't have to do any of that it would be amazing and remove a ton of code on my end (I needed it working fast, so I had no choice but to work around it). Ideally if this can be resolved on the library side this is obviously way better. I think if I understand correctly what the _noliveedit flag would do, then it would solve 99% of my issues. |
Apologies for lack of reaction on this, I intended to get the fix for 1.86 but didn't, I hope it'll come soon. I basically have a 99% fix for this nearly ready but there are subtle edge cases that are problematic (e.g. programmatic change of focus which would be equivalent of clicking on an earlier widget, making the previously active widget clipped due to instant scrolling, making it unable to "commit" its data). The work on clipper changes for 1.86 should allow that stuff to be fixed, marking specific ID or zone as non-clippable for e.g an extra frame. |
… 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.
I have pushed a fix for this with 5a2b1e8. Automated regression test: To be clear, I believe this is improving support for a sort-of-out-of-specs features of accepting user not retaining their data, but I do understand that in the absence of a "late commit" / "no live edit" flag it may be desirable to take advantage of that. When/if we add that option down the line and depending on the state of the InputText()-rewrite we MIGHT want or need to ditch support for user not retaining their data, if that happens I'll post instructions else and elsewhere. Below a interactive test-bed for this: ImGui::Begin("#4714");
ImGui::Button("Dummy"); // Dummy button to test earlier item stealing active id without reusing InputText() internal buffer.
{
static ImVec4 color0(1.0f, 0.0f, 0.0f, 1.0f);
static ImVec4 color1(0.0f, 1.0f, 0.0f, 1.0f);
static int edited_ret_frame = -1;
static int edited_ret_field = 0;
static int edited_query_frame = -1;
static int edited_query_field = 0;
static int deactivated_frame = -1;
static int deactivated_field = 0;
if (ImGui::ColorEdit4("color0", &color0.x)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 0; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 0; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 0; }
if (ImGui::ColorEdit4("color1", &color1.x)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 1; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 1; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 1; }
ImGui::Text("Edited (ret) frame %d, field %d", edited_ret_frame, edited_ret_field);
ImGui::Text("Edited (query) frame %d, field %d", edited_query_frame, edited_query_field);
ImGui::Text("Deactivated frame %d, field %d", deactivated_frame, deactivated_field);
}
ImGui::Separator();
{
static ImVec4 w;
static int activated_frame = -1;
static int activated_field = 0;
static int deactivated_frame = -1;
static int deactivated_field = 0;
ImGui::InputFloat("w.x", &w.x, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 0; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 0; }
ImGui::InputFloat("w.y", &w.y, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 1; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 1; }
ImGui::InputFloat("w.z", &w.z, 0, 0, "%.3f", 0);
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 2; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 2; }
ImGui::Text("w %.3f %.3f %.3f %.3f", w.x, w.y, w.z, w.w);
ImGui::Text("Activated frame %d, field %d", activated_frame, activated_field);
ImGui::Text("Deactivated frame %d, field %d", deactivated_frame, deactivated_field);
}
ImGui::Separator();
{
static ImVec4 temp;
static int activated_frame = -1;
static int activated_field = 0;
static int edited_ret_frame = -1;
static int edited_ret_field = 0;
static int edited_query_frame = -1;
static int edited_query_field = 0;
static int deactivated_frame = -1;
static int deactivated_field = 0;
ImVec4 v = temp;
//ImVec4& v = temp;
if (ImGui::InputFloat("temp##f4x", &v.x, 0, 0, "%.3f", 0)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 0; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 0; }
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 0; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 0; }
if (ImGui::IsItemDeactivatedAfterEdit())
{
temp = v;
}
if (ImGui::InputFloat("##f4y", &v.y, 0, 0, "%.3f", 0)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 1; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 1; }
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 1; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 1; }
if (ImGui::IsItemDeactivatedAfterEdit())
{
temp = v;
}
if (ImGui::InputFloat("##f4z", &v.z, 0, 0, "%.3f", 0)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 2; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 2; }
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 2; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 2; }
if (ImGui::IsItemDeactivatedAfterEdit())
{
temp = v;
}
ImGui::Text("Temp %.3f %.3f %.3f %.3f", temp.x, temp.y, temp.z, temp.w);
ImGui::Text("Activated frame %d, field %d", activated_frame, activated_field);
ImGui::Text("Edited (ret) frame %d, field %d", edited_ret_frame, edited_ret_field);
ImGui::Text("Edited (query) frame %d, field %d", edited_query_frame, edited_query_field);
ImGui::Text("Deactivated frame %d, field %d", deactivated_frame, deactivated_field);
}
ImGui::Separator();
{
static char buf1[100];
static char buf2[100];
static char buf3[100];
static int activated_frame = -1;
static int activated_field = 0;
static int edited_ret_frame = -1;
static int edited_ret_field = 0;
static int edited_query_frame = -1;
static int edited_query_field = 0;
static int deactivated_frame = -1;
static int deactivated_field = 0;
//ImVec4 v = temp;
//ImVec4& v = temp;
if (ImGui::InputText("##str1", buf1, 100)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 0; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 0; }
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 0; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 0; }
if (ImGui::InputText("##str2", buf2, 100)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 1; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 1; }
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 1; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 1; }
if (ImGui::InputText("##str3", buf3, 100)) { edited_ret_frame = ImGui::GetFrameCount(); edited_ret_field = 2; }
if (ImGui::IsItemEdited()) { edited_query_frame = ImGui::GetFrameCount(); edited_query_field = 2; }
if (ImGui::IsItemActivated()) { activated_frame = ImGui::GetFrameCount(); activated_field = 2; }
if (ImGui::IsItemDeactivatedAfterEdit()) { deactivated_frame = ImGui::GetFrameCount(); deactivated_field = 2; }
ImGui::Text("bufs: \"%s\" \"%s\" \"%s\"", buf1, buf2, buf3);
ImGui::Text("Activated frame %d, field %d", activated_frame, activated_field);
ImGui::Text("Edited (ret) frame %d, field %d", edited_ret_frame, edited_ret_field);
ImGui::Text("Edited (query) frame %d, field %d", edited_query_frame, edited_query_field);
ImGui::Text("Deactivated frame %d, field %d", deactivated_frame, deactivated_field);
}
ImGui::Button("Dummy2");
ImGui::End(); |
Version/Branch of Dear ImGui:
Version: 1.85
Branch: docking
My Issue/Question:
I noticed some strange behavior with imgui when editing stack vars.
In my case, I have a stack var that is set to a copy of a specific variable each frame, I then edit that stack copy with a basic imgui widget. I wish to only change the original value if the widget is deactivated, so I use the IsItemDeactivatedAfterEdit function to detect that and them update the original value.
This doesnt work reliably, if I switch focus to a widget that was declared/submitted BEFORE the active widget, I get the signal the value changed but it actually didn't change the stack var. Now if I change focus to a widget declared after the currently active one everything works as expected. This doesnt seem correct...
I am not hitting return/enter at all, just trying to commit the value via the focus switch/deactivation of the widget.
Screenshots/Video
bandicam.2021-11-11.16-46-56-267.mp4
(sorry about the black frames, video capture on W11 has some issues it seems)
Standalone, minimal, complete and verifiable example:
The text was updated successfully, but these errors were encountered: