Skip to content

Instantly share code, notes, and snippets.

@thennequin
Last active May 26, 2024 18:12
Show Gist options
  • Save thennequin/64b4b996ec990c6ddc13a48c6a0ba68c to your computer and use it in GitHub Desktop.
Save thennequin/64b4b996ec990c6ddc13a48c6a0ba68c to your computer and use it in GitHub Desktop.
PieMenuAdv
struct PieMenuContext
{
static const int c_iMaxPieMenuStack = 8;
static const int c_iMaxPieItemCount = 12;
static const int c_iRadiusEmpty = 30;
static const int c_iRadiusMin = 30;
static const int c_iMinItemCount = 3;
static const int c_iMinItemCountPerLevel = 3;
struct PieMenu
{
int m_iCurrentIndex;
float m_fMaxItemSqrDiameter;
float m_fLastMaxItemSqrDiameter;
int m_iHoveredItem;
int m_iLastHoveredItem;
int m_iClickedItem;
bool m_oItemIsSubMenu[c_iMaxPieItemCount];
ImVector<char> m_oItemNames[c_iMaxPieItemCount];
ImVec2 m_oItemSizes[c_iMaxPieItemCount];
};
PieMenuContext()
{
m_iCurrentIndex = -1;
m_iLastFrame = 0;
}
PieMenu m_oPieMenuStack[c_iMaxPieMenuStack];
int m_iCurrentIndex;
int m_iMaxIndex;
int m_iLastFrame;
ImVec2 m_oCenter;
int m_iMouseButton;
bool m_bClose;
};
static PieMenuContext s_oPieMenuContext;
bool IsPopupOpen(const char* pName)
{
ImGuiID iID = ImGui::GetID(pName);
ImGuiState& g = *GImGui;
return g.OpenedPopupStack.Size > g.CurrentPopupStack.Size && g.OpenedPopupStack[g.CurrentPopupStack.Size].PopupID == iID;
}
void BeginPieMenuEx()
{
IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex < PieMenuContext::c_iMaxPieMenuStack);
++s_oPieMenuContext.m_iCurrentIndex;
++s_oPieMenuContext.m_iMaxIndex;
PieMenuContext::PieMenu& oPieMenu = s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
oPieMenu.m_iCurrentIndex = 0;
oPieMenu.m_fMaxItemSqrDiameter = 0.f;
if( !ImGui::IsMouseReleased( s_oPieMenuContext.m_iMouseButton ) )
oPieMenu.m_iHoveredItem = -1;
if (s_oPieMenuContext.m_iCurrentIndex > 0)
oPieMenu.m_fMaxItemSqrDiameter = s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex - 1].m_fMaxItemSqrDiameter;
}
void EndPieMenuEx()
{
IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0);
PieMenuContext::PieMenu& oPieMenu = s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
--s_oPieMenuContext.m_iCurrentIndex;
}
bool BeginPiePopup(const char* pName, int iMouseButton)
{
if (IsPopupOpen(pName))
{
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f);
s_oPieMenuContext.m_iMouseButton = iMouseButton;
s_oPieMenuContext.m_bClose = false;
ImGui::SetNextWindowPos( ImVec2( -100.f, -100.f ), ImGuiSetCond_Appearing );
bool bOpened = ImGui::BeginPopup(pName, ImGuiWindowFlags_NoBackground);
if (bOpened)
{
int iCurrentFrame = ImGui::GetFrameCount();
if (s_oPieMenuContext.m_iLastFrame < (iCurrentFrame - 1))
{
s_oPieMenuContext.m_oCenter = ImGui::GetIO().MousePos;
}
s_oPieMenuContext.m_iLastFrame = iCurrentFrame;
s_oPieMenuContext.m_iMaxIndex = -1;
BeginPieMenuEx();
return true;
}
else
{
ImGui::End();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(2);
}
}
return false;
}
void EndPiePopup()
{
EndPieMenuEx();
ImGuiStyle& oStyle = ImGui::GetStyle();
const ImVec2& vTexUvWhitePixel = ImGui::GetDrawListSharedData()->TexUvWhitePixel;
ImDrawList* pDrawList = ImGui::GetWindowDrawList();
pDrawList->PushClipRectFullScreen();
const ImVec2 oMousePos = ImGui::GetIO().MousePos;
const ImVec2 oDragDelta = ImVec2(oMousePos.x - s_oPieMenuContext.m_oCenter.x, oMousePos.y - s_oPieMenuContext.m_oCenter.y);
const float fDragDistSqr = oDragDelta.x*oDragDelta.x + oDragDelta.y*oDragDelta.y;
float fCurrentRadius = (float)PieMenuContext::c_iRadiusEmpty;
ImRect oArea = ImRect( s_oPieMenuContext.m_oCenter, s_oPieMenuContext.m_oCenter );
bool bItemHovered = false;
const float c_fDefaultRotate = -IM_PI / 2.f;
float fLastRotate = c_fDefaultRotate;
for (int iIndex = 0; iIndex <= s_oPieMenuContext.m_iMaxIndex; ++iIndex)
{
PieMenuContext::PieMenu& oPieMenu = s_oPieMenuContext.m_oPieMenuStack[iIndex];
float fMenuHeight = sqrt(oPieMenu.m_fMaxItemSqrDiameter);
const float fMinRadius = fCurrentRadius;
const float fMaxRadius = fMinRadius + (fMenuHeight * oPieMenu.m_iCurrentIndex) / ( 2.f );
const float item_arc_span = 2 * IM_PI / ImMax(PieMenuContext::c_iMinItemCount + PieMenuContext::c_iMinItemCountPerLevel * iIndex, oPieMenu.m_iCurrentIndex);
float drag_angle = atan2f(oDragDelta.y, oDragDelta.x);
float fRotate = fLastRotate - item_arc_span * ( oPieMenu.m_iCurrentIndex - 1.f ) / 2.f;
int item_hovered = -1;
for( int item_n = 0; item_n < oPieMenu.m_iCurrentIndex; item_n++ )
{
const char* item_label = oPieMenu.m_oItemNames[ item_n ].Data;
const float inner_spacing = oStyle.ItemInnerSpacing.x / fMinRadius / 2;
const float fMinInnerSpacing = oStyle.ItemInnerSpacing.x / ( fMinRadius * 2.f );
const float fMaxInnerSpacing = oStyle.ItemInnerSpacing.x / ( fMaxRadius * 2.f );
const float item_inner_ang_min = item_arc_span * ( item_n - 0.5f + fMinInnerSpacing ) + fRotate;
const float item_inner_ang_max = item_arc_span * ( item_n + 0.5f - fMinInnerSpacing ) + fRotate;
const float item_outer_ang_min = item_arc_span * ( item_n - 0.5f + fMaxInnerSpacing ) + fRotate;
const float item_outer_ang_max = item_arc_span * ( item_n + 0.5f - fMaxInnerSpacing ) + fRotate;
bool hovered = false;
if( fDragDistSqr >= fMinRadius * fMinRadius && fDragDistSqr < fMaxRadius * fMaxRadius )
{
while( ( drag_angle - item_inner_ang_min ) < 0.f )
drag_angle += 2.f * IM_PI;
while( ( drag_angle - item_inner_ang_min ) > 2.f * IM_PI )
drag_angle -= 2.f * IM_PI;
if( drag_angle >= item_inner_ang_min && drag_angle < item_inner_ang_max )
{
hovered = true;
bItemHovered = !oPieMenu.m_oItemIsSubMenu[ item_n ];
}
}
int arc_segments = (int)( 32 * item_arc_span / ( 2 * IM_PI ) ) + 1;
ImU32 iColor = hovered ? ImColor( 100, 100, 150 ) : ImColor( 70, 70, 70 );
iColor = ImGui::GetColorU32( hovered ? ImGuiCol_HeaderHovered : ImGuiCol_FrameBg );
iColor = ImGui::GetColorU32( hovered ? ImGuiCol_Button : ImGuiCol_ButtonHovered );
//iColor |= 0xFF000000;
const float fAngleStepInner = (item_inner_ang_max - item_inner_ang_min) / arc_segments;
const float fAngleStepOuter = ( item_outer_ang_max - item_outer_ang_min ) / arc_segments;
pDrawList->PrimReserve(arc_segments * 6, (arc_segments + 1) * 2);
for (int iSeg = 0; iSeg <= arc_segments; ++iSeg)
{
float fCosInner = cosf(item_inner_ang_min + fAngleStepInner * iSeg);
float fSinInner = sinf(item_inner_ang_min + fAngleStepInner * iSeg);
float fCosOuter = cosf(item_outer_ang_min + fAngleStepOuter * iSeg);
float fSinOuter = sinf(item_outer_ang_min + fAngleStepOuter * iSeg);
if (iSeg < arc_segments)
{
pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 0);
pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 2);
pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 1);
pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 3);
pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 2);
pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 1);
}
pDrawList->PrimWriteVtx(ImVec2(s_oPieMenuContext.m_oCenter.x + fCosInner * (fMinRadius + oStyle.ItemInnerSpacing.x), s_oPieMenuContext.m_oCenter.y + fSinInner * (fMinRadius + oStyle.ItemInnerSpacing.x)), vTexUvWhitePixel, iColor);
pDrawList->PrimWriteVtx(ImVec2(s_oPieMenuContext.m_oCenter.x + fCosOuter * (fMaxRadius - oStyle.ItemInnerSpacing.x), s_oPieMenuContext.m_oCenter.y + fSinOuter * (fMaxRadius - oStyle.ItemInnerSpacing.x)), vTexUvWhitePixel, iColor);
}
float fRadCenter = ( item_arc_span * item_n ) + fRotate;
ImVec2 oOuterCenter = ImVec2( s_oPieMenuContext.m_oCenter.x + cosf( fRadCenter ) * fMaxRadius, s_oPieMenuContext.m_oCenter.y + sinf( fRadCenter ) * fMaxRadius );
oArea.Add( oOuterCenter );
if (oPieMenu.m_oItemIsSubMenu[item_n])
{
ImVec2 oTrianglePos[3];
float fRadLeft = fRadCenter - 5.f / fMaxRadius;
float fRadRight = fRadCenter + 5.f / fMaxRadius;
oTrianglePos[ 0 ].x = s_oPieMenuContext.m_oCenter.x + cosf( fRadCenter ) * ( fMaxRadius - 5.f );
oTrianglePos[ 0 ].y = s_oPieMenuContext.m_oCenter.y + sinf( fRadCenter ) * ( fMaxRadius - 5.f );
oTrianglePos[ 1 ].x = s_oPieMenuContext.m_oCenter.x + cosf( fRadLeft ) * ( fMaxRadius - 10.f );
oTrianglePos[ 1 ].y = s_oPieMenuContext.m_oCenter.y + sinf( fRadLeft ) * ( fMaxRadius - 10.f );
oTrianglePos[ 2 ].x = s_oPieMenuContext.m_oCenter.x + cosf( fRadRight ) * ( fMaxRadius - 10.f );
oTrianglePos[ 2 ].y = s_oPieMenuContext.m_oCenter.y + sinf( fRadRight ) * ( fMaxRadius - 10.f );
pDrawList->AddTriangleFilled(oTrianglePos[0], oTrianglePos[1], oTrianglePos[2], ImColor(255, 255, 255));
}
ImVec2 text_size = oPieMenu.m_oItemSizes[item_n];
ImVec2 text_pos = ImVec2(
s_oPieMenuContext.m_oCenter.x + cosf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (fMinRadius + fMaxRadius) * 0.5f - text_size.x * 0.5f,
s_oPieMenuContext.m_oCenter.y + sinf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (fMinRadius + fMaxRadius) * 0.5f - text_size.y * 0.5f);
pDrawList->AddText(text_pos, ImColor(255, 255, 255), item_label);
if (hovered)
item_hovered = item_n;
}
fCurrentRadius = fMaxRadius;
oPieMenu.m_fLastMaxItemSqrDiameter = oPieMenu.m_fMaxItemSqrDiameter;
oPieMenu.m_iHoveredItem = item_hovered;
if (fDragDistSqr >= fMaxRadius * fMaxRadius)
item_hovered = oPieMenu.m_iLastHoveredItem;
oPieMenu.m_iLastHoveredItem = item_hovered;
fLastRotate = item_arc_span * oPieMenu.m_iLastHoveredItem + fRotate;
if( item_hovered == -1 || !oPieMenu.m_oItemIsSubMenu[item_hovered] )
break;
}
pDrawList->PopClipRect();
if( oArea.Min.x < 0.f )
{
s_oPieMenuContext.m_oCenter.x = ( s_oPieMenuContext.m_oCenter.x - oArea.Min.x );
}
if( oArea.Min.y < 0.f )
{
s_oPieMenuContext.m_oCenter.y = ( s_oPieMenuContext.m_oCenter.y - oArea.Min.y );
}
ImVec2 oDisplaySize = ImGui::GetIO().DisplaySize;
if( oArea.Max.x > oDisplaySize.x )
{
s_oPieMenuContext.m_oCenter.x = ( s_oPieMenuContext.m_oCenter.x - oArea.Max.x ) + oDisplaySize.x;
}
if( oArea.Max.y > oDisplaySize.y )
{
s_oPieMenuContext.m_oCenter.y = ( s_oPieMenuContext.m_oCenter.y - oArea.Max.y ) + oDisplaySize.y;
}
if( s_oPieMenuContext.m_bClose ||
( !bItemHovered && ImGui::IsMouseReleased( s_oPieMenuContext.m_iMouseButton ) ) )
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(2);
}
bool BeginPieMenu(const char* pName, bool bEnabled)
{
IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0 && s_oPieMenuContext.m_iCurrentIndex < PieMenuContext::c_iMaxPieItemCount);
PieMenuContext::PieMenu& oPieMenu = s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
ImVec2 oTextSize = ImGui::CalcTextSize(pName, NULL, true);
oPieMenu.m_oItemSizes[oPieMenu.m_iCurrentIndex] = oTextSize;
float fSqrDiameter = oTextSize.x * oTextSize.x + oTextSize.y * oTextSize.y;
if (fSqrDiameter > oPieMenu.m_fMaxItemSqrDiameter)
{
oPieMenu.m_fMaxItemSqrDiameter = fSqrDiameter;
}
oPieMenu.m_oItemIsSubMenu[oPieMenu.m_iCurrentIndex] = true;
int iLen = strlen(pName);
ImVector<char>& oName = oPieMenu.m_oItemNames[oPieMenu.m_iCurrentIndex];
oName.resize(iLen + 1);
oName[iLen] = '\0';
memcpy(oName.Data, pName, iLen);
if (oPieMenu.m_iLastHoveredItem == oPieMenu.m_iCurrentIndex)
{
++oPieMenu.m_iCurrentIndex;
BeginPieMenuEx();
return true;
}
++oPieMenu.m_iCurrentIndex;
return false;
}
void EndPieMenu()
{
IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0 && s_oPieMenuContext.m_iCurrentIndex < PieMenuContext::c_iMaxPieItemCount);
--s_oPieMenuContext.m_iCurrentIndex;
}
bool PieMenuItem(const char* pName, bool bEnabled)
{
IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0 && s_oPieMenuContext.m_iCurrentIndex < PieMenuContext::c_iMaxPieItemCount);
PieMenuContext::PieMenu& oPieMenu = s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
ImVec2 oTextSize = ImGui::CalcTextSize(pName, NULL, true);
oPieMenu.m_oItemSizes[oPieMenu.m_iCurrentIndex] = oTextSize;
float fSqrDiameter = oTextSize.x * oTextSize.x + oTextSize.y * oTextSize.y;
if (fSqrDiameter > oPieMenu.m_fMaxItemSqrDiameter)
{
oPieMenu.m_fMaxItemSqrDiameter = fSqrDiameter;
}
oPieMenu.m_oItemIsSubMenu[oPieMenu.m_iCurrentIndex] = false;
int iLen = strlen(pName);
ImVector<char>& oName = oPieMenu.m_oItemNames[oPieMenu.m_iCurrentIndex];
oName.resize(iLen + 1);
oName[iLen] = '\0';
memcpy(oName.Data, pName, iLen);
bool bActive = oPieMenu.m_iCurrentIndex == oPieMenu.m_iHoveredItem;
++oPieMenu.m_iCurrentIndex;
if( bActive )
s_oPieMenuContext.m_bClose = true;
return bActive;
}
/* Declaration */
bool BeginPiePopup( const char* pName, int iMouseButton = 0 );
void EndPiePopup();
bool PieMenuItem( const char* pName, bool bEnabled = true );
bool BeginPieMenu( const char* pName, bool bEnabled = true );
void EndPieMenu();
/* Example */
std::string sText;
if( ImGui::IsWindowHovered() && ImGui::IsMouseClicked( 1 ) )
{
ImGui::OpenPopup( "PieMenu" );
}
if( BeginPiePopup( "PieMenu", 1 ) )
{
if( PieMenuItem( "Test1" ) ) sText = "Test1";
if( PieMenuItem( "Test2" ) )
{
sText = "Test2";
new MyImwWindow3();
}
if( PieMenuItem( "Test3", false ) ) sText = "Test3";
if( BeginPieMenu( "Sub" ) )
{
if( BeginPieMenu( "Sub sub\nmenu" ) )
{
if( PieMenuItem( "SubSub" ) ) sText = "SubSub";
if( PieMenuItem( "SubSub2" ) ) sText = "SubSub2";
EndPieMenu();
}
if( PieMenuItem( "TestSub" ) ) sText = "TestSub";
if( PieMenuItem( "TestSub2" ) ) sText = "TestSub2";
EndPieMenu();
}
if( BeginPieMenu( "Sub2" ) )
{
if( PieMenuItem( "TestSub" ) ) sText = "TestSub";
if( BeginPieMenu( "Sub sub\nmenu" ) )
{
if( PieMenuItem( "SubSub" ) ) sText = "SubSub";
if( PieMenuItem( "SubSub2" ) ) sText = "SubSub2";
EndPieMenu();
}
if( PieMenuItem( "TestSub2" ) ) sText = "TestSub2";
EndPieMenu();
}
EndPiePopup();
}
@apaly
Copy link

apaly commented Jun 16, 2022

Hello,
Thank for providing this code.

I tried using it but got the following errors:

  • Identifier ImGuiSetCond_Appearing is not defined
  • Identifier ImGuiState is not defined

I will appreciate if you could please help me. Thanks.

@VDorchester14
Copy link

VDorchester14 commented Jun 17, 2022

Hi, ImGuiSetCond_Appearing has been replaced with ImGuiCond_Appearing, and ImGuiState is now the ImGuiContext. You should be able to get a pointer to that with ImGui::GetCurrentContext().

@haferburg
Copy link

Summary of what needs to be changed (I'm on 1.84.2):

  • ImGuiSetCond_Appearing -> ImGuiCond_Appearing
  • ImGuiState -> ImGuiContext
  • ImGuiState::OpenedPopupStack -> ImGuiContext::OpenPopupStack
  • ImGuiState::CurrentPopupStack -> ImGuiContext::BeginPopupStack

@k0mp1ex
Copy link

k0mp1ex commented Jan 19, 2023

@thennequin, this worked great, thanks for sharing!
I see that your GH projects are under MIT license. Does this gist fall under the same license?

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