Imgui: Alternate (odd-even) row background for lists and trees

Created on 16 Jul 2019  路  4Comments  路  Source: ocornut/imgui

Version/Branch of Dear ImGui:

Version: 1.72 WIP
Branch: master

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_dx11.cpp
Compiler: MSVC 2019
Operating System: Win10

My Issue/Question:

Alternate (odd-even) row background for lists and trees. Only one extra call is required (see function below), just before actual list/tree UI code. List/tree items are supposed to be of the same height (ImGui::GetTextLineHeight()), or alternatively you can specify any other height via optional argument. The color of alternate (highlighted) rows is passed as second optional argument.

I know, this is supposed to be standard functionality someday, maybe by using additional flags, but this is what I currently use and possibly it will help in any way. Otherwise, just ignore this. Feedback is appreciated.

Screenshots/Video

shot1
shot2

Standalone, minimal, complete and verifiable example:

void ItemRowsBackground(float lineHeight = -1.0f, const ImColor& color = ImColor(20, 20, 20, 64))
{
    auto* drawList = ImGui::GetWindowDrawList();
    const auto& style = ImGui::GetStyle();

    if (lineHeight < 0)
    {
        lineHeight = ImGui::GetTextLineHeight();
    }
    lineHeight += style.ItemSpacing.y;

    float scrollOffsetH = ImGui::GetScrollX();
    float scrollOffsetV = ImGui::GetScrollY();
    float scrolledOutLines = floorf(scrollOffsetV / lineHeight);
    scrollOffsetV -= lineHeight * scrolledOutLines;

    ImVec2 clipRectMin(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y);
    ImVec2 clipRectMax(clipRectMin.x + ImGui::GetWindowWidth(), clipRectMin.y + ImGui::GetWindowHeight());

    if (ImGui::GetScrollMaxX() > 0)
    {
        clipRectMax.y -= style.ScrollbarSize;
    }

    drawList->PushClipRect(clipRectMin, clipRectMax);

    bool isOdd = (static_cast<int>(scrolledOutLines) % 2) == 0;

    float yMin = clipRectMin.y - scrollOffsetV + ImGui::GetCursorPosY();
    float yMax = clipRectMax.y - scrollOffsetV + lineHeight;
    float xMin = clipRectMin.x + scrollOffsetH + ImGui::GetWindowContentRegionMin().x;
    float xMax = clipRectMin.x + scrollOffsetH + ImGui::GetWindowContentRegionMax().x;

    for (float y = yMin; y < yMax; y += lineHeight, isOdd = !isOdd)
    {
        if (isOdd)
        {
            drawList->AddRectFilled({ xMin, y - style.ItemSpacing.y }, { xMax, y + lineHeight }, color);
        }
    }

    drawList->PopClipRect();
}


// Please do not forget this!
ImGui::Begin("Example Bug");
ItemRowsBackground();
// whatever code that draws list or tree items
ImGui::End();
drawindrawlist tablecolumns tree useful widgets

Most helpful comment

Hello @mrduda, thanks for the suggestion and sorry for my late answer.

This will be available in the upcoming Tables API, however I do understand it may be useful even without tables.

Starting from your suggestion, I tried to rewrite it to make it simpler and improve on some points.Here's what I eventually settled on:

void DrawRowsBackground(int row_count, float line_height, float x1, float x2, float y_offset, ImU32 col_even, ImU32 col_odd)
{
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    float y0 = ImGui::GetCursorScreenPos().y + (float)(int)y_offset;

    int row_display_start;
    int row_display_end;
    ImGui::CalcListClipping(row_count, line_height, &row_display_start, &row_display_end);
    for (int row_n = row_display_start; row_n < row_display_end; row_n++)
    {
        ImU32 col = (row_n & 1) ? col_odd : col_even;
        if ((col & IM_COL32_A_MASK) == 0)
            continue;
        float y1 = y0 + (line_height * row_n);
        float y2 = y1 + line_height;
        draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col);
    }
}

It's a little different from yours:

  • Doesn't assume filling the entire window (so it can be used within only a part of the window)
  • Can provide both odd and even colors.
  • Colors are evenly spaced (if you look at your screenshot above the black section are taller than the grey ones).
  • Doesn't provide a default for line_height. Doesn't assume highlight needs to be offset by half line_height, both are parameters.
  • X coordinates have to be provided.

Usage demo:

ImGui::Begin("Manual Row background Test");
ImGui::Text("Some text before..");

float x1 = ImGui::GetCurrentWindow()->WorkRect.Min.x;
float x2 = ImGui::GetCurrentWindow()->WorkRect.Max.x;
float item_spacing_y = ImGui::GetStyle().ItemSpacing.y;
float item_offset_y = -item_spacing_y * 0.5f;
float line_height = ImGui::GetTextLineHeight() + item_spacing_y;
DrawRowsBackground(50, line_height, x1, x2, item_offset_y, 0, ImGui::GetColorU32(ImVec4(0.4f, 0.4f, 0.4f, 0.5f)));

for (int n = 0; n < 50; n++)
    ImGui::Text("Item %03d", n);

ImGui::End();

image

Using ImGui::GetCurrentWindow()->WorkRect.Min.x / Max.x for the extents is the more correct thing to do, but this is not a currently public API, so it needs including `imgui_internal.h" to work. You may replace it with:

float x1 = ImGui::GetWindowPos().x;
float x2 = x1 + ImGui::GetWindowSize().x;

I'll close this now as the topic is available for searching. I would consider adding the function as an internal helper if it was useful but being such a simpler and potentially tweakable function it may be reasonable for people to copy it.

Thanks again!

All 4 comments

Hi, first of all great idea. I took it for a spin and worked right away. Just having a tiny artifact at the bottom of my scroll, the line appears and disappears while scrolling, seems like a "Z" ordering problem, any idea how I might solve this?

Honeycam 2019-08-02 15-39-53

Hi DJLink, just a quick guess: try decrease clipRectMax.y by 1.

Hi DJLink, just a quick guess: try decrease clipRectMax.y by 1.

Ah silly me, simple enough, thank you for the help, and again for this code :)

Hello @mrduda, thanks for the suggestion and sorry for my late answer.

This will be available in the upcoming Tables API, however I do understand it may be useful even without tables.

Starting from your suggestion, I tried to rewrite it to make it simpler and improve on some points.Here's what I eventually settled on:

void DrawRowsBackground(int row_count, float line_height, float x1, float x2, float y_offset, ImU32 col_even, ImU32 col_odd)
{
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    float y0 = ImGui::GetCursorScreenPos().y + (float)(int)y_offset;

    int row_display_start;
    int row_display_end;
    ImGui::CalcListClipping(row_count, line_height, &row_display_start, &row_display_end);
    for (int row_n = row_display_start; row_n < row_display_end; row_n++)
    {
        ImU32 col = (row_n & 1) ? col_odd : col_even;
        if ((col & IM_COL32_A_MASK) == 0)
            continue;
        float y1 = y0 + (line_height * row_n);
        float y2 = y1 + line_height;
        draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col);
    }
}

It's a little different from yours:

  • Doesn't assume filling the entire window (so it can be used within only a part of the window)
  • Can provide both odd and even colors.
  • Colors are evenly spaced (if you look at your screenshot above the black section are taller than the grey ones).
  • Doesn't provide a default for line_height. Doesn't assume highlight needs to be offset by half line_height, both are parameters.
  • X coordinates have to be provided.

Usage demo:

ImGui::Begin("Manual Row background Test");
ImGui::Text("Some text before..");

float x1 = ImGui::GetCurrentWindow()->WorkRect.Min.x;
float x2 = ImGui::GetCurrentWindow()->WorkRect.Max.x;
float item_spacing_y = ImGui::GetStyle().ItemSpacing.y;
float item_offset_y = -item_spacing_y * 0.5f;
float line_height = ImGui::GetTextLineHeight() + item_spacing_y;
DrawRowsBackground(50, line_height, x1, x2, item_offset_y, 0, ImGui::GetColorU32(ImVec4(0.4f, 0.4f, 0.4f, 0.5f)));

for (int n = 0; n < 50; n++)
    ImGui::Text("Item %03d", n);

ImGui::End();

image

Using ImGui::GetCurrentWindow()->WorkRect.Min.x / Max.x for the extents is the more correct thing to do, but this is not a currently public API, so it needs including `imgui_internal.h" to work. You may replace it with:

float x1 = ImGui::GetWindowPos().x;
float x2 = x1 + ImGui::GetWindowSize().x;

I'll close this now as the topic is available for searching. I would consider adding the function as an internal helper if it was useful but being such a simpler and potentially tweakable function it may be reasonable for people to copy it.

Thanks again!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SlNPacifist picture SlNPacifist  路  3Comments

mkanakis picture mkanakis  路  3Comments

Folling picture Folling  路  3Comments

noche-x picture noche-x  路  3Comments

ocornut picture ocornut  路  3Comments