--[[ Changelog --------- ### Rev 04 ### - Dropdowns can now overwrite the label in their init and selectvalue funcs. - Empty menus now show "No items". ### Rev 05 ### - A menu entry now supports a .tip key, and will show a tooltip if it's a string. - The SelectValueFunc() now has a third parameter, the menu item index that was clicked. - The checkmark texture is now the green one used for readychecks in the raid tab. - Will now obey the .checked key if set, not only when it's true. - The autoselect feature of the last selected value, will not just select everything when nil. - The InitSelectedItem() function will not ignore an initialisation with nil anymore. ### Rev 06 ### - The "menu.list" table now has a meta table which will automatically create a table or take one from storage. - If tables are creates through the new metatable index method, it will recycle old tables from storage. ### Rev 07 ### - The menu will now hide itself, if any of it's parents was hidden. ### Rev 09 ### - DropDown Text Object can no longer be wider than the width of the DropDown itself. ### Rev 10 - 7.0.3/Legion ### - Fixed issue related to frame level, making the dropdown appear behind other frames. Thanks vincentSDSH. ### Rev 11 - 7.3 ### - Fixed the PlaySound() issue ### Rev 12 - 8.0/BfA ### - Disabled wordwrap for the labels - Rewrote some parts of the code - Added some documentation - The dropdown menu will now copy the backdrop from the parent frame Keys set in the parent frame table ---------------------------------- initFunc function function(self,list) that is run before the dropdown menu is displayed selectValueFunc function function(self,entry,index) that is called when an item is selected isAutoSelect boolean when true, the parent frame will have its text set to the selected item (default true) isAutoClose boolean if false, the dropdown menu is not closed when selecting an item (default true) minDropdownWidth number the minimum allowed width of the dropdown menu maxShownItems number the maximum items shown at once in the dropdown menu before enabling the scrollbar (default 16) Valid keys for a dropdown menu item (filled in during "initFunc") ----------------------------------------------------------------- text The text that is displayed in the dropdown menu value Value of the item, is passed on to the "selectValueFunc" header If true, the item appears as a header item, and cannot be selected checked Item appears selected by having a checkmark next to it tip Tooltip will be shown when mouse is over item --]] local REVISION = 12; if (type(AzDropDown) == "table") and (AzDropDown.vers >= REVISION) then return; end AzDropDown = AzDropDown or {}; AzDropDown.vers = REVISION; local menu; -- only one dropdown menu exists globally that is shared across all created dropdowns local storage = {}; -- storage for empty tables that will be recycled for the "initFunc" list parameter local backdrop = { bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 10, insets = { left = 2, right = 2, top = 2, bottom = 2 }, backdropColor = CreateColor(0.1,0.1,0.1,1), backdropBorderColor = CreateColor(0.4,0.4,0.4,1), }; --[[ local backdrop = { bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 1, right = 1, top = 1, bottom = 1 }, backdropColor = CreateColor(0.06,0.132,0.21,0.7), backdropBorderColor = CreateColor(0.7,0.7,0.8,0), };--]] AzDropDown.backdrop = backdrop; -- Used to copy backdrop colors from parent local backdropColor = CreateColor(0,0,0); local backdropBorderColor = CreateColor(0,0,0); -- Constants local MENU_ITEM_HEIGHT = 14; local DEF_MAX_MENU_ITEMS = 16; -------------------------------------------------------------------------------------------------------- -- Helper Functions -- -------------------------------------------------------------------------------------------------------- -- Used for both the DropDownMenuMixin & DropDownFrameMixin local function ApplyBackdrop(self,backdrop,backdropColor,backdropBorderColor) if (not backdropColor) then backdropColor = backdrop and backdrop.backdropColor or AzDropDown.backdrop.backdropColor; end if (not backdropBorderColor) then backdropBorderColor = backdrop and backdrop.backdropBorderColor or AzDropDown.backdrop.backdropBorderColor; end if (not backdrop) then backdrop = AzDropDown.backdrop; end self:SetBackdrop(backdrop); self:SetBackdropColor(backdropColor:GetRGBA()); self:SetBackdropBorderColor(backdropBorderColor:GetRGBA()); end -------------------------------------------------------------------------------------------------------- -- DropDown Menu Mixin -- -------------------------------------------------------------------------------------------------------- local DropDownMenuMixin = { ApplyBackdrop = ApplyBackdrop }; -- Calls the parent's "initFunc" to query the items function DropDownMenuMixin:QueryItems(parent) -- Clear old list for _, tbl in ipairs(self.list) do wipe(tbl); storage[#storage + 1] = tbl; end wipe(self.list); -- Init new list parent:initFunc(self.list); -- Show "No items" for empty lists & Update List if (#self.list == 0) then self.list[1].text = "No items"; self.list[1].header = true; end end -- Update the items used in the dropdown menu function DropDownMenuMixin:UpdateItems() local parent = self.parent; local maxItems = self:GetMaxShownItems(); -- Query updated items from the parent's "initFunc" self:QueryItems(parent); -- Update Scroll Items self.scroll:UpdateScroll(); -- Measure Width local maxItemWidth = parent.minDropdownWidth or 0; for _, tbl in ipairs(self.list) do self.measure:SetText(tbl.text); maxItemWidth = max(maxItemWidth,self.measure:GetWidth() + 10 + 38); end -- Adjust anchor point based on scrollbar being visible or not if (#self.list > maxItems) then maxItemWidth = (maxItemWidth + 12); self.items[1]:SetPoint("TOPRIGHT",-28,-8); else self.items[1]:SetPoint("TOPRIGHT",-16,-8); end -- Set width/height self:SetWidth(maxItemWidth); self:SetHeight(min(#self.list,maxItems) * MENU_ITEM_HEIGHT + 16); -- Anchor scrollbar bottom to the last item local lastItem = self.items[min(#self.items,maxItems)]; self.scroll:SetPoint("BOTTOMRIGHT",lastItem); end -- Initializes the menu to show for the given parent function DropDownMenuMixin:Initialize(parent,point,parentPoint) if (not parent) then return; end -- Set DropDown Parent self.parent = parent; self:SetParent(parent); -- Anchor to Parent self:ClearAllPoints(); self:SetPoint(point or "TOPLEFT",parent,parentPoint or "BOTTOMLEFT"); self:SetFrameLevel(parent:GetFrameLevel() + 4); -- Copy Backdrop from parent, or use default local backdrop = parent:GetBackdrop(); if (backdrop) then backdropColor:SetRGB(parent:GetBackdropColor()); backdropBorderColor:SetRGBA(parent:GetBackdropBorderColor()); self:ApplyBackdrop(backdrop,backdropColor,backdropBorderColor); else self:ApplyBackdrop(AzDropDown.backdrop); end -- updates the menu items self:UpdateItems(); end function DropDownMenuMixin:GetMaxShownItems() return self.parent.maxShownItems or DEF_MAX_MENU_ITEMS; end -------------------------------------------------------------------------------------------------------- -- Private DropDown Menu Functions -- -------------------------------------------------------------------------------------------------------- -- MenuItem OnClick local function MenuItem_OnClick(self,button) local entry = menu.list[self.index]; local parent = menu.parent; -- select the value if (parent.isAutoSelect) then parent:SetText(entry.text); parent.selectedValue = entry.value; end parent:selectValueFunc(entry,self.index); -- hide or update the menu items if (parent.isAutoClose == nil) or (parent.isAutoClose) then menu:Hide(); else menu:UpdateItems(); end end -- MenuItem OnEnter local function MenuItem_OnEnter(self) local entry = menu.list[self.index]; if (type(entry.tip) == "string") then GameTooltip_SetDefaultAnchor(GameTooltip,self); GameTooltip:AddLine(entry.text,1,1,1); GameTooltip:AddLine(entry.tip,nil,nil,nil,1); GameTooltip:Show(); end end -- HideGTT local function HideGTT() GameTooltip:Hide(); end -- Create Dropdown Menu Item Button local function CreateMenuItem() local item = CreateFrame("Button",nil,menu,"BackdropTemplate"); item:SetHeight(MENU_ITEM_HEIGHT); item:SetHitRectInsets(-12,-10,0,0); item:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight"); item:SetScript("OnClick",MenuItem_OnClick); item:SetScript("OnEnter",MenuItem_OnEnter); item:SetScript("OnLeave",HideGTT); item.text = item:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); item.text:SetPoint("LEFT",2,0); item.check = item:CreateTexture(nil,"ARTWORK"); item.check:SetTexture(READY_CHECK_READY_TEXTURE); item.check:SetSize(14,14); item.check:SetPoint("RIGHT",item,"LEFT"); item.check:Hide(); if (#menu.items == 0) then item:SetPoint("TOPLEFT",20,-8); menu.scroll:SetPoint("TOPLEFT",item); else item:SetPoint("TOPLEFT",menu.items[#menu.items],"BOTTOMLEFT"); item:SetPoint("TOPRIGHT",menu.items[#menu.items],"BOTTOMRIGHT"); end menu.items[#menu.items + 1] = item; return item; end -- Updates the visible dropdown menu items in scroll range local function UpdateScroll(self) local maxItems = menu:GetMaxShownItems(); local shownItems = min(#menu.list,maxItems); FauxScrollFrame_Update(self,#menu.list,maxItems,MENU_ITEM_HEIGHT); local index = self.offset; local lastItem = 0; for i = 1, shownItems do lastItem = i; index = (index + 1); local item = menu.items[i] or CreateMenuItem(); local entry = menu.list[index]; item.index = index; item.text:SetText(entry.text); item.text:SetTextColor(1,entry.header and 0.82 or 1,entry.header and 0 or 1); (entry.header and item.Disable or item.Enable)(item); local checkState = (entry.checked) or (entry.checked == nil and menu.parent.isAutoSelect and entry.value ~= nil and entry.value == menu.parent.selectedValue); item.check:SetShown(checkState); item:Show(); end -- hide the rest for i = lastItem + 1, #menu.items do menu.items[i]:Hide(); end end -- Creates the DropDown menu with item buttons and scrollbar local function CreateDropDownMenu() menu = CreateFrame("Frame",nil,nil,"BackdropTemplate"); menu:SetToplevel(true); menu:SetClampedToScreen(true); menu:SetFrameStrata("FULLSCREEN_DIALOG"); menu:SetScript("OnHide",function(self) if (self:IsShown()) then self:Hide(); end end); -- hides the menu if parent is hidden menu:Hide(); menu.scroll = CreateFrame("ScrollFrame","AzDropDownScroll"..REVISION,menu,"FauxScrollFrameTemplate"); menu.scroll:SetScript("OnVerticalScroll",function(self,offset) FauxScrollFrame_OnVerticalScroll(self,offset,MENU_ITEM_HEIGHT,self.UpdateScroll); end); menu.scroll.UpdateScroll = UpdateScroll; -- fontString used to measure text width menu.measure = menu:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); menu.measure:Hide(); menu.items = {}; menu.list = setmetatable({},{ __index = function(t,k) t[k] = #storage > 0 and tremove(storage,#storage) or {}; return t[k]; end }); Mixin(menu,DropDownMenuMixin); end -------------------------------------------------------------------------------------------------------- -- Public DropDown Menu Functions -- -------------------------------------------------------------------------------------------------------- -- Hides the menu function AzDropDown:HideMenu() if (menu) then menu:Hide(); end end -- Toggles menu visibility for the given parent -- Menu hides, if already showing for given parent, otherwise it shows for the new parent function AzDropDown:ToggleMenu(parent,point,parentPoint) if (not parent) then return; end if (not menu) then CreateDropDownMenu(); end PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); -- "igMainMenuOptionCheckBoxOn" if (menu:IsShown()) and (menu.parent == parent) then menu:Hide(); else menu:Initialize(parent,point,parentPoint); menu:Show(); end end -------------------------------------------------------------------------------------------------------- -- DropDown Frame Mixin -- -------------------------------------------------------------------------------------------------------- local DropDownFrameMixin = { ApplyBackdrop = ApplyBackdrop }; -- Sets the text of the label, but can be called on the dropdown frame function DropDownFrameMixin:SetText(text) self.label:SetText(text); end -- Init Selected DropDown Item function DropDownFrameMixin:InitSelectedItem(selectedValue) if (not menu) then CreateDropDownMenu(); end self:SetText("|cff00ff00Select Value..."); -- set before calling QueryItems() as that may override it self.selectedValue = selectedValue; menu:QueryItems(self); for _, entry in ipairs(menu.list) do if (entry.value == selectedValue) then self:SetText(entry.text); return; end end end -------------------------------------------------------------------------------------------------------- -- DropDown Frame Widget -- -------------------------------------------------------------------------------------------------------- -- DropDown OnClick local function DropDown_OnClick(self,button) AzDropDown:ToggleMenu(self:GetParent(),"TOPRIGHT","BOTTOMRIGHT"); end -- Creates a dropdown frame, which shows the selected value and handles clicking to summon the dropdown menu -- If "width" is set to a negative value, then "minDropdownWidth" is set to the absolute value of "width" function AzDropDown:CreateDropDown(parent,width,initFunc,selectValueFunc,isAutoSelect,isAutoClose,maxShownItems,minDropdownWidth) if (not parent or not width) then return; end local dd = CreateFrame("Frame",nil,parent,"BackdropTemplate"); dd:SetSize(abs(width),24); dd.button = CreateFrame("Button",nil,dd); dd.button:SetPoint("TOPRIGHT"); dd.button:SetPoint("BOTTOMRIGHT"); dd.button:SetWidth(24); dd.button:SetHitRectInsets(dd.button:GetWidth() - dd:GetWidth(),0,0,0); dd.button:SetScript("OnClick",DropDown_OnClick); dd.button:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up"); dd.button:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down"); dd.button:SetDisabledTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Disabled"); dd.button:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight"); dd.label = dd:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); dd.label:SetPoint("RIGHT",dd.button,"LEFT",-2,0); dd.label:SetPoint("LEFT",dd,"LEFT",6,0); dd.label:SetJustifyH("RIGHT"); dd.label:SetWordWrap(false); Mixin(dd,DropDownFrameMixin); dd:ApplyBackdrop(AzDropDown.backdrop); -- dropdown display variables dd.initFunc = initFunc; dd.selectValueFunc = selectValueFunc; dd.isAutoSelect = (isAutoSelect == nil and true) or (isAutoSelect); -- default nil value to true dd.isAutoClose = isAutoClose; dd.maxShownItems = maxShownItems; if (minDropdownWidth) then dd.minDropdownWidth = minDropdownWidth; elseif (width < 0) then dd.minDropdownWidth = abs(width); end return dd; end