Imgui: Tips: using BeginCombo()

Created on 3 Mar 2018  Â·  16Comments  Â·  Source: ocornut/imgui

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.
```
bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0);
void EndCombo(); // only call EndCombo() if BeginCombo() returns true!

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.

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();
}

You can easily build combo boxes for your custom types using this.

Today I asked extra flags:
`ImGuiComboFlags_NoArrowButton`

![image](https://user-images.githubusercontent.com/8225057/36938510-bbe7c05c-1f22-11e8-8564-c45f75f6f56f.png)

`ImGuiComboFlags_NoPreview`

![image](https://user-images.githubusercontent.com/8225057/36938523-ee86eba0-1f22-11e8-956f-c937debadb61.png)

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:

![image](https://user-images.githubusercontent.com/8225057/36938570-be837fbc-1f23-11e8-878a-a855cdb40713.png)

Also consider creating custom layout like:

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;
ImGuiComboFlags flags = ImGuiComboFlags_NoArrowButton;

ImGuiStyle& style = ImGui::GetStyle();
float w = ImGui::CalcItemWidth();
float spacing = style.ItemInnerSpacing.x;
float button_sz = ImGui::GetFrameHeight();
ImGui::PushItemWidth(w - spacing * 2.0f - button_sz * 2.0f);
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]);
if (ImGui::Selectable(items[n], is_selected))
current_item = items[n];
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::SameLine(0, spacing);
if (ImGui::ArrowButton("##r", ImGuiDir_Left))
{
}
ImGui::SameLine(0, spacing);
if (ImGui::ArrowButton("##r", ImGuiDir_Right))
{
}
ImGui::SameLine(0, style.ItemInnerSpacing.x);
ImGui::Text("Custom Combo");
```
image

Clicking the preview area will get you the normal combo popup, etc.

popups tricks & tips useful widgets

Most helpful comment

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

All 16 comments

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:

cmap

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);

image

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");

padding

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.

image

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):

gif

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.

image

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

ezgif com-video-to-gif

missing a pic!

missing a pic!

Done

Was this page helpful?
0 / 5 - 0 ratings

Related issues

the-lay picture the-lay  Â·  3Comments

bizehao picture bizehao  Â·  3Comments

BlackWatersInc picture BlackWatersInc  Â·  3Comments

NPatch picture NPatch  Â·  3Comments

DarkLinux picture DarkLinux  Â·  3Comments