Open ocornut opened 6 years ago
Hi,
Nice example! Is it possible to add an image before the label in the selection list, or in the selected choice?
Yes, they are regular popup so you can do anything within them.
I managed to display an image per item by calling a ImGui::Selectable("", is_selected);
, followed by ImGui::SameLine();
and then showing image + text:
However, I cannot figure out how to show the picture of the colormap inside the frame that shows the selected one.
EDIT: Well ok what I did doesn't really work actually. If I set the selectable size to ""
then I cannot click on the image or the text to change the selected value ... I'm sure it's possible to fix this with the current API, but I haven't figure out yet how.
EDIT: Well ok what I did doesn't really work actually. If I set the selectable size to "" then I cannot click on the image or the text to change the selected value ... I'm sure it's possible to fix this with the current API, but I haven't figure out yet how.
If you set the selectable label to "" make sure there is a PushID()
or use "##someid"
else all your selectable are using the same identifier and will conflict.
Selectable("") + SameLine + Image + SameLine + Text should work.
However, I cannot figure out how to show the picture of the colormap inside the frame that shows the selected one.
That's a trickier one unfortunately, thought you can draw inside the combo widget:
ImVec2 combo_pos = ImGui::GetCursorScreenPos();
if (ImGui::BeginCombo(label, ""))
{
[...]
ImGui::EndCombo();
}
[...]
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
ImGuiStyle& style = ImGui::GetStyle();
ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y));
ImGui::ColorButton("blah", ImVec4(1,0,0,1));
ImGui::SameLine();
ImGui::Text("Hello");
ImGui::SetCursorScreenPos(backup_pos);
But you will run into clipping issues (only you push a clipping rectangle or use internal functions that draw clipped text). Maybe this pattern could be formalized into something better.
I think you would be better off using Image + SameLine + Combo with a manipulation of item width.
Right now my window has a fixed size, and the text fit in the box, so what you suggest may just work (™). Thanks for the tip! It works indeed better with the PushID()
:p
While I'm at it, what is the correct way to calculate the size of the button? I am using ImGui::GetStyle().FrameRounding = 2.0f;
and ImGui::GetStyle().FrameBorderSize = 1.0f;
, and I am positioning my text and positions as so:
ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y + style.FramePadding.y));
float h = ImGui::GetTextLineHeightWithSpacing() - style.FramePadding.y;
ImGui::Image(tex_id, ImVec2(h, h);
ImGui::SameLine();
ImGui::Text("Viridis");
The image seems a bit too big and the padding is not even on top and bottom, so I may have been doing the calculation wrong.
While I'm at it, what is the correct way to calculate the size of the button?
Neither FrameRounding or FrameBorderSize after the size of elements (the border size is taken "inside" the item). The size of a button is typically size of text + FramePadding * 2.
float h = ImGui::GetTextLineHeightWithSpacing() - style.FramePadding.y;
Here you probably want to just float h = ImGui::GetTextLineHeight()
.
You are right of course =). GetTextLineHeight()
works just fine here. Thanks!
Hey,
I am browsing closed issues and cannot find any reference to create a filtered combo.
What's the best current option to create a FilterCombo widget as seen in UE4/Sublime/etc? Ie, some kind of Input filter on top then a filtered combo list below.
Ty!
PS: The pic below uses fuzzy pattern matching rather than simple filtering, but you get the idea.
I don't have a good answer for you, for not having tried to make an interactive one, but #718 is the thread to check to fish for ideas.
Ah kewl :)
I've started from your snippet in #718 and started to mess with it to add interactivity + basic fuzzy search. It could be 1,000 times better but works for me now :D
// imgui combo filter v1.0, by @r-lyeh (public domain)
// contains code by @harold-b (public domain?)
/* Demo: */
/*
{
// requisite: hints must be alphabetically sorted beforehand
const char *hints[] = {
"AnimGraphNode_CopyBone",
"ce skipaa",
"ce skipscreen",
"ce skipsplash",
"ce skipsplashscreen",
"client_unit.cpp",
"letrograd",
"level",
"leveler",
"MacroCallback.cpp",
"Miskatonic university",
"MockAI.h",
"MockGameplayTasks.h",
"MovieSceneColorTrack.cpp",
"r.maxfps",
"r.maxsteadyfps",
"reboot",
"rescale",
"reset",
"resource",
"restart",
"retrocomputer",
"retrograd",
"return",
"slomo 10",
"SVisualLoggerLogsList.h",
"The Black Knight",
};
static ComboFilterState s = {0};
static char buf[128] = "type text here...";
if( ComboFilter("my combofilter", buf, IM_ARRAYSIZE(buf), hints, IM_ARRAYSIZE(hints), s) ) {
puts( buf );
}
}
*/
#pragma once
struct ComboFilterState
{
int activeIdx; // Index of currently 'active' item by use of up/down keys
bool selectionChanged; // Flag to help focus the correct item when selecting active item
};
static bool ComboFilter__DrawPopup( ComboFilterState& state, int START, const char **ENTRIES, int ENTRY_COUNT )
{
using namespace ImGui;
bool clicked = 0;
// Grab the position for the popup
ImVec2 pos = GetItemRectMin(); pos.y += GetItemRectSize().y;
ImVec2 size = ImVec2( GetItemRectSize().x-60, GetItemsLineHeightWithSpacing() * 4 );
PushStyleVar( ImGuiStyleVar_WindowRounding, 0 );
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_HorizontalScrollbar |
ImGuiWindowFlags_NoSavedSettings |
0; //ImGuiWindowFlags_ShowBorders;
SetNextWindowFocus();
SetNextWindowPos ( pos );
SetNextWindowSize( size );
Begin("##combo_filter", nullptr, flags );
PushAllowKeyboardFocus( false );
for( int i = 0; i < ENTRY_COUNT; i++ ) {
// Track if we're drawing the active index so we
// can scroll to it if it has changed
bool isIndexActive = state.activeIdx == i;
if( isIndexActive ) {
// Draw the currently 'active' item differently
// ( used appropriate colors for your own style )
PushStyleColor( ImGuiCol_Border, ImVec4( 1, 1, 0, 1 ) );
}
PushID( i );
if( Selectable( ENTRIES[i], isIndexActive ) ) {
// And item was clicked, notify the input
// callback so that it can modify the input buffer
state.activeIdx = i;
clicked = 1;
}
if( IsItemFocused() && IsKeyPressed(GetIO().KeyMap[ImGuiKey_Enter]) ) {
// Allow ENTER key to select current highlighted item (w/ keyboard navigation)
state.activeIdx = i;
clicked = 1;
}
PopID();
if( isIndexActive ) {
if( state.selectionChanged ) {
// Make sure we bring the currently 'active' item into view.
SetScrollHere();
state.selectionChanged = false;
}
PopStyleColor(1);
}
}
PopAllowKeyboardFocus();
End();
PopStyleVar(1);
return clicked;
}
static bool ComboFilter( const char *id, char *buffer, int bufferlen, const char **hints, int num_hints, ComboFilterState &s ) {
struct fuzzy {
static int score( const char *str1, const char *str2 ) {
int score = 0, consecutive = 0, maxerrors = 0;
while( *str1 && *str2 ) {
int is_leading = (*str1 & 64) && !(str1[1] & 64);
if( (*str1 & ~32) == (*str2 & ~32) ) {
int had_separator = (str1[-1] <= 32);
int x = had_separator || is_leading ? 10 : consecutive * 5;
consecutive = 1;
score += x;
++str2;
} else {
int x = -1, y = is_leading * -3;
consecutive = 0;
score += x;
maxerrors += y;
}
++str1;
}
return score + (maxerrors < -9 ? -9 : maxerrors);
}
static int search( const char *str, int num, const char *words[] ) {
int scoremax = 0;
int best = -1;
for( int i = 0; i < num; ++i ) {
int score = fuzzy::score( words[i], str );
int record = ( score >= scoremax );
int draw = ( score == scoremax );
if( record ) {
scoremax = score;
if( !draw ) best = i;
else best = best >= 0 && strlen(words[best]) < strlen(words[i]) ? best : i;
}
}
return best;
}
};
using namespace ImGui;
bool done = InputText(id, buffer, bufferlen, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue );
bool hot = s.activeIdx >= 0 && strcmp(buffer, hints[s.activeIdx]);
if( hot ) {
int new_idx = fuzzy::search( buffer, num_hints, hints );
int idx = new_idx >= 0 ? new_idx : s.activeIdx;
s.selectionChanged = s.activeIdx != idx;
s.activeIdx = idx;
if( done || ComboFilter__DrawPopup( s, idx, hints, num_hints ) ) {
int i = s.activeIdx;
if( i >= 0 ) {
strcpy(buffer, hints[i]);
done = true;
}
}
}
return done;
}
PS: sorry no gifs today!
EDIT: updated code to v1.0
I've updated the snippet above to a cleaner implementation. Also, here's latest gif anim (showcasing mouse & keyboardnav picking):
Thanks @r-lyeh for the code and gif, this is really nice!
I just stumbled upon how FlatUI handles the combo filtering, and its logic fits better with IMGUI mentality IMO.
I will try to put the filter inside the combo popup (and leave the header to behave exactly like current Combo). Will create a new snippet someday.
flat-ui demo: http://designmodo.github.io/Flat-UI/
Hello guys, check out my solution over this problem. I have taken approach @r-lyeh about fuzzy search and tried to create total combo-like widget
/* Demo: */
/*
const char *hints[] = {
"AnimGraphNode_CopyBone",
"ce skipaa",
"ce skipscreen",
"ce skipsplash",
"ce skipsplashscreen",
"client_unit.cpp",
"letrograd",
"level",
"leveler",
"MacroCallback.cpp",
"Miskatonic university",
"MockAI.h",
"MockGameplayTasks.h",
"MovieSceneColorTrack.cpp",
"r.maxfps",
"r.maxsteadyfps",
"reboot",
"rescale",
"reset",
"resource",
"restart",
"retrocomputer",
"retrograd",
"return",
"slomo 10",
"SVisualLoggerLogsList.h",
"The Black Knight",
};
static ComboFilterState s = {0, false};
static char buf[128];
static bool once = false;
if(!once) {
memcpy(buf, hints[0], strlen(hints[0]) + 1);
once = true;
}
if( ComboFilter("my combofilter", buf, IM_ARRAYSIZE(buf), hints, IM_ARRAYSIZE(hints), s) ) {
//...picking was occured
}
*/
#pragma once
struct ComboFilterState {
int activeIdx;
bool selectionChanged;
};
bool ComboFilter(const char *label, char *buffer, int bufferlen, const char **hints, int num_hints, ComboFilterState &s, ImGuiComboFlags flags = 0) {
using namespace ImGui;
s.selectionChanged = false;
// Always consume the SetNextWindowSizeConstraint() call in our early return paths
ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return false;
const ImGuiID id = window->GetID(label);
bool popup_open = IsPopupOpen(id);
bool popupNeedBeOpen = strcmp(buffer, hints[s.activeIdx]);
bool popupJustOpened = false;
IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
const ImGuiStyle& style = g.Style;
const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
const ImVec2 label_size = CalcTextSize(label, NULL, true);
const float expected_w = CalcItemWidth();
const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w;
const ImRect frame_bb(window->DC.CursorPos, ImVec2(window->DC.CursorPos.x + w, window->DC.CursorPos.y + label_size.y + style.FramePadding.y*2.0f));
const ImRect total_bb(frame_bb.Min, ImVec2((label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f) + frame_bb.Max.x, frame_bb.Max.y));
const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size);
ItemSize(total_bb, style.FramePadding.y);
if (!ItemAdd(total_bb, id, &frame_bb))
return false;
bool hovered, held;
bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
if(!popup_open) {
const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
RenderNavHighlight(frame_bb, id);
if (!(flags & ImGuiComboFlags_NoPreview))
window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Left);
}
if (!(flags & ImGuiComboFlags_NoArrowButton))
{
ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
ImU32 text_col = GetColorU32(ImGuiCol_Text);
window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x)
RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
}
if(!popup_open) {
RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
if (buffer != NULL && !(flags & ImGuiComboFlags_NoPreview))
RenderTextClipped(ImVec2(frame_bb.Min.x + style.FramePadding.x, frame_bb.Min.y + style.FramePadding.y), ImVec2(value_x2, frame_bb.Max.y), buffer, NULL, NULL, ImVec2(0.0f,0.0f));
if ((pressed || g.NavActivateId == id || popupNeedBeOpen) && !popup_open)
{
if (window->DC.NavLayerCurrent == 0)
window->NavLastIds[0] = id;
OpenPopupEx(id);
popup_open = true;
popupJustOpened = true;
}
}
if (label_size.x > 0)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
if (!popup_open) {
return false;
}
const float totalWMinusArrow = w - arrow_size;
struct ImGuiSizeCallbackWrapper {
static void sizeCallback(ImGuiSizeCallbackData* data)
{
float* totalWMinusArrow = (float*)(data->UserData);
data->DesiredSize = ImVec2(*totalWMinusArrow, 200.f);
}
};
SetNextWindowSizeConstraints(ImVec2(0 ,0), ImVec2(totalWMinusArrow, 150.f), ImGuiSizeCallbackWrapper::sizeCallback, (void*)&totalWMinusArrow);
char name[16];
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
// Peak into expected window size so we can position it
if (ImGuiWindow* popup_window = FindWindowByName(name))
if (popup_window->WasActive)
{
ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
if (flags & ImGuiComboFlags_PopupAlignLeft)
popup_window->AutoPosLastDirection = ImGuiDir_Left;
ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
pos.y -= label_size.y + style.FramePadding.y*2.0f;
SetNextWindowPos(pos);
}
// Horizontally align ourselves with the framed text
ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
// PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
bool ret = Begin(name, NULL, window_flags);
ImGui::PushItemWidth(ImGui::GetWindowWidth());
ImGui::SetCursorPos(ImVec2(0.f, window->DC.CurrLineTextBaseOffset));
if(popupJustOpened) {
ImGui::SetKeyboardFocusHere(0);
}
bool done = InputTextEx("", NULL, buffer, bufferlen, ImVec2(0, 0), ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, NULL, NULL);
ImGui::PopItemWidth();
if(s.activeIdx < 0) {
IM_ASSERT(false); //Undefined behaviour
return false;
}
if (!ret)
{
ImGui::EndChild();
ImGui::PopItemWidth();
EndPopup();
IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
return false;
}
ImGuiWindowFlags window_flags2 = 0; //ImGuiWindowFlags_HorizontalScrollbar
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), false, window_flags2);
struct fuzzy {
static int score( const char *str1, const char *str2 ) {
int score = 0, consecutive = 0, maxerrors = 0;
while( *str1 && *str2 ) {
int is_leading = (*str1 & 64) && !(str1[1] & 64);
if( (*str1 & ~32) == (*str2 & ~32) ) {
int had_separator = (str1[-1] <= 32);
int x = had_separator || is_leading ? 10 : consecutive * 5;
consecutive = 1;
score += x;
++str2;
} else {
int x = -1, y = is_leading * -3;
consecutive = 0;
score += x;
maxerrors += y;
}
++str1;
}
return score + (maxerrors < -9 ? -9 : maxerrors);
}
static int search( const char *str, int num, const char *words[] ) {
int scoremax = 0;
int best = -1;
for( int i = 0; i < num; ++i ) {
int score = fuzzy::score( words[i], str );
int record = ( score >= scoremax );
int draw = ( score == scoremax );
if( record ) {
scoremax = score;
if( !draw ) best = i;
else best = best >= 0 && strlen(words[best]) < strlen(words[i]) ? best : i;
}
}
return best;
}
};
int new_idx = fuzzy::search( buffer, num_hints, hints );
int idx = new_idx >= 0 ? new_idx : s.activeIdx;
s.selectionChanged = s.activeIdx != idx;
bool selectionChangedLocal = s.selectionChanged;
s.activeIdx = idx;
if(done) {
CloseCurrentPopup();
}
for (int n = 0; n < num_hints; n++) {;
bool is_selected = n == s.activeIdx;
if (is_selected && (IsWindowAppearing() || selectionChangedLocal)) {
SetScrollHereY();
// ImGui::SetItemDefaultFocus();
}
if (ImGui::Selectable(hints[n], is_selected)) {
s.selectionChanged = s.activeIdx != n;
s.activeIdx = n;
strcpy(buffer, hints[n]);
CloseCurrentPopup();
}
}
ImGui::EndChild();
EndPopup();
return s.selectionChanged && !strcmp(hints[s.activeIdx], buffer);
}
Keep in mind that you need to keep hints list sorted
missing a pic!
missing a pic!
Done
Using this thread and drawImage seems to work, thanks all!
float button_sz = ImGui::GetFrameHeight();
if(ImGui::BeginCombo("##custom combo",current_item,ImGuiComboFlags_NoArrowButton))
{
for(int n = 0; n < IM_ARRAYSIZE(items); n++)
{
bool is_selected = (current_item == items[n]);
auto drawList = ImGui::GetWindowDrawList();
if(ImGui::Selectable(items[n],is_selected))
{
selected_item_index = n;
current_item = items[n];
}
auto rect_min = ImGui::GetItemRectMin();
auto rect_max = ImGui::GetItemRectMax();
rect_max.x = rect_min.x + 32;
drawList->AddImage(ImGui_GetOpenGLTexture(s_noteIcons[n]),rect_min,rect_max,ImVec2(0,0),ImVec2(1,1),IM_COL32(255,255,255,255));
if(is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
else
{
auto rect_min = ImGui::GetItemRectMin();
auto rect_max = ImGui::GetItemRectMax();
rect_max.x = rect_min.x + 32;
auto drawList = ImGui::GetWindowDrawList();
drawList->AddImage(ImGui_GetOpenGLTexture(s_noteIcons[selected_item_index]),rect_min,rect_max,ImVec2(0,0),ImVec2(1,1),IM_COL32(255,255,255,255));
}
const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO", "PPPP", "QQQQQQQQQQ", "RRR", "SSSS" }; static const char* current_item = NULL; if (ImGui::BeginCombo("##combo", current_item)) // The second parameter is the label previewed before opening the combo. { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { bool is_selected = (current_item == items[n]); // You can store your selection however you want, outside or inside your objects if (ImGui::Selectable(items[n], is_selected) current_item = items[n]; if (is_selected) ImGui::SetItemDefaultFocus(); // You may set the initial focus when opening the combo (scrolling + for keyboard navigation support) } ImGui::EndCombo(); }
Just letting you know. You're missing a ) at
if (ImGui::Selectable(items[n], is_selected)
I have pushed an experimental API (declared in imgui_internal.h)
if (ImGui::BeginCombo("combo custom", "", flags | ImGuiComboFlags_CustomPreview))
{
// ...
ImGui::EndCombo();
}
if (ImGui::BeginComboPreview())
{
ImGui::ColorButton("##color", ImVec4(1.0f, 0.5f, 0.5f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, color_square_size);
ImGui::TextUnformatted(items[item_current_idx]);
ImGui::EndComboPreview();
}
The idea is that BeginComboPreview() will set cursor and clip rect, and EndComboPreview() restores things. Whereas most custom solutions above would leave the preview unclipped (so glitches would appear when resizing down). The clipping and drawcmd merging currently relies on ImGui:: side max cursor pos, but we will switch to use upcoming ImDrawCmd AABB when available.
If it is works well this may eventually be promoted to a public api (imgui.h)
With 060b6ee7d I did a small refactor of BeginCombo()
to extract BeginComboPopup()
out of it.
The idea being that if you have to copy the content of BeginCombo()
you are more likely to be able to reuse the BeginComboPopup()
parts unchanged, and so your copy of BeginCombo()
can be smaller (removed ~45 lines).
That said, I think it should now be possible to implement variety of filter idioms without copying/touching BeginCombo() (possibly by using SetItemAllowOverlap). I encourage you all to try and let me know.
So, is it already possible to make a simple ComboFilter/Autocomplete widget using this additional functionality? Such useful widget, yet all provided "examples" in this thread and others are non-working/non-compiling.
I don't know I haven't tried to make one. Perhaps something worth investigating for a demo.
Cool. Will check this. By the way, I've spent a while to fix code from @kovewnikov Not sure if I did this best way, but works quite OK for me. Here's the code: imguiComboFilter.zip
I just stumbled upon how FlatUI handles the combo filtering, and its logic fits better with IMGUI mentality IMO.
I will try to put the filter inside the combo popup (and leave the header to behave exactly like current Combo). Will create a new snippet someday.
flat-ui demo: http://designmodo.github.io/Flat-UI/
Edit: updated my version of ComboWithFilter for v1.89 WIP. Looks the same as old gif below.
Testing on imgui 1.78 WIP, I tried some of the above combo filters.
After fixing some deprecated functions, I still couldn't get r-lyeh's ComboFilter to accept any text (immediately loses focus). kovewnikov's and slajerek's versions work better, but not if there are multiple ComboFilters visible at once (then it won't accept more than one character). Possibly it's my fault since I used a static buffer and ComboFilterState.
ChenRenault's ComboWithFilter worked wonderfully. Putting the filter inside the combo makes the api simpler and I can have multiple visible at once.
I extended ComboWithFilter to add arrow navigation, Enter to confirm, and max_height_in_items
. It uses BeginCombo to avoids drawing beyond the edge of the window and I fixed focus on open:
https://gist.github.com/idbrii/5ddb2135ca122a0ec240ce046d9e6030
Dear idbrii
hi I tried your source code on version 1.87. But it's not working properly. Can you test it on version 1.87? thank you.
Hi @kwonjinyoung I left a comment on the gist because it did not work for me either in 1.87. I believe the changes that I've posted in the comment should be all that's necessary to get this to work in 1.87.
@ocornut
On the topic of customizing the combo preview
I have pushed an experimental API (declared in imgui_internal.h)
If it is works well this may eventually be promoted to a public api (imgui.h)
Hey I cannot find this experimental branch, and I can't find this in master. Did this get removed?
Its all still here in imgui_internal.h
If somebody is interested I changed https://github.com/ocornut/imgui/issues/1658#issuecomment-862692546 into imgui_combo_autoselect.h and did a minor code cleanup, changed function signature as per old API Combo() with item_getter callback (in this way can be used in much more dynamic data context)
https://gist.github.com/ozlb/9cd35891aa4de3450e8e4c844837e7f9
const char* hints[] = {
"AnimGraphNode_CopyBone",
"ce skipaa",
"ce skipscreen",
"ce skipsplash",
"ce skipsplashscreen",
"client_unit.cpp",
"letrograd",
"level",
"leveler",
"MacroCallback.cpp",
"Miskatonic university",
"MockAI.h",
"MockGameplayTasks.h",
"MovieSceneColorTrack.cpp",
"r.maxfps",
"r.maxsteadyfps",
"reboot",
"rescale",
"reset",
"resource",
"restart",
"retrocomputer",
"retrograd",
"return",
"slomo 10",
"SVisualLoggerLogsList.h",
"The Black Knight",
};
static int comboSelection = 0;
static char buf[128] = { 0x00 };
static char sel[128] = { 0x00 };
struct Funcs { static bool ItemGetter(void* data, int n, const char** out_str) { *out_str = ((const char**)data)[n]; return true; } };
if (ImGui::ComboAutoSelect("my combofilter", buf, IM_ARRAYSIZE(buf), &comboSelection, &Funcs::ItemGetter, hints, IM_ARRAYSIZE(hints), NULL)) {
//...picking has occurred
sprintf(sel, "%s", buf);
}
ImGui::Text("Selection: %s", sel);
Hi @idbrii
Thanks fo your code, howerver, it dose not support the CKJ characters, and the search icon is gone, can you fix? I set the default font to display Chinese.
io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\msyh.ttc", 16.0f, NULL, io.Fonts->GetGlyphRangesChineseFull());
@idbrii I fixed the Chinese input issue with the solution from https://github.com/ocornut/imgui/issues/1807#issuecomment-394383995
And fixed an assertion crash, but the search icon is still not displayed.
diff --git a/imgui/fts_fuzzy_match.h b/imgui/fts_fuzzy_match.h
index 523982e..2267dcb 100644
--- a/imgui/fts_fuzzy_match.h
+++ b/imgui/fts_fuzzy_match.h
@@ -184,7 +184,8 @@ namespace fts {
char neighbor = strBegin[currIdx - 1];
char curr = strBegin[currIdx];
- if (::islower(neighbor) && ::isupper(curr))
+ // fix for utf-8
+ if (neighbor > 0 && curr > 0 && ::islower(neighbor) && ::isupper(curr))
outScore += camel_bonus;
// Separator
I also tried with @ozlb ImGui::ComboAutoSelect
, it just works, but RETURN key dose not select the first match! Anyway, thanks!
My attempt to imitate VSCode, source code by https://github.com/hnOsmium0001/imgui-command-palette dear imgui, v1.84
Source Code
I have improved @ozlb version of ComboAutoSelect as follow
Demo
static ImGui::ComboAutoSelectData data = {{
"",
"AnimGraphNode_CopyBone",
"ce skipaa",
"ce skipscreen",
"ce skipsplash",
"ce skipsplashscreen",
"client_unit.cpp",
"letrograd",
"level",
"leveler",
"MacroCallback.cpp",
"Miskatonic university",
"MockAI.h",
"MockGameplayTasks.h",
"MovieSceneColorTrack.cpp",
"r.maxfps",
"r.maxsteadyfps",
"reboot",
"rescale",
"reset",
"resource",
"restart",
"retrocomputer",
"retrograd",
"return",
"slomo 10",
"SVisualLoggerLogsList.h",
"The Black Knight",
}};
if (ImGui::ComboAutoSelect("my combofilter", data)) {
// selection occurred
}
ImGui::Text("Selection: %s, index = %d", data.input, data.index);
Source files https://github.com/sweihub/photon/blob/main/imgui/imgui_combo_autoselect.h https://github.com/sweihub/photon/blob/main/imgui/imgui_combo_autoselect.cpp
@sweihub The magnifying glass isn't part of imgui or standard fonts. I use imgui from within bgfx and they setup several icon fonts as part of their build:
// bgfx packs kenney image font with its imgui and this is a magnifying glass.
static const char* ICON_FA_SEARCH = u8"\ue935";
These fonts use the unicode private use area, so they shouldn't conflict with your CJK fonts.
You can see how bgfx do it by following their use of s_iconsKenneyTtf. It's packed into a header (I assume to avoid including assets with their lib).
If you're already familiar with setting up imgui fonts, then it might be easier to add the font yourself. Here's the font in the bgfx repo.
See more information about the font in nicodinh/kenney-icon-font.
fts_fuzzy_match is maintained here could you report the issue and the fix there? I don't quite understand what went wrong for you (maybe two issues: not displaying CJK and crashing when filtering for CJK characters?).
Thanks, this is a great topic, I'll definitely try to integrate these with my own code, you guys rock. Last time when I integrated this that simple UX case did not work: press TAB, press TAB, press Enter and select Dropdown, Enter text, press Enter.
I tried to improved @sweihub's ComboAutoSelect by:
Of course, there are some cons to this:
Cons of CRTP
Example:
struct Pair
{
std::string Name;
int IDNumber;
};
class ComboFilterPair : public ComboFilter<Pair, ComboFilterPair>
{
public:
ComboFilterPair(std::string_view combo_name, std::span
// This is the getter function for the span of objects you need to represent as string
// You can also make ComboFilter a friend of the inheriting class if you need GetItemString function to be private
const char* GetItemString(int index) const
{
return Items[index].Name.c_str();
}
};
static std::vector
}
https://gist.github.com/khlorz/5fbc89fa0c179341c6465fbca1425510
Fixing my previous bad attempt at improving ComboAutoSelect https://github.com/ocornut/imgui/issues/1658#issuecomment-1416914207:
#include <vector>
static const char* item_getter(const std::vector
static std::vector
![combo_auto_select_demo](https://user-images.githubusercontent.com/105799772/226155715-47e14437-2345-490c-acf9-cd688d932ff8.gif)
__Required C++ standard__: C++17 or later
https://github.com/khlorz/imgui-combo-filter
I feel like this is one of the small/nice feature that may have passed under the radar so I'm going to write a small blurb about it here.
There's a BeginCombo/EndCombo() api which is much more flexible that the "old"
Combo()
function.Basically with this api you can control the way you store your current selection and item storage (they don't have to be stored sequentially and randomly accessible, so if your natural "selection" data is a pointer you can use that, you can submit filtered lists easily, etc.
You can easily build combo boxes for your custom types using this.
Today I asked extra flags:
ImGuiComboFlags_NoArrowButton
ImGuiComboFlags_NoPreview
You could previously achieve this by pushing an item width the width of the button only, but it's not doable with just a flag.
ImGuiComboFlags_NoPreview
+ hidden label:Also consider creating custom layout like:
Clicking the preview area will get you the normal combo popup, etc.