Column headers #513

Closed
opened 2016-01-29 08:21:57 +00:00 by itamago · 23 comments
itamago commented 2016-01-29 08:21:57 +00:00 (Migrated from github.com)

Hello,

In case you are creating tables with lot of columns, here is a short code snippet to quickly manage the column headers :

struct ColumnHeader
{
    const char* label;
    float size;
};

ColumnHeader headers[] =
    {
        { "Idx", 50 },
        { "Name", 500 },
        { "RT", 50 },
        { "Depth", 50 },
        { "Full", 50 },
        { "ClearColor", 50 },
        { "Width", 250 },
        { "Height", 250 },
        { "Filter", 250 },
        { "WrapU", 250 },
        { "WrapV", 250 },
        { "Flip", 50 },
        { "Filename", 450 }
    };

ImGui::Columns(IM_ARRAYSIZE(headers), "TableTextureColumns", true);
ImGui::Separator();
float offset = 0.0f;
for(ColumnHeader & header : headers)
{
    ImGui::Text(header.label);
    ImGui::SetColumnOffset(-1, offset);
    offset += header.size;
    ImGui::NextColumn();
}
ImGui::Separator();

// and then iterate over the rows of your table...

I'm wondering if there is a simple way to make the headers "fixed", like in Excel when the top row is fixed ?
Because you obviously want to see the headers while scrolling in your table.

Thx

Hello, In case you are creating _tables_ with lot of columns, here is a short code snippet to quickly manage the column headers : ``` struct ColumnHeader { const char* label; float size; }; ColumnHeader headers[] = { { "Idx", 50 }, { "Name", 500 }, { "RT", 50 }, { "Depth", 50 }, { "Full", 50 }, { "ClearColor", 50 }, { "Width", 250 }, { "Height", 250 }, { "Filter", 250 }, { "WrapU", 250 }, { "WrapV", 250 }, { "Flip", 50 }, { "Filename", 450 } }; ImGui::Columns(IM_ARRAYSIZE(headers), "TableTextureColumns", true); ImGui::Separator(); float offset = 0.0f; for(ColumnHeader & header : headers) { ImGui::Text(header.label); ImGui::SetColumnOffset(-1, offset); offset += header.size; ImGui::NextColumn(); } ImGui::Separator(); // and then iterate over the rows of your table... ``` I'm wondering if there is a simple way to make the headers "fixed", like in Excel when the top row is fixed ? Because you obviously want to see the headers while scrolling in your table. Thx
itamago commented 2016-01-29 08:44:05 +00:00 (Migrated from github.com)

A more elaborate code snippet for headers.
If you put a negative column-width, the width is calculated according to the column's label.
There is a flag (makeColumsWidthFixed) to set column widths every frames or only the first time.

// Header
struct ColumnHeader
{
    const char* label;
    float size;
};

ColumnHeader headers[] =
    {
        { "Idx",        -1 },
        { "Name",       500 },
        { "RT",         -1 },
        { "Depth",      -1 },
        { "Full",       -1 },
        { "ClearColor", -1 },
        { "Width",      150 },
        { "Height",     150 },
        { "Filter",     150 },
        { "WrapU",      150 },
        { "WrapV",      150 },
        { "Flip",       -1 },
        { "Filename",   450 }
    };

ImGui::Columns(EASE_ARRAYSIZE(headers), "WinTextureColumns", true);
ImGui::Separator();
float offset = 0.0f;
ImGuiStyle & style  = ImGui::GetStyle();

const bool makeColumsWidthFixed = false;
static bool s_firstTime = true;

for(ColumnHeader & header : headers)
{
    if(s_firstTime || makeColumsWidthFixed)
    {
        ImGui::SetColumnOffset(-1, offset);
        if(header.size >= 0)
        {
            offset += header.size;
        }
        else
        {
            ImVec2 textsize = ImGui::CalcTextSize(header.label, NULL, true);
            offset += (textsize.x + 2 * style.ItemSpacing.x);
        }
    }
    ImGui::Text(header.label);
    ImGui::NextColumn();
}
ImGui::Separator();

if(s_firstTime)
    s_firstTime = false;
A more elaborate code snippet for headers. If you put a negative column-width, the width is calculated according to the column's label. There is a flag (makeColumsWidthFixed) to set column widths every frames or only the first time. ``` // Header struct ColumnHeader { const char* label; float size; }; ColumnHeader headers[] = { { "Idx", -1 }, { "Name", 500 }, { "RT", -1 }, { "Depth", -1 }, { "Full", -1 }, { "ClearColor", -1 }, { "Width", 150 }, { "Height", 150 }, { "Filter", 150 }, { "WrapU", 150 }, { "WrapV", 150 }, { "Flip", -1 }, { "Filename", 450 } }; ImGui::Columns(EASE_ARRAYSIZE(headers), "WinTextureColumns", true); ImGui::Separator(); float offset = 0.0f; ImGuiStyle & style = ImGui::GetStyle(); const bool makeColumsWidthFixed = false; static bool s_firstTime = true; for(ColumnHeader & header : headers) { if(s_firstTime || makeColumsWidthFixed) { ImGui::SetColumnOffset(-1, offset); if(header.size >= 0) { offset += header.size; } else { ImVec2 textsize = ImGui::CalcTextSize(header.label, NULL, true); offset += (textsize.x + 2 * style.ItemSpacing.x); } } ImGui::Text(header.label); ImGui::NextColumn(); } ImGui::Separator(); if(s_firstTime) s_firstTime = false; ```
itamago commented 2016-01-29 09:38:47 +00:00 (Migrated from github.com)

Ok, after 1 coffee (I'm sick today...), here is a cleaner helper for column headers.

Considering you have two user files imgui_user.h and imgui_user.inl, here is the code for the helper :

In imgui_user.h :

namespace ImGui
{
    // ColumnHeader
    struct ColumnHeader
    {
        const char* label = NULL;           // Label of the header
              float size  = -1.0f;          // Negative value will calculate the size to fit label
    };

    // Draw column header
    IMGUI_API void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool setWidths, bool border=true, bool separator=true);

} // namespace ImGui

In imgui_user.inl :

namespace ImGui
{

    // Draw column header
    void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool setWidths, bool border, bool separator)
    {
        ImGui::Columns(count, columnsId, border);

        if(separator)
            ImGui::Separator();

        float offset = 0.0f;
        ImGuiStyle & style  = ImGui::GetStyle();

        for(int i=0; i < count; i++)
        {
            const ColumnHeader & header = headers[i];
            if(setWidths)
            {
                ImGui::SetColumnOffset(-1, offset);
                if(header.size >= 0)
                {
                    offset += header.size;
                }
                else
                {
                    ImVec2 textsize = ImGui::CalcTextSize(header.label, NULL, true);
                    offset += (textsize.x + 2 * style.ItemSpacing.x);
                }
            }
            ImGui::Text(header.label);
            ImGui::NextColumn();
        }

        if(separator)
            ImGui::Separator();
    }

} // namespace ImGui

And the code you put in your GUI :

    // Column headers
    const bool makeColumsWidthFixed = false;
    static bool s_firstTime = true;

    ImGui::ColumnHeader headers[] =
        {
            { "Idx",        -1 },
            { "Name",       500 },
            { "RT",         -1 },
            { "Depth",      -1 },
            { "Full",       -1 },
            { "ClearColor", -1 },
            { "Width",      150 },
            { "Height",     150 },
            { "Filter",     150 },
            { "WrapU",      150 },
            { "WrapV",      150 },
            { "Flip",       -1 },
            { "Filename",   450 }
        };

    ImGui::ColumnHeaders("WinTextureColumns", headers, EASE_ARRAYSIZE(headers), s_firstTime || makeColumsWidthFixed, true, true);

    if(s_firstTime)
        s_firstTime = false;

Ok, the "s_firstTime" flag could be handled internally the same way it is done for window's sizing... But I'm sick today...

Ok, after 1 coffee (I'm sick today...), here is a cleaner helper for column headers. Considering you have two user files _imgui_user.h_ and _imgui_user.inl_, here is the code for the helper : In imgui_user.h : ``` cpp namespace ImGui { // ColumnHeader struct ColumnHeader { const char* label = NULL; // Label of the header float size = -1.0f; // Negative value will calculate the size to fit label }; // Draw column header IMGUI_API void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool setWidths, bool border=true, bool separator=true); } // namespace ImGui ``` In imgui_user.inl : ``` cpp namespace ImGui { // Draw column header void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool setWidths, bool border, bool separator) { ImGui::Columns(count, columnsId, border); if(separator) ImGui::Separator(); float offset = 0.0f; ImGuiStyle & style = ImGui::GetStyle(); for(int i=0; i < count; i++) { const ColumnHeader & header = headers[i]; if(setWidths) { ImGui::SetColumnOffset(-1, offset); if(header.size >= 0) { offset += header.size; } else { ImVec2 textsize = ImGui::CalcTextSize(header.label, NULL, true); offset += (textsize.x + 2 * style.ItemSpacing.x); } } ImGui::Text(header.label); ImGui::NextColumn(); } if(separator) ImGui::Separator(); } } // namespace ImGui ``` And the code you put in your GUI : ``` cpp // Column headers const bool makeColumsWidthFixed = false; static bool s_firstTime = true; ImGui::ColumnHeader headers[] = { { "Idx", -1 }, { "Name", 500 }, { "RT", -1 }, { "Depth", -1 }, { "Full", -1 }, { "ClearColor", -1 }, { "Width", 150 }, { "Height", 150 }, { "Filter", 150 }, { "WrapU", 150 }, { "WrapV", 150 }, { "Flip", -1 }, { "Filename", 450 } }; ImGui::ColumnHeaders("WinTextureColumns", headers, EASE_ARRAYSIZE(headers), s_firstTime || makeColumsWidthFixed, true, true); if(s_firstTime) s_firstTime = false; ``` Ok, the "s_firstTime" flag could be handled internally the same way it is done for window's sizing... But I'm sick today...
itamago commented 2016-01-29 09:39:38 +00:00 (Migrated from github.com)

I'm still wondering how to make the column-headers fixed (like a top row in excel).
Anyone has an idea ?

I'm still wondering how to make the column-headers fixed (like a top row in excel). Anyone has an idea ?
ocornut commented 2016-01-29 09:55:26 +00:00 (Migrated from github.com)

Thanks. We may want to use that for inspiration as the columns API develops.
Linking to https://github.com/ocornut/imgui/issues/125 where older columns discussions happened.
My feeling is that we should introduce full-featured BeginColumns() / EndColumns() - (old Api Columns() can stay) so it is good to identify the different needs it may have and how those parameters would fit.

To answer your question, it can be done by duplicating the column set outside and inside a child window, but the columns offset needs to be synchronized across two sets. It's possible right now but a bit messy - figuring out minor offsetting issues due to child window padding and the presence of a scrollbar. That's the sort of thing that once we figure out we should include as part of a helper, maybe BeginColumns()/EndColumns(), so it's good to experiment with it.

Some other consideration: (some of them are discussed in #125)

  • clicking on columns header to provide a sort order to the app (optional)
  • moving / re-ordering columns headers (optional)
  • allowing the user to create finer interaction with columns header, perhaps add a context menu, might lead to an api more consistent with other ImGui where headers are declared one by one instead of packed into a table.
  • allowing user to add/remove columns headers (optional - aka for storage columns may need to be identified by name and not by indices, with a single hashing cache when starting the columns set).

We don't need all those features ready but I want to be confident that we can tackle all of them properly before introducing a new BeginColumns() api. So any research in those area is useful.

PS: You don't have to user imgui_user files anyway since you can include imgui_internal.h and have access to the most of the no-warantee-given innards there.

Thanks. We may want to use that for inspiration as the columns API develops. Linking to https://github.com/ocornut/imgui/issues/125 where older columns discussions happened. My feeling is that we should introduce full-featured BeginColumns() / EndColumns() - (old Api Columns() can stay) so it is good to identify the different needs it may have and how those parameters would fit. To answer your question, it can be done by duplicating the column set outside and inside a child window, but the columns offset needs to be synchronized across two sets. It's possible right now but a bit messy - figuring out minor offsetting issues due to child window padding and the presence of a scrollbar. That's the sort of thing that once we figure out we should include as part of a helper, maybe BeginColumns()/EndColumns(), so it's good to experiment with it. Some other consideration: (some of them are discussed in #125) - clicking on columns header to provide a sort order to the app (optional) - moving / re-ordering columns headers (optional) - allowing the user to create finer interaction with columns header, perhaps add a context menu, might lead to an api more consistent with other ImGui where headers are declared one by one instead of packed into a table. - allowing user to add/remove columns headers (optional - aka for storage columns may need to be identified by name and not by indices, with a single hashing cache when starting the columns set). We don't need all those features ready but I want to be confident that we can tackle all of them properly before introducing a new BeginColumns() api. So any research in those area is useful. PS: You don't have to user imgui_user files anyway since you can include `imgui_internal.h` and have access to the most of the no-warantee-given innards there.
itamago commented 2016-01-29 13:24:20 +00:00 (Migrated from github.com)

Ok, I followed your advice, and it works pretty well when syncing both set of columns.

imgui_user.h :

namespace ImGui
{

    // ColumnHeader
    struct ColumnHeader
    {
        const char* label      = NULL;          // Label of the header
              float size       = -1.0f;         // Negative value will calculate the size to fit label
        // Internal
              float syncOffset = -1.0f;         // Internal offset used for sync purpose
    };

    // Draw column headers
    IMGUI_API void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool border=true);

    // Synchronize with column headers
    IMGUI_API void BeginColumnHeadersSync(const char* columnsId, ColumnHeader* headers, int count, bool border=true);
    IMGUI_API void EndColumnHeadersSync(ColumnHeader* headers, int count);

} // namespace ImGui

imgui_user.inl :

    // Draw column headers
    void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool border)
    {
        if(count<=0)
            return;

        ImGuiStyle & style = ImGui::GetStyle();
        const ImVec2 firstTextSize = ImGui::CalcTextSize(headers[0].label, NULL, true);

        ImGui::BeginChild(columnsId, ImVec2(0,firstTextSize.y + 2 * style.ItemSpacing.y), true);


        char str_id[256];
        ImFormatString(str_id, IM_ARRAYSIZE(str_id), "col_%s", columnsId);

        ImGui::Columns(count, str_id, border);

        float offset = 0.0f;

        for(int i=0; i < count; i++)
        {
            ColumnHeader & header = headers[i];
            printf("SetColumnOffset %d -> %d\n", i, int(header.syncOffset));
            if(header.syncOffset < 0.0f)
            {
                ImGui::SetColumnOffset(i, offset);
                if(header.size >= 0)
                {
                    offset += header.size;
                }
                else
                {
                    const ImVec2 textsize = ImGui::CalcTextSize(header.label, NULL, true);
                    offset += (textsize.x + 2 * style.ItemSpacing.x);
                }
            }
            else
            {
                ImGui::SetColumnOffset(i, header.syncOffset);
            }
            header.syncOffset = ImGui::GetColumnOffset(i);
            printf("Header %d -> %d\n", i, int(header.syncOffset));
            ImGui::Text(header.label);
            ImGui::NextColumn();
        }

        ImGui::Columns(1);
        ImGui::EndChild();
    }

    // Synchronize with column headers
    void BeginColumnHeadersSync(const char* columnsId, ColumnHeader* headers, int count, bool border)
    {
        if(count<=0)
            return;

        ImGui::BeginChild(columnsId, ImVec2(0,0), true);
        ImGui::Columns(count, columnsId, border);

        float offset = 0.0f;
        ImGuiStyle & style  = ImGui::GetStyle();

        for(int i=0; i < count; i++)
        {
            ColumnHeader & header = headers[i];
            ImGui::SetColumnOffset(i, header.syncOffset);
            header.syncOffset = ImGui::GetColumnOffset(i);
            printf("Content %d -> %d\n", i, int(header.syncOffset));
        }
    }

    void EndColumnHeadersSync(ColumnHeader* headers, int count)
    {
        if(count<=0)
            return;

        ImGui::Columns(1);
        ImGui::EndChild();
    }

And the usage becomes a lot easier :

    // Header definition must be STATIC in order to keep internal data alive. Else, columns will not be dragable.
    static ImGui::ColumnHeader headers[] =
        {
            { "Idx",        -1 },
            { "Name",       500 },
            { "RT",         -1 },
            { "Depth",      -1 },
            { "Full",       -1 },
            { "ClearColor", -1 },
            { "Width",      150 },
            { "Height",     150 },
            { "Filter",     150 },
            { "WrapU",      150 },
            { "WrapV",      150 },
            { "Flip",       -1 },
            { "Filename",   450 }
        };

    ImGui::ColumnHeaders("WinTextureHeader", headers, IM_ARRAYSIZE(headers), true);

    // Table content
    ImGui::BeginColumnHeadersSync("WinTextureContent", headers, IM_ARRAYSIZE(headers), true);

    // DRAW ALL YOUR ROWS HERE

    ImGui::EndColumnHeadersSync(headers, EASE_ARRAYSIZE(headers));

Here is a capture of a test :
screen shot 2016-01-29 at 14 18 29

The only 2 things I still dislike is :

  • When syncing top Columns with bottom ones, there is no lag. But when syncing from bottom ones to top ones, there is one frame lag.
  • When you resize the window, the columns are not resized anymore because of the sync mechanism.
    But these artefacts are acceptable.

Ok.

Next, I would like to handle the double-click on a column-bar in order to resize the column to come back to its original size (same than Excel).
Any ideas about that ? (I'm not very familiar with ImGUI inputs... sorry)

Ok, I followed your advice, and it works pretty well when syncing both set of columns. imgui_user.h : ``` cpp namespace ImGui { // ColumnHeader struct ColumnHeader { const char* label = NULL; // Label of the header float size = -1.0f; // Negative value will calculate the size to fit label // Internal float syncOffset = -1.0f; // Internal offset used for sync purpose }; // Draw column headers IMGUI_API void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool border=true); // Synchronize with column headers IMGUI_API void BeginColumnHeadersSync(const char* columnsId, ColumnHeader* headers, int count, bool border=true); IMGUI_API void EndColumnHeadersSync(ColumnHeader* headers, int count); } // namespace ImGui ``` imgui_user.inl : ``` cpp // Draw column headers void ColumnHeaders(const char* columnsId, ColumnHeader* headers, int count, bool border) { if(count<=0) return; ImGuiStyle & style = ImGui::GetStyle(); const ImVec2 firstTextSize = ImGui::CalcTextSize(headers[0].label, NULL, true); ImGui::BeginChild(columnsId, ImVec2(0,firstTextSize.y + 2 * style.ItemSpacing.y), true); char str_id[256]; ImFormatString(str_id, IM_ARRAYSIZE(str_id), "col_%s", columnsId); ImGui::Columns(count, str_id, border); float offset = 0.0f; for(int i=0; i < count; i++) { ColumnHeader & header = headers[i]; printf("SetColumnOffset %d -> %d\n", i, int(header.syncOffset)); if(header.syncOffset < 0.0f) { ImGui::SetColumnOffset(i, offset); if(header.size >= 0) { offset += header.size; } else { const ImVec2 textsize = ImGui::CalcTextSize(header.label, NULL, true); offset += (textsize.x + 2 * style.ItemSpacing.x); } } else { ImGui::SetColumnOffset(i, header.syncOffset); } header.syncOffset = ImGui::GetColumnOffset(i); printf("Header %d -> %d\n", i, int(header.syncOffset)); ImGui::Text(header.label); ImGui::NextColumn(); } ImGui::Columns(1); ImGui::EndChild(); } // Synchronize with column headers void BeginColumnHeadersSync(const char* columnsId, ColumnHeader* headers, int count, bool border) { if(count<=0) return; ImGui::BeginChild(columnsId, ImVec2(0,0), true); ImGui::Columns(count, columnsId, border); float offset = 0.0f; ImGuiStyle & style = ImGui::GetStyle(); for(int i=0; i < count; i++) { ColumnHeader & header = headers[i]; ImGui::SetColumnOffset(i, header.syncOffset); header.syncOffset = ImGui::GetColumnOffset(i); printf("Content %d -> %d\n", i, int(header.syncOffset)); } } void EndColumnHeadersSync(ColumnHeader* headers, int count) { if(count<=0) return; ImGui::Columns(1); ImGui::EndChild(); } ``` And the usage becomes a lot easier : ``` cpp // Header definition must be STATIC in order to keep internal data alive. Else, columns will not be dragable. static ImGui::ColumnHeader headers[] = { { "Idx", -1 }, { "Name", 500 }, { "RT", -1 }, { "Depth", -1 }, { "Full", -1 }, { "ClearColor", -1 }, { "Width", 150 }, { "Height", 150 }, { "Filter", 150 }, { "WrapU", 150 }, { "WrapV", 150 }, { "Flip", -1 }, { "Filename", 450 } }; ImGui::ColumnHeaders("WinTextureHeader", headers, IM_ARRAYSIZE(headers), true); // Table content ImGui::BeginColumnHeadersSync("WinTextureContent", headers, IM_ARRAYSIZE(headers), true); // DRAW ALL YOUR ROWS HERE ImGui::EndColumnHeadersSync(headers, EASE_ARRAYSIZE(headers)); ``` Here is a capture of a test : <img width="1022" alt="screen shot 2016-01-29 at 14 18 29" src="https://cloud.githubusercontent.com/assets/16628136/12676513/32a39e7c-c693-11e5-99ca-693834941b30.png"> The only 2 things I still dislike is : - When syncing top Columns with bottom ones, there is no lag. But when syncing from bottom ones to top ones, there is one frame lag. - When you resize the window, the columns are not resized anymore because of the sync mechanism. But these artefacts are acceptable. Ok. Next, I would like to handle the double-click on a column-bar in order to resize the column to come back to its original size (same than Excel). Any ideas about that ? (I'm not very familiar with ImGUI inputs... sorry)
ocornut commented 2016-01-29 14:02:32 +00:00 (Migrated from github.com)

When syncing top Columns with bottom ones, there is no lag. But when syncing from bottom ones to top ones, there is one frame lag.

That fix is definitively desirable. We should definitively find a way to fix that. However, when using headers it may also be acceptable to restrict resizing to the headers section only and then it's easy to solve.

When you resize the window, the columns are not resized anymore because of the sync mechanism. But these artefacts are acceptable.

That's also something that could be considered as part of the "how do we express the column width". Your current system has width values (or -1) only, perhaps columns can be marked as "use remaining space" or have a finer way to express the constraints. Again this needs a little research. Note that I currently store width as % of the total width to handle this sort of thing in the basic.

Next, I would like to handle the double-click on a column-bar in order to resize the column to come back to its original size (same than Excel). Any ideas about that ? (I'm not very familiar with ImGUI inputs... sorry)

That interaction is currently handled in Columns()

            bool hovered, held;
            ButtonBehavior(column_rect, column_id, &hovered, &held, true);
            if (hovered || held)
                g.MouseCursor = ImGuiMouseCursor_ResizeEW;

            // Draw before resize so our items positioning are in sync with the line being drawn
            const ImU32 col = GetColorU32(held ? ImGuiCol_ColumnActive : hovered ? ImGuiCol_ColumnHovered : ImGuiCol_Column);
            const float xi = (float)(int)x;
            window->DrawList->AddLine(ImVec2(xi, y1+1.0f), ImVec2(xi, y2), col);

            if (held)
            {
                if (g.ActiveIdIsJustActivated)
                    g.ActiveClickDeltaToCenter.x = x - g.IO.MousePos.x;

                x = GetDraggedColumnOffset(i);
                SetColumnOffset(i, x);
            }

This ties with my earlier comment "allowing the user to create finer interaction with columns header, perhaps add a context menu, might lead to an api more consistent with other ImGui where headers are declared one by one instead of packed into a table.". We either need to code in this feature in that code, either expose the interaction somehow. If we have an api were columns are declared separately, it'd be easier to allow for the user to handle custom interactions.

Lots of open problems, thanks for trying those things. It is worth pushing those problems for now, and then we can design better api that would handle all/most of them properly.

Minor tips:

  • call AlignFirstTextHeightToWidgets() helper before your text field to vertically align them (and/or remove padding on the input field). Normally you'd have to do it once per line (on the Idx field) but the line height doesn't spread across columns (also an interesting thing to consider for later).
  • you can use ImGuiListClipper helper to "seek" directly to the visible portion of your list, reducing the CPU cost to a fraction if you have thousands of items.
> When syncing top Columns with bottom ones, there is no lag. But when syncing from bottom ones to top ones, there is one frame lag. That fix is definitively desirable. We should definitively find a way to fix that. However, when using headers it may also be acceptable to restrict resizing to the headers section only and then it's easy to solve. > When you resize the window, the columns are not resized anymore because of the sync mechanism. But these artefacts are acceptable. That's also something that could be considered as part of the "how do we express the column width". Your current system has width values (or -1) only, perhaps columns can be marked as "use remaining space" or have a finer way to express the constraints. Again this needs a little research. Note that I currently store width as % of the total width to handle this sort of thing in the basic. > Next, I would like to handle the double-click on a column-bar in order to resize the column to come back to its original size (same than Excel). Any ideas about that ? (I'm not very familiar with ImGUI inputs... sorry) That interaction is currently handled in Columns() ``` bool hovered, held; ButtonBehavior(column_rect, column_id, &hovered, &held, true); if (hovered || held) g.MouseCursor = ImGuiMouseCursor_ResizeEW; // Draw before resize so our items positioning are in sync with the line being drawn const ImU32 col = GetColorU32(held ? ImGuiCol_ColumnActive : hovered ? ImGuiCol_ColumnHovered : ImGuiCol_Column); const float xi = (float)(int)x; window->DrawList->AddLine(ImVec2(xi, y1+1.0f), ImVec2(xi, y2), col); if (held) { if (g.ActiveIdIsJustActivated) g.ActiveClickDeltaToCenter.x = x - g.IO.MousePos.x; x = GetDraggedColumnOffset(i); SetColumnOffset(i, x); } ``` This ties with my earlier comment "allowing the user to create finer interaction with columns header, perhaps add a context menu, might lead to an api more consistent with other ImGui where headers are declared one by one instead of packed into a table.". We either need to code in this feature in that code, either expose the interaction somehow. If we have an api were columns are declared separately, it'd be easier to allow for the user to handle custom interactions. Lots of open problems, thanks for trying those things. It is worth pushing those problems for now, and then we can design better api that would handle all/most of them properly. Minor tips: - call AlignFirstTextHeightToWidgets() helper before your text field to vertically align them (and/or remove padding on the input field). Normally you'd have to do it once per line (on the Idx field) but the line height doesn't spread across columns (also an interesting thing to consider for later). - you can use ImGuiListClipper helper to "seek" directly to the visible portion of your list, reducing the CPU cost to a fraction if you have thousands of items.
itamago commented 2016-01-29 17:05:49 +00:00 (Migrated from github.com)

Disabling the sync from bottom to top Columns solves both issues : no more lag (obviously), and when resizing the window all the columns follow the sizes of the top ones.
So, everything is quite good.
But, there is a new behaviour not really appealing : the bottom columns are still draggable, and when I drag one of them it takes back its place when drag-event ends (same place than the top-column).

I'll look at a way to disable dragging for bottom-columns only.

Thanks for the AlignFirstTextHeightToWidgets() tip ! I didn't see the misalignment at all, too much focused on the columns !
I had to put this call before every text fields of a single line to make them Venter-aligned, as if the NextColumn() is resetting the context.

Great that you gave me the tip for clipping, because that would have been my next question :)

Disabling the sync from bottom to top Columns solves both issues : no more lag (obviously), and when resizing the window all the columns follow the sizes of the top ones. So, everything is quite good. But, there is a new behaviour not really appealing : the bottom columns are still draggable, and when I drag one of them it takes back its place when drag-event ends (same place than the top-column). I'll look at a way to disable dragging for bottom-columns only. Thanks for the AlignFirstTextHeightToWidgets() tip ! I didn't see the misalignment at all, too much focused on the columns ! I had to put this call before every text fields of a single line to make them Venter-aligned, as if the NextColumn() is resetting the context. Great that you gave me the tip for clipping, because that would have been my next question :)
itamago commented 2016-01-29 17:27:42 +00:00 (Migrated from github.com)

Mmmh... The clipper has side effects : the first line is not correctly detected, and when I scroll by dragging the scrollbar it is flickering. I'll look at what is wrong with it..

screen shot 2016-01-29 at 18 32 26

Edit : I found that issue #512 may be related.

Mmmh... The clipper has side effects : the first line is not correctly detected, and when I scroll by dragging the scrollbar it is flickering. I'll look at what is wrong with it.. <img width="940" alt="screen shot 2016-01-29 at 18 32 26" src="https://cloud.githubusercontent.com/assets/16628136/12682985/b23abef4-c6b6-11e5-8b59-d619657ce8a2.png"> Edit : I found that issue #512 may be related.
ocornut commented 2016-01-29 17:38:24 +00:00 (Migrated from github.com)

You probably aren't passing the right height for your items. There is a helper that gives you FontSize + ItemSpacing.y + FramePadding.y*2

If you aren't sure of your height try seeking position +1 manually (using SetCursorPosY()) when eg. io.KeyCtrl is held for an easy comparison.

On 2016/01/29, at 17:27, Pacôme Danhiez notifications@github.com wrote:

Mmmh... The clipper has side effects : the first line is not correctly detected, and when I scroll by dragging the scrollbar it is flickering. I'll look at what is wrong with it..

—
Reply to this email directly or view it on GitHub.

You probably aren't passing the right height for your items. There is a helper that gives you `FontSize + ItemSpacing.y + FramePadding.y*2` If you aren't sure of your height try seeking position +1 manually (using SetCursorPosY()) when eg. io.KeyCtrl is held for an easy comparison. > On 2016/01/29, at 17:27, Pacôme Danhiez notifications@github.com wrote: > > Mmmh... The clipper has side effects : the first line is not correctly detected, and when I scroll by dragging the scrollbar it is flickering. I'll look at what is wrong with it.. > > — > Reply to this email directly or view it on GitHub.
itamago commented 2016-01-30 07:54:31 +00:00 (Migrated from github.com)

Here are the sources from yesterday evening (yes, I discovered Gist... ah ah) :

imgui_column_headers.h : https://gist.github.com/itamago/27f09c429d625a0d31ec
imgui_column_headers.cpp : https://gist.github.com/itamago/f297dee5788c4d9b9441
imgui_column_headers_usage.cpp : https://gist.github.com/itamago/7b305ee9aaf31843b8d1

Works quite well.

In the following capture, you will recognise the wip color-picker :
screen shot 2016-01-30 at 08 43 59

Having ideas lot more clear than yesterday (no more fever), I have some questions which may be totally newbie ones...

  • How do you make these widgets horizontal-centered in a column : CheckBox, ColorButton, Text ?
  • How do you left-align the text of a button ? (It seems pretty simple to modify the Button and ButtonEx functions by forwarding a ImGuiAlign flag which will be used in RenderTextClipped, so I guess there is another reason it's not done)
  • I love the wip-colorPicker ! Do you plan to make it resizable ?
  • What is the best way to handle multiple font-sizes ? For instance, I would like to reduce the font-size in Comboboxes, is it possible to apply a font-scale or something like that ?

Sorry if they are newbie question, and I would totally understand that I should more carefully RTFM, but I'm still sick lol :)

Here are the sources from yesterday evening (yes, I discovered Gist... ah ah) : imgui_column_headers.h : https://gist.github.com/itamago/27f09c429d625a0d31ec imgui_column_headers.cpp : https://gist.github.com/itamago/f297dee5788c4d9b9441 imgui_column_headers_usage.cpp : https://gist.github.com/itamago/7b305ee9aaf31843b8d1 Works quite well. In the following capture, you will recognise the wip color-picker : <img width="1500" alt="screen shot 2016-01-30 at 08 43 59" src="https://cloud.githubusercontent.com/assets/16628136/12694429/c15aec7a-c72d-11e5-8b3f-ff78da2b3834.png"> Having ideas lot more clear than yesterday (no more fever), I have some questions which may be totally newbie ones... - How do you make these widgets horizontal-centered in a column : CheckBox, ColorButton, Text ? - How do you left-align the text of a button ? (It seems pretty simple to modify the Button and ButtonEx functions by forwarding a ImGuiAlign flag which will be used in RenderTextClipped, so I guess there is another reason it's not done) - I love the wip-colorPicker ! Do you plan to make it resizable ? - What is the best way to handle multiple font-sizes ? For instance, I would like to reduce the font-size in Comboboxes, is it possible to apply a font-scale or something like that ? Sorry if they are newbie question, and I would totally understand that I should more carefully RTFM, but I'm still sick lol :)
ocornut commented 2016-01-30 14:32:50 +00:00 (Migrated from github.com)

Thanks. We should aim to make all this code invisible eventually.

I am wondering if the performances are holding up with so much columns switches. In particular NextColumn() does a PopClipRect()/PushColumnClipRect() which will always end up in a merged command, I think with recent changes to ImDrawList I should be able to remove those calls and only do them once at the begining/end of the set. Same for PushItemWidth which ideally should be preserved on a per-column basis. It's all fairly small/fast stuff but it may add up when you are drawing thousands of calls. Hope to remove those 4 calls from NextColumns() at least.

Typically I wouldn't even use so many columns, note how most of your elements don't really need to be resized. I'm always a little concerned when people try to push ImGui in a direction of trying to mimic well known UI systems and losing on the usage simplicity benefits of ImGui.. You can't turn a blind eye on the core design/benefits/limitations of ImGui and expect mimic to happen. You may end up disappointed!

I am not sure I want to turn ImGui into pretty-face options because that would defeat lots of its benefits. And frankly I don't have sufficient bandwidth to handle it, every micro visual decision has side-effects on code and performance (if I had the time I would consider finding the best spot between those two opposites).

How do you make these widgets horizontal-centered in a column : CheckBox, ColorButton, Text ?

You can't, or you set the position yourself.

How do you left-align the text of a button ? (It seems pretty simple to modify the Button and ButtonEx functions by forwarding a ImGuiAlign flag which will be used in RenderTextClipped, so I guess there is another reason it's not done)

You can't, and yes it'd be pretty simple to change. Probably best set within the Style?
Adding the flag to ButtonEx() would also work but maybe give too much importance to that feature.

I love the wip-colorPicker ! Do you plan to make it resizable ?

I plan to try to finish it and get it working first, I haven't figured out how to parametrize it yet.

What is the best way to handle multiple font-sizes ? For instance, I would like to reduce the font-size in Comboboxes, is it possible to apply a font-scale or something like that ?

There is a font scale but the best way is to bake a smaller font and call PushFont.

Thanks. We should aim to make all this code invisible eventually. I am wondering if the performances are holding up with so much columns switches. In particular NextColumn() does a PopClipRect()/PushColumnClipRect() which will always end up in a merged command, I think with recent changes to ImDrawList I should be able to remove those calls and only do them once at the begining/end of the set. Same for PushItemWidth which ideally should be preserved on a per-column basis. It's all fairly small/fast stuff but it may add up when you are drawing thousands of calls. Hope to remove those 4 calls from NextColumns() at least. Typically I wouldn't even use so many columns, note how most of your elements don't really need to be resized. I'm always a little concerned when people try to push ImGui in a direction of trying to mimic well known UI systems and losing on the usage simplicity benefits of ImGui.. You can't turn a blind eye on the core design/benefits/limitations of ImGui and expect mimic to happen. You may end up disappointed! I am not sure I want to turn ImGui into pretty-face options because that would defeat lots of its benefits. And frankly I don't have sufficient bandwidth to handle it, every micro visual decision has side-effects on code and performance (if I had the time I would consider finding the best spot between those two opposites). > How do you make these widgets horizontal-centered in a column : CheckBox, ColorButton, Text ? You can't, or you set the position yourself. > How do you left-align the text of a button ? (It seems pretty simple to modify the Button and ButtonEx functions by forwarding a ImGuiAlign flag which will be used in RenderTextClipped, so I guess there is another reason it's not done) You can't, and yes it'd be pretty simple to change. Probably best set within the Style? Adding the flag to ButtonEx() would also work but maybe give too much importance to that feature. > I love the wip-colorPicker ! Do you plan to make it resizable ? I plan to try to finish it and get it working first, I haven't figured out how to parametrize it yet. > What is the best way to handle multiple font-sizes ? For instance, I would like to reduce the font-size in Comboboxes, is it possible to apply a font-scale or something like that ? There is a font scale but the best way is to bake a smaller font and call PushFont.
itamago commented 2016-01-30 19:58:46 +00:00 (Migrated from github.com)

You're right, I need only 4 columns (and 2 resizable only).
I created so much columns to benchmark the API, and ImGui is pretty fast :)

Thanks for your advices, I will implement that tomorrow !

You're right, I need only 4 columns (and 2 resizable only). I created so much columns to benchmark the API, and ImGui is pretty fast :) Thanks for your advices, I will implement that tomorrow !
itamago commented 2016-01-31 10:20:36 +00:00 (Migrated from github.com)

I did a test by reducing from 13 columns to only 4. The widgets were the same, and I kept row-clipping disabled in order to maximise the number of vertices.

The gain is negligible :

  • 13 columns => 65920 vertices / 237117 indices / 79039 triangles
  • 4 columns => 64676 vertices / 235191 indices / 78397 triangles

It means that Columns have a very low overhead, that's great :)

I did a test by reducing from 13 columns to only 4. The widgets were the same, and I kept row-clipping disabled in order to maximise the number of vertices. The gain is negligible : - 13 columns => 65920 vertices / 237117 indices / 79039 triangles - 4 columns => 64676 vertices / 235191 indices / 78397 triangles It means that Columns have a very low overhead, that's great :)
itamago commented 2016-01-31 12:24:38 +00:00 (Migrated from github.com)

Updated source code for column-headers, making them more configurable and easier to manage :

  • label : pretty obvious...
  • size : if negative, the size will match the label's one
  • sizeMIN : minimum acceptable size (and if negative will match the label's size)
  • allowResize : whether the column is resizable.

imgui_column_headers.h : https://gist.github.com/itamago/27f09c429d625a0d31ec
imgui_column_headers.cpp : https://gist.github.com/itamago/f297dee5788c4d9b9441
imgui_column_headers_usage.cpp : https://gist.github.com/itamago/7b305ee9aaf31843b8d1

Updated source code for column-headers, making them more configurable and easier to manage : - label : pretty obvious... - size : if negative, the size will match the label's one - sizeMIN : minimum acceptable size (and if negative will match the label's size) - allowResize : whether the column is resizable. imgui_column_headers.h : https://gist.github.com/itamago/27f09c429d625a0d31ec imgui_column_headers.cpp : https://gist.github.com/itamago/f297dee5788c4d9b9441 imgui_column_headers_usage.cpp : https://gist.github.com/itamago/7b305ee9aaf31843b8d1
itamago commented 2016-04-14 20:55:04 +00:00 (Migrated from github.com)

Nothing new on this topic, so I close it.

Nothing new on this topic, so I close it.
ocornut commented 2016-04-14 20:57:51 +00:00 (Migrated from github.com)

Lots of ideas to filter and process. We still need official columns headers, etc. I'd rather keep topics open when they talk about stuff that I intend to do!

Lots of ideas to filter and process. We still need official columns headers, etc. I'd rather keep topics open when they talk about stuff that I intend to do!
r-lyeh-archived commented 2016-08-17 11:47:40 +00:00 (Migrated from github.com)

Hey there, simplified header-only snippet because I wanted it shorter and more ImGui styled.
Thank you very much to both of you :)

/*  // [src] https://github.com/ocornut/imgui/issues/513

    // Usage:
    static const char *headers[] = {
        "Index", "Color", "Flip?", "Filename"
    };
    static float widths[ IM_ARRAYSIZE(headers) ] = {}; 
    if( ImGui::BeginTable("WinTextureContent", headers, widths, IM_ARRAYSIZE(headers)) ) {
        // Draw as many rows as needed
        for( int i = 0; i < 10; ++i ) {
            ImGui::Text("%d", i);                              ImGui::NextColumn();
            ImGui::ColorButton( ImVec4(0.5f,0.2f,i*0.3f,1.f)); ImGui::NextColumn();
            ImGui::Text("%s", i % 2 ? "yes" : "no");           ImGui::NextColumn();
            ImGui::Text(__FILE__);                             ImGui::NextColumn();
        }
        ImGui::EndTable();
    }
*/

// .h
namespace ImGui {
IMGUI_API int  BeginTable(const char* columnsId, const char** headers, float *widths, int count, bool border=true);
IMGUI_API void EndTable();
}

// .cpp
namespace ImGui {
static inline IMGUI_API
int BeginTable(const char* columnsId, const char** headers, float* widths, int count, bool draw_border)
{
    if(count<=0)
        return 0;

    // Draw column headers
    ImGuiStyle & style = ImGui::GetStyle();
    const ImVec2 firstTextSize = ImGui::CalcTextSize(headers[0], NULL, true);

    ImGui::BeginChild(columnsId, ImVec2(0,firstTextSize.y + 2 * style.ItemSpacing.y), true);

    char str_id[256];
    sprintf(str_id, "tbl0_%s", columnsId);
    ImGui::Columns(count, str_id, draw_border);

    float offset = 0.0f;
    for(int i=0; i < count; i++)
    {
        ImGui::SetColumnOffset(i, offset);

        if(widths[i] <= 0)
        {
            const ImVec2 textsize = ImGui::CalcTextSize(headers[i], NULL, true);
            const float colSizeX = (textsize.x + 2 * style.ItemSpacing.x);
            widths[i] = colSizeX + 1;
        }

        if(i < (count-1))
        {
            float curOffset = offset;
            offset = ImGui::GetColumnOffset(i+1);
            widths[i] = offset - curOffset + 1;
        }

        ImGui::Text(headers[i]);
        ImGui::NextColumn();
    }

    ImGui::Columns(1);
    ImGui::EndChild();

    // Draw body
    str_id[3] = '1';
    columnsId = str_id;

    ImGui::BeginChild(columnsId, ImVec2(0,0), true);
    ImGui::Columns(count, columnsId, draw_border);

    offset = 0.0f;
    for(int i=0; i < count; i++)
    {
        ImGui::SetColumnOffset(i, offset);
        offset += widths[i] - 1;
    }

    return 1;
}

static inline IMGUI_API
void EndTable()
{
    ImGui::Columns(1);
    ImGui::EndChild();
}

}
Hey there, simplified header-only snippet because I wanted it shorter and more ImGui styled. Thank you very much to both of you :) ``` c++ /* // [src] https://github.com/ocornut/imgui/issues/513 // Usage: static const char *headers[] = { "Index", "Color", "Flip?", "Filename" }; static float widths[ IM_ARRAYSIZE(headers) ] = {}; if( ImGui::BeginTable("WinTextureContent", headers, widths, IM_ARRAYSIZE(headers)) ) { // Draw as many rows as needed for( int i = 0; i < 10; ++i ) { ImGui::Text("%d", i); ImGui::NextColumn(); ImGui::ColorButton( ImVec4(0.5f,0.2f,i*0.3f,1.f)); ImGui::NextColumn(); ImGui::Text("%s", i % 2 ? "yes" : "no"); ImGui::NextColumn(); ImGui::Text(__FILE__); ImGui::NextColumn(); } ImGui::EndTable(); } */ // .h namespace ImGui { IMGUI_API int BeginTable(const char* columnsId, const char** headers, float *widths, int count, bool border=true); IMGUI_API void EndTable(); } // .cpp namespace ImGui { static inline IMGUI_API int BeginTable(const char* columnsId, const char** headers, float* widths, int count, bool draw_border) { if(count<=0) return 0; // Draw column headers ImGuiStyle & style = ImGui::GetStyle(); const ImVec2 firstTextSize = ImGui::CalcTextSize(headers[0], NULL, true); ImGui::BeginChild(columnsId, ImVec2(0,firstTextSize.y + 2 * style.ItemSpacing.y), true); char str_id[256]; sprintf(str_id, "tbl0_%s", columnsId); ImGui::Columns(count, str_id, draw_border); float offset = 0.0f; for(int i=0; i < count; i++) { ImGui::SetColumnOffset(i, offset); if(widths[i] <= 0) { const ImVec2 textsize = ImGui::CalcTextSize(headers[i], NULL, true); const float colSizeX = (textsize.x + 2 * style.ItemSpacing.x); widths[i] = colSizeX + 1; } if(i < (count-1)) { float curOffset = offset; offset = ImGui::GetColumnOffset(i+1); widths[i] = offset - curOffset + 1; } ImGui::Text(headers[i]); ImGui::NextColumn(); } ImGui::Columns(1); ImGui::EndChild(); // Draw body str_id[3] = '1'; columnsId = str_id; ImGui::BeginChild(columnsId, ImVec2(0,0), true); ImGui::Columns(count, columnsId, draw_border); offset = 0.0f; for(int i=0; i < count; i++) { ImGui::SetColumnOffset(i, offset); offset += widths[i] - 1; } return 1; } static inline IMGUI_API void EndTable() { ImGui::Columns(1); ImGui::EndChild(); } } ```
v71 commented 2017-05-23 10:02:43 +00:00 (Migrated from github.com)

Hello, there is a severe bug when the window hosting the control is resized, the columns lose spacing and its not possibile to reset to their original size.

Hello, there is a severe bug when the window hosting the control is resized, the columns lose spacing and its not possibile to reset to their original size.
ocornut commented 2017-05-23 10:55:03 +00:00 (Migrated from github.com)

@v71

Hello, there is a severe bug when the window hosting the control is resized, the columns lose spacing and its not possibile to reset to their original size.

With which code? Please provide a repro.

@v71 > Hello, there is a severe bug when the window hosting the control is resized, the columns lose spacing and its not possibile to reset to their original size. With which code? Please provide a repro.
v71 commented 2017-05-23 11:00:17 +00:00 (Migrated from github.com)

With the code from r-lyeh , i basically copy pasted into a plain window and it showed the problem as soon as i resized the window, it takes 30 seconds to reproduce, i do not have a gif encoder in this computer right now.

With the code from r-lyeh , i basically copy pasted into a plain window and it showed the problem as soon as i resized the window, it takes 30 seconds to reproduce, i do not have a gif encoder in this computer right now.
CallumDev commented 2018-06-22 17:03:12 +00:00 (Migrated from github.com)

This code doesn't seem to work with latest ImGUI. In the case of the header-only snippet I can use ImGui::BeginColumns(str_id,count,ImGuiColumnsFlags_NoPreserveWidths) but that keeps it to header-only resizing.

This code doesn't seem to work with latest ImGUI. In the case of the header-only snippet I can use `ImGui::BeginColumns(str_id,count,ImGuiColumnsFlags_NoPreserveWidths)` but that keeps it to header-only resizing.
ocornut commented 2020-08-27 15:55:09 +00:00 (Migrated from github.com)

Hello @itamago and all,
As the official Tables API is looming in, I'm closing this old topic about recreating headers with the Columns API.
Thanks for the idea and suggestion discussed here :)

Hello @itamago and all, As the official Tables API is looming in, I'm closing this old topic about recreating headers with the Columns API. Thanks for the idea and suggestion discussed here :)
ocornut commented 2020-12-08 15:21:29 +00:00 (Migrated from github.com)

Tables API now merged in master.

Tables API now merged in master.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#513
No description provided.