--[[ DropMenu.lua @Author : DengSir (tdaddon@163.com) @Link : https://dengsir.github.io ]] local MAJOR, MINOR = 'DropMenu', 2 local GUI = LibStub('tdGUI-1.0') local DropMenu, oldminor, ns = GUI:NewClass(MAJOR, MINOR, GUI:GetClass('GridView')) if not DropMenu then return end ns._Objects = ns._Objects or {} local function check(value, data, owner) if type(value) == 'function' then return value(data, owner) end return value end function DropMenu:Constructor(_, style, withOtherMenu, menuList, level) self.menuList = menuList or setmetatable({}, {__index = function(t, i) local menu = DropMenu:New(UIParent, style, withOtherMenu, self.menuList, i) t[i] = menu return menu end}) level = level or 1 if style == 'MENU' then self:SetPadding(15) self:SetBackdrop{ bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], edgeSize = 16, tileSize = 16, tile = true, insets = {left = 4, right = 4, top = 4, bottom = 4}, } self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) else self:SetPadding(18, 18, 15, 15) self:SetBackdrop{ bgFile = [[Interface\DialogFrame\UI-DialogBox-Background-Dark]], edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]], insets = { left = 11, right = 12, top = 12, bottom = 9 }, tileSize = 32, edgeSize = 32, tile = true, } self.scrollBar:ClearAllPoints() self.scrollBar:SetPoint('TOPRIGHT', -10, -26) self.scrollBar:SetPoint('BOTTOMRIGHT', -10, 26) end self:Hide() self:SetSize(100, 100) self:SetItemClass(GUI:GetClass('DropMenuItem')) self:SetItemHeight(16) self:SetAutoSize(true) self:SetClampedToScreen(true) self:SetFrameStrata('FULLSCREEN_DIALOG') self:EnableMouse(true) self:SetLevel(level) self:SetCallback('OnItemFormatting', self.OnItemFormatting) self:SetCallback('OnItemClick', self.OnItemClick) self:SetCallback('OnItemEnter', self.OnItemEnter) self:SetCallback('OnItemLeave', self.OnItemLeave) self:SetScript('OnHide', self.OnHide) self.withOtherMenu = withOtherMenu if level == 1 then tinsert(ns._Objects, self) self.menuList[1] = self local AutoHide = GUI:GetClass('AutoHideController'):New(self) do AutoHide.IsMenuOver = function() for _, menu in ipairs(self.menuList) do if menu:IsMouseOver() then return true end end end end if not withOtherMenu then AutoHide:SetCallback('OnUpdateCheck', function() return DropDownList1:IsVisible() end) self:SetScript('OnShow', self.CloseOtherMenu) end self.AutoHide = AutoHide else self:SetCallback('OnRefresh', self.OnRefresh) self:SetFrameLevel(self.menuList[level-1]:GetFrameLevel()+5) end end function DropMenu:Open(level, menuTable, owner, ...) if type(menuTable) == 'function' then local list = {} menuTable = menuTable(list) or list end if type(menuTable) ~= 'table' then return self:Close() end for i = #menuTable, 1, -1 do local v = menuTable[i] if check(v.hidden, v, owner) then tremove(menuTable, i) end end level = level or 1 for i = level + 1, #self.menuList do self.menuList[i]:Hide() end if not self.withOtherMenu then CloseDropDownMenus(1) end local maxItem, hasMaxItem if select('#', ...) > 0 then local arg1 = ... if type(arg1) == 'number' or type(arg1) == 'nil' then maxItem = tonumber(arg1) hasMaxItem = true end end local menu = self.menuList[level] menu.maxItem = maxItem menu:SetRowCount(maxItem) menu:SetScrollStep(max((maxItem or 0)-1, 1)) if menu:GetItemList() ~= menuTable then menu:SetOffset(1) end menu:SetItemList(menuTable) menu:SetOwner(owner) menu:ClearAllPoints() menu:SetPoint(self:GetOpenPosition(owner, select(hasMaxItem and 2 or 1, ...))) menu:Show() menu:Refresh() menu:SetScale(menu:GetParent() and 1 or GetCVarBool('useUIScale') and min(UIParent:GetScale(), tonumber(GetCVar('uiscale'))) or UIParent:GetScale()) end function DropMenu:Toggle(level, menuTable, owner, ...) level = level or 1 local menu = self.menuList[level] if menu:IsShown() and menu:GetOwner() == owner then menu:Hide() return end menu:Hide() self:Open(level, menuTable, owner, ...) end function DropMenu:RefreshMenu(level) local menu = self.menuList[level or 1] if menu then menu:Refresh() end end function DropMenu:Close(level) level = level or 1 self.menuList[level]:Hide() end function DropMenu:CloseOtherMenu() for _, object in ipairs(ns._Objects) do if object ~= self then object:Close(1) end end end function DropMenu:OnRefresh() local prevMenu = self.menuList[self:GetLevel() - 1] local anchor = select(2, self:GetPoint()) self:ClearAllPoints() if self:GetWidth() > GetScreenWidth() - prevMenu:GetRight() then self:SetPoint('TOPRIGHT', anchor, 'TOPLEFT', 0, 10) else self:SetPoint('TOPLEFT', anchor, 'TOPRIGHT', 0, 10) end end function DropMenu:OnItemClick(button, data) if data.notClickable then return end if not data.keepShownOnClick then self:Hide() end local owner = self:GetOwner() if type(data.func) == 'function' then -- if data.confirm then -- if data.confirmInput then -- GUI:CallInputDialog(data.confirm, data.func, data.confirmKey, data.confirmDefault, data.confirmMaxBytes, data.confirmInput, owner, data) -- else -- GUI:CallMessageDialog(data.confirm, data.func, data.confirmKey, owner, data) -- end -- else data.func(owner, data, self, button:GetChecked()) -- end end if owner then if type(owner.SetItem) == 'function' then owner:SetItem(data) end end if data.keepShownOnClick then self:Refresh() end if data.refreshParentOnClick and self:GetLevel() > 1 then self:RefreshMenu(self:GetLevel() - 1) end end function DropMenu:OnItemFormatting(button, data) if data.isSeparator then button:Disable() button:SetText() button:SetCheckState() button:SetHasArrow() button:SetSeparator(true) else button:SetText(check(data.text, self:GetOwner())) button:SetCheckState(data.checkable, data.isNotRadio, check(data.checked, data, self:GetOwner())) button:SetHasArrow(data.hasArrow) button:SetSeparator() button:SetFontObject(check(data.fontObject, data, self:GetOwner())) if check(data.disabled, data, self:GetOwner()) then button:Disable() button:SetDisabledFontObject('GameFontDisableSmallLeft') elseif data.isTitle then button:Disable() button:SetDisabledFontObject('GameFontNormalSmallLeft') else button:Enable() end end button:SetWidth(button:GetAutoWidth()) end function DropMenu:OnItemEnter(button, data) if not data.hasArrow then self:Close(self:GetLevel() + 1) else self:Open(self:GetLevel() + 1, data.menuTable, self:GetOwner(), self.maxItem, 'TOPLEFT', button, 'TOPRIGHT', 0, 10) end local tip = GameTooltip if (data.tooltipTitle or data.tooltipText or data.tooltipMore) and (data.tooltipWhileDisabled or button:IsEnabled()) then if data.tooltipOnButton then tip:SetOwner(button, button:GetLeft() < GetScreenWidth() / 2 and 'ANCHOR_RIGHT' or 'ANCHOR_LEFT') else GameTooltip_SetDefaultAnchor(tip, button) end if data.tooltipTitle then tip:SetText(data.tooltipTitle, 1, 1, 1) end if data.tooltipText then tip:AddLine(data.tooltipText, nil, nil, nil, true) end if type(data.tooltipMore) == 'function' then data.tooltipMore(tip, data) end tip:Show() end end function DropMenu:OnItemLeave() GameTooltip:Hide() end function DropMenu:OnHide() self:Close(self:GetLevel() + 1) end function DropMenu:GetOpenPosition(owner, point, ...) if point == 'cursor' then local x, y = GetCursorPosition() local uiScale = owner:GetEffectiveScale() x = x / uiScale - owner:GetLeft() + 20 y = y / uiScale - owner:GetTop() - 2 return 'TOPLEFT', owner, 'TOPLEFT', x, y elseif point then return point, ... elseif owner:GetBottom() >= GetScreenHeight() - owner:GetTop() then return 'TOPLEFT', owner, 'BOTTOMLEFT' else return 'BOTTOMLEFT', owner, 'TOPLEFT' end end function DropMenu:SetLevel(level) self.level = level end function DropMenu:GetLevel() return self.level end ---- Public if ns.Public then ns.Public:Hide() ns.Public:SetParent(nil) tDeleteItem(ns._Objects, ns.Public) end ns.Public = GUI:GetClass('DropMenu'):New(nil, 'MENU') function GUI:OpenMenu(owner, menuTable, ...) return ns.Public:Open(1, menuTable, owner, ...) end function GUI:ToggleMenu(owner, menuTable, ...) return ns.Public:Toggle(1, menuTable, owner, ...) end function GUI:CloseMenu() return ns.Public:Close(1) end function GUI:RefreshMenu(level) return ns.Public:RefreshMenu(level) end