ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
59.57k stars 10.15k forks source link

IsItemDeactivatedAfterEdit and InputTextMultiline #8004

Open w0utert opened 6 hours ago

w0utert commented 6 hours ago

Version/Branch of Dear ImGui:

imgui-node-editor docking branch

Back-ends:

imgui_impl_sdl2.cpp + imgui_impl_opengl.cpp

Compiler, OS:

Linux, gcc-12

Full config/build information:

No response

Details:

I've read the discussion on this issue and some of the related issues like #4714 , #5904, #6766 but I'm still left confused about the status of IsItemDeactivatedAfterEdit().

In my case I'm not using IsItemDeactivatedAfterEdit() to implement undo/redo, but simply because I want to delay updating my model state until my text edit is deactivated, either by pressing enter, clicking outside of it, tabbing out, a popup appearing, basically any reason the control would lose focus.

Right now I do this using something that looks like this:

std::string edit_value = model_value;

ImGui::InputText("my_input", &edit_value);

if (ImGui::IsItemDeactivatedAfterEdit())
{
  model.setValue(edit_value)
}

This has been working for InputText, in the sense that IsItemDeactivatedAfterEdit() is triggered when the control is deactivated by UI interactions, and edit_value will hold the last edit state (whatever was shown in the control at the time of the event).

For InputTextMultiline though, this approach does not seem to work at all. Depending on how the control was deactivated, the edit_value may either hold the last edit state, or the model_value that it was assigned right before outputting the control. For example, clicking outside the control in some empty area or the UI or on a checkbox, edit_value will hold the last edit state, but when tabbing out or clicking on a different text input control, edit_value will hold the model_value. It looks as if, depending on the reason the multi-line control was changed, IsItemDeactivated() is triggered with 1 frame delay, which means the edit_value will already have been overwritten with the model_value.

My feeling right now is that what I'm doing is fundamentally wrong and only works for InputText by accident. But the only alternative approach I can come up with is to implement my own temporary backing buffer for any text control I want to use for delayed model updates, and manually track which one was active so I can copy the model value to the edit value whenever the active control changes. In my case this could become a huge mess because most of my input controls are not fixed but output by iterating some dynamic model state, so I cannot rely on compile-time known variables to use as backing buffers for each control. It also seems like this would be duplicating effort I would expect to be on the ImGui side?

Maybe I'm lacking some understanding on how to deal with widget deactivation or have not discovered some common ImGui idioms to help me out in which case I apologize adding noise, but by now I've spent way too much time trying to get ImGui to do what I want. It would be great to know if it's difficult because of some ImGui limitation/bug or because I'm doing something wrong.

ocornut commented 5 hours ago

I will try to look into this next week.

InputText() does support something that's unusual: it is in theory supported that you don't provide persistent storage for the value while being edited, but I am certain there are paths/issues with it such as the deactivation cases you mentioned.

Underlying problem has been that InputText() implementation was a little too complicated for its own good, which made exactly that sort of thing a little complicated to investigate or fix. One good news is that a few weeks ago we started a first batch of refactor aiming to simplify it.

So there are two approaches here:

I'm not thrilled by the long term prospect of something that feature of allowing user to not persist the modified buffer, so (2) is evidently the ideal target, but we'll have to see based on ease of implementation.

Both approaches might be a little simpler to implement thanks to some of the recent changes made.

w0utert commented 4 hours ago

Aha yes, ImGuiInputTextFlags_NoLiveEdit would be exactly what I need (assuming this would also work for InputTextMultiline of course).

I have to say that currently, for my purpose InputText is behaving mostly as needed, I guess the way I use it now will fail to persist the value correctly if the control loses focus because of some programmatic action or maybe in case of popups etc. but I could live with that. For multi-line text fields however, this isn't working.

In the mean time, before either (1) or (2) are realised, does this mean my only option is to keep around my own storage buffer for every multi-line text control, and explicitly detect when to copy model values into it?

In pseudo-code what I would be writing (for each control) would be some equivalent of this:

void render_page(Thing &selectedThing)
{
  static std::optional<Thing> previously_selected_thing;
  static std::string edit_value;

  if (!previously_selected_thing || (selected_thing != *previously_selected_thing))
  {
    edit_value = selectedThing.getModelValue();
    previously_selected_thing = selectedThing;
  }

  ImGui::InputTextMultiLine("my_input", &edit_value);

  if (ImGui::IsItemDeactivatedAfterEdit())
  {
    selectedThing.setModelValue(edit_value);
  }

  if (something_happens)
  {
    previously_selected_thing.reset();
  }
}

And basically repeating this for every editable field of Thing (or write some wrapper function or class around it of course). Or is there a simpler/more idiomatic solution to this.

ocornut commented 4 hours ago

Yes unfortunately I think you currently have to do that until we investigate further. You might wrap it into code that uses the identifier (here ImGui::GetID("my_input") as a key instead of object if it makes any difference for you.