-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
Popup without input blocking? #718
Comments
I got it working... The biggest issues was that I wanted the helper window to show on top of the console, which is why I was having a hard time... Since by using a popup, whatever is underneath it loses input. I ended up managing to get it to work by having the console window use the |
Nice! Sorry I hadn't had time to look into this in details. I was thinking about ChildWindow would provide something close to what you want but haven't looked at all the input details yet. |
@harold-b Could you clarify your description and maybe provide pseudo-code? I am interested in this and investigating if it can be made easier to the end-user.
|
The dumb version that you can't interact with is to use a tooltip (created within the calback because we love living dangerously!)
|
Why you daredevil! Here's an image to facilitate explaining more specifically what I wanted to achieve, realizing now my initial description was lacking: What I wanted to do was open an arbitrary I think the best working example would be Google's search box as per my initial post. If you leave an auto-suggest popup open and click around the page, etc. The popup remains open and unaltered. This what I wanted to mimic. With a regular imgui popup the input below the popup gets blocked. I wanted my I managed to fake that by having the popup be a regular window and using the It went something like this: ImGuiWindowFlags winFlags = ImGuiWindowFlags_MenuBar;
if( isPopupVisible )
winFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus;
if( !ImGui::Begin( "Developer Console", &isWinOpen, winFlags ) )
{
ImGui::End();
return;
}
// Draw arbitrary stuff...
// Done drawing window
ImGui::End();
// Draw the popup as a new regular window on top of the last one
if( isPopupVisible )
{
// Draw popup window
ImGuiWindowFlags popupFlags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_HorizontalScrollbar |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_ShowBorders;
bool isOpenDummy = true;
ImGui::Begin( "history_popup", &isOpenDummy, popupFlags );
ImGui::PushAllowKeyboardFocus( false );
// Draw arbitrary popup content...
ImGui::PopAllowKeyboardFocus();
ImGui::End();
} There was some tricky focus checking stuff I had to do too if I recall, but nothing fancy. Perhaps the simplest way to facilitate this kind of behavior would be to have some kind of z-layering per root window, allowing each window to have it's own stack of regular windows drawn on top of it (but not intersecting other root windows and their z-layered stack)? |
@harold-b I'm trying to implement the same sort of thing, though I'm fine with it being a popup/window that is located underneath/below the position of the input text field (in fact, that is how I need it). I can partially get what I want using BeginPopup(), but like your initial attempts the keyboard focus stealing keeps getting in the way. Do you have a functional outline handy? I've tried to replicate based on your work above without success (right now soon as I press the first key it just closes the modal popup I have where I have my search fields ) and the single character I pressed is all that gets put in to the said field. Regards, |
If I recall, there was no way to do it with the regular Popup, because whatever is underneath is simply loses input. This is why I went hunting for workarounds. If you don't need mouse interaction with the popup, you can use the technique @ocornut presented above which uses the Tooltip instead. You can then listen to the keyboard events on the text input's callbacks and change the current selection on the popup. If you need mouse interaction with the popup, I'm afraid you're probably stuck the workaround I used with the regular window on top, by using the I'll strip out the bloat from my implementation and post in a few minutes so you can have a functional example. |
Thanks for that @harold-b . Ideally what I'd be trying to do is have the popup of candidates respond to up/down/enter (to select a candidate) or mouseclick, which then collapses the popup giving the main dialog full keyboard control again. At the moment I'm doing a real "hack job" and simply building a list of small buttons under the field, only downside is that they have no keyboard control (ie, I can't pick a specific one with up/down->enter). |
For some reason I'm not getting some events fired on the popup window now in the stripped down version... It's very late here so unfortunately I'm going to have to pause it for tonight. But I can fix it up and post it tomorrow. Please bare with me. |
That's fine, no rush. I'm only looking to improve the usability of things here, it's not 'critical' 👍 |
@inflex Thanks for your patience. https://gist.github.com/harold-b/7dcc02557c2b15d76c61fde1186e31d0 |
@harold-b simply brilliant, many thanks for that block of code, certainly an item to come in great use with many people I think 👍 |
My pleasure :) |
static char input[32]{""};
ImGui::InputText("##input", input, sizeof(input));
ImGui::SameLine();
static bool isOpen = false;
bool isFocused = ImGui::IsItemFocused();
isOpen |= ImGui::IsItemActive();
if (isOpen)
{
ImGui::SetNextWindowPos({ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y});
ImGui::SetNextWindowSize({ImGui::GetItemRectSize().x, 0});
if (ImGui::Begin("##popup", &isOpen, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_Tooltip))
{
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
isFocused |= ImGui::IsWindowFocused();
static const char* autocomplete[] = {"cats", "dogs", "rabbits", "turtles"};
for (int i = 0; i < IM_ARRAYSIZE(autocomplete); i++)
{
if (strstr(autocomplete[i], input) == NULL)
continue;
if (ImGui::Selectable(autocomplete[i]) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter)))
{
strcpy(input, autocomplete[i]);
isOpen = false;
}
}
}
ImGui::End();
isOpen &= isFocused;
} There are still problems:
|
@ocornut - Are there any plans to have a proper laid out solution for this? The above solutions don't work if you want the same functionality with popups or perhaps I might have missed something. For example in my case, my file dialog is a Popup Modal and an
Any other way, you can think of, I can hack to get this working? Here is a picture.
Code:
|
Ok so I managed to solve the issue in 3. Apparently, either I misunderstood how the Z ordering works or there isn't proper support for it as the OP seems to be suggesting that as well. The issue in 3 happened because I drew the child window first and then drew the bottom checkboxe and buttons in the parent window. I thought the child window should appear on top regardless of "when" it's drawn. Drawing the checkboxes and other things before drawing the child window solved the transparency issue. |
I need a Popup that let use mouse input outside it (without closing) because it is a mesh editor that uses parameters from the Popup but also mouse actions done outside the Popup. I think they are not applicable to my use case as I dont want to interactuate with other window but with the whole imgui viewport. |
Don’t use a popup then?
|
Yes!! |
I've seen several solutions here. Is there agreed canonical way to do this yet? I saw the note referring to this issue from #2119 which seems like most people's use case and looked at #1658 as well. Seems like there are a lot of ways to provide similar results. Would be nice to have a correct way to do it... |
It would be great if this would be a built in component/behavior for drop down lists. |
I spent all day messing with this and I think I finally figured out a hack/workaround that works for me - maybe someone else will benefit. I was unable to use the above "fake" popup implementation because when the item was selected, it would close my parent window, which happened to be a different popup. After much messing around and reading how menus are done,I figured out that if I pass the ImGuiWindowFlags_ChildWindow to the BeginPopup function, everything works as expected!. Here is my code (edited to remove my actual list contents):
|
I got it working by creating a new flag for the TextInput item, called
And the code at the end of
This allows the popup window to display over the console, without taking the keyboard input away from the text input widget. |
Moving here from duplicate issue #5513.
The big question is - and I know I’m going to sound like an echo in here, but maybe something has emerged since this thread started - is there any Correct™ way to implement this? |
I had a better look at this thread today. I'll try to do a recap, with comments and fixes.
Rokups' suggestion ( #718 (comment) ) EricStancliff's suggestion ( #718 (comment) ) Here's what a believe is a decent solution based on the later: (reminder: need 1dd964f) // State
static char input[32]{ "" };
// Code
const bool is_input_text_enter_pressed = ImGui::InputText("##input", input, sizeof(input), ImGuiInputTextFlags_EnterReturnsTrue);
const bool is_input_text_active = ImGui::IsItemActive();
const bool is_input_text_activated = ImGui::IsItemActivated();
if (is_input_text_activated)
ImGui::OpenPopup("##popup");
{
ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y));
//ImGui::SetNextWindowSize({ ImGui::GetItemRectSize().x, 0 });
if (ImGui::BeginPopup("##popup", ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_ChildWindow))
{
static const char* autocomplete[] = { "cats", "dogs", "rabbits", "turtles" };
for (int i = 0; i < IM_ARRAYSIZE(autocomplete); i++)
{
//if (strstr(autocomplete[i], input) == NULL)
// continue;
if (ImGui::Selectable(autocomplete[i]))
{
ImGui::ClearActiveID();
strcpy(input, autocomplete[i]);
}
}
if (is_input_text_enter_pressed || (!is_input_text_active && !ImGui::IsWindowFocused()))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
} It's not ideal but seemingly does the job decently. It does work better with
I'll try to massage that, add regression tests for it, and see if some of it can be standardized or demoed. |
I have a somewhat related question. How can I make a popup (using any of the methods in #718 (comment)) be positioned where the text cursor currently is? Especially for |
I managed to hack something together:
Is there a better way? |
#718 (comment) Anything I can follow to resolve this? |
EDIT Original Comment:Posting to confirm I also have encountered this bug. If the auto-complete popup is open when you click the 'X' button of the window, you'll get the failed assertion, mentioned by @damian-kos Here is a minimal reproduction, using 1.90 WIP imgui-docking branch (viewports enabled) void MinimalBug()
{
static bool open = true;
if (!open) return;
if (ImGui::Begin("Bug Window", &open))
{
// State
static char input[32]{ "" };
// Code
const bool is_input_text_enter_pressed = ImGui::InputText("##input", input, sizeof(input), ImGuiInputTextFlags_EnterReturnsTrue);
const bool is_input_text_active = ImGui::IsItemActive();
const bool is_input_text_activated = ImGui::IsItemActivated();
if (is_input_text_activated)
ImGui::OpenPopup("##popup");
{
ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y));
//ImGui::SetNextWindowSize({ ImGui::GetItemRectSize().x, 0 });
if (ImGui::BeginPopup("##popup", ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_ChildWindow))
{
static const char* autocomplete[] = { "cats", "dogs", "rabbits", "turtles" };
for (int i = 0; i < IM_ARRAYSIZE(autocomplete); i++)
{
//if (strstr(autocomplete[i], input) == NULL)
// continue;
if (ImGui::Selectable(autocomplete[i]))
{
ImGui::ClearActiveID();
strcpy(input, autocomplete[i]);
}
}
if (is_input_text_enter_pressed || (!is_input_text_active && !ImGui::IsWindowFocused()))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
}
ImGui::End();
} |
You can use Was named this way as I initially envisioned is for history of e.g. a console input. I'll spend some time now working on this and #2057 and see if I can come up with canonical or out-of-box code. |
I understand this is a recurrent issue for many. Earlier this year I spent some time trying to implement something like this. I haven't finished as providing a public interface is a little tricky, but nevertheless I thought I'd provide some pointers. Opening the popup with: // Popup
// - use ImGuiWindowFlags_NoFocusOnAppearing to avoid losing active id.
// without _ChildWindow this would make us stays behind on subsequent reopens.
// - use ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NavFlattened: even though we use _NoNav this makes us share the focus scope,
// allowing e.g. Shortcut() to work from within the child when parent inputtext is focused.
// (or if we used normal navigation this would permit request to be handled while InputText is focused)
// - use ImGuiWindowFlags_NoNav and handle keys ourselves (it's currently easier)
ImGuiWindowFlags popup_window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings;
popup_window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
popup_window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NavFlattened;
popup_window_flags |= ImGuiWindowFlags_NoNav;
if (BeginPopupEx(GetID("SuggestionPopup"), popup_window_flags)) You can use Below is my (unfinished) experiment, AS-IS. It's been helpful to figure out what to improve. Interface: // Item access interface
// Could be straight parameters to InputTextWithCombo() but since we pass this to callbacks its easier this way.
struct ImGuiInputTextWithComboItems
{
const char* (*ItemGetter)(void* user_data, int idx);
void* UserData;
int ItemCount;
};
namespace ImGui
{
IMGUI_API bool InputTextWithCombo(const char* label, char* buf, size_t buf_size, ImGuiInputTextWithComboItems* items);
} Implementation #define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h"
#include "imgui_internal.h"
// Popup input with keyboard navigation and completion.
// This is tricky to implement for several reasons:
// - InputText() notoriously makes it difficult to edit contents/state while active
// - this currently create inconsistency dealing with text depending of whether processing happens inside InputText() callbacks or outside.
// - there are many good reasons, but this will be improved hopefully this year, as many requests are reliant on big changes in InputText()
// - I guess however we currently workaround the issues or complexity, this is going to be a good test case for InputText V2.
// - Keyboard navigation doesn't play well with an active item and when it is in another window than the navigated window.
// Additionally:
// - If you dig into possible flags/features for this, it tends to get very deep.
// - We should ideally tend toward a standard API but this is harder to design than for a custom widget.
// Expected controls:
// - Enter should validate input and close popup
// - Escape should close popup, clear active id, possibly revet?
// - Clicking outside should close popup
// - Up/down should lose text edit and move up/down (may be better handled with _NoNav + manual code for now)
// - Tab should perform completion.
// TODO: Possible options:
// - Scrolling.
// - [A] Can validate a value which is not in list? on/off
// - [B] Look: add combo button
// - [B] Suggestion list: could filter instead of highlight (need to maintain indices)
// - Actual manual input on/off: in which case it's not even an InputText?
// - Completion: configurable separators.
// - Completion: trailing blank on completion (for inputs expected to be multi-words).
// - Completion: display completion preview in buffer.
// - Completion: enable/disable when empty?
// - Completion: casing replacement on/off
// - Suggestion list: highlight on/off?
// - We could use the TypingSelect API if there wasn't a visible text edit. Very useful then.
// - Popup auto-open on/off? has incidence on up/down behavior and notably Home/End/PageUp/PageDown
static bool ImCharIsWordSeparator(char c)
{
return c == ' ' || c == '\t' || c == ',' || c == ';';
}
static const char* ImStrLocateWordEnd(const char* p, const char* buf_end)
{
while (p < buf_end && !ImCharIsWordSeparator(p[0]))
p++;
return p;
}
static const char* ImStrLocateWordStart(const char* p, const char* buf_start)
{
while (p > buf_start && !ImCharIsWordSeparator(p[-1]))
p--;
return p;
}
// FIXME: Now that we have input routing and that InputText() doesn't carry a ImWchar version
// of the buffer, this could be moved outside of the callback. The only missing thing if we
// wrote manually to the buffer is that undo state would be messed.
static int InputTextWithCombo_InputTextCompletionCallback(ImGuiInputTextCallbackData* data)
{
if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
{
// Locate beginning of current word
const char* word_end = data->Buf + data->CursorPos;
const char* word_start = ImStrLocateWordStart(word_end, data->Buf);
if (word_start == word_end)
return 0;
// Build a list of candidates
ImGuiInputTextWithComboItems* items = (ImGuiInputTextWithComboItems*)data->UserData;
ImVector<const char*> candidates;
for (int i = 0; i < items->ItemCount; i++)
if (const char* item_name = items->ItemGetter(items->UserData, i))
if (ImStrnicmp(item_name, word_start, (int)(word_end - word_start)) == 0)
candidates.push_back(item_name);
if (candidates.Size == 1)
{
// Delete the beginning of the word and replace it entirely so we've got nice casing.
data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start));
data->InsertChars(data->CursorPos, candidates[0]);
//data->InsertChars(data->CursorPos, " ");
}
else if (candidates.Size > 1)
{
// Multiple matches. Complete as much as we can..
int match_len = (int)(word_end - word_start);
for (;;)
{
int c = 0;
bool all_candidates_matches = true;
for (int i = 0; i < candidates.Size && all_candidates_matches; i++)
if (i == 0)
c = toupper(candidates[i][match_len]);
else if (c == 0 || c != toupper(candidates[i][match_len]))
all_candidates_matches = false;
if (!all_candidates_matches)
break;
match_len++;
}
IM_ASSERT(match_len > 0);
data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start));
data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len);
}
}
return 0;
}
bool ImGui::InputTextWithCombo(const char* label, char* buf, size_t buf_size, ImGuiInputTextWithComboItems* items)
{
ImGuiContext& g = *GImGui;
// Disable completion when text field is empty
ImGuiInputTextFlags input_text_flags = buf[0] ? ImGuiInputTextFlags_CallbackCompletion : ImGuiInputTextFlags_None;
//input_text_flags |= ImGuiInputTextFlags_EnterReturnsTrue;
InputText(label, buf, buf_size, input_text_flags, InputTextWithCombo_InputTextCompletionCallback, items);
ImGuiID input_text_id = GetItemID();
const bool input_text_active = IsItemActive();
ImGuiInputTextState* input_state = input_text_active ? GetInputTextState(input_text_id) : NULL;
// Using an external Shortcut() while active would work neatly instead of using CallbackCompletion,
// now that we have access to ImGuiInputTextState::ReloadUserBufAndMoveToEnd().
// But would require a little bit of custom text insertion code, for now using CallbackCompletion.
//if (input_text_active && Shortcut(ImGuiKey_Tab, ImGuiInputFlags_None, input_text_id))
// InputTextWithCombo_HandleCompletion();
// Locate word boundaries for highlight
const char* word_start = NULL;
const char* word_end = NULL;
if (input_state != NULL)
{
word_end = ImStrLocateWordEnd(input_state->TextA.Data + input_state->GetCursorPos(), input_state->TextA.Data + input_state->TextLen);
word_start = ImStrLocateWordStart(word_end, input_state->TextA.Data);
}
if (input_text_active)
OpenPopup("SuggestionPopup", ImGuiPopupFlags_NoReopen);
// Position and size popup
SetNextWindowPos(ImVec2(GetItemRectMin().x, GetItemRectMax().y + GetStyle().ItemSpacing.y));
SetNextWindowSize({ g.LastItemData.NavRect.GetWidth(), 0 }, ImGuiCond_Appearing);
// Popup
// - use ImGuiWindowFlags_NoFocusOnAppearing to avoid losing active id.
// without _ChildWindow this would make us stays behind on subsequent reopens.
// - use ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NavFlattened: even though we use _NoNav this makes us share the focus scope,
// allowing e.g. Shortcut() to work from within the child when parent inputtext is focused.
// (or if we used normal navigation this would permit request to be handled while InputText is focused)
// - use ImGuiWindowFlags_NoNav and handle keys ourselves (it's currently easier)
ImGuiWindowFlags popup_window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings;
popup_window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
popup_window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NavFlattened;
popup_window_flags |= ImGuiWindowFlags_NoNav;
if (BeginPopupEx(GetID("SuggestionPopup"), popup_window_flags))
{
ImGuiWindow* popup_window = g.CurrentWindow;
const bool popup_is_appearing = IsWindowAppearing();
const int cursor_idx_prev = GetStateStorage()->GetInt(GetID("CursorIdx"), -1);
int cursor_idx = cursor_idx_prev;
if (popup_is_appearing)
cursor_idx = -1;
// Custom keyboard navigation
bool rewrite_buf = false;
if (Shortcut(ImGuiKey_DownArrow, ImGuiInputFlags_Repeat, input_text_id) && (items->ItemCount > 0))
{
cursor_idx = (cursor_idx + 1) % items->ItemCount;
rewrite_buf = true;
}
if (Shortcut(ImGuiKey_UpArrow, ImGuiInputFlags_Repeat, input_text_id) && (items->ItemCount > 0))
{
cursor_idx = (cursor_idx - 1 + items->ItemCount) % items->ItemCount;
rewrite_buf = true;
}
if (Shortcut(ImGuiKey_PageUp, 0, input_text_id)) {} // Steal that away from navigation
if (Shortcut(ImGuiKey_PageDown, 0, input_text_id)) {}
if (rewrite_buf)
{
ImFormatString(buf, buf_size, "%s", items->ItemGetter(items->UserData, cursor_idx));
if (input_state)
input_state->ReloadUserBufAndSelectAll();
else
ActivateItemByID(input_text_id);
}
// Suggestion list
for (int item_idx = 0; item_idx < items->ItemCount; item_idx++)
{
const char* item_name = items->ItemGetter(items->UserData, item_idx);
const ImVec2 item_pos = popup_window->DC.CursorPos;
if (popup_is_appearing && strcmp(buf, item_name) == 0)
cursor_idx = item_idx;
if (Selectable(item_name, item_idx == cursor_idx))
{
ClearActiveID();
ImFormatString(buf, buf_size, "%s", item_name);
CloseCurrentPopup();
}
// Highlight when matching prefix
if (input_state && word_end > word_start)
if (ImStrnicmp(item_name, word_start, word_end - word_start) == 0)
popup_window->DrawList->AddRectFilled(item_pos, item_pos + CalcTextSize(word_start, word_end), GetColorU32(ImGuiCol_TextSelectedBg));
#if 0
// Nav: Replace text on navigation moves
if (g.NavJustMovedToId == g.LastItemData.ID)
{
ImFormatString(buf, buf_size, "%s", item_name);
if (input_state != NULL)
input_state->ReloadUserBufAndSelectAll();
}
if (IsWindowAppearing() && strcmp(buf, item_name) == 0)
SetItemDefaultFocus();
#endif
}
// Close popup on deactivation (unless we are mouse-clicking in our popup)
if (!input_text_active && !IsWindowFocused())
CloseCurrentPopup();
// Store cursor
if (cursor_idx != cursor_idx_prev)
GetStateStorage()->SetInt(GetID("CursorIdx"), cursor_idx);
EndPopup();
}
return false;
} |
Hello, fantastic library!
I'm trying to implement an autocomplete popup, but I've not been able to find a way to create a popup that can received mouse input and not block input from flowing through the underlying windows.
To be more specific of the context in which I'm trying to use it. I'd like to create an autocomplete popup a la google search. In which the popup does not close when you click elsewhere, you can hover and click it's items, and the input box ( or other widget ) retains keyboard input throughout.
Example:
Is it possible to do currently with the public API?
Thank you.
The text was updated successfully, but these errors were encountered: