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
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;
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...
I'm still wondering how to make the column-headers fixed (like a top row in excel).
Anyone has an idea ?
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)
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.
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 :

The only 2 things I still dislike is :
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)
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:
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 :)
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..

Edit : I found that issue #512 may be related.
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 [email protected] 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.
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 :

Having ideas lot more clear than yesterday (no more fever), I have some questions which may be totally newbie ones...
Sorry if they are newbie question, and I would totally understand that I should more carefully RTFM, but I'm still sick lol :)
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.
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 !
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 :
It means that Columns have a very low overhead, that's great :)
Updated source code for column-headers, making them more configurable and easier to manage :
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
Nothing new on this topic, so I close it.
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!
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();
}
}
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.
@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.
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.
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.
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 :)
Tables API now merged in master.
Most helpful comment
Ok, I followed your advice, and it works pretty well when syncing both set of columns.
imgui_user.h :
imgui_user.inl :
And the usage becomes a lot easier :
Here is a capture of a test :

The only 2 things I still dislike is :
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)