You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bastion/libs/StdUi/widgets/ContextMenu.lua

265 lines
6.7 KiB

--- @type StdUi
local StdUi = LibStub and LibStub('StdUi', true);
if not StdUi then
return
end
local module, version = 'ContextMenu', 3;
if not StdUi:UpgradeNeeded(module, version) then
return
end
--- ContextMenuItem Events
local ContextMenuItemOnEnter = function(itemFrame, button)
itemFrame.parentContext:CloseSubMenus();
itemFrame.childContext:ClearAllPoints();
itemFrame.childContext:SetPoint('TOPLEFT', itemFrame, 'TOPRIGHT', 0, 0);
itemFrame.childContext:Show();
end
local ContextMenuItemOnMouseUp = function(itemFrame, button)
local hide
if button == 'LeftButton' and itemFrame.contextMenuData.callback then
hide = itemFrame.contextMenuData.callback(itemFrame, itemFrame.parentContext)
elseif button == 'RightButton' then
hide = true
end
if hide == true and itemFrame.mainContext then
itemFrame.mainContext:Hide()
end
end
--- ContextMenuEvents
local ContextMenuOnMouseUp = function(self, button)
if button == 'RightButton' then
local uiScale = UIParent:GetScale();
local cursorX, cursorY = GetCursorPosition();
cursorX = cursorX / uiScale;
cursorY = cursorY / uiScale;
self:ClearAllPoints();
if self:IsShown() then
self:Hide();
else
self:SetPoint('TOPLEFT', nil, 'BOTTOMLEFT', cursorX, cursorY);
self:Show();
end
end
end
---@type ContextMenu
StdUi.ContextMenuMethods = {
CloseMenu = function(self)
self:CloseSubMenus();
self:Hide();
end,
CloseSubMenus = function(self)
for i = 1, #self.optionFrames do
local optionFrame = self.optionFrames[i];
if optionFrame.childContext then
optionFrame.childContext:CloseMenu();
end
end
end,
HookRightClick = function(self)
local parent = self:GetParent();
if parent then
-- ContextMenuOnMouseUp requires a reference to this menu (self)
local menu = self -- don't trust magic variable names
parent:HookScript('OnMouseUp', function (_, button) ContextMenuOnMouseUp(menu, button) end);
end
end,
HookChildrenClick = function(self)
end,
CreateItem = function(parent, data, i)
local itemFrame;
if data.constructor and type(data.constructor) == 'function' then
itemFrame = data.constructor(parent, data, i);
elseif data.title then
itemFrame = parent.stdUi:Frame(parent, nil, 20);
itemFrame.text = parent.stdUi:Label(itemFrame);
parent.stdUi:GlueLeft(itemFrame.text, itemFrame, 0, 0, true);
elseif data.isSeparator then
itemFrame = parent.stdUi:Frame(parent, nil, 20);
itemFrame.texture = parent.stdUi:Texture(itemFrame, nil, 8,
[[Interface\COMMON\UI-TooltipDivider-Transparent]]);
itemFrame.texture:SetPoint('CENTER');
itemFrame.texture:SetPoint('LEFT');
itemFrame.texture:SetPoint('RIGHT');
elseif data.checkbox then
itemFrame = parent.stdUi:Checkbox(parent, '');
elseif data.radio then
itemFrame = parent.stdUi:Radio(parent, '', data.radioGroup);
elseif data.text then
itemFrame = parent.stdUi:HighlightButton(parent, nil, 20);
end
itemFrame.contextMenuData = data;
-- Need mainContext on all items for right click compatibility.
-- This will also keep propagating mainContext thru all children.
-- Note: In the top-most level of items frames, the parent does NOT have a
-- mainContext, and in that case the parent itself IS the mainContext.
itemFrame.mainContext = parent.mainContext or parent
if not data.isSeparator then
itemFrame.text:SetJustifyH('LEFT');
end
if not data.isSeparator and data.children then
itemFrame.icon = parent.stdUi:Texture(itemFrame, 10, 10, [[Interface\Buttons\SquareButtonTextures]]);
itemFrame.icon:SetTexCoord(0.42187500, 0.23437500, 0.01562500, 0.20312500);
parent.stdUi:GlueRight(itemFrame.icon, itemFrame, -4, 0, true);
itemFrame.childContext = parent.stdUi:ContextMenu(parent, data.children, true, parent.level + 1);
itemFrame.parentContext = parent;
itemFrame:HookScript('OnEnter', ContextMenuItemOnEnter);
end
if data.events then
for eventName, eventHandler in pairs(data.events) do
itemFrame:SetScript(eventName, eventHandler);
end
end
-- Always need Right click capability in item frames to close the menu
if data.hookOnClickIndicator then
itemFrame:HookScript('OnClick', ContextMenuItemOnMouseUp)
else
itemFrame:SetScript('OnMouseUp', ContextMenuItemOnMouseUp)
end
if data.custom then
for key, value in pairs(data.custom) do
itemFrame[key] = value;
end
end
return itemFrame;
end,
UpdateItem = function(parent, itemFrame, data, i)
local padding = parent.padding;
if data.renderer and type(data.renderer) == 'function' then
data.renderer(parent, itemFrame, data, i);
elseif data.title then
itemFrame.text:SetText(data.title);
parent.stdUi:ButtonAutoWidth(itemFrame);
elseif data.checkbox or data.radio then
itemFrame.text:SetText(data.checkbox or data.radio);
itemFrame:AutoWidth();
if data.value then
itemFrame:SetValue(data.value);
end
elseif data.text then
itemFrame:SetText(data.text);
parent.stdUi:ButtonAutoWidth(itemFrame);
end
if data.children then
-- add arrow size
itemFrame:SetWidth(itemFrame:GetWidth() + 16);
end
if (parent:GetWidth() - padding * 2) < itemFrame:GetWidth() then
parent:SetWidth(itemFrame:GetWidth() + padding * 2);
end
itemFrame:SetPoint('LEFT', padding, 0);
itemFrame:SetPoint('RIGHT', -padding, 0);
if data.color and not data.isSeparator then
itemFrame.text:SetTextColor(unpack(data.color));
end
end,
DrawOptions = function(self, options)
if not self.optionFrames then
self.optionFrames = {};
end
local _, totalHeight = self.stdUi:ObjectList(
self,
self.optionFrames,
self.CreateItem,
self.UpdateItem,
options,
0,
self.padding,
-self.padding
);
self:SetHeight(totalHeight + self.padding);
end,
StartHideCounter = function(self)
if self.timer then
self.timer:Cancel();
end
self.timer = C_Timer:NewTimer(3, self.TimerCallback);
end,
StopHideCounter = function()
end
};
StdUi.ContextMenuEvents = {
OnEnter = function(self)
end,
OnLeave = function(self)
end
};
function StdUi:ContextMenu(parent, options, stopHook, level)
---@class ContextMenu
local panel = self:Panel(parent);
panel.stdUi = self;
panel.level = level or 1;
panel.padding = 16;
panel:SetFrameStrata('FULLSCREEN_DIALOG');
-- force context menus to stay on the screen where they can be used
panel:SetClampedToScreen(true)
for k, v in pairs(self.ContextMenuMethods) do
panel[k] = v;
end
for k, v in pairs(self.ContextMenuEvents) do
panel:SetScript(k, v);
end
panel:DrawOptions(options);
if panel.level == 1 then
-- self reference for children
panel.mainContext = panel;
if not stopHook then
panel:HookRightClick();
end
end
panel:Hide();
return panel;
end
StdUi:RegisterModule(module, version);