Skip to content
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

InputText: Add a way to reset input text state. #2890

Closed
wants to merge 1 commit into from

Conversation

kudaba
Copy link
Contributor

@kudaba kudaba commented Nov 11, 2019

In response to #2881, I added an internal flag to trigger a text input box to reinitialize to it's current value. This resets any selection and undo state but adds opportunity for new interaction opportunities.

@kudaba kudaba changed the title InputText: InputText: Add a way to reset input text state. Nov 11, 2019
@ocornut
Copy link
Owner

ocornut commented Mar 3, 2020

It's hard to decide what's the desired behavior here, the PR is too specific to your exact need (reset undo stack, reset cursor position, select all, with widget still holding the active id) so I'll need to think about it a little further but basically I am lacking enough use case to take a suitable decision.

@kudaba
Copy link
Contributor Author

kudaba commented Mar 3, 2020

I realized I never added a link to the use case: https://gist.github.com/kudaba/01337c088a0bcef0c2e22a4e846f729c

Unfortunately the mouse drag drop part is using private utilities, but applying changes using the mouse wheel has the same issue of requiring this flag to make the input text box refresh its value.

@JPGygax68
Copy link

I also need a way to reset a text field after the call to InputText(...).

My use case is a bit peculiar, but I think not really unique: I need to change the content of the input field when Backspace is pressed in a specific situation. That part is easy to do using the CallbackAlways flag and the provided ImGuiInputTextCallbackData pointer. But there is a twist: I also need to modify another part of the UI, which at that point has already been committed to the draw list. This causes a very, very annoying flicker in the UI, as inconsistent data is being displayed for one frame.

I tried hard to work around the problem, and I see a way in this particular situation - setting a flag that will trigger the update during the next time the "always" callback gets called. (I haven't done this yet, but I'm fairly sure it will work.)

But this is a bit cumbersome. What I really need is a way to feed new content to the input field either before or after it is being committed to the draw list - after would be better, since else I would again need to define a flag.

So I think the easiest and most versatile way to solve this would be to add a function that gives access to the InputText state after it has just been drawn. Combined with IsItemActive(), this would allow users to react to keyboard input after the text field has been drawn.

@JPGygax68
Copy link

JPGygax68 commented Apr 4, 2020

@ocornut EDIT 2020-04-05: Problem solved at last. I've deleted the original text of the comment and replaced it with the following working solution.

Two situations need to be covered:

  1. changing the content of InputText when it's active
  2. changing the content when it's not active

I have to repeat here that neither of these would present a real challenge if it wasn't for the fact that, in my case, I cannot directly change the content of the field from inside the callback - which would be quite easy - because it would cause a flicker: for a single frame, the input field and the text items on the same line just before it, and thus already drawn would conflict and display a wrong combination of data (a chain of folder names belonging to the same path).

So, I came up with the following solution:

  1. define a std::<optional>::std::string that can be set anywhere - either in "outside" code or from inside the InputText callback - to trigger a reload of the input buffer in the next frame
  2. prepend the ImGui::InputText() call with a block of code that will execute if the input field is not active, checking the optional and, if set, copy its value into the input buffer
  3. add a similar check in the ImGuiInputTextFlags_CallbackAlways handler, which will do the same thing for when the input field is active, this time using the methods and fields of the ImGuiInputTextCallbackData struct.

Here is the most relevant code snippet:

if (new_foldername.has_value() && ImGui::GetActiveID() != ImGui::GetID("###FolderName")) {
    strcpy_s(folder_buffer, new_foldername.value().c_str());
    new_foldername.reset();
}
auto flags = ImGuiInputTextFlags_CallbackAlways | ImGuiInputTextFlags_EnterReturnsTrue |
    ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackCharFilter |
    ImGuiInputTextFlags_CallbackHistory;
if (ImGui::InputText("###FolderName", folder_buffer, IM_ARRAYSIZE(folder_buffer), flags, [](ImGuiInputTextCallbackData* data) {
    auto that = static_cast<FileDialog*>(data->UserData);
    // TODO: protect against being used when path is completely empty -> crashes
    if (data->EventFlag == ImGuiInputTextFlags_CallbackAlways) {
        if (that->new_foldername.has_value() ) {
            data->DeleteChars(0, data->BufTextLen);
            data->InsertChars(0, that->new_foldername.value().c_str());
            data->CursorPos = that->new_foldername.value().size();
            data->SelectionStart = data->SelectionEnd = 0;
            data->BufDirty = true;
            that->new_foldername.reset();
        }
        if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)) && strlen(that->folder_buffer) == 0) {
            if (that->selected_folder) {
                auto parent = that->selected_folder->parent;
                auto& path = that->selected_folder->dir_entry.path();
                that->new_foldername = parent ? path.filename().string() : path.string();
                that->selectFolder(parent);
            }
        }
        return 0;
    }
    ...
}

@ocornut
Copy link
Owner

ocornut commented Aug 19, 2020

@JPGygax68 I am really confused by the complexity of your two posts above. AFAIK the flag as proposed by @kudaba would exactly solve your problem as you can set the reinit/reload frame whenever works for you.

I see the desire for the behavior requested in this PR, and want to add it, but my issue boils down to that comment:

It's hard to decide what's the desired behavior here, the PR is too specific to your exact need (reset undo stack, reset cursor position, select all, with widget still holding the active id)

People may want to reload buffer but not select all etc. It's trivial to express those variations in user code but a single flag can't express them.

@JPGygax68
Copy link

JPGygax68 commented Aug 19, 2020

@ocornut I'm not at all saying that @kudaba's solution wouldn't work for me - it's just that I wanted to use the original, unpatched code at the time.

@kudaba
Copy link
Contributor Author

kudaba commented Mar 5, 2021

FYI, I found one issue when trying to implement an auto complete system where it would keep the cursor position when reinitializing. I'm trying to push an updated version but sourcetree is fighting me atm.

in the else block of if (recycle_state) (imgui_widgets.cpp(~3981). it should have the following:

            // when (re)initializing we send the cursor to the end so typing will be at the right spot. Any options that will cause a select all will overwrite this anyway
            state->Stb.cursor = state->CurLenW;

            if (!is_multiline && (focus_requested_by_code || (reinitialize && (flags & ImGuiInputTextFlags_AutoSelectAll))))
                select_all = true;

I saw you mention this in some issues so I wanted to make sure to get this modification in before you accept the changes.

Auto Complete system for reference: https://gist.github.com/kudaba/af24f859f46ca339a5dedba9074f4dee

@kudaba
Copy link
Contributor Author

kudaba commented Mar 10, 2021

Managed to get my local state working and updated the PR with the latest version

@ocornut
Copy link
Owner

ocornut commented Mar 12, 2021

I would like to find a solution - or even workaround for imgui_internal.h, but your last change pinpoint at exactly what the problem is that I pointed by my first answer.

// when (re)initializing we send the cursor to the end so typing will be at the right spot.

This is absolutely arbitrary to your specific use case.
Some people will need to keep cursor position and/or selection etc.
At which point the solution is either to provide wider access to the state, either design flags carrying more semantic (even if they are temporary or internal).

* This is useful if the value gets modified while the text input control is in focus.
* Add multiple reset modes to control the selection state after the reset occurs
@kudaba
Copy link
Contributor Author

kudaba commented Mar 13, 2021

Updated to an enum so users can control the selection behavior.

@ocornut ocornut force-pushed the master branch 3 times, most recently from c817acb to 8d39063 Compare February 15, 2022 16:25
ocornut added a commit that referenced this pull request Feb 7, 2024
Very highly requested feature (#6962, #5219, #3290, #4627, #5054, #3878, #2881, #1506, #1216, #968).
Also useful for interactive completion/selection popups (#2057, #718)
Based on @kudaba PR. Design for Inputtext V2 should make this obsolete.
@ocornut
Copy link
Owner

ocornut commented Feb 7, 2024

I have merged this now - better late than never :)
It's not exactly your PR but closely based on same logic, except I replaced the enum with entry points and specifying cursor positions. You can find it as 06ce312.

This has been a very frequently mentioned issue, so even though I am hoping InputText V2 won't need this, this is a good workaround. My apologies for not looking into this earlier!

Typical usage:

if (ImGuiInputTextState* input_state = ImGui::GetInputTextState(ImGui::GetID("input"))
    input_state->ReloadUserBufAndSelectAll();
ImGui::InputText("input", ....);

Or

ImGui::InputText("input", ...);
if (ImGuiInputTextState* input_state =  ImGui::GetInputTextState(ImGui::GetItemID()))
    input_state->ReloadUserBufAndSelectAll();

@ocornut ocornut closed this Feb 7, 2024
ocornut added a commit that referenced this pull request Feb 8, 2024
@ocornut
Copy link
Owner

ocornut commented Feb 8, 2024

I have amended this with 1e8fc01 + 7d67623 (fix) to make the ReloadUserBufXXX() not affect the InitialTextA[] buffer which is solely used for Reverting when pressing Escape. In other word, when the user presses Escape it always revert to initial value regardless of ReloadUserBufXXX calls. We may make this optional.

ocornut added a commit that referenced this pull request Feb 8, 2024
simdax added a commit to simdax/imgui that referenced this pull request Feb 12, 2024
commit 5360903
Author: ocornut <omarcornut@gmail.com>
Date:   Fri Feb 9 16:33:42 2024 +0100

    Version 1.90.2

commit 7b5357d
Author: ocornut <omarcornut@gmail.com>
Date:   Fri Feb 9 16:17:59 2024 +0100

    Debug Tools: Metrics: Improved Monitors and Viewports minimap display. Highlight on hover.

    Added ImGuiViewport ID in Master branch.

commit 70aa717
Author: ocornut <omarcornut@gmail.com>
Date:   Fri Feb 9 15:23:43 2024 +0100

    Combo: Fixed not reusing windows optimally when used inside a popup stack.

commit 5cdc4a2
Author: ocornut <omarcornut@gmail.com>
Date:   Fri Feb 9 14:20:12 2024 +0100

    Demo: use ImGui::MemAlloc/MemFree for consistency. (ocornut#7300)

commit 76e09c4
Author: ocornut <omarcornut@gmail.com>
Date:   Thu Feb 8 17:08:01 2024 +0100

    ClosePopupsOverWindow(): amend to remove _ChildWindow test.

    Said test seems unnecessary and incorrect as we test hierarchy now. See test "nav_ctrl_tab_popups" in ImGuiTestSuite.

commit 3a07846
Author: ocornut <omarcornut@gmail.com>
Date:   Thu Feb 8 16:06:55 2024 +0100

    Nav: ImGuiWindowFlags_NoNavInputs is tested during scoring so NavFlattened windows can use it.

commit 7d67623
Author: ocornut <omarcornut@gmail.com>
Date:   Thu Feb 8 15:46:17 2024 +0100

    InputText: Internal: ReloadUserBufXXX functions don't override revert value. (ocornut#2890) fix accidental comment.

commit a5e0e90
Author: ocornut <omarcornut@gmail.com>
Date:   Thu Feb 8 15:44:46 2024 +0100

    Nav: tweak RenderNavHighlight() syntax. ImGuiNavHighlightFlags_TypeThin -> ImGuiNavHighlightFlags_Compact.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants