ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.16k stars 10.31k forks source link

Selectable in nested table #7442

Closed minh0722 closed 7 months ago

minh0722 commented 7 months ago

Version/Branch of Dear ImGui:

Version 1.90.1, Branch: master

Back-ends:

imgui_impl_win32.cpp + imgui_impl_dx11.cpp

Compiler, OS:

Windows 10 + MSVC 2022

Full config/build information:

Dear ImGui 1.90.1 WIP (19002)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1938
define: _MSVC_LANG=202002
--------------------------------
io.BackendPlatformName: imgui_impl_win32
io.BackendRendererName: imgui_impl_dx11
io.ConfigFlags: 0x00000000
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigWindowsMoveFromTitleBarOnly
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000F
 HasGamepad
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64
io.DisplaySize: 1680.00,987.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

My Issue/Question: How to get selectable to work on a nested table?

I have a table with 2 columns, with the first column containing another table (with call/put options), and the other column contains navigation buttons. Below it the string where I marked in red rectangle specifies whether user clicks on call or put options. By default it says None if nothing has been clicked yet. I'm using nested table and selectable in the standard way in the demo, but selectable doesn't seem to work in nested table.

Screenshots/Video:

imgui-selectable-nested-table-issue

Minimal, Complete and Verifiable Example code:

static std::string selectedColumn = "None";
ImGui::BeginTable("##wrapOptionTable", 2, (tableFlags & ~ImGuiTableFlags_Reorderable));
{
    const std::string& selectedSymbol = client->getOptionChainContractDescription().contract.symbol;
    ImGui::TableSetupColumn(selectedSymbol.c_str());
    ImGui::TableSetupColumn("##pageNavColumn", ImGuiTableColumnFlags_WidthStretch);
    ImGui::TableHeadersRow();

    ImGui::TableNextRow();

    // column 0
    ImGui::TableNextColumn();
    {
        ImGui::BeginTable("##optionChainTable", COLUMN_COUNT, (tableFlags & ~ImGuiTableFlags_Reorderable) | ImGuiTableFlags_BordersInnerV);

        ImGui::TableSetupScrollFreeze(0, 1);
        ImGui::TableSetupColumn("Open interest", ImGuiTableColumnFlags_WidthFixed);
        ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Bid size", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Bid", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Ask", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Ask size", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Strike", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Open interest", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Bid size", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Bid", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Ask", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("Ask size", ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableHeadersRow();

        static bool callSelected = false;
        static bool putSelected = false;
        for (int columnIdx = 0; columnIdx < COLUMN_COUNT; ++columnIdx)
        {
            ImGui::TableSetColumnIndex(columnIdx);

            if (ImGui::TableGetColumnFlags(columnIdx) & ImGuiTableColumnFlags_IsHovered)
            {
                callSelected = columnIdx <= 5;
                putSelected = columnIdx >= 7;
            }
        }

        size_t optionEntriesCount = callOptionEntries.size();
        for (size_t entryIdx = 0; entryIdx < optionEntriesCount; ++entryIdx)
        {
            // make sure we have the same strike prices
            if (callOptionStrikes[entryIdx] != putOptionStrikes[entryIdx])
            {
                throw std::exception("Option strike prices for call and put don't match");
            }

            const OptionChainEntry& callOptionEntry = callOptionEntries[entryIdx];
            const OptionChainEntry& putOptionEntry = putOptionEntries[entryIdx];

            // push id for unique selectable id, since its label is id and they can be duplicated
            ImGui::PushID(&callOptionEntry);
            ImGui::TableNextRow();

            ImGui::TableSetColumnIndex(0);
            if (ImGui::Selectable(decimalStringToDisplay(callOptionEntry.m_openInterest).c_str(), false, ImGuiSelectableFlags_SpanAllColumns))
            {
                if (callSelected)
                    selectedColumn = "Call";
                else if (putSelected)
                    selectedColumn = "Put";
            }

            ImGui::TableSetColumnIndex(1);
            ImGui::Text(decimalStringToDisplay(callOptionEntry.m_volume).c_str());

            ImGui::TableSetColumnIndex(2);
            ImGui::Text(decimalStringToDisplay(callOptionEntry.m_bidSize).c_str());

            ImGui::TableSetColumnIndex(3);
            ImGui::Text(Utils::doubleMaxString(callOptionEntry.m_bidPrice).c_str());

            ImGui::TableSetColumnIndex(4);
            ImGui::Text(Utils::doubleMaxString(callOptionEntry.m_askPrice).c_str());

            ImGui::TableSetColumnIndex(5);
            ImGui::Text(decimalStringToDisplay(callOptionEntry.m_askSize).c_str());

            ImGui::TableSetColumnIndex(6);
            ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImVec4(0.45f, 0.47f, 0.52f, 1.0f)));
            ImGui::Text("%s", Utils::doubleMaxString(callOptionStrikes[entryIdx]).c_str());

            ImGui::TableSetColumnIndex(7);
            ImGui::Text(decimalStringToDisplay(putOptionEntry.m_openInterest).c_str());

            ImGui::TableSetColumnIndex(8);
            ImGui::Text(decimalStringToDisplay(putOptionEntry.m_volume).c_str(), false, ImGuiSelectableFlags_SpanAllColumns);

            ImGui::TableSetColumnIndex(9);
            ImGui::Text(decimalStringToDisplay(putOptionEntry.m_bidSize).c_str(), false, ImGuiSelectableFlags_SpanAllColumns);

            ImGui::TableSetColumnIndex(10);
            ImGui::Text(Utils::doubleMaxString(putOptionEntry.m_bidPrice).c_str(), false, ImGuiSelectableFlags_SpanAllColumns);

            ImGui::TableSetColumnIndex(11);
            ImGui::Text(Utils::doubleMaxString(putOptionEntry.m_askPrice).c_str(), false, ImGuiSelectableFlags_SpanAllColumns);

            ImGui::TableSetColumnIndex(12);
            ImGui::Text(decimalStringToDisplay(putOptionEntry.m_askSize).c_str(), false, ImGuiSelectableFlags_SpanAllColumns);

            ImGui::PopID();
        }

        ImGui::EndTable();

        optionChainTableSize = ImGui::GetItemRectSize();
    }

    // column 1
    ImGui::TableNextColumn();
    {
        if (ImGui::Button("^", ImVec2(-FLT_MIN, optionChainTableSize.y / 2)))
        {
            BackgroundTaskScheduler::AddTask([]()
            {
                optionChainPageChanging = true;
                g_Clients[0]->ChangeOptionChainPage(OptionChainPageDirection::PageUp);
                optionChainPageChanging = false;
            });
        }

        if (ImGui::Button("v", ImVec2(-FLT_MIN, optionChainTableSize.y / 2)))
        {
            BackgroundTaskScheduler::AddTask([]()
            {
                optionChainPageChanging = true;
                g_Clients[0]->ChangeOptionChainPage(OptionChainPageDirection::PageDown);
                optionChainPageChanging = false;
            });
        }
    }

    ImGui::EndTable();
}
ocornut commented 7 months ago

Hello,

I don't think your issue has anything to do with nested tables.

if (ImGui::Selectable(decimalStringToDisplay(callOptionEntry.m_openInterest).c_str(), false, 

You are passing selected = false to your selectable aka specifying that it is always unselected. You are supposed to maintain your selection state somehow, e.g. store the current selection index.

When submitting repro code, always try to narrow it down to the minimum amount of possible code that other people can compile (aka remove any extraneous code, and remove external dependency by e.g. replacing them with hardcoded values), and by the time you have done this you are likely to have solved your issue.

minh0722 commented 7 months ago

Thanks for the answer, will do better next time!