ocornut / imgui

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

Vertical centering of text in table rows #8021

Open merravid opened 4 days ago

merravid commented 4 days ago

Version/Branch of Dear ImGui:

Version 1.90.9, Branch: Docking

Back-ends:

imgui_impl_win32.cpp + imgui_impl_dx11.cpp

Compiler, OS:

Windows 11 + MSVC 2022

Full config/build information:

No response

Details:

When you add custom drawn items to one or more table cells, the row height increases to accommodate those items.

Any text in other cells in that row is aligned to the top of the cell instead of vertically centered.

What is the best way to achieve vertical centering of text in this case?

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

ocornut commented 4 days ago

There is a way but honestly at this point I strongly suggest to avoid it. It will make everything more complex and error prone.

A future update of the layout system will aim to allow it with more ease.

MartinClementson commented 1 day ago

Hi @merravid.

Here's the code that I use to centre the text vertically in a row.

if (auto* table = ImGui::GetCurrentTable()) {
    if (table->CurrentColumn >= 0) {
    auto& style = ImGui::GetStyle();
    auto colRect = ImGui::TableGetCellBgRect(table, table->CurrentColumn);
    ImGui::SetCursorPosY(ImGui::GetCursorPos().y + (colRect.GetHeight() / 2.f) - style.FramePadding.y - style.CellPadding.y );
   }
}
...
...
ImGui::Text       (drawText.c_str());   

Note: I was first testing this in a debug project, where I also had to take into account the text height, when I applied this solution to my main project I had to remove the text height to make it centred, I don't know why that was and since my project was too complex I didn't bother figuring out the reason, it depends on the style state I suppose. With that said, if the code above doesn't do it for you, replace the SetCursorPosY call with this

ImGui::SetCursorPosY(ImGui::GetCursorPos().y + (colRect.GetHeight() / 2.f) - style.FramePadding.y - style.CellPadding.y - ImGui::CalcTextSize(drawText.c_str()).y * 0.5f);

I hope it helps

ocornut commented 1 day ago

The lesser evil and most consistent thing I found was to use a helper and call it before every item in the line:

// Remember that "current line height" is not the same concept as "table row height".
// Because a table cell can contains multiple lines.
// In practice, when vertical alignment is desired in a table row it is generally when there is a single line of items in it.
static void AlignItemYInCurrentLine(float item_height, float item_alignment = 0.5f)
{
    // Use line extents
    ImGuiContext& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;
    float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y);
    float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y);
    float line_height = line_y2 - line_y1;
    window->DC.CursorPos.y = line_y1 + ImCeil((line_height - item_height) * item_alignment);
    window->DC.CurrLineTextBaseOffset = 0.0f; // FIXME: Disable text baseline offset
}

Again I don't recommend it until it is supported by the layout system. But this is more correct.

static void TestTableVerticalAlignment()
{
    ImGuiIO& io = ImGui::GetIO();
    static float item_alignment = 0.5f;
    ImGui::SliderFloat("item_alignment", &item_alignment, 0.0f, 1.0f, "%.2f");

    if (ImGui::BeginTable("##table", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
    {
        for (int row = 0; row < 5; row++)
        {
            // (1) New row
            const float row_height = 50;
            ImGui::TableNextRow(ImGuiTableRowFlags_None);

            // Submitting row height explicitly can be done this way:
            // - HOWEVER it's not useful as we submit an full height item below + our centering function behave on Line Height rather than Table Row Height.
            // - In order to keep the centering function more generic (e.g. can work outside tables) they use Line Height rather than Table Row Height
            // - TableNextRow() height currently specified with padding included, so we compute it. Which IHMO is not great API design might want to change but that's another topic.
            //const float row_height_with_cell_padding = row_height + ImGui::GetStyle().CellPadding.y * 2.0f;
            //ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height_with_cell_padding);

            // (2) Submit a Selectable or full-height item as the first thing
            // - this locks the Line Y1 value to our item/selectable Y1 value.
            // - so we don't need to bother passing any value to TableNextRow() anyway
            ImGui::TableSetColumnIndex(0);
#if 1
            // (2-A) Submit a spanning selectable (will also start Line Y1 position and Line Height)
            ImGui::Selectable("##selectable", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0.0f, row_height));
#else
            // (2-B) Alternative if you don't need a Selectable(): Submit a dummy item to Lock Line Y1 and Line Height..
            // If we don't do that and our first item submitted after is vertically centered, next item will set Line Y1 at wrong position.
            ImGui::Dummy(ImVec2(0.0f, row_height));
#endif
            ImGui::SameLine(0.0f, 0.0f);
            if (io.KeyShift)
                ImGui::DebugDrawLineExtents();

            // Column 0
            ImGui::TableSetColumnIndex(0);
            AlignItemYInCurrentLine(ImGui::GetTextLineHeight(), item_alignment);
            ImGui::Text("ICON1");
            ImGui::SameLine();
            AlignItemYInCurrentLine(ImGui::GetTextLineHeight(), item_alignment);
            ImGui::Text("ICON2");

            // Column 1
            ImGui::TableSetColumnIndex(1);

            // Call SameLine(0,0) when entering the column
            // This is important as Line Height is not shared by default between columns (because they could contains different things)
            // This is now demonstrated in 'Demo->Tables->Row Height'
            // FIXME: As this is a relatively current/desirable pattern, may make it opt-in a table flags?
            ImGui::SameLine(0.0f, 0.0f);
            if (io.KeyShift)
                ImGui::DebugDrawLineExtents();

            // Submit item
            if ((row & 1) == 0)
            {
                AlignItemYInCurrentLine(ImGui::GetTextLineHeight(), item_alignment);
                ImGui::Text("ICON3");
            }
            else
            {
                AlignItemYInCurrentLine(32.0f, item_alignment);
                ImGui::ColorButton("##button", ImVec4(0.0f, 1.0f, 0.0f, 1.0f), 0, ImVec2(32.0f, 32.0f));
            }
            ImGui::SameLine();
            if (io.KeyShift)
                ImGui::DebugDrawLineExtents();

            AlignItemYInCurrentLine(ImGui::GetTextLineHeight(), item_alignment);
            ImGui::Text("Text");

            ImGui::SameLine();
            AlignItemYInCurrentLine(ImGui::GetTextLineHeight(), item_alignment);
            ImGui::Text("MoreText");

        }
        ImGui::EndTable();
    }
}