Skip to content

Instantly share code, notes, and snippets.

@Flix01
Last active October 12, 2021 22:36
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Flix01/3bc3d7b3d996582e034e to your computer and use it in GitHub Desktop.
Save Flix01/3bc3d7b3d996582e034e to your computer and use it in GitHub Desktop.
A ImGui::TabLabels(...) that supports line wrapping, tab reordering through drag'n'drop and tab closing through MMB press.
#pragma once
#include <imgui.h>
// 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 tabItemOrdering[numTabs] = {0,1,2,3,4,5,6,7,8,9,10,11};
static int selectedTab = 0;
static int optionalHoveredTab = 0;
/*const bool tabSelectedChanged =*/ ImGui::TabLabels(numTabs,tabNames,selectedTab,tabTooltips,true,&optionalHoveredTab,&tabItemOrdering[0],true,true);
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]);
*/
namespace ImGui {
// Based on the code by krys-spectralpixel (https://github.com/krys-spectralpixel), posted here: https://github.com/ocornut/imgui/issues/261
/* pOptionalHoveredIndex: a ptr to an optional int that is set to -1 if no tab label is hovered by the mouse.
* pOptionalItemOrdering: an optional static array of unique integers from 0 to numTabs-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.
* allowTabReorder (requires pOptionalItemOrdering): allows tab reordering through drag and drop (it modifies pOptionalItemOrdering).
* allowTabClosingThroughMMB (requires pOptionalItemOrdering): closes the tabs when MMB is clicked on them, by setting the tab value in pOptionalItemOrdering to -1.
* pOptionalClosedTabIndex (requires allowTabClosingThroughMMB): out variable (int pointer) that returns the index of the closed tab in last call or -1.
* pOptionalClosedTabIndexInsideItemOrdering: same as above, but index of the pOptionalItemOrdering array.
*/
IMGUI_API bool TabLabels(int numTabs, const char** tabLabels, int& selectedIndex, const char** tabLabelTooltips=NULL, bool wrapMode=true, int *pOptionalHoveredIndex=NULL, int* pOptionalItemOrdering=NULL, bool allowTabReorder=true, bool allowTabClosingThroughMMB=true, int *pOptionalClosedTabIndex=NULL, int *pOptionalClosedTabIndexInsideItemOrdering=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(color.x,color.y,color.z,color.w*0.5f);
const ImVec4 colorSelectedTabHovered(colorHover.x,colorHover.y,colorHover.z,colorHover.w*0.5f);
const ImVec4 colorSelectedTabText(colorText.x*0.8f,colorText.y*0.8f,colorText.z*0.6f,colorText.w*0.8f);
//ImGui::ClampColor(colorSelectedTabText);
if (numTabs>0 && (selectedIndex<0 || selectedIndex>=numTabs)) {
if (!pOptionalItemOrdering) selectedIndex = 0;
else selectedIndex = -1;
}
if (pOptionalHoveredIndex) *pOptionalHoveredIndex = -1;
if (pOptionalClosedTabIndex) *pOptionalClosedTabIndex = -1;
if (pOptionalClosedTabIndexInsideItemOrdering) *pOptionalClosedTabIndexInsideItemOrdering = -1;
float windowWidth = 0.f,sumX=0.f;
if (wrapMode) windowWidth = ImGui::GetWindowWidth() - style.WindowPadding.x - (ImGui::GetScrollMaxY()>0 ? style.ScrollbarSize : 0.f);
static int draggingTabIndex = -1;int draggingTabTargetIndex = -1; // These are indices inside pOptionalItemOrdering
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,newSelectedIndex = selectedIndex;
bool selection_changed = false;bool noButtonDrawn = true;
for (int j = 0,i; j < numTabs; j++)
{
i = pOptionalItemOrdering ? pOptionalItemOrdering[j] : j;
if (i==-1) continue;
if (!wrapMode) {if (!noButtonDrawn) ImGui::SameLine();}
else 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 == selectedIndex) {
// 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 = (selectedIndex!=i);newSelectedIndex = i;}
ImGui::PopID();
if (i == selectedIndex) {
// 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 (wrapMode) {
if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line
}
else if (isMouseDragging && allowTabReorder && pOptionalItemOrdering) {
// We still need sumX
if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line
else sumX+=style.ItemSpacing.x + ImGui::GetItemRectSize().x;
}
if (ImGui::IsItemHoveredRect()) {
if (pOptionalHoveredIndex) *pOptionalHoveredIndex = i;
if (tabLabelTooltips && tabLabelTooltips[i] && strlen(tabLabelTooltips[i])>0) ImGui::SetTooltip("%s",tabLabelTooltips[i]);
if (pOptionalItemOrdering) {
if (allowTabReorder) {
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<numTabs && 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)
}
}
if (allowTabClosingThroughMMB) {
if (isMMBreleased) {
justClosedTabIndex = i;
if (pOptionalClosedTabIndex) *pOptionalClosedTabIndex = i;
if (pOptionalClosedTabIndexInsideItemOrdering) *pOptionalClosedTabIndexInsideItemOrdering = j;
pOptionalItemOrdering[j] = -1;
}
}
}
}
}
selectedIndex = newSelectedIndex;
// Draw tab label while mouse drags it
if (draggingTabIndex>=0 && draggingTabIndex<numTabs) {
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[pOptionalItemOrdering[draggingTabIndex]]);
ImGui::SetMouseCursor(ImGuiMouseCursor_Move);
}
// Drop tab label
if (draggingTabTargetIndex!=-1) {
// swap draggingTabIndex and draggingTabTargetIndex in pOptionalItemOrdering
const int tmp = pOptionalItemOrdering[draggingTabTargetIndex];
pOptionalItemOrdering[draggingTabTargetIndex] = pOptionalItemOrdering[draggingTabIndex];
pOptionalItemOrdering[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 (selectedIndex == justClosedTabIndex && selectedIndex>=0) {
selectedIndex = -1;
for (int j = 0,i; j < numTabs; j++) {
i = pOptionalItemOrdering ? pOptionalItemOrdering[j] : j;
if (i==-1) continue;
selectedIndex = 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
Copy link
Author

Flix01 commented Dec 6, 2015

It's an extension of the code I posted here: ocornut/imgui#261
selection_010

The implementation is very basic/compact (no dragging visuals, no close buttons).

KNOWN BUG:

  • Tab reordering through drag'n'drop seems to work only when dragging tabs from the left (top) to the right (bottom) and not vice-versa (this is bad, but can't lock the tab order in any way).

@Flix01
Copy link
Author

Flix01 commented Dec 15, 2015

UPDATE:

  • Fixed all the width-related variables (it should work well both with and without a vertical scroll bar).
  • Added last argument: it could be used to prevent a tab label from closing inside user-code.
  • Added "dragging visuals":

selection_013

KNOWN BUGS:

  • Tab reordering through drag and drop seems to work only when dragging tabs from the left (top) to the right (bottom) and not vice versa (this is bad, even if it can't lock the tab order in any way). It seems like ImGui "owns" the mouse in some way while dragging and ImGui::IsItemHovered() sometimes does not work when the mouse is being dragged.
  • Sometimes middle-mouse-clicking for closing the tabs seems a bit buggy... at least on my imgui "back-end".

@Flix01
Copy link
Author

Flix01 commented Dec 15, 2015

UPDATE:

  • Fixed first bug. Now drag and drop works in both directions (and it now uses the "move cursor" image).
  • Fixed second bug (actually I did nothing). It must be something related to my glfw3 mouse button handling code, because when I use SDL2 it works as expected. (The live demo at the bottom uses SDL2 too and seems to work perfectly).
  • Changed colors a bit:

selection_014

LIVE DEMO:

Added it to my ImGui Addons Branch first live demo here:
Demo1

@Flix01
Copy link
Author

Flix01 commented Jan 7, 2016

UPDATE: I've moved this gist and merged with this: https://gist.github.com/Flix01/2cdf1db8d936100628c0 .
This way I can have proper tab labels:
selection_017

@vivienneanthony
Copy link

Hi

I tried copying the two files into my im_gui folder but I was getting compiliation errors. How simple it to merge this into code? I run Urho3D with imgui setup as a thirdparty add on.

Vivienne

@Flix01
Copy link
Author

Flix01 commented Jan 22, 2016

I tried copying the two files into my im_gui folder but I was getting compiliation errors. How simple it to merge this into code? I run Urho3D with imgui setup as a thirdparty add on.

Are you referring to the two files in the other link ? Does the code in this gist work or not ? What compilation error ? What system/OS ?

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