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

How to avoid a pressed button stealing the input from an InputText #1418

Open
Pagghiu opened this issue Nov 7, 2017 · 15 comments
Open

How to avoid a pressed button stealing the input from an InputText #1418

Pagghiu opened this issue Nov 7, 2017 · 15 comments

Comments

@Pagghiu
Copy link
Contributor

Pagghiu commented Nov 7, 2017

I am trying to create an on screen keyboard to edit text on systems without a keyboard.

Something like that.

screen shot 2017-11-07 at 15 56 39

The problem is that pressing any button "steals" the keyboard focus from the input text widget.
What's the most intelligent way of handling this scenario?
I have seen a few similar topics in the issues but nothing 100% equal to this case.

@Pagghiu Pagghiu changed the title How to avoid that a pressed button How to avoid a pressed button stealing the input from an InputText Nov 7, 2017
ocornut added a commit that referenced this issue Nov 7, 2017
…ctly setting ActiveIdClickOffset, which probably have no known effect, but it is more correct this way. (#1418)
@ocornut
Copy link
Owner

ocornut commented Nov 7, 2017

Hello Stefano,

At the moment there's no way to avoid that with the default button behavior.
Here are a few ideas and workaround:

  • You may create your own button using ButtonBehavior() with ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID and it should work, but you would be losing the regular "need to hold and release over button to press" behavior, instead it will activate the button on press.

  • You may do like the demo console and force reactivate focus after it has been lost, which will work but will probably look awkward ("flickering" cursor). Also note that current syntax may be obsolete as the Nav branch will provide more complete control over focus, but it won't be too hard to fix later.

The demo code does the awkward:

if (ImGui::IsItemHovered() || (ImGui::IsRootWindowOrAnyChildFocused() && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)))
    ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget

Current Nav branch uses an explicit flag that you could set when pressing a keyboard key:

// Demonstrate keeping focus on the input box
if (reclaim_focus)
    ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget

Also linking to #622 and #727 which relate to similar things.

@ocornut ocornut added the focus label Nov 7, 2017
@Pagghiu
Copy link
Contributor Author

Pagghiu commented Nov 7, 2017

Hello Omar,

Thanks for the response, just incredibly fast as always.

  • Your first option to use ButtonBehavior with the NoHoldingActiveID doesn't look correct to me because it's clearing the active id and not restoring InputText one

  • The second tip, will not preserve the state of the input text. If you use the SetKeyboardFocusHere, InputText will select all text inside of it.

I have also tried (hacking in the internal api) to save and restore the entire ActiveID* state variables and it works, but I have found another problem that prevents me from getting the behavior I like.
The textbox is using the X component of the mouseclick to move the current cursor position, so even if you restore the focus correctly you get the character inserted in the wrong place.

untitled

The relevant code in InputTextEx is:

// Edit in progress
        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));

        const bool osx_double_click_selects_words = io.OSXBehaviors;      // OS X style: Double click selects by word instead of selecting whole text
        if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0]))
        {
            edit_state.SelectAll();
            edit_state.SelectedAllMouseLock = true;
        }
        else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0])
        {
            // Select a word only, OS X style (by simulating keystrokes)
            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
        }
        else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
        {
            stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
            edit_state.CursorAnimReset();
        }
        else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
        {
            stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
            edit_state.CursorAnimReset();
            edit_state.CursorFollow = true;
        }
        if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
            edit_state.SelectedAllMouseLock = false;

I was trying to get this done without modifying imgui core, but I am thinking that is not possible.

Thanks again for your inputs.

@ocornut
Copy link
Owner

ocornut commented Nov 7, 2017

Your first option to use ButtonBehavior with the NoHoldingActiveID doesn't look correct to me because it's clearing the active id and not restoring InputText one

That's correct. Removing the ClearActiveID() in that block would be harmless and we could do that (but your other problem stands).

The second tip, will not preserve the state of the input text. If you use the SetKeyboardFocusHere, InputText will select all text inside of it.

I cannot reproduce that. The code is normally programmed to restore cursor/selection of an InputText (it works as tested in "Focus from code" now).

I see the problem with testing for io.MouseDown[0] if we try to restore the ActiveID, to handle that "ActiveID stack" we should be perhaps tracking which active ID is owning which click. I will look try to look into that later.

In the meanwhile it may be worth investigating why SetKeyboardFocusHere() is losing your selection/cursor because it should not. It does if the ImGuiInputTextFlags_AutoSelectAll flag is set.

@Pagghiu
Copy link
Contributor Author

Pagghiu commented Nov 7, 2017

You are right, I cannot reproduce the issue with the text being selected automatically with SetKeyboardFocusHere, I was probably doing something wrong during the many tests I've been doing.
I have found one very simple way of getting rid of the problem with io.MouseDown[0] when restoring ActiveID...I just draw the keyboard before the input text!

working keyboard

It's not ideal but works for what I need. I am thinking that I could "reserve" the space for the text box without drawing it, draw the keyboard and then roll-back the Cursor position to draw the inputtext...or force another window or a child/popup window to be drawn before the input text.

@ratchetfreak
Copy link

A true hack would be to use a second imgui context just for the keyboard...

It's output then gets sent to the primary context that hold the textbox.

@Pagghiu
Copy link
Contributor Author

Pagghiu commented Nov 8, 2017

This is an excellent idea!
I got it working somehow but if I will ever revisit this topic that's something I would try for sure.

ocornut added a commit that referenced this issue Jan 17, 2018
…m that's submitted after the InputText(). It was only noticeable when restoring focus programmatically. (#1418, #1554)
@ocornut
Copy link
Owner

ocornut commented Jan 17, 2018

Part of the issue here and #1554 (but that's not all there is to it), is that the code path when programmatically focusing an InputText() automatically select all the text. This was an arbitrary decision but I think it came mostly to mimic the behavior of Tabbing through widgets (which auto select the text).

When I rework the focus/activation API (it's currently a big big mess) I imagine there could be different flags to specify more detailed behavior (positioning the Nav focus/selection vs activating the widget vs activating + selecting all). If this is specified on the calling function the flags have to be designed in an InputText-agnostic way (focus vs activate vs xxx?). This is a perfect use case to design that focus/activation API.

Short term I could either:
A. Introduce a flag to disable this. I don't have a name yet that's both accurate and not awkward (e.g. ImGuiInputTextFlags_NoSelectOnProgrammaticFocus).
B. Reverse the behavior: do NOT select on programmatic focus by default but tabbing will keep the selection. If the input text doesn't have a cursor set the cursor at the end of text.

I think (B) is best though it is technically breaking the existing select-all behavior.
Any opinion on that? @Pagghiu @sebasobotka, Anyone using SetKeyboardFocusHere() to activate InputText() fields?

@ocornut
Copy link
Owner

ocornut commented Jan 17, 2018

I have found one very simple way of getting rid of the problem with io.MouseDown[0] when restoring ActiveID...I just draw the keyboard before the input text!

Btw I think this is the bug I fixed in 7ccbb76

@Pagghiu
Copy link
Contributor Author

Pagghiu commented Jan 23, 2018

Thanks for the bugfix, I will try at some point. I have solved also by reserving space for the input box, draw the keyboard first and the input box later, rolling back the cursor position appropriately. It's a little bit of a hack but it works.
I will try removing this hack at some point once I will update to latest version of the lib.

Regarding the behaviour I prefer option B, that is not to select on programmatic focus.
I would figure out if it's possible (and not too complex) to add a kinda of ImGuiInputTextFlags_NoSelectOnProgrammaticFocus (or the reverse) to SetKeyboardFocusHere() as a parameter and not to the InputText. If that's possible, the default could be either selecting or not selecting, it doesn't really matter to me.

@ocornut
Copy link
Owner

ocornut commented Jan 23, 2018

Thanks!

Yes I intend to move those flag to the focus functions it makes a lot more sense. My short term plan will be to try to merge the Navigation branch (what's done of it) because it add extra emphasis on focus concepts, and then I can work on the focus functions over the upcoming weeks/months.

@Pagghiu
Copy link
Contributor Author

Pagghiu commented Jan 24, 2018

Ok, in other words you will be able to focus more on focus related things ;)

@hippyau
Copy link

hippyau commented Apr 5, 2021

Hi @Pagghiu, any chance you can post the code for your keyboard? It's looks really good!

@ocornut
Copy link
Owner

ocornut commented Mar 27, 2024

I'm going to work on this a little bit and provided updated code or workaround soon.

@ocornut
Copy link
Owner

ocornut commented Mar 27, 2024

The most common workaround mentioned in #1418 (comment) would today be likely written as:

ImGui::InputText("Input", ....);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy))
    if (!ImGui::IsAnyItemActive() && !ImGui::IsAnyMouseDown())
        ImGui::SetKeyboardFocusHere(-1);

But that will essentially break keyboard navigation.
So an alternative would be:

if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy))
    if (!ImGui::IsAnyItemActive() && !ImGui::IsAnyMouseDown())
        if (!ImGui::GetIO().NavVisible)
            ImGui::SetKeyboardFocusHere(-1);

But it also means that the auto-focus will not work while using keyboard navigation, but currently if you press Escape to disable it (which itself is a feature that desktop apps are hoping to disable, another story) it'll clear NavVisible and therefore resurrect the focus.


experimental idea
Recently I was toying with the idea of adding a ImGuiNavMoveFlags_NoClearActiveId flag so moving wouldn't clear active id right away (see comment added in 06ce312) so I went ahead and ahead that with 9638c28 but it's highly experimental in the sense that you currently can't reliably use it. Even if you hijack move requests at the beginning of the window:

ImGui::Begin(...);
// Make move requests in this window not steal active id:
ImGuiContext& g = *GImGui;
if (ImGui::NavMoveRequestButNoResultYet() && g.NavWindow == g.CurrentWindow)
    g.NavMoveFlags |= ImGuiNavMoveFlags_NoClearActiveId;

It "appears" to work but then InputText() it still polling characters and keys and you have a conflict.
Where e.g. should left/right arrows be piped to?
Most widgets code until now have been assuming that when g.ActiveId == id then also g.NavId == id.
I suppose we could stretch it toward making InputText() explicitly only accept characters then g.NavId != id but it's hard to design how people would expect keyboard inputs to work in that situation.

ocornut added a commit that referenced this issue Mar 27, 2024
…'s currently no satisfying way to take advantage of it. (#1418)
pull bot pushed a commit to TeamREPENTOGON/imgui that referenced this issue Mar 27, 2024
…'s currently no satisfying way to take advantage of it. (ocornut#1418)
@chnoma
Copy link

chnoma commented Jun 29, 2024

Has that flag to not select text ever been implemented? I'm struggling with this right now for an autocomplete textbox. The issue is that I'm using the return key as a completion key and that deselects the textbox. Upon refocusing with SetKeyboardFocusHere it automatically selects all text.

edit:
actually I think I've misinterpreted this, here's an example and I'm pretty sure it's a result of manipulating the std::string directly

ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue;

static std::string text = "something";
if(ImGui::InputText("input", &text, flags)) {
    ImGui::SetKeyboardFocusHere(-1);
    text = "else";
}

edit 2:

I've just switched it over to a char array and it behaves the same, so it's not related to std::string.
I had to move the buffer change into a callback to set the text and maintain focus without selecting all. Sorry if this is something dumb I'm missing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants