do -- pie menu sub library
local function ImRectAdd(rect, rhs)
local Min, Max = rect.Min, rect.Max
if Min.x > rhs.x then Min.x = rhs.x end
if Min.y > rhs.y then Min.y = rhs.y end
if Max.x < rhs.x then Max.x = rhs.x end
if Max.y < rhs.y then Max.y = rhs.y end
end
local function NewPieMenu()
local obj = {
m_iCurrentIndex = 0,
m_fMaxItemSqrDiameter = 0,
m_iHoveredItem = 0,
m_iLastHoveredItem = 0,
m_iClickedItem = 0,
m_oItemIsSubMenu = {},
m_oItemNames = {},
m_oItemSizes = {}
}
return obj
end
local function NewPieMenuContext()
local obj = {
m_oPieMenuStack = {},
m_iCurrentIndex = -1,
m_iLastFrame = 0,
m_iMaxIndex = 0,
m_oCenter = ImVec2(0, 0),
m_oCenterArg = ImVec2(0, 0),
m_iMouseButton = 0,
m_bClose = false,
}
for i = 0, 8 - 1 do
obj.m_oPieMenuStack[i] = NewPieMenu()
end
return obj
end
local function BeginPieMenuEx(menuCtx)
menuCtx.m_iCurrentIndex = menuCtx.m_iCurrentIndex + 1
menuCtx.m_iMaxIndex = menuCtx.m_iMaxIndex + 1
local oPieMenu = menuCtx.m_oPieMenuStack[menuCtx.m_iCurrentIndex]
oPieMenu.m_iCurrentIndex = 0
oPieMenu.m_fMaxItemSqrDiameter = 0
if not ImGui.IsMouseReleased( menuCtx.m_iMouseButton ) then
oPieMenu.m_iHoveredItem = -1
end
if menuCtx.m_iCurrentIndex > 0 then
oPieMenu.m_fMaxItemSqrDiameter = menuCtx.m_oPieMenuStack[menuCtx.m_iCurrentIndex - 1].m_fMaxItemSqrDiameter
end
end
local function EndPieMenuEx(menuCtx)
menuCtx.m_iCurrentIndex = menuCtx.m_iCurrentIndex - 1
end
local function BeginPiePopup(menuCtx, position)
menuCtx.m_iMouseButton = 0
local iCurrentFrame = ImGui.GetFrameCount()
if menuCtx.m_iLastFrame < (iCurrentFrame - 1) or menuCtx.m_oCenterArg ~= position then
menuCtx.m_oCenter = ImGui.GetWindowPos() + position
menuCtx.m_oCenterArg = position
end
menuCtx.m_iLastFrame = iCurrentFrame
menuCtx.m_iMaxIndex = -1
ImGui.PushFont(ImGuiFonts.Text13)
BeginPieMenuEx(menuCtx)
end
local function EndPiePopup(menuCtx)
EndPieMenuEx(menuCtx)
local oStyle = ImGui.GetStyle()
local pDrawList = ImGui.GetWindowDrawList()
---@diagnostic disable-next-line: undefined-field
pDrawList:PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize())
local oMousePos = ImGui.GetIO().MousePos
local oDragDelta = ImVec2(oMousePos.x - menuCtx.m_oCenter.x, oMousePos.y - menuCtx.m_oCenter.y)
local fDragDistSqr = oDragDelta.x*oDragDelta.x + oDragDelta.y*oDragDelta.y
local fCurrentRadius = 30
---@diagnostic disable-next-line: missing-parameter
local oArea = {Min = ImVec2(menuCtx.m_oCenter), Max = ImVec2(menuCtx.m_oCenter)}
local c_fDefaultRotate = -math.pi / 2
local fLastRotate = c_fDefaultRotate
for iIndex = 0, menuCtx.m_iMaxIndex do
local oPieMenu = menuCtx.m_oPieMenuStack[iIndex]
local fMenuHeight = math.sqrt(oPieMenu.m_fMaxItemSqrDiameter)
local fMinRadius = fCurrentRadius
local fMaxRadius = fMinRadius + (fMenuHeight * oPieMenu.m_iCurrentIndex) / 2
menuCtx.fMaxRadius = fMaxRadius
local item_arc_span = 2 * math.pi / math.max(3 + 3 * iIndex, oPieMenu.m_iCurrentIndex)
local drag_angle = math.atan2(oDragDelta.y, oDragDelta.x)
local fRotate = fLastRotate - item_arc_span * ( oPieMenu.m_iCurrentIndex - 1 ) / 2
local item_hovered = -1
for item_n = 0, oPieMenu.m_iCurrentIndex - 1 do
local item_label = oPieMenu.m_oItemNames[ item_n ]
local fMinInnerSpacing = oStyle.ItemInnerSpacing.x / ( fMinRadius * 2 )
local fMaxInnerSpacing = oStyle.ItemInnerSpacing.x / ( fMaxRadius * 2 )
local item_inner_ang_min = item_arc_span * ( item_n - 0.5 + fMinInnerSpacing ) + fRotate
local item_inner_ang_max = item_arc_span * ( item_n + 0.5 - fMinInnerSpacing ) + fRotate
local item_outer_ang_min = item_arc_span * ( item_n - 0.5 + fMaxInnerSpacing ) + fRotate
local item_outer_ang_max = item_arc_span * ( item_n + 0.5 - fMaxInnerSpacing ) + fRotate
local hovered = false
if fDragDistSqr >= fMinRadius * fMinRadius and fDragDistSqr < fMaxRadius * fMaxRadius then
while (drag_angle - item_inner_ang_min) < 0 do
drag_angle = drag_angle + (2 * math.pi)
end
while (drag_angle - item_inner_ang_min) > 2 * math.pi do
drag_angle = drag_angle - (2 * math.pi)
end
if drag_angle >= item_inner_ang_min and drag_angle < item_inner_ang_max then
hovered = true
end
end
-- draw segments
local arc_segments = math.floor(( 32 * item_arc_span / ( 2 * math.pi ) ) + 1)
local iColor = ImGui.GetColorU32( hovered and ImGui.Col.ButtonHovered or ImGui.Col.Button )
local fAngleStepInner = (item_inner_ang_max - item_inner_ang_min) / arc_segments
local fAngleStepOuter = ( item_outer_ang_max - item_outer_ang_min ) / arc_segments
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimReserve(arc_segments * 6, (arc_segments + 1) * 2)
for iSeg = 0, arc_segments do
local fCosInner = math.cos(item_inner_ang_min + fAngleStepInner * iSeg)
local fSinInner = math.sin(item_inner_ang_min + fAngleStepInner * iSeg)
local fCosOuter = math.cos(item_outer_ang_min + fAngleStepOuter * iSeg)
local fSinOuter = math.sin(item_outer_ang_min + fAngleStepOuter * iSeg)
if iSeg < arc_segments then
---@diagnostic disable-next-line: undefined-field
local VtxCurrentIdx = pDrawList._VtxCurrentIdx
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteIdx(VtxCurrentIdx + 0)
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteIdx(VtxCurrentIdx + 2)
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteIdx(VtxCurrentIdx + 1)
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteIdx(VtxCurrentIdx + 3)
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteIdx(VtxCurrentIdx + 2)
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteIdx(VtxCurrentIdx + 1)
end
local pos = ImVec2(menuCtx.m_oCenter.x + fCosInner * (fMinRadius + oStyle.ItemInnerSpacing.x), menuCtx.m_oCenter.y + fSinInner * (fMinRadius + oStyle.ItemInnerSpacing.x))
local pos2 = ImVec2(menuCtx.m_oCenter.x + fCosOuter * (fMaxRadius - oStyle.ItemInnerSpacing.x), menuCtx.m_oCenter.y + fSinOuter * (fMaxRadius - oStyle.ItemInnerSpacing.x))
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteVtx(pos, ImVec2(0, 0), iColor)
---@diagnostic disable-next-line: undefined-field
pDrawList:PrimWriteVtx(pos2, ImVec2(0, 0), iColor)
end
local fRadCenter = ( item_arc_span * item_n ) + fRotate
local oOuterCenter = ImVec2( menuCtx.m_oCenter.x + math.cos( fRadCenter ) * fMaxRadius, menuCtx.m_oCenter.y + math.sin( fRadCenter ) * fMaxRadius )
ImRectAdd(oArea, oOuterCenter)
if oPieMenu.m_oItemIsSubMenu[item_n] then
---@diagnostic disable-next-line: missing-parameter
local oTrianglePos = {ImVec2(), ImVec2(), ImVec2()}
local fRadLeft = fRadCenter - 5 / fMaxRadius
local fRadRight = fRadCenter + 5 / fMaxRadius
oTrianglePos[ 0+1 ].x = menuCtx.m_oCenter.x + math.cos( fRadCenter ) * ( fMaxRadius - 5 )
oTrianglePos[ 0+1 ].y = menuCtx.m_oCenter.y + math.sin( fRadCenter ) * ( fMaxRadius - 5 )
oTrianglePos[ 1+1 ].x = menuCtx.m_oCenter.x + math.cos( fRadLeft ) * ( fMaxRadius - 10 )
oTrianglePos[ 1+1 ].y = menuCtx.m_oCenter.y + math.sin( fRadLeft ) * ( fMaxRadius - 10 )
oTrianglePos[ 2+1 ].x = menuCtx.m_oCenter.x + math.cos( fRadRight ) * ( fMaxRadius - 10 )
oTrianglePos[ 2+1 ].y = menuCtx.m_oCenter.y + math.sin( fRadRight ) * ( fMaxRadius - 10 )
pDrawList:AddTriangleFilled(oTrianglePos[1], oTrianglePos[2], oTrianglePos[3], 0xFFFFFFFF)
end
---@diagnostic disable-next-line: missing-parameter
local text_size = ImVec2(oPieMenu.m_oItemSizes[item_n])
local text_pos = ImVec2(
menuCtx.m_oCenter.x + math.cos((item_inner_ang_min + item_inner_ang_max) * 0.5) * (fMinRadius + fMaxRadius) * 0.5 - text_size.x * 0.5,
menuCtx.m_oCenter.y + math.sin((item_inner_ang_min + item_inner_ang_max) * 0.5) * (fMinRadius + fMaxRadius) * 0.5 - text_size.y * 0.5)
pDrawList:AddText(text_pos, ImGui.GetColorU32(ImGui.Col.Text), item_label)
if hovered then
item_hovered = item_n
end
end
fCurrentRadius = fMaxRadius
oPieMenu.m_iHoveredItem = item_hovered
if fDragDistSqr >= fMaxRadius * fMaxRadius then
item_hovered = oPieMenu.m_iLastHoveredItem
end
oPieMenu.m_iLastHoveredItem = item_hovered
fLastRotate = item_arc_span * oPieMenu.m_iLastHoveredItem + fRotate
if item_hovered == -1 or not oPieMenu.m_oItemIsSubMenu[item_hovered] then
break
end
end
---@diagnostic disable-next-line: undefined-field
pDrawList:PopClipRect()
if oArea.Min.x < 0 then
menuCtx.m_oCenter.x = ( menuCtx.m_oCenter.x - oArea.Min.x )
end
if oArea.Min.y < 0 then
menuCtx.m_oCenter.y = ( menuCtx.m_oCenter.y - oArea.Min.y )
end
local oDisplaySize = ImGui.GetIO().DisplaySize
if oArea.Max.x > oDisplaySize.x then
menuCtx.m_oCenter.x = ( menuCtx.m_oCenter.x - oArea.Max.x ) + oDisplaySize.x
end
if oArea.Max.y > oDisplaySize.y then
menuCtx.m_oCenter.y = ( menuCtx.m_oCenter.y - oArea.Max.y ) + oDisplaySize.y
end
ImGui.PopFont()
end
local function BeginPieMenu(menuCtx, pName)
local oPieMenu = menuCtx.m_oPieMenuStack[menuCtx.m_iCurrentIndex]
local oTextSize = ImGui.CalcTextSize(pName, nil, true)
oPieMenu.m_oItemSizes[oPieMenu.m_iCurrentIndex] = oTextSize
local fSqrDiameter = oTextSize.x * oTextSize.x + oTextSize.y * oTextSize.y
fSqrDiameter = fSqrDiameter * 0.4
if fSqrDiameter > oPieMenu.m_fMaxItemSqrDiameter then
oPieMenu.m_fMaxItemSqrDiameter = math.min(fSqrDiameter, 3000)
end
oPieMenu.m_oItemIsSubMenu[oPieMenu.m_iCurrentIndex] = true
oPieMenu.m_oItemNames[oPieMenu.m_iCurrentIndex] = pName
if oPieMenu.m_iLastHoveredItem == oPieMenu.m_iCurrentIndex then
oPieMenu.m_iCurrentIndex = oPieMenu.m_iCurrentIndex + 1
BeginPieMenuEx(menuCtx)
return true
end
oPieMenu.m_iCurrentIndex = oPieMenu.m_iCurrentIndex + 1
return false
end
local function EndPieMenu(menuCtx)
menuCtx.m_iCurrentIndex = menuCtx.m_iCurrentIndex - 1
end
local function PieMenuItem(menuCtx, pName)
local oPieMenu = menuCtx.m_oPieMenuStack[menuCtx.m_iCurrentIndex]
local oTextSize = ImGui.CalcTextSize(pName, nil, true)
oPieMenu.m_oItemSizes[oPieMenu.m_iCurrentIndex] = oTextSize
local fSqrDiameter = oTextSize.x * oTextSize.x + oTextSize.y * oTextSize.y
fSqrDiameter = fSqrDiameter * 0.4
if fSqrDiameter > oPieMenu.m_fMaxItemSqrDiameter then
oPieMenu.m_fMaxItemSqrDiameter = math.min(fSqrDiameter, 3000)
end
oPieMenu.m_oItemIsSubMenu[oPieMenu.m_iCurrentIndex] = false
oPieMenu.m_oItemNames[oPieMenu.m_iCurrentIndex] = pName
local bActive = oPieMenu.m_iCurrentIndex == oPieMenu.m_iHoveredItem
oPieMenu.m_iCurrentIndex = oPieMenu.m_iCurrentIndex + 1
return bActive
end
function ImGui.NewPieContext()
local pie_context = NewPieMenuContext()
return {
ctx = pie_context,
Begin = function(position)
return BeginPiePopup(pie_context, position)
end,
End = function()
return EndPiePopup(pie_context)
end,
Item = function(name)
return PieMenuItem(pie_context, name)
end,
BeginFolder = function(name)
return BeginPieMenu(pie_context, name)
end,
EndFolder = function()
return EndPieMenu(pie_context)
end
}
end
ImGui.Pie = ImGui.NewPieContext()
end