Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SeparatorText / TextSeparator / CenteredSeparator (which respects columns and SameLine) #1643

Closed
phed opened this issue Feb 24, 2018 · 16 comments

Comments

@phed
Copy link

phed commented Feb 24, 2018

I wanted to write a decorative combined text and separator.
So, this TextSeparator can be used inside columns and respects SameLine().

There is a number of variations, and CenteredSeparator is the main function, which is obviously an edit of ImGui::Separator

The main difference between this and ImGui::Separator is that it is aligned centered vertically, and that it does not align to the edges of the window when used alone, since I haven't found a clean way to detect if it is the first element on a line.

Maybe someone will find it useful. I would love any feedback for how to make it cleaner.

Other issues discussing the topic is #759 #205 #697 - Probably more too

namespace ImGui {
    // This one is not public, so we just re-declare it :(
    // It is not used so it can be safely discarded. But here it is in case I/you want to put the original ::Separator back
    static void PushColumnClipRect(int column_index = -1)
    {
        ImGuiWindow* window = ImGui::GetCurrentWindow();
        if (column_index < 0)
            column_index = window->DC.ColumnsCurrent;

        float x1 = ImFloor(0.5f + window->Pos.x + ImGui::GetColumnOffset(column_index) - 1.0f);
        float x2 = ImFloor(0.5f + window->Pos.x + ImGui::GetColumnOffset(column_index+1) - 1.0f);
        ImGui::PushClipRect(ImVec2(x1,-FLT_MAX), ImVec2(x2,+FLT_MAX), true);
    }

    void CenteredSeparator(float width=0)
    {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->SkipItems)
            return;
        ImGuiContext& g = *GImGui;
        /* // Commented out because it is not tested, but it should work, but it won't be centered
        ImGuiWindowFlags flags = 0;
        if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0)
            flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
        IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))));   // Check that only 1 option is selected
        if (flags & ImGuiSeparatorFlags_Vertical)
        {
            VerticalSeparator();
            return;
        }
        */

        // Horizontal Separator
        float x1, x2;
        if ((window->DC.ColumnsCount == 1) && (width == 0))
        {
            // Span whole window
            ///x1 = window->Pos.x; // This fails with SameLine(); CenteredSeparator();
            // Nah, we have to detect if we have a sameline in a different way
            x1 = window->DC.CursorPos.x;
            x2 = x1 + window->Size.x;
        }
        else
        {
            // Start at the cursor
            x1 = window->DC.CursorPos.x;
            if (width != 0) {
                x2 = x1 + width;
            }
            else {
                x2 = window->ClipRect.Max.x;
                // Pad right side of columns (except the last one)
                if (window->DC.ColumnsCount > 1 && (window->DC.ColumnsCurrent < window->DC.ColumnsCount-1)) x2 -= g.Style.ItemSpacing.x; 
            }
        }
        float y1 = window->DC.CursorPos.y + int(window->DC.CurrentLineHeight / 2.0f);
        float y2 = y1 + 1.0f;

        window->DC.CursorPos.x += width; //+ g.Style.ItemSpacing.x;

        if (!window->DC.GroupStack.empty())
            x1 += window->DC.IndentX;

        const ImRect bb(ImVec2(x1, y1), ImVec2(x2, y2));
        ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
        if (!ItemAdd(bb, NULL))
        {
            return;
        }

        window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Border));
            
        /* // Commented out because LogText is hard to reach outside imgui.cpp
        if (g.LogEnabled)
            LogText(IM_NEWLINE "--------------------------------");
        */
    }

    // Create a centered separator right after the current item.
    // Eg.: 
    // ImGui::PreSeparator(10);
    // ImGui::Text("Section VI");
    // ImGui::SameLineSeparator();
    void SameLineSeparator(float width = 0) {
        ImGui::SameLine();
        CenteredSeparator(width);
    }

    // Create a centered separator which can be immediately followed by a item
    void PreSeparator(float width) {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->DC.CurrentLineHeight == 0)
            window->DC.CurrentLineHeight = ImGui::GetTextLineHeight();
        CenteredSeparator(width);
        ImGui::SameLine();
    }

    // The value for width is arbitrary. But it looks nice.
    void TextSeparator(char* text, int pre_width = 10) {
        ImGui::PreSeparator(pre_width);
        ImGui::Text(text);
        ImGui::SameLineSeparator();
    }

    void test_fancy_separator() {
        ImGuiIO io = ImGui::GetIO();
        static float t = 0.0f;
        t += io.DeltaTime;
        float f = sinf(4 * t * 3.14 / 9) * sinf(4 * t * 3.14 / 7);
        ImGui::PreSeparator(20 + 100*abs(f));
        ImGui::TextColored(ImColor(0.6f, 0.3f, 0.3f, 1.0f), "Fancy separators");
        ImGui::SameLineSeparator();
        ImGui::Bullet();
        ImGui::CenteredSeparator(100);
        ImGui::SameLine();
        ImGui::Text("Centered separator");
        ImGui::Columns(2);
        ImGui::PreSeparator(10);
        ImGui::Text("Separator");
        ImGui::SameLineSeparator();
        ImGui::CenteredSeparator();
        ImGui::Text("Column 1");
        ImGui::SameLineSeparator();

        ImGui::NextColumn();

        ImGui::PreSeparator(10);
        ImGui::Text("The Same Separator");
        ImGui::SameLineSeparator();
        ImGui::CenteredSeparator();
        ImGui::Text("Column 2");
        ImGui::SameLineSeparator();

        ImGui::Columns(1);
        ImGui::TextSeparator("So decorative");
        ImGui::CenteredSeparator();
    }
}
@ocornut
Copy link
Owner

ocornut commented Feb 28, 2018

Hello @phed,

Thanks for submitting that. I probably won't be able to look at it in depth or comment on it for a bit but it's good to have those references and ideas posted.

And here's a GIF for reference of what your demo looks like:
fancy_separator

Here's the same code with minor tweak to make it compatible with imgui's latest internals (your version of imgui is a little old I guess). Also PushColumnClipRect() is declared in imgui_internal.h so you don't have to copy, but you are not using it in your code either way.

#include "imgui_internal.h"

namespace ImGui 
{
    void CenteredSeparator(float width = 0)
    {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->SkipItems)
            return;
        ImGuiContext& g = *GImGui;
        /* 
        // Commented out because it is not tested, but it should work, but it won't be centered
        ImGuiWindowFlags flags = 0;
        if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0)
            flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
        IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))));   // Check that only 1 option is selected
        if (flags & ImGuiSeparatorFlags_Vertical)
        {
            VerticalSeparator();
            return;
        }
        */

        // Horizontal Separator
        float x1, x2;
        if (window->DC.ColumnsSet == NULL && (width == 0))
        {
            // Span whole window
            ///x1 = window->Pos.x; // This fails with SameLine(); CenteredSeparator();
            // Nah, we have to detect if we have a sameline in a different way
            x1 = window->DC.CursorPos.x;
            x2 = x1 + window->Size.x;
        }
        else
        {
            // Start at the cursor
            x1 = window->DC.CursorPos.x;
            if (width != 0) {
                x2 = x1 + width;
            }
            else 
            {
                x2 = window->ClipRect.Max.x;
                // Pad right side of columns (except the last one)
                if (window->DC.ColumnsSet && (window->DC.ColumnsSet->Current < window->DC.ColumnsSet->Count - 1))
                    x2 -= g.Style.ItemSpacing.x;
            }
        }
        float y1 = window->DC.CursorPos.y + int(window->DC.CurrentLineHeight / 2.0f);
        float y2 = y1 + 1.0f;

        window->DC.CursorPos.x += width; //+ g.Style.ItemSpacing.x;

        if (!window->DC.GroupStack.empty())
            x1 += window->DC.IndentX;

        const ImRect bb(ImVec2(x1, y1), ImVec2(x2, y2));
        ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
        if (!ItemAdd(bb, NULL))
        {
            return;
        }

        window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Border));

        /* // Commented out because LogText is hard to reach outside imgui.cpp
        if (g.LogEnabled)
        LogText(IM_NEWLINE "--------------------------------");
        */
    }

    // Create a centered separator right after the current item.
    // Eg.: 
    // ImGui::PreSeparator(10);
    // ImGui::Text("Section VI");
    // ImGui::SameLineSeparator();
    void SameLineSeparator(float width = 0) {
        ImGui::SameLine();
        CenteredSeparator(width);
    }

    // Create a centered separator which can be immediately followed by a item
    void PreSeparator(float width) {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->DC.CurrentLineHeight == 0)
            window->DC.CurrentLineHeight = ImGui::GetTextLineHeight();
        CenteredSeparator(width);
        ImGui::SameLine();
    }

    // The value for width is arbitrary. But it looks nice.
    void TextSeparator(char* text, float pre_width = 10.0f) 
    {
        ImGui::PreSeparator(pre_width);
        ImGui::Text(text);
        ImGui::SameLineSeparator();
    }

    void test_fancy_separator() 
    {
        ImGuiIO io = ImGui::GetIO();
        static float t = 0.0f;
        t += io.DeltaTime;
        float f = sinf(4 * t * 3.14f / 9.0f) * sinf(4 * t * 3.14f / 7.0f);
        ImGui::PreSeparator(20 + 100 * abs(f));
        ImGui::TextColored(ImColor(0.6f, 0.3f, 0.3f, 1.0f), "Fancy separators");
        ImGui::SameLineSeparator();
        ImGui::Bullet();
        ImGui::CenteredSeparator(100);
        ImGui::SameLine();
        ImGui::Text("Centered separator");
        ImGui::Columns(2);
        ImGui::PreSeparator(10);
        ImGui::Text("Separator");
        ImGui::SameLineSeparator();
        ImGui::CenteredSeparator();
        ImGui::Text("Column 1");
        ImGui::SameLineSeparator();

        ImGui::NextColumn();

        ImGui::PreSeparator(10);
        ImGui::Text("The Same Separator");
        ImGui::SameLineSeparator();
        ImGui::CenteredSeparator();
        ImGui::Text("Column 2");
        ImGui::SameLineSeparator();

        ImGui::Columns(1);
        ImGui::TextSeparator("So decorative");
        ImGui::CenteredSeparator();
    }
}

@hebohang
Copy link

Hello @phed,

Thanks for submitting that. I probably won't be able to look at it in depth or comment on it for a bit but it's good to have those references and ideas posted.

And here's a GIF for reference of what your demo looks like: fancy_separator fancy_separator

Here's the same code with minor tweak to make it compatible with imgui's latest internals (your version of imgui is a little old I guess). Also PushColumnClipRect() is declared in imgui_internal.h so you don't have to copy, but you are not using it in your code either way.

#include "imgui_internal.h"

namespace ImGui 
{
    void CenteredSeparator(float width = 0)
    {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->SkipItems)
            return;
        ImGuiContext& g = *GImGui;
        /* 
        // Commented out because it is not tested, but it should work, but it won't be centered
        ImGuiWindowFlags flags = 0;
        if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0)
            flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
        IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))));   // Check that only 1 option is selected
        if (flags & ImGuiSeparatorFlags_Vertical)
        {
            VerticalSeparator();
            return;
        }
        */

        // Horizontal Separator
        float x1, x2;
        if (window->DC.ColumnsSet == NULL && (width == 0))
        {
            // Span whole window
            ///x1 = window->Pos.x; // This fails with SameLine(); CenteredSeparator();
            // Nah, we have to detect if we have a sameline in a different way
            x1 = window->DC.CursorPos.x;
            x2 = x1 + window->Size.x;
        }
        else
        {
            // Start at the cursor
            x1 = window->DC.CursorPos.x;
            if (width != 0) {
                x2 = x1 + width;
            }
            else 
            {
                x2 = window->ClipRect.Max.x;
                // Pad right side of columns (except the last one)
                if (window->DC.ColumnsSet && (window->DC.ColumnsSet->Current < window->DC.ColumnsSet->Count - 1))
                    x2 -= g.Style.ItemSpacing.x;
            }
        }
        float y1 = window->DC.CursorPos.y + int(window->DC.CurrentLineHeight / 2.0f);
        float y2 = y1 + 1.0f;

        window->DC.CursorPos.x += width; //+ g.Style.ItemSpacing.x;

        if (!window->DC.GroupStack.empty())
            x1 += window->DC.IndentX;

        const ImRect bb(ImVec2(x1, y1), ImVec2(x2, y2));
        ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
        if (!ItemAdd(bb, NULL))
        {
            return;
        }

        window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Border));

        /* // Commented out because LogText is hard to reach outside imgui.cpp
        if (g.LogEnabled)
        LogText(IM_NEWLINE "--------------------------------");
        */
    }

    // Create a centered separator right after the current item.
    // Eg.: 
    // ImGui::PreSeparator(10);
    // ImGui::Text("Section VI");
    // ImGui::SameLineSeparator();
    void SameLineSeparator(float width = 0) {
        ImGui::SameLine();
        CenteredSeparator(width);
    }

    // Create a centered separator which can be immediately followed by a item
    void PreSeparator(float width) {
        ImGuiWindow* window = GetCurrentWindow();
        if (window->DC.CurrentLineHeight == 0)
            window->DC.CurrentLineHeight = ImGui::GetTextLineHeight();
        CenteredSeparator(width);
        ImGui::SameLine();
    }

    // The value for width is arbitrary. But it looks nice.
    void TextSeparator(char* text, float pre_width = 10.0f) 
    {
        ImGui::PreSeparator(pre_width);
        ImGui::Text(text);
        ImGui::SameLineSeparator();
    }

    void test_fancy_separator() 
    {
        ImGuiIO io = ImGui::GetIO();
        static float t = 0.0f;
        t += io.DeltaTime;
        float f = sinf(4 * t * 3.14f / 9.0f) * sinf(4 * t * 3.14f / 7.0f);
        ImGui::PreSeparator(20 + 100 * abs(f));
        ImGui::TextColored(ImColor(0.6f, 0.3f, 0.3f, 1.0f), "Fancy separators");
        ImGui::SameLineSeparator();
        ImGui::Bullet();
        ImGui::CenteredSeparator(100);
        ImGui::SameLine();
        ImGui::Text("Centered separator");
        ImGui::Columns(2);
        ImGui::PreSeparator(10);
        ImGui::Text("Separator");
        ImGui::SameLineSeparator();
        ImGui::CenteredSeparator();
        ImGui::Text("Column 1");
        ImGui::SameLineSeparator();

        ImGui::NextColumn();

        ImGui::PreSeparator(10);
        ImGui::Text("The Same Separator");
        ImGui::SameLineSeparator();
        ImGui::CenteredSeparator();
        ImGui::Text("Column 2");
        ImGui::SameLineSeparator();

        ImGui::Columns(1);
        ImGui::TextSeparator("So decorative");
        ImGui::CenteredSeparator();
    }
}

Hello ocornut, Im very thankful for your excellent work.

In the latest version (v1.89.1) I noted that there are some changes in the code above (in the 'CenteredSeparator' function):

ColumnsSet -> CurrentColumns
CurrentLineHeight -> CurrLineSize.y
IndentX -> Indent.x
GroupStack -> ItemWidthStack (not sure)

@ypujante
Copy link
Contributor

@hebohang yes indeed the code, as-is, does not compile with the latest ImGui release.

@ocornut I wished this control was part of the standard ImGui as it is quite useful and seems basic, especially when it comes to creating section. I have been using pre-opened TreeNode to combine separation/header but this control replaces it hands-down.

I am definitely 👍 on this control.

@ypujante
Copy link
Contributor

Here is an example on how it significantly help with my popup menu.

Before:

Screen Shot 2023-01-24 at 10 51 00

After
Screen Shot 2023-01-24 at 10 49 27

The only thing I wished is that the line would be at least as long before and after the text (it looks a little too short after CVIn1MaxMaxAutoReset or Selected Widgets (5)). But even with this minor visual glitch, it makes the popup menu so much nicer/easier to use.

@lukaasm
Copy link
Contributor

lukaasm commented Jan 27, 2023

Here is an example on how it significantly help with my popup menu.

Before:

Screen Shot 2023-01-24 at 10 51 00

After Screen Shot 2023-01-24 at 10 49 27

The only thing I wished is that the line would be at least as long before and after the text (it looks a little too short after CVIn1MaxMaxAutoReset or Selected Widgets (5)). But even with this minor visual glitch, it makes the popup menu so much nicer/easier to use.

image

Yeah, with some tweaks here and there it improves context menu clarity and general user experience. I am loving it :)

@ocornut ocornut changed the title CenteredSeparator (which respects columns and SameLine) TextSeparator / CenteredSeparator (which respects columns and SameLine) Jan 27, 2023
@ocornut
Copy link
Owner

ocornut commented Jan 27, 2023

I'm going to focus on a TextSeparator() function soon.

@ocornut
Copy link
Owner

ocornut commented Jan 27, 2023

I think we ought to have a simple function with only text parameters, and padding/centering are part of the Style structure.

My issue is that it would make sense to make it use its own color but that means associating more semantic to the widget, at which point the semantic may be more generic than a separator, and we need to know more about that in order to name things.

@ypujante
Copy link
Contributor

@ocornut I agree with you. It's better to follow the model already in place for other widgets

@ocornut
Copy link
Owner

ocornut commented Jan 27, 2023

I implemented this now:

// FIXME: How to fit e.g. a (?) helpmarker
void ImGui::SeparatorTextV(const char* fmt, va_list args)
{
    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return;

    ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;

    const char* label, *label_end;
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
    label_end = FindRenderedTextEnd(label, label_end);

    static float Style_SeparatorPaddingY = style.FramePadding.y;
    static float Style_SeparatorMinWidth = 10.0f;
    static float Style_SeparatorAlign = 0.0f;       // When set to >0.0f (most often 0.5f or 1.0f) generally MinLeadingWidth would be 0.0f
    static float Style_SeparatorSize = 1.0f;        // FIXME-STYLE: If intending a Separator (as per current name) abs thickness is good. If intending a larger fill may better be expressed as a % of total height.

#if 0 // [DEBUG] Until moved to style structure
    static ImGuiOnceUponAFrame ouaf;
    if (ouaf)
    {
        ImGui::Begin("WIP style");
        ImGui::SliderFloat("Align", &Style_SeparatorAlign, 0.0f, 1.0f);
        ImGui::DragFloat("MinWidth", &Style_SeparatorMinWidth, 1.0f, 0.0f, 100.0f);
        ImGui::DragFloat("PaddingY", &Style_SeparatorPaddingY, 1.0f, 0.0f, 20.0f);
        ImGui::DragFloat("Size", &Style_SeparatorSize, 0.2f, 0.0f, 40.0f, "%.0f");
        ImGui::End();
    }
#endif

    const ImVec2 label_size = CalcTextSize(label, label_end, false);
    const ImVec2 pos = window->DC.CursorPos;
    const float sep1_min_w = Style_SeparatorMinWidth;
    const float sep2_min_w = Style_SeparatorMinWidth;// 0.0f;

    const float min_size_padding = (sep1_min_w > 0.0f ? sep1_min_w + style.ItemSpacing.x : 0.0f) + (sep2_min_w > 0.0f ? sep2_min_w + style.ItemSpacing.x : 0.0f);
    const ImVec2 min_size(min_size_padding + label_size.x, label_size.y + Style_SeparatorPaddingY * 2.0f);
    const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
    ItemSize(min_size);
    if (!ItemAdd(bb, 0))
        return;

    const float seps_y = ImFloor((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
    const float sep1_x1 = pos.x;
    const float sep2_x2 = bb.Max.x;

    const float sep1_spacing = (sep1_min_w > 0.0f || Style_SeparatorAlign > 0.0f) ? style.ItemSpacing.x : 0.0f;
    const float sep2_spacing = (sep2_min_w > 0.0f || Style_SeparatorAlign < 1.0f) ? style.ItemSpacing.x : 0.0f;

    const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - sep1_spacing - sep1_min_w - sep2_spacing - sep2_min_w);

    ImVec2 label_pos;
    label_pos.x = pos.x + sep1_min_w + sep1_spacing + ImMax(0.0f, (label_avail_w - label_size.x) * Style_SeparatorAlign);
    label_pos.y = pos.y + Style_SeparatorPaddingY;

    const ImU32 col = GetColorU32(ImGuiCol_Separator);
    if (label_size.x > 0.0f)
    {
        const float sep1_x2 = label_pos.x - sep1_spacing;
        const float sep2_x1 = label_pos.x + label_size.x + sep2_spacing;
        if (sep1_x2 > sep1_x1)
            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), col, Style_SeparatorSize);
        if (sep2_x2 > sep2_x1)
            window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), col, Style_SeparatorSize);
        if (g.LogEnabled)
            LogSetNextTextDecoration("----", NULL);
        RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);
    }
    else
    {
        if (g.LogEnabled)
            LogText("----");
        window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), col, Style_SeparatorSize);
    }
}

image

With text ellipsis:

image

Centering etc.

image

It's better to follow the model already in place for other widgets

I probably didn't express myself clearly. My problem is that it's not easy to take the decision of how/what to present in Style and with which semantic, it has many ramifications and needs to be future-proof / forward-thinking. As with many of the style related issues that are still open, it may difficult to solve them without a more holistic approach of styling. Not being able to decide how to express those values in the style structure today means it is difficult for me to merge this widget in the code-base, but trivial for anyone to use the widget themselves.

e.g.
The control thickness. If we suggest the control is drawing a line (which is suggested by the name "Separator"), it makes sense for thickness to be expressed as an absolute size (e.g. 1~2 pixels). But if the look of the control because fatter it may make more sense for thickness to be expressed as a % of total height.

e.g.
If we add e.g. SeparatorThickness to the style structure that suggest that Separator() would use it.
That also suggest that they do have similar look, whereas you can imagine what we currently call TextSeparator() (and may not be a good name) to have MANY possible look & feel & shape.
Also Separator() currently wouldn't be able to honor a thicker thickness: it's been hard-coded as thickness == 1.0f because it has a ZERO Y advancement for layout (v1.70 tried to make that advancement match the thickness, but it was reverted in v1.71 because that caused issues for frequently used pattern of measuring heights ahead of time where we suggested that accounting for Separator height was not necessary).

I believe

  • We could decide on a different name for this widget, that doesn't suggest it is a "Separator". If we do have we would have more freedom to style this. EDIT TextHeader perhaps?
  • We should make another effort to have Separator() declare its height, which would break some code/layout in small ways but could be documented. That would be more consistent with other items, and facilitate making Separator() use thicker heights.

@ocornut
Copy link
Owner

ocornut commented Jan 27, 2023

  • I think we could solve the aforementioned problems by focusing on this being a "header" rather than a Separator.

e.g. TextHeader("hello")

  • 1/ This would dissociate our styling features and requirements from Separator(). The layout issue with Separator() can then be handled separately/later.
  • 2/ We can provide styling options that are more flexible (e.g. text color change, styles that don't even have a separator).

As that point we may draw inspiration from css/web headings and consider the possibility we would later provide several level of heading, where H1 to H3 are expected to be larger than current font size. So need to pick a name that will be compatible with those future extensions.

  • There's also the problem of wanting to fit additional items on the same line, commonly a (?) help marker. This is analogous to the generic version of Collapsing header optional close button #600. If the item semantic doesn't describe a given look it becomes hard. At least, to make this possible using lower-level API, I can rework/split the function so "text width" may be provided and somehow can build a helper to include extra stuff. I'll try that now.

@ocornut
Copy link
Owner

ocornut commented Jan 27, 2023

I went back on forth several times to settle on something..
Eventually settling on a hard-coded Separator semantic is currently simpler.
I could find reasonable names for style variables that are not confusing with Separator().

I have pushed a WIP branch:
https://github.com/ocornut/imgui/commits/features/separator_text
Current commit is 9670a4d

There are a few things I need to do

  • verify that auto-sizing declarations are correct
  • add missing ImGuiStyleVar_ to alter settings with PushStyleVar().
  • investigate splitting into lower-level helpers so it can combined with other things (not necessarily trivial)
  • investigate creating a collapsible version, which may interfere with TreeNode/CollapsingHeader and it'd be interesting to evaluate that.

@ypujante
Copy link
Contributor

ypujante commented Jan 27, 2023

@ocornut Thank you for spending cycles on this.

This is my view, but consider it just as brainstorming as I clearly do not know the internals of ImGui like you do...

I actually really like the concept of header. And from you said here are some ideas:

Idea 1:
What about a ImGui::BeginHeader() / ImGui::EndHeader() so you could write something like:

ImGui::BeginHeader();
ImGui::Text("my text");
if(!error.empty())
{
  ImGui::PushStyleColor(ImGuiCol_Text, kErrorColorU32);
  ImGui::TextUnformatted(ReGui::kErrorIcon);
  ImGui::PopStyleColor();
  if(ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal))
    {
      ImGui::BeginTooltip();
      ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
      ImGui::TextUnformatted(error.c_str());
      ImGui::PopTextWrapPos();
      ImGui::EndTooltip();
    }
}
ImGui::EndHeader();

Idea 2:

Like you mentioned css/websettings, you could imagine having styles associated to each "level", like ImGuiCol_H1_Text, etc... and then you could use something like:

ImGui::BeginHeader(1) // for using *_H1_* styles/colors
ImGui::BeginHeader(2) // for using *_H2_* styles/colors

Idea 3: How about extending the Separator concept so you could write something like

ImGui::BeginHorizontalLayout(); // I don't know if it exists but to prevent having to write SameLine() between each item
ImGui::SeparatorExactSize(10); // a 10 pixels wide line
ImGui::Text("my header");
// more stuff...
ImGui::SeparatorMinSize(10); // a line which extends until the end of the window but is at least 10 pixels wide
ImGui::EndHorizontalLayout();

@ocornut ocornut changed the title TextSeparator / CenteredSeparator (which respects columns and SameLine) SeparatorText / TextSeparator / CenteredSeparator (which respects columns and SameLine) Feb 10, 2023
ocornut added a commit that referenced this issue Feb 10, 2023
@ocornut
Copy link
Owner

ocornut commented Feb 10, 2023

I have pushed a SeparatorText() widget based on my code above.
Commit 99c0bd6

image

I believe the sizes/distances/alignment should be standardized and not provided by finer API calls, hence a simpler design than the finer-grained functions suggested by original post, and settings in style structure.
We need to preserve some semantic in the function calls in order to allow you to improve the styling system.

I consider this solved and would encourage you to use this now :)

@ocornut ocornut closed this as completed Feb 10, 2023
@ypujante
Copy link
Contributor

I updated to 1.89.3 (docking) and confirm that it works great. Thank you @ocornut

@aleksandaratanasov
Copy link

Hi. This is a great feature that allows separating the clutter in a nice way. I have a question though. Does the SeparatorText support different text alignments (centered, right)?

@ocornut
Copy link
Owner

ocornut commented Oct 27, 2023

Yes. See style editor and commit 99c0bd6 for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants