ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

mrduda opened this issue · comments

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

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

commented

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:

**EDIT Since 1.90 (2023) please use this updated version in comment below --> ** #2668 (comment)

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!

Is there any way to get this to work with wrapped text? I've been trying to find out how to get the number of lines written, but it seems this is only calculated as part of ImFont::RenderText in which we loop over the whole string and calculate it. Is my only option to recreate this code to calculate the number of lines?

** Edit **
I solved this by doing the following to calculate the number of lines:

float Y = ImGui::GetCursorPosY();
ImGui::TextUnformatted(Start, End);
float NewY = ImGui::GetCursorPosY();
int NumLines = (NewY - Y) / LineHeight;

And then drawing the rect after the fact. This sort of works, but the rect changes the colour of the text because it's drawn overtop now.

Is there a correct way to achieve this?

Since 1.90+ code from here (#2668 (comment)) should be something like this:

const auto pos = ImGui::GetCursorPos();
ImGuiListClipper clipper;
clipper.Begin(row_count, line_height);
while (clipper.Step())
{
	for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; ++row_n)
	{
		ImU32 col = (row_n & 1) ? col_odd : col_even;
		if ((col & IM_COL32_A_MASK) == 0)
			continue;
		float y1 = y0 + (line_height * static_cast<float>(row_n));
		float y2 = y1 + line_height;
		draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col);
	}
}
ImGui::SetCursorPos(pos);
commented

Since 1.90+ code from here (#2668 (comment)) should be something like this:

That's correct. Standalone CalcListClipping() was removed since it can't handle non contiguous ranges. Thank you!