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

Tabs #261

Open
Roflraging opened this Issue Jul 6, 2015 · 30 comments

Comments

@Roflraging

Roflraging commented Jul 6, 2015

EDIT Jan. 22, 2016:
TL;DR; Here's the relevant pieces of code highlighted to make your own version:

https://gist.github.com/Roflraging/fe9a582cb7a79400b667#file-gistfile1-cpp-L1-L19
https://gist.github.com/Roflraging/fe9a582cb7a79400b667#file-gistfile1-cpp-L180-L213
https://gist.github.com/Roflraging/fe9a582cb7a79400b667#file-gistfile1-cpp-L243-L296

This is kind of a "here's what I'm trying out now, maybe others can try it" post, rather than a specific issue/feature request.

The quick screenshot comparisons. Up first, the original version I had before tabs:
screenshot-brigador 231ef9e6d7a1a482302d01298b9fc010b46a3d9c 13 45 10 jul 6 2015 release-assertions

Tabs version:
screenshot-brigador fb8e92748d471f1208235c814022bf0c976a09d4 13 59 50 jul 6 2015 release-assertions

Video comparisons:

Since I first started using ImGui, our usage of it in the game has exploded quite rapidly to a point where the GUI itself has become cumbersome. Ever since I had 3 different debugging windows in our game, I've wanted to have a tab functionality (like all modern browsers). I think I could have come up with a reasonable implementation for a long time now, but it wasn't until 1.40 with the menu stuff that it really popped into my head that I could do it reasonably.

Over the weekend I went to work at it, but funnily enough, I didn't really need much of the menu API to really be able to pull it off (Popups sure come in handy though!).

My approach is really quite simple and behaves nicely with most of the API, but columns remain an issue, which I'll detail a little bit later. Instead of trying to explain everything in English, I'll just drop in the relevant code blocks here. You probably won't be able to use it directly, but should show enough of the approach that people can try it out on their own.

Code overview: https://gist.github.com/Roflraging/fe9a582cb7a79400b667

The one issue I've encountered with this is columns from one tab can interfere with columns from another tab. For example, if I resize columns from Tab 1 and Tab 3 also has columns, it is possible for Tab 3 to have the same offsets as columns in Tab 1, even though I never touched Tab 3's columns. I tried to work around this at the top level of my code by having PanelTab contain memory to retain column offsets and simply setting the column offsets before calling into the handler and retrieving them afterward. But this doesn't quite work... mainly because right now you'll hit asserts in the column offset code. I might have gotten this wrong, but I couldn't work around it.

An improvement I could make on this is to allow for tabs to be closable or reordered, but I don't need that functionality right now so I didn't implement it. Reordering is very easy to do. Just swap array elements and you're done!

@Roflraging

This comment has been minimized.

Show comment
Hide comment
@Roflraging

Roflraging Jul 6, 2015

I've actually made a slight tweak which to the gist which creates a child region before calling into the gui handler. This allows the tab bar to always be visible when the tab contents are scrollable!

Roflraging commented Jul 6, 2015

I've actually made a slight tweak which to the gist which creates a child region before calling into the gui handler. This allows the tab bar to always be visible when the tab contents are scrollable!

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Jul 6, 2015

Owner

Functionality wise if you use a set of RadioButton() widgets you are getting the same feature as basic tab (apart from re-ordering).

So proper tab would have to render differently than RadioButton() and allow for more natural ordering and horizontal scrolling. What else would be desirable?

Not sure why you have both the selectables and a popup menu here (I suppose the popup menu helps if you have too many tabs, for the lack of arrows to scroll horizontally).

For the column conflicting you can either name the columnset or just do a PushId()/PopId() encompassing the entire tab content.

What assert did you get in the column code? I recall there was an id bug which would led to asserts but it was fixed more than two months ago.

Owner

ocornut commented Jul 6, 2015

Functionality wise if you use a set of RadioButton() widgets you are getting the same feature as basic tab (apart from re-ordering).

So proper tab would have to render differently than RadioButton() and allow for more natural ordering and horizontal scrolling. What else would be desirable?

Not sure why you have both the selectables and a popup menu here (I suppose the popup menu helps if you have too many tabs, for the lack of arrows to scroll horizontally).

For the column conflicting you can either name the columnset or just do a PushId()/PopId() encompassing the entire tab content.

What assert did you get in the column code? I recall there was an id bug which would led to asserts but it was fixed more than two months ago.

@Roflraging

This comment has been minimized.

Show comment
Hide comment
@Roflraging

Roflraging Jul 7, 2015

So proper tab would have to render differently than RadioButton() and allow for more natural ordering and horizontal scrolling. What else would be desirable?

That's all I can think of right now!

Not sure why you have both the selectables and a popup menu here (I suppose the popup menu helps if you have too many tabs, for the lack of arrows to scroll horizontally).

That is the only reason. It was the simplest solution I could come up with to be able to get to all the tabs without forcing you to resize the window to see all of them.

With respect to the column assert, I'd have to go back and try to recollect what I was doing exactly, but it was basically me doing a ImGui::GetColumnOffset() and ImGui::SetColumnOffset() just before and after the call to the gui handler, like this:

PanelTab *selected = tabs + selectedTab;
ImGui::BeginChild("tab main contents");

for (int i = 0; i < numCols; ++i)
{
    ImGui::SetColumnOffset(i, selected->colOffset[i]);
}

selected->guiHandler(selected->userdata);

for (int i = 0; i < numCols; ++i)
{
    selected->colOffset[i] = ImGui::GetColumnOffset(i);
}

ImGui::EndChild()

And the assertion would occur inside one of the get/set column offsets. Clearly this was just my fault, because there's no guarantee that any columns even exist when I call these functions at these locations.

This was a really poorly thought out attempt at solving this problem anyways... I just tried your suggestion with the pushing IDs and that totally solves it. The reason why it didn't work for me was because I was pushing the same string for all the tabs. I changed it so that when I create the child region, I send the tab name instead so each tab can have their own ID space. Columns don't interfere now!

Roflraging commented Jul 7, 2015

So proper tab would have to render differently than RadioButton() and allow for more natural ordering and horizontal scrolling. What else would be desirable?

That's all I can think of right now!

Not sure why you have both the selectables and a popup menu here (I suppose the popup menu helps if you have too many tabs, for the lack of arrows to scroll horizontally).

That is the only reason. It was the simplest solution I could come up with to be able to get to all the tabs without forcing you to resize the window to see all of them.

With respect to the column assert, I'd have to go back and try to recollect what I was doing exactly, but it was basically me doing a ImGui::GetColumnOffset() and ImGui::SetColumnOffset() just before and after the call to the gui handler, like this:

PanelTab *selected = tabs + selectedTab;
ImGui::BeginChild("tab main contents");

for (int i = 0; i < numCols; ++i)
{
    ImGui::SetColumnOffset(i, selected->colOffset[i]);
}

selected->guiHandler(selected->userdata);

for (int i = 0; i < numCols; ++i)
{
    selected->colOffset[i] = ImGui::GetColumnOffset(i);
}

ImGui::EndChild()

And the assertion would occur inside one of the get/set column offsets. Clearly this was just my fault, because there's no guarantee that any columns even exist when I call these functions at these locations.

This was a really poorly thought out attempt at solving this problem anyways... I just tried your suggestion with the pushing IDs and that totally solves it. The reason why it didn't work for me was because I was pushing the same string for all the tabs. I changed it so that when I create the child region, I send the tab name instead so each tab can have their own ID space. Columns don't interfere now!

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Jul 7, 2015

Owner

Good to hear. Let me know if you spot an assert next time, they shouldn't happen in that sort of use case.

Seems like you are losing lots of horizontal space with large widgets, I've seen this problem appears frequently, there's not a single answer unfortunately. Perhaps use more DragFloat, fit them two columns (save vertical space), include the label in the format string instead. Maybe you can allow to resize the game viewport to take say 75% of the space so that the rest is always free for ui, matching the typical Unity layout. If you have really large list e.g. for filename you can also consider using a smaller font such as ProggyTiny for those panels as well.

Owner

ocornut commented Jul 7, 2015

Good to hear. Let me know if you spot an assert next time, they shouldn't happen in that sort of use case.

Seems like you are losing lots of horizontal space with large widgets, I've seen this problem appears frequently, there's not a single answer unfortunately. Perhaps use more DragFloat, fit them two columns (save vertical space), include the label in the format string instead. Maybe you can allow to resize the game viewport to take say 75% of the space so that the rest is always free for ui, matching the typical Unity layout. If you have really large list e.g. for filename you can also consider using a smaller font such as ProggyTiny for those panels as well.

@ocornut ocornut added the enhancement label Jul 11, 2015

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Dec 3, 2015

Hi,

I have create a simple class that help to create a set of tabs. It can be easily converted into a ImGUI full version.

Just hope that this code can help someone ;-)

class ImGuiTab
{
public:
vector Labels;

void Draw(int* selected)
{
    ImGuiStyle& style = ImGui::GetStyle();
    ImVec4 color = style.Colors[ImGuiCol_Button];
    ImVec4 colorActive = style.Colors[ImGuiCol_ButtonActive];
    ImVec4 colorHover = style.Colors[ImGuiCol_ButtonHovered];

    for (int i = 0; i < Labels.size(); i++)
    {
        // push the style
        if (i == *selected)
        {
            style.Colors[ImGuiCol_Button] = colorActive;
            style.Colors[ImGuiCol_ButtonActive] = colorActive;
            style.Colors[ImGuiCol_ButtonHovered] = colorActive;
        }
        else
        {
            style.Colors[ImGuiCol_Button] = color;
            style.Colors[ImGuiCol_ButtonActive] = colorActive;
            style.Colors[ImGuiCol_ButtonHovered] = colorHover;
        }

        // Draw the button
        if (ImGui::Button(Labels[i].c_str()))
            *selected = i;

        if (i != Labels.size() - 1)
            ImGui::SameLine();
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] = color;
}

};

Example of use:

    ImGuiTab tab;
    tab.Labels.push_back("Materials");
    tab.Labels.push_back("Colors");
    tab.Labels.push_back("Textures");
    tab.Labels.push_back("Environments");
    tab.Labels.push_back("Backgrounds");

    tab.Draw(&_selectedTab);

ghost commented Dec 3, 2015

Hi,

I have create a simple class that help to create a set of tabs. It can be easily converted into a ImGUI full version.

Just hope that this code can help someone ;-)

class ImGuiTab
{
public:
vector Labels;

void Draw(int* selected)
{
    ImGuiStyle& style = ImGui::GetStyle();
    ImVec4 color = style.Colors[ImGuiCol_Button];
    ImVec4 colorActive = style.Colors[ImGuiCol_ButtonActive];
    ImVec4 colorHover = style.Colors[ImGuiCol_ButtonHovered];

    for (int i = 0; i < Labels.size(); i++)
    {
        // push the style
        if (i == *selected)
        {
            style.Colors[ImGuiCol_Button] = colorActive;
            style.Colors[ImGuiCol_ButtonActive] = colorActive;
            style.Colors[ImGuiCol_ButtonHovered] = colorActive;
        }
        else
        {
            style.Colors[ImGuiCol_Button] = color;
            style.Colors[ImGuiCol_ButtonActive] = colorActive;
            style.Colors[ImGuiCol_ButtonHovered] = colorHover;
        }

        // Draw the button
        if (ImGui::Button(Labels[i].c_str()))
            *selected = i;

        if (i != Labels.size() - 1)
            ImGui::SameLine();
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] = color;
}

};

Example of use:

    ImGuiTab tab;
    tab.Labels.push_back("Materials");
    tab.Labels.push_back("Colors");
    tab.Labels.push_back("Textures");
    tab.Labels.push_back("Environments");
    tab.Labels.push_back("Backgrounds");

    tab.Draw(&_selectedTab);

@ocornut ocornut referenced this issue Dec 4, 2015

Closed

Tab control #427

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Dec 4, 2015

Owner

New code from @krys-spectralpixel from #427

IMGUI_API bool Tab(unsigned int index, const char* label, const char* tooltip, int* selected)
{
    ImGuiStyle& style = ImGui::GetStyle();
    ImVec2 itemSpacing = style.ItemSpacing;
    ImVec4 color = style.Colors[ImGuiCol_Button];
    ImVec4 colorActive = style.Colors[ImGuiCol_ButtonActive];
    ImVec4 colorHover = style.Colors[ImGuiCol_ButtonHovered];
    style.ItemSpacing.x = 1;

    if (index > 0)
        ImGui::SameLine();

    // push the style
    if (index == *selected)
    {
        style.Colors[ImGuiCol_Button] = colorActive;
        style.Colors[ImGuiCol_ButtonActive] = colorActive;
        style.Colors[ImGuiCol_ButtonHovered] = colorActive;
    }
    else
    {
        style.Colors[ImGuiCol_Button] = color;
        style.Colors[ImGuiCol_ButtonActive] = colorActive;
        style.Colors[ImGuiCol_ButtonHovered] = colorHover;
    }

    // Draw the button
    if (ImGui::Button(label))
        *selected = index;

    if (ImGui::IsItemHovered())
    {
        ImGui::BeginTooltip();
        ImGui::Text("%s", tooltip);
        ImGui::EndTooltip();
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] = color;
    style.Colors[ImGuiCol_ButtonActive] = colorActive;
    style.Colors[ImGuiCol_ButtonHovered] = colorHover;
    style.ItemSpacing = itemSpacing;

    return *selected == index;
}
Owner

ocornut commented Dec 4, 2015

New code from @krys-spectralpixel from #427

IMGUI_API bool Tab(unsigned int index, const char* label, const char* tooltip, int* selected)
{
    ImGuiStyle& style = ImGui::GetStyle();
    ImVec2 itemSpacing = style.ItemSpacing;
    ImVec4 color = style.Colors[ImGuiCol_Button];
    ImVec4 colorActive = style.Colors[ImGuiCol_ButtonActive];
    ImVec4 colorHover = style.Colors[ImGuiCol_ButtonHovered];
    style.ItemSpacing.x = 1;

    if (index > 0)
        ImGui::SameLine();

    // push the style
    if (index == *selected)
    {
        style.Colors[ImGuiCol_Button] = colorActive;
        style.Colors[ImGuiCol_ButtonActive] = colorActive;
        style.Colors[ImGuiCol_ButtonHovered] = colorActive;
    }
    else
    {
        style.Colors[ImGuiCol_Button] = color;
        style.Colors[ImGuiCol_ButtonActive] = colorActive;
        style.Colors[ImGuiCol_ButtonHovered] = colorHover;
    }

    // Draw the button
    if (ImGui::Button(label))
        *selected = index;

    if (ImGui::IsItemHovered())
    {
        ImGui::BeginTooltip();
        ImGui::Text("%s", tooltip);
        ImGui::EndTooltip();
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] = color;
    style.Colors[ImGuiCol_ButtonActive] = colorActive;
    style.Colors[ImGuiCol_ButtonHovered] = colorHover;
    style.ItemSpacing = itemSpacing;

    return *selected == index;
}
@Flix01

This comment has been minimized.

Show comment
Hide comment
@Flix01

Flix01 Dec 5, 2015

@krys-spectralpixel : I have modified your code slightly, trying to include dynamic layout (useful in windows without horizontal scrollbars). It works, but the calculation of the widths should be improved.
Here is the code:

namespace ImGui {
IMGUI_API  bool TabLabels(int numTabs, const char** tabLabels, int& selectedIndex, const char** tabLabelTooltips, bool autoLayout, int *pOptionalHoveredIndex) {
    ImGuiStyle& style = ImGui::GetStyle();

    const ImVec2 itemSpacing =  style.ItemSpacing;
    const ImVec4 color =        style.Colors[ImGuiCol_Button];
    const ImVec4 colorActive =  style.Colors[ImGuiCol_ButtonActive];
    const ImVec4 colorHover =   style.Colors[ImGuiCol_ButtonHovered];
    style.ItemSpacing.x =       1;
    style.ItemSpacing.y =       1;


    if (numTabs>0 && (selectedIndex<0 || selectedIndex>=numTabs)) selectedIndex = 0;
    if (pOptionalHoveredIndex) *pOptionalHoveredIndex = -1;

    // Parameters to adjust to make autolayout work as expected:----------
    // The correct values are probably the ones in the comments, but I took some margin so that they work well
    // with a (medium size) vertical scrollbar too [Ok I should detect its presence and use the appropriate values...].
    const float btnOffset =         2.f*style.FramePadding.x;   // [2.f*style.FramePadding.x] It should be: ImGui::Button(text).size.x = ImGui::CalcTextSize(text).x + btnOffset;
    const float sameLineOffset =    2.f*style.ItemSpacing.x;    // [style.ItemSpacing.x]      It should be: sameLineOffset = ImGui::SameLine().size.x;
    const float uniqueLineOffset =  2.f*style.WindowPadding.x;  // [style.WindowPadding.x]    Width to be sutracted by windowWidth to make it work.
    //--------------------------------------------------------------------

    float windowWidth = 0.f,sumX=0.f;
    if (autoLayout) windowWidth = ImGui::GetWindowWidth() - uniqueLineOffset;

    bool selection_changed = false;
    for (int i = 0; i < numTabs; i++)
    {
        // push the style
        if (i == selectedIndex)
        {
            style.Colors[ImGuiCol_Button] =         colorActive;
            style.Colors[ImGuiCol_ButtonActive] =   colorActive;
            style.Colors[ImGuiCol_ButtonHovered] =  colorActive;
        }
        else
        {
            style.Colors[ImGuiCol_Button] =         color;
            style.Colors[ImGuiCol_ButtonActive] =   colorActive;
            style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
        }

        ImGui::PushID(i);   // otherwise two tabs with the same name would clash.

        if (!autoLayout) {if (i>0) ImGui::SameLine();}
        else if (sumX > 0.f) {
            sumX+=sameLineOffset;   // Maybe we can skip it if we use SameLine(0,0) below
            sumX+=ImGui::CalcTextSize(tabLabels[i]).x+btnOffset;
            if (sumX>windowWidth) sumX = 0.f;
            else ImGui::SameLine();
        }

        // Draw the button
        if (ImGui::Button(tabLabels[i]))   {selection_changed = (selectedIndex!=i);selectedIndex = i;}
        if (autoLayout && sumX==0.f) {
            // First element of a line
            sumX = ImGui::GetItemRectSize().x;
        }
        if (pOptionalHoveredIndex) {
            if (ImGui::IsItemHovered()) {
                *pOptionalHoveredIndex = i;
                if (tabLabelTooltips && tabLabelTooltips[i] && strlen(tabLabelTooltips[i])>0)  ImGui::SetTooltip("%s",tabLabelTooltips[i]);
            }
        }
        else if (tabLabelTooltips && tabLabelTooltips[i] && ImGui::IsItemHovered() && strlen(tabLabelTooltips[i])>0) ImGui::SetTooltip("%s",tabLabelTooltips[i]);
        ImGui::PopID();
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] =         color;
    style.Colors[ImGuiCol_ButtonActive] =   colorActive;
    style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
    style.ItemSpacing =                     itemSpacing;

    return selection_changed;
}
} // namespace ImGui

Usage example:

ImGui::Text("Tabs (based on the code by krys-spectralpixel):");
static const char* tabNames[] = {"Render","Layers","Scene","World","Object","Constraints","Modifiers","Data","Material","Texture","Particle","Physics"};
static const int numTabs = sizeof(tabNames)/sizeof(tabNames[0]);
static const char* tabTooltips[numTabs] = {"Render Tab Tooltip","","","","Object Type Tooltip","","","","","Tired to add tooltips...",""};
static int selectedTab = 0;
static int optionalHoveredTab = 0;
/*const bool tabSelectedChanged =*/ImGui::TabLabels(numTabs,tabNames,selectedTab,tabTooltips,true,&optionalHoveredTab);
ImGui::Text("\nTab Page For Tab: \"%s\" here.\n",tabNames[selectedTab]);
if (optionalHoveredTab>=0) ImGui::Text("Mouse is hovering Tab Label: \"%s\".\n\n",tabNames[optionalHoveredTab]);

selection_011

It would be nice if we could fix it (and maybe add tab reordering functionality through drag and drop).

EDIT: made some changes suggested by the posts following this code. However now this code is more like a snippet to be used as a reference rather than a possible extension to the library.

EDIT again: I've just posted an extended version here: https://gist.github.com/Flix01/3bc3d7b3d996582e034e

Flix01 commented Dec 5, 2015

@krys-spectralpixel : I have modified your code slightly, trying to include dynamic layout (useful in windows without horizontal scrollbars). It works, but the calculation of the widths should be improved.
Here is the code:

namespace ImGui {
IMGUI_API  bool TabLabels(int numTabs, const char** tabLabels, int& selectedIndex, const char** tabLabelTooltips, bool autoLayout, int *pOptionalHoveredIndex) {
    ImGuiStyle& style = ImGui::GetStyle();

    const ImVec2 itemSpacing =  style.ItemSpacing;
    const ImVec4 color =        style.Colors[ImGuiCol_Button];
    const ImVec4 colorActive =  style.Colors[ImGuiCol_ButtonActive];
    const ImVec4 colorHover =   style.Colors[ImGuiCol_ButtonHovered];
    style.ItemSpacing.x =       1;
    style.ItemSpacing.y =       1;


    if (numTabs>0 && (selectedIndex<0 || selectedIndex>=numTabs)) selectedIndex = 0;
    if (pOptionalHoveredIndex) *pOptionalHoveredIndex = -1;

    // Parameters to adjust to make autolayout work as expected:----------
    // The correct values are probably the ones in the comments, but I took some margin so that they work well
    // with a (medium size) vertical scrollbar too [Ok I should detect its presence and use the appropriate values...].
    const float btnOffset =         2.f*style.FramePadding.x;   // [2.f*style.FramePadding.x] It should be: ImGui::Button(text).size.x = ImGui::CalcTextSize(text).x + btnOffset;
    const float sameLineOffset =    2.f*style.ItemSpacing.x;    // [style.ItemSpacing.x]      It should be: sameLineOffset = ImGui::SameLine().size.x;
    const float uniqueLineOffset =  2.f*style.WindowPadding.x;  // [style.WindowPadding.x]    Width to be sutracted by windowWidth to make it work.
    //--------------------------------------------------------------------

    float windowWidth = 0.f,sumX=0.f;
    if (autoLayout) windowWidth = ImGui::GetWindowWidth() - uniqueLineOffset;

    bool selection_changed = false;
    for (int i = 0; i < numTabs; i++)
    {
        // push the style
        if (i == selectedIndex)
        {
            style.Colors[ImGuiCol_Button] =         colorActive;
            style.Colors[ImGuiCol_ButtonActive] =   colorActive;
            style.Colors[ImGuiCol_ButtonHovered] =  colorActive;
        }
        else
        {
            style.Colors[ImGuiCol_Button] =         color;
            style.Colors[ImGuiCol_ButtonActive] =   colorActive;
            style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
        }

        ImGui::PushID(i);   // otherwise two tabs with the same name would clash.

        if (!autoLayout) {if (i>0) ImGui::SameLine();}
        else if (sumX > 0.f) {
            sumX+=sameLineOffset;   // Maybe we can skip it if we use SameLine(0,0) below
            sumX+=ImGui::CalcTextSize(tabLabels[i]).x+btnOffset;
            if (sumX>windowWidth) sumX = 0.f;
            else ImGui::SameLine();
        }

        // Draw the button
        if (ImGui::Button(tabLabels[i]))   {selection_changed = (selectedIndex!=i);selectedIndex = i;}
        if (autoLayout && sumX==0.f) {
            // First element of a line
            sumX = ImGui::GetItemRectSize().x;
        }
        if (pOptionalHoveredIndex) {
            if (ImGui::IsItemHovered()) {
                *pOptionalHoveredIndex = i;
                if (tabLabelTooltips && tabLabelTooltips[i] && strlen(tabLabelTooltips[i])>0)  ImGui::SetTooltip("%s",tabLabelTooltips[i]);
            }
        }
        else if (tabLabelTooltips && tabLabelTooltips[i] && ImGui::IsItemHovered() && strlen(tabLabelTooltips[i])>0) ImGui::SetTooltip("%s",tabLabelTooltips[i]);
        ImGui::PopID();
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] =         color;
    style.Colors[ImGuiCol_ButtonActive] =   colorActive;
    style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
    style.ItemSpacing =                     itemSpacing;

    return selection_changed;
}
} // namespace ImGui

Usage example:

ImGui::Text("Tabs (based on the code by krys-spectralpixel):");
static const char* tabNames[] = {"Render","Layers","Scene","World","Object","Constraints","Modifiers","Data","Material","Texture","Particle","Physics"};
static const int numTabs = sizeof(tabNames)/sizeof(tabNames[0]);
static const char* tabTooltips[numTabs] = {"Render Tab Tooltip","","","","Object Type Tooltip","","","","","Tired to add tooltips...",""};
static int selectedTab = 0;
static int optionalHoveredTab = 0;
/*const bool tabSelectedChanged =*/ImGui::TabLabels(numTabs,tabNames,selectedTab,tabTooltips,true,&optionalHoveredTab);
ImGui::Text("\nTab Page For Tab: \"%s\" here.\n",tabNames[selectedTab]);
if (optionalHoveredTab>=0) ImGui::Text("Mouse is hovering Tab Label: \"%s\".\n\n",tabNames[optionalHoveredTab]);

selection_011

It would be nice if we could fix it (and maybe add tab reordering functionality through drag and drop).

EDIT: made some changes suggested by the posts following this code. However now this code is more like a snippet to be used as a reference rather than a possible extension to the library.

EDIT again: I've just posted an extended version here: https://gist.github.com/Flix01/3bc3d7b3d996582e034e

@ZahlGraf

This comment has been minimized.

Show comment
Hide comment
@ZahlGraf

ZahlGraf Dec 5, 2015

Looks nice 👍

ZahlGraf commented Dec 5, 2015

Looks nice 👍

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Dec 6, 2015

Owner

Some ideas

  • I think Krys's API where you can provide tabs one at a time is better suited to ImGui style API (but it may make some features easier to implement this year)?
  • I don't understand why your parameters are called with "persistentStorrage" in their name there's nothing persistent about them.
  • Tabs would need more interaction, such as a closing button per tab.
  • Reordering would be great. For that people an API where you feed data one at a time may make it hard to achieve. Unless we decide that the ordering data is on user's side.
  • Need top corner rounded. And better colors, spacing, etc.
Owner

ocornut commented Dec 6, 2015

Some ideas

  • I think Krys's API where you can provide tabs one at a time is better suited to ImGui style API (but it may make some features easier to implement this year)?
  • I don't understand why your parameters are called with "persistentStorrage" in their name there's nothing persistent about them.
  • Tabs would need more interaction, such as a closing button per tab.
  • Reordering would be great. For that people an API where you feed data one at a time may make it hard to achieve. Unless we decide that the ordering data is on user's side.
  • Need top corner rounded. And better colors, spacing, etc.
@Flix01

This comment has been minimized.

Show comment
Hide comment
@Flix01

Flix01 Dec 6, 2015

I think Krys's API where you can provide tabs one at a time is better suited to ImGui style API (but it may make some features easier to implement this year)?

True (with my current code we can't add a tab-label popup menu for example).
The main problem is that we have to write the button-wrapping code ourselves.
Some API like ImGui::SameLineIfFits() would ease this task greatly, but I guess it wouldn't be easy to add
(and maybe ImGui::CalcButtonSize(text) would be useful too).

I don't understand why your parameters are called with "persistentStorrage" in their name there's nothing persistent about them.

True. I usually use "persistent storage" or "not owned" inside C++ classes when strings are not copied to prevent users from passing temporary strings, but there's no need to use it here.

Tabs would need more interaction, such as a closing button per tab.

Yes, I was thinking about displaying it manually over the button rectangle (and maybe adding an ItemAdd(), or something like that, to support hovering on it), but I don't know if it would work and that is an advanced task for now.

Reordering would be great. For that people an API where you feed data one at a time may make it hard to achieve. Unless we decide that the ordering data is on user's side.

Yes.
In addition, I was thinking, if we keep the "multi-tab" version, we should add an int that returns the hovered tab-label index, to alleviate some of the problems arisng from losing control over single tab-buttons.

Need top corner rounded. And better colors, spacing, etc.

Maybe we can add some "tab-specific" style variables, or just leave the user override the "Button" style variables.

Flix01 commented Dec 6, 2015

I think Krys's API where you can provide tabs one at a time is better suited to ImGui style API (but it may make some features easier to implement this year)?

True (with my current code we can't add a tab-label popup menu for example).
The main problem is that we have to write the button-wrapping code ourselves.
Some API like ImGui::SameLineIfFits() would ease this task greatly, but I guess it wouldn't be easy to add
(and maybe ImGui::CalcButtonSize(text) would be useful too).

I don't understand why your parameters are called with "persistentStorrage" in their name there's nothing persistent about them.

True. I usually use "persistent storage" or "not owned" inside C++ classes when strings are not copied to prevent users from passing temporary strings, but there's no need to use it here.

Tabs would need more interaction, such as a closing button per tab.

Yes, I was thinking about displaying it manually over the button rectangle (and maybe adding an ItemAdd(), or something like that, to support hovering on it), but I don't know if it would work and that is an advanced task for now.

Reordering would be great. For that people an API where you feed data one at a time may make it hard to achieve. Unless we decide that the ordering data is on user's side.

Yes.
In addition, I was thinking, if we keep the "multi-tab" version, we should add an int that returns the hovered tab-label index, to alleviate some of the problems arisng from losing control over single tab-buttons.

Need top corner rounded. And better colors, spacing, etc.

Maybe we can add some "tab-specific" style variables, or just leave the user override the "Button" style variables.

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Dec 6, 2015

Owner

The main problem is that we have to write the button-wrapping code ourselves.
Some API like ImGui::SameLineIfFits() would ease this task greatly, but I guess it wouldn't be easy to add (and maybe ImGui::CalcButtonSize(text) would be useful too).

That would be covered by #404, need some refactoring but not a very big amount. Button size like any framed object is by default TextSize + FramePadding*2. After the Button call you can also call GetItemRectSize().

Owner

ocornut commented Dec 6, 2015

The main problem is that we have to write the button-wrapping code ourselves.
Some API like ImGui::SameLineIfFits() would ease this task greatly, but I guess it wouldn't be easy to add (and maybe ImGui::CalcButtonSize(text) would be useful too).

That would be covered by #404, need some refactoring but not a very big amount. Button size like any framed object is by default TextSize + FramePadding*2. After the Button call you can also call GetItemRectSize().

@Flix01

This comment has been minimized.

Show comment
Hide comment
@Flix01

Flix01 Dec 6, 2015

That would be covered by #404, need some refactoring but not a very big amount. Button size like any framed object is by default TextSize + FramePadding*2. After the Button call you can also call GetItemRectSize().

Perfect!
Probably the best solution for now is to keep the code on the user side and use "single-tab" code like the one @krys-spectralpixel posted.
"Wrapping buttons" is going to become very easy with the new support methods.

EDIT: I renamed the arguments, the method name and changed the widths in the code above, in case someone finds it useful.

EDIT again: I've just posted an extended version here: https://gist.github.com/Flix01/3bc3d7b3d996582e034e

Flix01 commented Dec 6, 2015

That would be covered by #404, need some refactoring but not a very big amount. Button size like any framed object is by default TextSize + FramePadding*2. After the Button call you can also call GetItemRectSize().

Perfect!
Probably the best solution for now is to keep the code on the user side and use "single-tab" code like the one @krys-spectralpixel posted.
"Wrapping buttons" is going to become very easy with the new support methods.

EDIT: I renamed the arguments, the method name and changed the widths in the code above, in case someone finds it useful.

EDIT again: I've just posted an extended version here: https://gist.github.com/Flix01/3bc3d7b3d996582e034e

@vivienneanthony

This comment has been minimized.

Show comment
Hide comment
@vivienneanthony

vivienneanthony Jan 22, 2016

Question. Is this code implemented in the new source or is it simple I will have to merge in? I prefer tab windows for a cleaner interface look.

vivienneanthony commented Jan 22, 2016

Question. Is this code implemented in the new source or is it simple I will have to merge in? I prefer tab windows for a cleaner interface look.

@Roflraging Roflraging referenced this issue Jan 22, 2016

Closed

Tab Windows #498

@ocornut ocornut changed the title from Window tabs to Tabs Apr 3, 2016

@r-lyeh-archived

This comment has been minimized.

Show comment
Hide comment
@r-lyeh-archived

r-lyeh-archived Aug 15, 2016

I've simplified and cleaned up that snippet above
May be of some use to those that need very basic tab handling.

Feat. simplified API, no tooltips and sane default options (and colors :)

Thanks to everyone involved!

gif

// Based on the code by krys-spectralpixel+Flix01 
// [ref] https://github.com/ocornut/imgui/issues/261

#pragma once
#include <imgui.h>

namespace ImGui {
/* 
    tabLabels: name of all tabs involved
    tabSize: number of elements
    tabIndex: holds the current active tab
    tabOrder: optional array of integers from 0 to tabSize-1 that maps the tab label order. If one of the numbers is replaced by -1 the tab label is not visible (closed). It can be read/modified at runtime.

    // USAGE EXAMPLE
    static const char* tabNames[] = {"First tab","Second tab","Third tab"};
    static int tabOrder[] = {0,1,2};
    static int tabSelected = 0;
    const bool tabChanged = ImGui::TabLabels(tabNames,sizeof(tabNames)/sizeof(tabNames[0]),tabSelected,tabOrder);
    ImGui::Text("\nTab Page For Tab: \"%s\" here.\n",tabNames[tabSelected]);
*/

IMGUI_API bool TabLabels(const char **tabLabels, int tabSize, int &tabIndex, int *tabOrder=NULL) {
    ImGuiStyle& style = ImGui::GetStyle();

    const ImVec2 itemSpacing =  style.ItemSpacing;
    const ImVec4 color =        style.Colors[ImGuiCol_Button];
    const ImVec4 colorActive =  style.Colors[ImGuiCol_ButtonActive];
    const ImVec4 colorHover =   style.Colors[ImGuiCol_ButtonHovered];
    const ImVec4 colorText =    style.Colors[ImGuiCol_Text];
    style.ItemSpacing.x =       1;
    style.ItemSpacing.y =       1;
    const ImVec4 colorSelectedTab = ImVec4(color.x,color.y,color.z,color.w*0.5f);
    const ImVec4 colorSelectedTabHovered = ImVec4(colorHover.x,colorHover.y,colorHover.z,colorHover.w*0.5f);
    const ImVec4 colorSelectedTabText = ImVec4(colorText.x*0.8f,colorText.y*0.8f,colorText.z*0.8f,colorText.w*0.8f);

    if (tabSize>0 && (tabIndex<0 || tabIndex>=tabSize)) {
        if (!tabOrder)  tabIndex = 0;
        else tabIndex = -1;
    }

    float windowWidth = 0.f,sumX=0.f;
    windowWidth = ImGui::GetWindowWidth() - style.WindowPadding.x - (ImGui::GetScrollMaxY()>0 ? style.ScrollbarSize : 0.f);

    static int draggingTabIndex = -1;int draggingTabTargetIndex = -1;   // These are indices inside tabOrder
    static ImVec2 draggingtabSize(0,0);
    static ImVec2 draggingTabOffset(0,0);

    const bool isMMBreleased = ImGui::IsMouseReleased(2);
    const bool isMouseDragging = ImGui::IsMouseDragging(0,2.f);
    int justClosedTabIndex = -1,newtabIndex = tabIndex;


    bool selection_changed = false;bool noButtonDrawn = true;
    for (int j = 0,i; j < tabSize; j++)
    {
        i = tabOrder ? tabOrder[j] : j;
        if (i==-1) continue;

        if (sumX > 0.f) {
            sumX+=style.ItemSpacing.x;   // Maybe we can skip it if we use SameLine(0,0) below
            sumX+=ImGui::CalcTextSize(tabLabels[i]).x+2.f*style.FramePadding.x;
            if (sumX>windowWidth) sumX = 0.f;
            else ImGui::SameLine();
        }

        if (i != tabIndex) {
            // Push the style
            style.Colors[ImGuiCol_Button] =         colorSelectedTab;
            style.Colors[ImGuiCol_ButtonActive] =   colorSelectedTab;
            style.Colors[ImGuiCol_ButtonHovered] =  colorSelectedTabHovered;
            style.Colors[ImGuiCol_Text] =           colorSelectedTabText;
        }
        // Draw the button
        ImGui::PushID(i);   // otherwise two tabs with the same name would clash.
        if (ImGui::Button(tabLabels[i]))   {selection_changed = (tabIndex!=i);newtabIndex = i;}
        ImGui::PopID();
        if (i != tabIndex) {
            // Reset the style
            style.Colors[ImGuiCol_Button] =         color;
            style.Colors[ImGuiCol_ButtonActive] =   colorActive;
            style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
            style.Colors[ImGuiCol_Text] =           colorText;
        }
        noButtonDrawn = false;

        if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line

        if (ImGui::IsItemHoveredRect()) {
            if (tabOrder)  {
                // tab reordering
                if (isMouseDragging) {
                    if (draggingTabIndex==-1) {
                        draggingTabIndex = j;
                        draggingtabSize = ImGui::GetItemRectSize();
                        const ImVec2& mp = ImGui::GetIO().MousePos;
                        const ImVec2 draggingTabCursorPos = ImGui::GetCursorPos();
                        draggingTabOffset=ImVec2(
                                    mp.x+draggingtabSize.x*0.5f-sumX+ImGui::GetScrollX(),
                                    mp.y+draggingtabSize.y*0.5f-draggingTabCursorPos.y+ImGui::GetScrollY()
                                    );

                    }
                }
                else if (draggingTabIndex>=0 && draggingTabIndex<tabSize && draggingTabIndex!=j){
                    draggingTabTargetIndex = j; // For some odd reasons this seems to get called only when draggingTabIndex < i ! (Probably during mouse dragging ImGui owns the mouse someway and sometimes ImGui::IsItemHovered() is not getting called)
                }
            }
        }

    }

    tabIndex = newtabIndex;

    // Draw tab label while mouse drags it
    if (draggingTabIndex>=0 && draggingTabIndex<tabSize) {
        const ImVec2& mp = ImGui::GetIO().MousePos;
        const ImVec2 wp = ImGui::GetWindowPos();
        ImVec2 start(wp.x+mp.x-draggingTabOffset.x-draggingtabSize.x*0.5f,wp.y+mp.y-draggingTabOffset.y-draggingtabSize.y*0.5f);
        const ImVec2 end(start.x+draggingtabSize.x,start.y+draggingtabSize.y);
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        const float draggedBtnAlpha = 0.65f;
        const ImVec4& btnColor = style.Colors[ImGuiCol_Button];
        drawList->AddRectFilled(start,end,ImColor(btnColor.x,btnColor.y,btnColor.z,btnColor.w*draggedBtnAlpha),style.FrameRounding);
        start.x+=style.FramePadding.x;start.y+=style.FramePadding.y;
        const ImVec4& txtColor = style.Colors[ImGuiCol_Text];
        drawList->AddText(start,ImColor(txtColor.x,txtColor.y,txtColor.z,txtColor.w*draggedBtnAlpha),tabLabels[tabOrder[draggingTabIndex]]);

        ImGui::SetMouseCursor(ImGuiMouseCursor_Move);
    }

    // Drop tab label
    if (draggingTabTargetIndex!=-1) {
        // swap draggingTabIndex and draggingTabTargetIndex in tabOrder
        const int tmp = tabOrder[draggingTabTargetIndex];
        tabOrder[draggingTabTargetIndex] = tabOrder[draggingTabIndex];
        tabOrder[draggingTabIndex] = tmp;
        //fprintf(stderr,"%d %d\n",draggingTabIndex,draggingTabTargetIndex);
        draggingTabTargetIndex = draggingTabIndex = -1;
    }

    // Reset draggingTabIndex if necessary
    if (!isMouseDragging) draggingTabIndex = -1;

    // Change selected tab when user closes the selected tab
    if (tabIndex == justClosedTabIndex && tabIndex>=0)    {
        tabIndex = -1;
        for (int j = 0,i; j < tabSize; j++) {
            i = tabOrder ? tabOrder[j] : j;
            if (i==-1) continue;
            tabIndex = i;
            break;
        }
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] =         color;
    style.Colors[ImGuiCol_ButtonActive] =   colorActive;
    style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
    style.Colors[ImGuiCol_Text] =           colorText;
    style.ItemSpacing =                     itemSpacing;

    return selection_changed;
}
} // namespace ImGui

r-lyeh-archived commented Aug 15, 2016

I've simplified and cleaned up that snippet above
May be of some use to those that need very basic tab handling.

Feat. simplified API, no tooltips and sane default options (and colors :)

Thanks to everyone involved!

gif

// Based on the code by krys-spectralpixel+Flix01 
// [ref] https://github.com/ocornut/imgui/issues/261

#pragma once
#include <imgui.h>

namespace ImGui {
/* 
    tabLabels: name of all tabs involved
    tabSize: number of elements
    tabIndex: holds the current active tab
    tabOrder: optional array of integers from 0 to tabSize-1 that maps the tab label order. If one of the numbers is replaced by -1 the tab label is not visible (closed). It can be read/modified at runtime.

    // USAGE EXAMPLE
    static const char* tabNames[] = {"First tab","Second tab","Third tab"};
    static int tabOrder[] = {0,1,2};
    static int tabSelected = 0;
    const bool tabChanged = ImGui::TabLabels(tabNames,sizeof(tabNames)/sizeof(tabNames[0]),tabSelected,tabOrder);
    ImGui::Text("\nTab Page For Tab: \"%s\" here.\n",tabNames[tabSelected]);
*/

IMGUI_API bool TabLabels(const char **tabLabels, int tabSize, int &tabIndex, int *tabOrder=NULL) {
    ImGuiStyle& style = ImGui::GetStyle();

    const ImVec2 itemSpacing =  style.ItemSpacing;
    const ImVec4 color =        style.Colors[ImGuiCol_Button];
    const ImVec4 colorActive =  style.Colors[ImGuiCol_ButtonActive];
    const ImVec4 colorHover =   style.Colors[ImGuiCol_ButtonHovered];
    const ImVec4 colorText =    style.Colors[ImGuiCol_Text];
    style.ItemSpacing.x =       1;
    style.ItemSpacing.y =       1;
    const ImVec4 colorSelectedTab = ImVec4(color.x,color.y,color.z,color.w*0.5f);
    const ImVec4 colorSelectedTabHovered = ImVec4(colorHover.x,colorHover.y,colorHover.z,colorHover.w*0.5f);
    const ImVec4 colorSelectedTabText = ImVec4(colorText.x*0.8f,colorText.y*0.8f,colorText.z*0.8f,colorText.w*0.8f);

    if (tabSize>0 && (tabIndex<0 || tabIndex>=tabSize)) {
        if (!tabOrder)  tabIndex = 0;
        else tabIndex = -1;
    }

    float windowWidth = 0.f,sumX=0.f;
    windowWidth = ImGui::GetWindowWidth() - style.WindowPadding.x - (ImGui::GetScrollMaxY()>0 ? style.ScrollbarSize : 0.f);

    static int draggingTabIndex = -1;int draggingTabTargetIndex = -1;   // These are indices inside tabOrder
    static ImVec2 draggingtabSize(0,0);
    static ImVec2 draggingTabOffset(0,0);

    const bool isMMBreleased = ImGui::IsMouseReleased(2);
    const bool isMouseDragging = ImGui::IsMouseDragging(0,2.f);
    int justClosedTabIndex = -1,newtabIndex = tabIndex;


    bool selection_changed = false;bool noButtonDrawn = true;
    for (int j = 0,i; j < tabSize; j++)
    {
        i = tabOrder ? tabOrder[j] : j;
        if (i==-1) continue;

        if (sumX > 0.f) {
            sumX+=style.ItemSpacing.x;   // Maybe we can skip it if we use SameLine(0,0) below
            sumX+=ImGui::CalcTextSize(tabLabels[i]).x+2.f*style.FramePadding.x;
            if (sumX>windowWidth) sumX = 0.f;
            else ImGui::SameLine();
        }

        if (i != tabIndex) {
            // Push the style
            style.Colors[ImGuiCol_Button] =         colorSelectedTab;
            style.Colors[ImGuiCol_ButtonActive] =   colorSelectedTab;
            style.Colors[ImGuiCol_ButtonHovered] =  colorSelectedTabHovered;
            style.Colors[ImGuiCol_Text] =           colorSelectedTabText;
        }
        // Draw the button
        ImGui::PushID(i);   // otherwise two tabs with the same name would clash.
        if (ImGui::Button(tabLabels[i]))   {selection_changed = (tabIndex!=i);newtabIndex = i;}
        ImGui::PopID();
        if (i != tabIndex) {
            // Reset the style
            style.Colors[ImGuiCol_Button] =         color;
            style.Colors[ImGuiCol_ButtonActive] =   colorActive;
            style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
            style.Colors[ImGuiCol_Text] =           colorText;
        }
        noButtonDrawn = false;

        if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line

        if (ImGui::IsItemHoveredRect()) {
            if (tabOrder)  {
                // tab reordering
                if (isMouseDragging) {
                    if (draggingTabIndex==-1) {
                        draggingTabIndex = j;
                        draggingtabSize = ImGui::GetItemRectSize();
                        const ImVec2& mp = ImGui::GetIO().MousePos;
                        const ImVec2 draggingTabCursorPos = ImGui::GetCursorPos();
                        draggingTabOffset=ImVec2(
                                    mp.x+draggingtabSize.x*0.5f-sumX+ImGui::GetScrollX(),
                                    mp.y+draggingtabSize.y*0.5f-draggingTabCursorPos.y+ImGui::GetScrollY()
                                    );

                    }
                }
                else if (draggingTabIndex>=0 && draggingTabIndex<tabSize && draggingTabIndex!=j){
                    draggingTabTargetIndex = j; // For some odd reasons this seems to get called only when draggingTabIndex < i ! (Probably during mouse dragging ImGui owns the mouse someway and sometimes ImGui::IsItemHovered() is not getting called)
                }
            }
        }

    }

    tabIndex = newtabIndex;

    // Draw tab label while mouse drags it
    if (draggingTabIndex>=0 && draggingTabIndex<tabSize) {
        const ImVec2& mp = ImGui::GetIO().MousePos;
        const ImVec2 wp = ImGui::GetWindowPos();
        ImVec2 start(wp.x+mp.x-draggingTabOffset.x-draggingtabSize.x*0.5f,wp.y+mp.y-draggingTabOffset.y-draggingtabSize.y*0.5f);
        const ImVec2 end(start.x+draggingtabSize.x,start.y+draggingtabSize.y);
        ImDrawList* drawList = ImGui::GetWindowDrawList();
        const float draggedBtnAlpha = 0.65f;
        const ImVec4& btnColor = style.Colors[ImGuiCol_Button];
        drawList->AddRectFilled(start,end,ImColor(btnColor.x,btnColor.y,btnColor.z,btnColor.w*draggedBtnAlpha),style.FrameRounding);
        start.x+=style.FramePadding.x;start.y+=style.FramePadding.y;
        const ImVec4& txtColor = style.Colors[ImGuiCol_Text];
        drawList->AddText(start,ImColor(txtColor.x,txtColor.y,txtColor.z,txtColor.w*draggedBtnAlpha),tabLabels[tabOrder[draggingTabIndex]]);

        ImGui::SetMouseCursor(ImGuiMouseCursor_Move);
    }

    // Drop tab label
    if (draggingTabTargetIndex!=-1) {
        // swap draggingTabIndex and draggingTabTargetIndex in tabOrder
        const int tmp = tabOrder[draggingTabTargetIndex];
        tabOrder[draggingTabTargetIndex] = tabOrder[draggingTabIndex];
        tabOrder[draggingTabIndex] = tmp;
        //fprintf(stderr,"%d %d\n",draggingTabIndex,draggingTabTargetIndex);
        draggingTabTargetIndex = draggingTabIndex = -1;
    }

    // Reset draggingTabIndex if necessary
    if (!isMouseDragging) draggingTabIndex = -1;

    // Change selected tab when user closes the selected tab
    if (tabIndex == justClosedTabIndex && tabIndex>=0)    {
        tabIndex = -1;
        for (int j = 0,i; j < tabSize; j++) {
            i = tabOrder ? tabOrder[j] : j;
            if (i==-1) continue;
            tabIndex = i;
            break;
        }
    }

    // Restore the style
    style.Colors[ImGuiCol_Button] =         color;
    style.Colors[ImGuiCol_ButtonActive] =   colorActive;
    style.Colors[ImGuiCol_ButtonHovered] =  colorHover;
    style.Colors[ImGuiCol_Text] =           colorText;
    style.ItemSpacing =                     itemSpacing;

    return selection_changed;
}
} // namespace ImGui
@Flix01

This comment has been minimized.

Show comment
Hide comment
@Flix01

Flix01 Aug 15, 2016

@r-lyeh: Indeed this is very good for people who want to have Tab Labels in a more compact way!

P.S. I had to change the styles, because I shared my ImGui::TabLabels code with ImGui::TabWindow and so it made sense to reuse its own style.

P.S.2. I've recently added to my ImGui fork vertical Tab Labels too (but without "Wrap Mode"). Unfortunately the code for making them cannot be compacted in an easy way for better sharing... 😒
tablabels
[Edit]: People from this issue: #705 might be interested in vertical Tab Labels too.

Flix01 commented Aug 15, 2016

@r-lyeh: Indeed this is very good for people who want to have Tab Labels in a more compact way!

P.S. I had to change the styles, because I shared my ImGui::TabLabels code with ImGui::TabWindow and so it made sense to reuse its own style.

P.S.2. I've recently added to my ImGui fork vertical Tab Labels too (but without "Wrap Mode"). Unfortunately the code for making them cannot be compacted in an easy way for better sharing... 😒
tablabels
[Edit]: People from this issue: #705 might be interested in vertical Tab Labels too.

@codz01

This comment has been minimized.

Show comment
Hide comment
@codz01

codz01 Aug 15, 2016

@Flix01 very nice , you've made bunch of useful controls already , many thanks

codz01 commented Aug 15, 2016

@Flix01 very nice , you've made bunch of useful controls already , many thanks

@Flix01

This comment has been minimized.

Show comment
Hide comment
@Flix01

Flix01 Sep 19, 2016

P.S.2. I've recently added to my ImGui fork vertical Tab Labels too (but without "Wrap Mode"). Unfortunately the code for making them cannot be compacted in an easy way for better sharing... 😒

Actually @guillaumechereau posted here: #705 a link to an extremely compact alternative implementation.

Flix01 commented Sep 19, 2016

P.S.2. I've recently added to my ImGui fork vertical Tab Labels too (but without "Wrap Mode"). Unfortunately the code for making them cannot be compacted in an easy way for better sharing... 😒

Actually @guillaumechereau posted here: #705 a link to an extremely compact alternative implementation.

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Oct 16, 2017

Owner

I started working on Tabs!
Here's an early work-in-progress of my hacking attempts:

tabs_20171016-b

It won't be finished nor committed for a while because I want:

  • Tabs to be usable completely independently from docking (#351)
  • But I also want docking to use them.. so until docking is done the tabs aren't done. Will probably take a month-ish until things are stabilized.

Anyway I am gathering ideas and requests now - if you have any specific feature request, please post in the TABS (#261) or DOCKING (#351) threads.

Owner

ocornut commented Oct 16, 2017

I started working on Tabs!
Here's an early work-in-progress of my hacking attempts:

tabs_20171016-b

It won't be finished nor committed for a while because I want:

  • Tabs to be usable completely independently from docking (#351)
  • But I also want docking to use them.. so until docking is done the tabs aren't done. Will probably take a month-ish until things are stabilized.

Anyway I am gathering ideas and requests now - if you have any specific feature request, please post in the TABS (#261) or DOCKING (#351) threads.

@ocornut ocornut referenced this issue Oct 17, 2017

Closed

Tabs #1161

@ocornut ocornut added the in progress label Oct 17, 2017

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Oct 23, 2017

Owner

I have pushed some early test code for Tabs in this branch:
https://github.com/ocornut/imgui/tree/tabs

It's a little experimental in the sense that I expect that a lot of this code will be massaged, scrapped, rewritten when working on proper Docking. I only wrote this to give myself a base to think about Drag'n Drop, Docking, and expect the API to change/break.

If you are feeling adventurous or curious you can give it a try..

The main reason I'm releasing this is that I don't expect the real v1.0 to materialize before at least a month.

IMGUI_API void          BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0);
IMGUI_API void          EndTabBar();
IMGUI_API bool          TabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags = 0);

IMGUI_API void          SetTabItemClosed(const char* label); // Optional, allows to bypass a frame of flicker in situation where tab is closed programmatically
IMGUI_API void          SetTabItemSelected(const char* label);

It's in two separate files imgui_tabs.cpp imgui_tabs.h which you can grab (the rest of master is unchanged). I'm just using those two files for work and depending how the rest of the features evolve it may be merged into a single pair of files, etc. I don't know at the moment.

This is a GIF i recorded on Oct 19: (Various things got fixed/improved since but that should get you an idea where it is at).
tabs_20171019

Owner

ocornut commented Oct 23, 2017

I have pushed some early test code for Tabs in this branch:
https://github.com/ocornut/imgui/tree/tabs

It's a little experimental in the sense that I expect that a lot of this code will be massaged, scrapped, rewritten when working on proper Docking. I only wrote this to give myself a base to think about Drag'n Drop, Docking, and expect the API to change/break.

If you are feeling adventurous or curious you can give it a try..

The main reason I'm releasing this is that I don't expect the real v1.0 to materialize before at least a month.

IMGUI_API void          BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0);
IMGUI_API void          EndTabBar();
IMGUI_API bool          TabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags = 0);

IMGUI_API void          SetTabItemClosed(const char* label); // Optional, allows to bypass a frame of flicker in situation where tab is closed programmatically
IMGUI_API void          SetTabItemSelected(const char* label);

It's in two separate files imgui_tabs.cpp imgui_tabs.h which you can grab (the rest of master is unchanged). I'm just using those two files for work and depending how the rest of the features evolve it may be merged into a single pair of files, etc. I don't know at the moment.

This is a GIF i recorded on Oct 19: (Various things got fixed/improved since but that should get you an idea where it is at).
tabs_20171019

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Oct 23, 2017

Owner

FYI I am also closing #1083 as a duplicate. In that thread @scottmudge posted his implementation of Tab, which you can find here https://github.com/ScottMudge/imgui_tabs and discussions in #1083

Arguably my version is more complete, but in the absence of persistence settings (order are not saved), and docking or dragging out as floating window, etc. it is still sitting in a weird spot where it doesn't do everything desirable.

GIF of the code in #1083
687474703a2f2f692e696d6775722e636f6d2f3650534b394e4c2e676966

Owner

ocornut commented Oct 23, 2017

FYI I am also closing #1083 as a duplicate. In that thread @scottmudge posted his implementation of Tab, which you can find here https://github.com/ScottMudge/imgui_tabs and discussions in #1083

Arguably my version is more complete, but in the absence of persistence settings (order are not saved), and docking or dragging out as floating window, etc. it is still sitting in a weird spot where it doesn't do everything desirable.

GIF of the code in #1083
687474703a2f2f692e696d6775722e636f6d2f3650534b394e4c2e676966

@yecate

This comment has been minimized.

Show comment
Hide comment
@yecate

yecate Oct 23, 2017

good job

yecate commented Oct 23, 2017

good job

ocornut added a commit that referenced this issue Nov 6, 2017

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Nov 23, 2017

Owner

@aaslane It's in my list but really not a big priority for now.

Owner

ocornut commented Nov 23, 2017

@aaslane It's in my list but really not a big priority for now.

@codecat

This comment has been minimized.

Show comment
Hide comment
@codecat

codecat Feb 26, 2018

Contributor

Hey, I'm using these tabs! They work very nicely, I like the API, too! Do you have any idea when this will be merged into master? Having to juggle between branches when updating Imgui can become quite tedious.

Contributor

codecat commented Feb 26, 2018

Hey, I'm using these tabs! They work very nicely, I like the API, too! Do you have any idea when this will be merged into master? Having to juggle between branches when updating Imgui can become quite tedious.

@ocornut

This comment has been minimized.

Show comment
Hide comment
@ocornut

ocornut Feb 26, 2018

Owner

@codecat Sorry I have no ETA. Notice that imgui.cpp hasn't been modified in this branch, you could grab the latest _tabs files and update them occasionally.

Owner

ocornut commented Feb 26, 2018

@codecat Sorry I have no ETA. Notice that imgui.cpp hasn't been modified in this branch, you could grab the latest _tabs files and update them occasionally.

@zahirzohair

This comment has been minimized.

Show comment
Hide comment
@zahirzohair

zahirzohair Mar 19, 2018

how can i add "NEW TAB" functionality like web browsers which has a small button when we hover over it a message pops up " New tab" when user click on it , then new tab will open.

1

zahirzohair commented Mar 19, 2018

how can i add "NEW TAB" functionality like web browsers which has a small button when we hover over it a message pops up " New tab" when user click on it , then new tab will open.

1

@scottmudge

This comment has been minimized.

Show comment
Hide comment
@scottmudge

scottmudge Mar 19, 2018

@zahirzohair You can customize my implementation (which is arguably a little stale and needs to be refreshed). All you would need to do is use a vector (either ImGui's version or STL implementionation) of a struct describing the different tab details -- title, type of tab (to determine behavior in a switch statement), etc., -- and then use a for loop to iterate over its members, using AddTab() to insert them into the TabBar.

scottmudge commented Mar 19, 2018

@zahirzohair You can customize my implementation (which is arguably a little stale and needs to be refreshed). All you would need to do is use a vector (either ImGui's version or STL implementionation) of a struct describing the different tab details -- title, type of tab (to determine behavior in a switch statement), etc., -- and then use a for loop to iterate over its members, using AddTab() to insert them into the TabBar.

@zahirzohair

This comment has been minimized.

Show comment
Hide comment
@zahirzohair

zahirzohair Mar 21, 2018

@ocornut if two tabs have the same name, it displays on top of each other and looks like one tab.
is there any solution for that?

zahirzohair commented Mar 21, 2018

@ocornut if two tabs have the same name, it displays on top of each other and looks like one tab.
is there any solution for that?

@scottmudge

This comment has been minimized.

Show comment
Hide comment
@scottmudge

scottmudge Mar 21, 2018

scottmudge commented Mar 21, 2018

@BeachCoder76

This comment has been minimized.

Show comment
Hide comment
@BeachCoder76

BeachCoder76 Apr 19, 2018

@ocornut Hi Omar, first thanks for ImGui .. it is an awesome little piece of code! That being said, I integrated your wip imgui_tabs and wanted to report a problem with this piece of code:

// Position newly appearing tab at the end of the tab list
if (tab_appearing && !tab_bar_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_NoResetOrderOnAppearing))
{
    tab->OffsetTarget = tab_bar->OffsetMax + g.Style.ItemInnerSpacing.x;
    if (tab->CurrentOrder != -1 && tab_bar->TabsOrder.back() != tab->GlobalIndex)
    {
        // Move tab to end of the list
        IM_ASSERT(tab_bar->TabsOrder[tab->CurrentOrder] == tab->GlobalIndex);
        memmove(&tab_bar->TabsOrder[tab->CurrentOrder], &tab_bar->TabsOrder[tab->CurrentOrder + 1], (tab_bar->TabsOrder.Size - tab->CurrentOrder - 1) * sizeof(tab_bar->TabsOrder[0]));
        tab_bar->TabsOrder.back() = tab->GlobalIndex;
        tab_bar->CurrOrderInsideTabsIsValid = false;
    }
}

If in the same frame, 2 tabs are reappearing in the tab bar (let's say you have a filter on the tab names that is filtering 2 TABs out of 4 and you then clear the filter string so all TABs suddenly need to reappear) .. the first one switching its position to TabsOrder.back() will memmove() all others down (without reindexing/updating the other tabs->CurrentOrder) so the next one will still have its CurrentOrder set on the old position. The next one will ImAssert in that code with a bad CurrentOrder vs TabOrders GlobalIndex.

This code probably assumes one reappearing TAB per frame and never broke because the next call to TabBarLayout should fix all tabs CurrentOrder variable anyway.

You can workaround this by skipping that code (ie creating your BeginTabBar with the ImGuiTabBarFlags_NoResetOrderOnAppearing flags OR updating all subsequent TABs after the memmove().

BeachCoder76 commented Apr 19, 2018

@ocornut Hi Omar, first thanks for ImGui .. it is an awesome little piece of code! That being said, I integrated your wip imgui_tabs and wanted to report a problem with this piece of code:

// Position newly appearing tab at the end of the tab list
if (tab_appearing && !tab_bar_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_NoResetOrderOnAppearing))
{
    tab->OffsetTarget = tab_bar->OffsetMax + g.Style.ItemInnerSpacing.x;
    if (tab->CurrentOrder != -1 && tab_bar->TabsOrder.back() != tab->GlobalIndex)
    {
        // Move tab to end of the list
        IM_ASSERT(tab_bar->TabsOrder[tab->CurrentOrder] == tab->GlobalIndex);
        memmove(&tab_bar->TabsOrder[tab->CurrentOrder], &tab_bar->TabsOrder[tab->CurrentOrder + 1], (tab_bar->TabsOrder.Size - tab->CurrentOrder - 1) * sizeof(tab_bar->TabsOrder[0]));
        tab_bar->TabsOrder.back() = tab->GlobalIndex;
        tab_bar->CurrOrderInsideTabsIsValid = false;
    }
}

If in the same frame, 2 tabs are reappearing in the tab bar (let's say you have a filter on the tab names that is filtering 2 TABs out of 4 and you then clear the filter string so all TABs suddenly need to reappear) .. the first one switching its position to TabsOrder.back() will memmove() all others down (without reindexing/updating the other tabs->CurrentOrder) so the next one will still have its CurrentOrder set on the old position. The next one will ImAssert in that code with a bad CurrentOrder vs TabOrders GlobalIndex.

This code probably assumes one reappearing TAB per frame and never broke because the next call to TabBarLayout should fix all tabs CurrentOrder variable anyway.

You can workaround this by skipping that code (ie creating your BeginTabBar with the ImGuiTabBarFlags_NoResetOrderOnAppearing flags OR updating all subsequent TABs after the memmove().

@BeachCoder76

This comment has been minimized.

Show comment
Hide comment
@BeachCoder76

BeachCoder76 Apr 19, 2018

And while we're at it .. the tabs demo code doesn't use the usual early out strategy which I changed locally to :

void    ImGui::ShowTabsDemo(const char* title, bool* p_open)
{
    if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar))
    {
        // Early out if the window is collapsed, as an optimization.
        ImGui::End();
        return;
    }

.. small detail but it does prevent going into the tabbing code if the tab demo window is visible but closed. :)

BeachCoder76 commented Apr 19, 2018

And while we're at it .. the tabs demo code doesn't use the usual early out strategy which I changed locally to :

void    ImGui::ShowTabsDemo(const char* title, bool* p_open)
{
    if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar))
    {
        // Early out if the window is collapsed, as an optimization.
        ImGui::End();
        return;
    }

.. small detail but it does prevent going into the tabbing code if the tab demo window is visible but closed. :)

@sonoro1234 sonoro1234 referenced this issue Jul 5, 2018

Closed

Tabs #1928

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