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