1 // Code snippet provided by Thibault Hennequin
2 // https://gist.github.com/thennequin/64b4b996ec990c6ddc13a48c6a0ba68c
3 
4 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
5 #define _CRT_SECURE_NO_WARNINGS
6 #endif
7 
8 #include "imgui.h"
9 #ifndef IMGUI_DISABLE
10 
11 #include "imgui_internal.h"
12 
13 struct PieMenuContext
14 {
15   static const int c_iMaxPieMenuStack = 8;
16   static const int c_iMaxPieItemCount = 12;
17   static const int c_iRadiusEmpty = 30;
18   static const int c_iRadiusMin = 30;
19   static const int c_iMinItemCount = 3;
20   static const int c_iMinItemCountPerLevel = 3;
21 
22   struct PieMenu
23   {
24     int m_iCurrentIndex;
25     float m_fMaxItemSqrDiameter;
26     float m_fLastMaxItemSqrDiameter;
27     int m_iHoveredItem;
28     int m_iLastHoveredItem;
29     int m_iClickedItem;
30     bool m_oItemIsSubMenu[c_iMaxPieItemCount];
31     ImVector<char> m_oItemNames[c_iMaxPieItemCount];
32     ImVec2 m_oItemSizes[c_iMaxPieItemCount];
33   };
34 
PieMenuContextPieMenuContext35   PieMenuContext()
36   {
37     m_iCurrentIndex = -1;
38     m_iLastFrame = 0;
39   }
40 
41   PieMenu m_oPieMenuStack[c_iMaxPieMenuStack];
42   int m_iCurrentIndex;
43   int m_iMaxIndex;
44   int m_iLastFrame;
45   ImVec2 m_oCenter;
46   int m_iMouseButton;
47   bool m_bClose;
48 };
49 
50 static PieMenuContext s_oPieMenuContext;
51 
IsPopupOpen(const char * pName)52 bool IsPopupOpen(const char *pName)
53 {
54   ImGuiID iId = ImGui::GetID(pName);
55   ImGuiContext &g = *GImGui;
56   return g.OpenPopupStack.Size > g.BeginPopupStack.Size
57       && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == iId;
58 }
59 
BeginPieMenuEx()60 void BeginPieMenuEx()
61 {
62   IM_ASSERT(
63       s_oPieMenuContext.m_iCurrentIndex < PieMenuContext::c_iMaxPieMenuStack);
64 
65   ++s_oPieMenuContext.m_iCurrentIndex;
66   ++s_oPieMenuContext.m_iMaxIndex;
67 
68   PieMenuContext::PieMenu &oPieMenu =
69       s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
70   oPieMenu.m_iCurrentIndex = 0;
71   oPieMenu.m_fMaxItemSqrDiameter = 0.f;
72   if (!ImGui::IsMouseReleased(s_oPieMenuContext.m_iMouseButton))
73     oPieMenu.m_iHoveredItem = -1;
74   if (s_oPieMenuContext.m_iCurrentIndex > 0)
75     oPieMenu.m_fMaxItemSqrDiameter =
76         s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex - 1]
77             .m_fMaxItemSqrDiameter;
78 }
79 
EndPieMenuEx()80 void EndPieMenuEx()
81 {
82   IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0);
83   PieMenuContext::PieMenu &oPieMenu =
84       s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
85 
86   --s_oPieMenuContext.m_iCurrentIndex;
87 }
88 
BeginPiePopup(const char * pName,int iMouseButton)89 bool BeginPiePopup(const char *pName, int iMouseButton)
90 {
91   if (IsPopupOpen(pName)) {
92     ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
93     ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
94     ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
95     ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f);
96 
97     s_oPieMenuContext.m_iMouseButton = iMouseButton;
98     s_oPieMenuContext.m_bClose = false;
99 
100     ImGui::SetNextWindowPos(ImVec2(-100.f, -100.f), ImGuiCond_Appearing);
101     bool bOpened = ImGui::BeginPopup(pName);
102     if (bOpened) {
103       int iCurrentFrame = ImGui::GetFrameCount();
104       if (s_oPieMenuContext.m_iLastFrame < (iCurrentFrame - 1)) {
105         s_oPieMenuContext.m_oCenter = ImGui::GetIO().MousePos;
106       }
107       s_oPieMenuContext.m_iLastFrame = iCurrentFrame;
108 
109       s_oPieMenuContext.m_iMaxIndex = -1;
110       BeginPieMenuEx();
111 
112       return true;
113     } else {
114       ImGui::End();
115       ImGui::PopStyleColor(2);
116       ImGui::PopStyleVar(2);
117     }
118   }
119   return false;
120 }
121 
EndPiePopup()122 void EndPiePopup()
123 {
124   EndPieMenuEx();
125 
126   ImGuiStyle &oStyle = ImGui::GetStyle();
127 
128   ImDrawList *pDrawList = ImGui::GetWindowDrawList();
129   pDrawList->PushClipRectFullScreen();
130 
131   const ImVec2 oMousePos = ImGui::GetIO().MousePos;
132   const ImVec2 oDragDelta = ImVec2(oMousePos.x - s_oPieMenuContext.m_oCenter.x,
133       oMousePos.y - s_oPieMenuContext.m_oCenter.y);
134   const float fDragDistSqr =
135       oDragDelta.x * oDragDelta.x + oDragDelta.y * oDragDelta.y;
136 
137   float fCurrentRadius = (float)PieMenuContext::c_iRadiusEmpty;
138 
139   ImRect oArea =
140       ImRect(s_oPieMenuContext.m_oCenter, s_oPieMenuContext.m_oCenter);
141 
142   bool bItemHovered = false;
143 
144   const float c_fDefaultRotate = -IM_PI / 2.f;
145   float fLastRotate = c_fDefaultRotate;
146   for (int iIndex = 0; iIndex <= s_oPieMenuContext.m_iMaxIndex; ++iIndex) {
147     PieMenuContext::PieMenu &oPieMenu =
148         s_oPieMenuContext.m_oPieMenuStack[iIndex];
149 
150     float fMenuHeight = sqrt(oPieMenu.m_fMaxItemSqrDiameter);
151 
152     const float fMinRadius = fCurrentRadius;
153     const float fMaxRadius =
154         fMinRadius + (fMenuHeight * oPieMenu.m_iCurrentIndex) / (2.f);
155 
156     const float item_arc_span = 2 * IM_PI
157         / ImMax(PieMenuContext::c_iMinItemCount
158                 + PieMenuContext::c_iMinItemCountPerLevel * iIndex,
159             oPieMenu.m_iCurrentIndex);
160     float drag_angle = atan2f(oDragDelta.y, oDragDelta.x);
161 
162     float fRotate =
163         fLastRotate - item_arc_span * (oPieMenu.m_iCurrentIndex - 1.f) / 2.f;
164     int item_hovered = -1;
165     for (int item_n = 0; item_n < oPieMenu.m_iCurrentIndex; item_n++) {
166       const char *item_label = oPieMenu.m_oItemNames[item_n].Data;
167       const float inner_spacing = oStyle.ItemInnerSpacing.x / fMinRadius / 2;
168       const float fMinInnerSpacing =
169           oStyle.ItemInnerSpacing.x / (fMinRadius * 2.f);
170       const float fMaxInnerSpacing =
171           oStyle.ItemInnerSpacing.x / (fMaxRadius * 2.f);
172       const float item_inner_ang_min =
173           item_arc_span * (item_n - 0.5f + fMinInnerSpacing) + fRotate;
174       const float item_inner_ang_max =
175           item_arc_span * (item_n + 0.5f - fMinInnerSpacing) + fRotate;
176       const float item_outer_ang_min =
177           item_arc_span * (item_n - 0.5f + fMaxInnerSpacing) + fRotate;
178       const float item_outer_ang_max =
179           item_arc_span * (item_n + 0.5f - fMaxInnerSpacing) + fRotate;
180 
181       bool hovered = false;
182       if (fDragDistSqr >= fMinRadius * fMinRadius
183           && fDragDistSqr < fMaxRadius * fMaxRadius) {
184         while ((drag_angle - item_inner_ang_min) < 0.f)
185           drag_angle += 2.f * IM_PI;
186         while ((drag_angle - item_inner_ang_min) > 2.f * IM_PI)
187           drag_angle -= 2.f * IM_PI;
188 
189         if (drag_angle >= item_inner_ang_min
190             && drag_angle < item_inner_ang_max) {
191           hovered = true;
192           bItemHovered = !oPieMenu.m_oItemIsSubMenu[item_n];
193         }
194       }
195 
196       int arc_segments = (int)(32 * item_arc_span / (2 * IM_PI)) + 1;
197 
198       ImU32 iColor = hovered ? ImColor(100, 100, 150) : ImColor(70, 70, 70);
199       iColor = ImGui::GetColorU32(
200           hovered ? ImGuiCol_HeaderHovered : ImGuiCol_FrameBg);
201       iColor = ImGui::GetColorU32(
202           hovered ? ImGuiCol_Button : ImGuiCol_ButtonHovered);
203       //iColor |= 0xFF000000;
204 
205       const float fAngleStepInner =
206           (item_inner_ang_max - item_inner_ang_min) / arc_segments;
207       const float fAngleStepOuter =
208           (item_outer_ang_max - item_outer_ang_min) / arc_segments;
209       pDrawList->PrimReserve(arc_segments * 6, (arc_segments + 1) * 2);
210       for (int iSeg = 0; iSeg <= arc_segments; ++iSeg) {
211         float fCosInner = cosf(item_inner_ang_min + fAngleStepInner * iSeg);
212         float fSinInner = sinf(item_inner_ang_min + fAngleStepInner * iSeg);
213         float fCosOuter = cosf(item_outer_ang_min + fAngleStepOuter * iSeg);
214         float fSinOuter = sinf(item_outer_ang_min + fAngleStepOuter * iSeg);
215 
216         if (iSeg < arc_segments) {
217           pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 0);
218           pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 2);
219           pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 1);
220           pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 3);
221           pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 2);
222           pDrawList->PrimWriteIdx(pDrawList->_VtxCurrentIdx + 1);
223         }
224         pDrawList->PrimWriteVtx(
225             ImVec2(s_oPieMenuContext.m_oCenter.x
226                     + fCosInner * (fMinRadius + oStyle.ItemInnerSpacing.x),
227                 s_oPieMenuContext.m_oCenter.y
228                     + fSinInner * (fMinRadius + oStyle.ItemInnerSpacing.x)),
229             ImVec2(0.f, 0.f),
230             iColor);
231         pDrawList->PrimWriteVtx(
232             ImVec2(s_oPieMenuContext.m_oCenter.x
233                     + fCosOuter * (fMaxRadius - oStyle.ItemInnerSpacing.x),
234                 s_oPieMenuContext.m_oCenter.y
235                     + fSinOuter * (fMaxRadius - oStyle.ItemInnerSpacing.x)),
236             ImVec2(0.f, 0.f),
237             iColor);
238       }
239 
240       float fRadCenter = (item_arc_span * item_n) + fRotate;
241       ImVec2 oOuterCenter =
242           ImVec2(s_oPieMenuContext.m_oCenter.x + cosf(fRadCenter) * fMaxRadius,
243               s_oPieMenuContext.m_oCenter.y + sinf(fRadCenter) * fMaxRadius);
244       oArea.Add(oOuterCenter);
245 
246       if (oPieMenu.m_oItemIsSubMenu[item_n]) {
247         ImVec2 oTrianglePos[3];
248 
249         float fRadLeft = fRadCenter - 5.f / fMaxRadius;
250         float fRadRight = fRadCenter + 5.f / fMaxRadius;
251 
252         oTrianglePos[0].x = s_oPieMenuContext.m_oCenter.x
253             + cosf(fRadCenter) * (fMaxRadius - 5.f);
254         oTrianglePos[0].y = s_oPieMenuContext.m_oCenter.y
255             + sinf(fRadCenter) * (fMaxRadius - 5.f);
256         oTrianglePos[1].x = s_oPieMenuContext.m_oCenter.x
257             + cosf(fRadLeft) * (fMaxRadius - 10.f);
258         oTrianglePos[1].y = s_oPieMenuContext.m_oCenter.y
259             + sinf(fRadLeft) * (fMaxRadius - 10.f);
260         oTrianglePos[2].x = s_oPieMenuContext.m_oCenter.x
261             + cosf(fRadRight) * (fMaxRadius - 10.f);
262         oTrianglePos[2].y = s_oPieMenuContext.m_oCenter.y
263             + sinf(fRadRight) * (fMaxRadius - 10.f);
264 
265         pDrawList->AddTriangleFilled(oTrianglePos[0],
266             oTrianglePos[1],
267             oTrianglePos[2],
268             ImColor(255, 255, 255));
269       }
270 
271       ImVec2 text_size = oPieMenu.m_oItemSizes[item_n];
272       ImVec2 text_pos = ImVec2(s_oPieMenuContext.m_oCenter.x
273               + cosf((item_inner_ang_min + item_inner_ang_max) * 0.5f)
274                   * (fMinRadius + fMaxRadius) * 0.5f
275               - text_size.x * 0.5f,
276           s_oPieMenuContext.m_oCenter.y
277               + sinf((item_inner_ang_min + item_inner_ang_max) * 0.5f)
278                   * (fMinRadius + fMaxRadius) * 0.5f
279               - text_size.y * 0.5f);
280       pDrawList->AddText(text_pos, ImColor(255, 255, 255), item_label);
281 
282       if (hovered)
283         item_hovered = item_n;
284     }
285 
286     fCurrentRadius = fMaxRadius;
287 
288     oPieMenu.m_fLastMaxItemSqrDiameter = oPieMenu.m_fMaxItemSqrDiameter;
289 
290     oPieMenu.m_iHoveredItem = item_hovered;
291 
292     if (fDragDistSqr >= fMaxRadius * fMaxRadius)
293       item_hovered = oPieMenu.m_iLastHoveredItem;
294 
295     oPieMenu.m_iLastHoveredItem = item_hovered;
296 
297     fLastRotate = item_arc_span * oPieMenu.m_iLastHoveredItem + fRotate;
298     if (item_hovered == -1 || !oPieMenu.m_oItemIsSubMenu[item_hovered])
299       break;
300   }
301 
302   pDrawList->PopClipRect();
303 
304   if (oArea.Min.x < 0.f) {
305     s_oPieMenuContext.m_oCenter.x =
306         (s_oPieMenuContext.m_oCenter.x - oArea.Min.x);
307   }
308   if (oArea.Min.y < 0.f) {
309     s_oPieMenuContext.m_oCenter.y =
310         (s_oPieMenuContext.m_oCenter.y - oArea.Min.y);
311   }
312 
313   ImVec2 oDisplaySize = ImGui::GetIO().DisplaySize;
314   if (oArea.Max.x > oDisplaySize.x) {
315     s_oPieMenuContext.m_oCenter.x =
316         (s_oPieMenuContext.m_oCenter.x - oArea.Max.x) + oDisplaySize.x;
317   }
318   if (oArea.Max.y > oDisplaySize.y) {
319     s_oPieMenuContext.m_oCenter.y =
320         (s_oPieMenuContext.m_oCenter.y - oArea.Max.y) + oDisplaySize.y;
321   }
322 
323   if (s_oPieMenuContext.m_bClose
324       || (!bItemHovered
325           && ImGui::IsMouseReleased(s_oPieMenuContext.m_iMouseButton))) {
326     ImGui::CloseCurrentPopup();
327   }
328 
329   ImGui::EndPopup();
330   ImGui::PopStyleColor(2);
331   ImGui::PopStyleVar(2);
332 }
333 
BeginPieMenu(const char * pName,bool bEnabled)334 bool BeginPieMenu(const char *pName, bool bEnabled)
335 {
336   IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0
337       && s_oPieMenuContext.m_iCurrentIndex
338           < PieMenuContext::c_iMaxPieItemCount);
339 
340   PieMenuContext::PieMenu &oPieMenu =
341       s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
342 
343   ImVec2 oTextSize = ImGui::CalcTextSize(pName, NULL, true);
344   oPieMenu.m_oItemSizes[oPieMenu.m_iCurrentIndex] = oTextSize;
345 
346   float fSqrDiameter = oTextSize.x * oTextSize.x + oTextSize.y * oTextSize.y;
347 
348   if (fSqrDiameter > oPieMenu.m_fMaxItemSqrDiameter) {
349     oPieMenu.m_fMaxItemSqrDiameter = fSqrDiameter;
350   }
351 
352   oPieMenu.m_oItemIsSubMenu[oPieMenu.m_iCurrentIndex] = true;
353 
354   int iLen = strlen(pName);
355   ImVector<char> &oName = oPieMenu.m_oItemNames[oPieMenu.m_iCurrentIndex];
356   oName.resize(iLen + 1);
357   oName[iLen] = '\0';
358   memcpy(oName.Data, pName, iLen);
359 
360   if (oPieMenu.m_iLastHoveredItem == oPieMenu.m_iCurrentIndex) {
361     ++oPieMenu.m_iCurrentIndex;
362 
363     BeginPieMenuEx();
364     return true;
365   }
366   ++oPieMenu.m_iCurrentIndex;
367 
368   return false;
369 }
370 
EndPieMenu()371 void EndPieMenu()
372 {
373   IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0
374       && s_oPieMenuContext.m_iCurrentIndex
375           < PieMenuContext::c_iMaxPieItemCount);
376   --s_oPieMenuContext.m_iCurrentIndex;
377 }
378 
PieMenuItem(const char * pName,bool bEnabled)379 bool PieMenuItem(const char *pName, bool bEnabled)
380 {
381   IM_ASSERT(s_oPieMenuContext.m_iCurrentIndex >= 0
382       && s_oPieMenuContext.m_iCurrentIndex
383           < PieMenuContext::c_iMaxPieItemCount);
384 
385   PieMenuContext::PieMenu &oPieMenu =
386       s_oPieMenuContext.m_oPieMenuStack[s_oPieMenuContext.m_iCurrentIndex];
387 
388   ImVec2 oTextSize = ImGui::CalcTextSize(pName, NULL, true);
389   oPieMenu.m_oItemSizes[oPieMenu.m_iCurrentIndex] = oTextSize;
390 
391   float fSqrDiameter = oTextSize.x * oTextSize.x + oTextSize.y * oTextSize.y;
392 
393   if (fSqrDiameter > oPieMenu.m_fMaxItemSqrDiameter) {
394     oPieMenu.m_fMaxItemSqrDiameter = fSqrDiameter;
395   }
396 
397   oPieMenu.m_oItemIsSubMenu[oPieMenu.m_iCurrentIndex] = false;
398 
399   int iLen = strlen(pName);
400   ImVector<char> &oName = oPieMenu.m_oItemNames[oPieMenu.m_iCurrentIndex];
401   oName.resize(iLen + 1);
402   oName[iLen] = '\0';
403   memcpy(oName.Data, pName, iLen);
404 
405   bool bActive = oPieMenu.m_iCurrentIndex == oPieMenu.m_iHoveredItem;
406   ++oPieMenu.m_iCurrentIndex;
407 
408   if (bActive)
409     s_oPieMenuContext.m_bClose = true;
410   return bActive;
411 }
412 
413 #endif // #ifndef IMGUI_DISABLE
414