-- This can either be used as a normal dropdown or a context menu. If the parent is UIParent, it will be a context menu. -- Otherwise, it is a dropdown. This can be changed during runtime simply by calling SetParent through the GUI component. -- The positioning of the dropdown's components are modified to behave like you'd expect. SetWidth actually sets the width. -- SetHeight does nothing, and will always be 25 px. -- Initialization is handled by this component. Use SetOptions to set the options table. -- === New Fields === -- initFunc(self, gui) - Runs on level initialization before the button is added -- dropdownText - The text the dropdown will be set to when this option is clicked, runs before "func" -- closeOnClick - If true, closes all menus regardless of level, runs after "func" -- children - Array for a next level menu; If present, "hasArrow" is automatically set to true -- parent [Automatically Assigned] - Reference to the previous level element, or nil if root -- siblings [Automatically Assigned] - Reference to the array of elements in the level -- gui [Automatically Assigned] - Reference to the GUI instance -- === Adjusted Existing Fields === -- func(self, gui) - Always passes self as the first argument and the GUI instance as the second argument -- arg1, arg2 - Do not use, now internally used to pass self and gui to "func" -- value - Do not use, now internally used to reference self PTGuiDropdown = PTGuiComponent:Extend("dropdown") local _G = getfenv(0) local compost = PTPool PTGuiDropdown.Options = nil function PTGuiDropdown:New() local obj = setmetatable({}, self) local dropdown = CreateFrame("Frame", self:GenerateName(), nil, "UIDropDownMenuTemplate") obj:SetHandle(dropdown) -- Make SetWidth actually set the width, like one would hope local dropdownSetWidth = dropdown.SetWidth dropdownSetWidth(dropdown, 0) -- Allows points to size the dropdown dropdown.SetWidth = function(self, width) _G[dropdown:GetName().."Middle"]:SetWidth(width - 15) dropdownSetWidth(self, width) _G[dropdown:GetName().."Text"]:SetWidth(width - 25) dropdown.noResize = 1 end -- The dropdown doesn't stretch vertically, so the height is always 25 local dropdownSetHeight = dropdown.SetHeight dropdown.SetHeight = function(self, height) dropdownSetHeight(self, 25) end obj:SetShowShadow(true) -- Need to do this on creation to fix the visual position -- Make clicking anywhere on the dropdown open the menu dropdown:EnableMouse(true) dropdown:SetScript("OnMouseUp", function() if MouseIsOver(this) then PTUtil.CallWithThis(_G[this:GetName().."Button"], _G[this:GetName().."Button"]:GetScript("OnClick")) end end) dropdown:SetScript("OnSizeChanged", function() local width = this:GetWidth() _G[this:GetName().."Middle"]:SetWidth(width - 15) dropdownSetWidth(this, width) _G[this:GetName().."Text"]:SetWidth(width - 25) end) return obj end local partProps = { Default = { Left = { Width = 25, Height = 64, Coords = {0, 0.1953125, 0, 1}, Point = {"TOPLEFT", "$parent", "TOPLEFT", -18, 18} }, Middle = { Width = 115, Height = 64, Coords = {0.1953125, 0.8046875, 0, 1}, Point = {"LEFT", "$parentLeft", "RIGHT"} }, Right = { Width = 25, Height = 64, Coords = {0.8046875, 1, 0, 1}, Point = {"LEFT", "$parentMiddle", "RIGHT"} }, Text = { Point = {"RIGHT", "$parentRight", -43, 2} }, Button = { Point = {"TOPRIGHT", "$parentRight", -16, -18} } }, Shadowless = { Left = { Width = 6, Height = 24, Coords = {18 / 128, (18 + 6) / 128, 19 / 64, (19 + 24) / 64}, Point = {"TOPLEFT", 0, -1} }, Middle = { Width = 115, Height = 24, Coords = {0.1953125, 0.8046875, 19 / 64, (19 + 24) / 64}, Point = {"LEFT", "$parentLeft", "RIGHT", 0, 0} }, Right = { Width = 9, Height = 24, Coords = {102 / 128, (102 + 9) / 128, 19 / 64, (19 + 24) / 64}, Point = {"LEFT", "$parentMiddle", "RIGHT", 0, 0} }, Text = { Point = {"RIGHT", "$parentRight", -26, 1} }, Button = { Point = {"TOPRIGHT", "$parentRight", 1, 1} } } } function PTGuiDropdown:SetShowShadow(showShadow) local frameName = self:GetHandle():GetName() for partName, props in pairs(showShadow and partProps.Default or partProps.Shadowless) do local part = _G[frameName..partName] if props.Coords then part:SetTexCoord(unpack(props.Coords)) end if props.Width then part:SetWidth(props.Width) part:SetHeight(props.Height) end part:ClearAllPoints() part:SetPoint(unpack(props.Point)) end self:SetWidth(self:GetWidth()) UIDropDownMenu_SetAnchor(showShadow and 18 or 0, showShadow and 17 or 0, self:GetHandle(), "TOPLEFT", _G[frameName.."Left"], "BOTTOMLEFT") return self end local defaultTextures = { ButtonNormalTexture = "Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up", ButtonDisabledTexture = "Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Disabled", ButtonPushedTexture = "Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Down", ButtonHighlightTexture = "Interface\\Buttons\\UI-Common-MouseHilight" } function PTGuiDropdown:SetParent(obj) local menuType = obj == UIParent and "MENU" or nil if menuType ~= "MENU" and self:GetHandle().displayMode == "MENU" then -- Show the dropdown if we're switching from a context menu to a dropdown -- BROKEN: When put back as a context menu, the button shows forever??? local frameName = self:GetHandle():GetName() _G[frameName.."Left"]:Show() _G[frameName.."Middle"]:Show() _G[frameName.."Right"]:Show() for texName, texPath in pairs(defaultTextures) do _G[frameName..texName]:SetTexture(texPath) end self:SetShowShadow(true) self:GetHandle().displayMode = nil end UIDropDownMenu_Initialize(self:GetHandle(), function(level) self:Initialize(level) end, menuType) self.super.SetParent(self, obj) end function PTGuiDropdown:OnDispose() self:SetToggleState(false) self:SetShowShadow(true) self.super.OnDispose(self) self.Options = nil if self.DynamicOptions then compost:Reclaim(self.DynamicOptions, 1) self.DynamicOptionsArgs = nil end self.TextUpdater = nil self:SetText("") end local dynamicOptions local function addDynamicOption(k1,v1,k2,v2,k3,v3,k4,v4,k5,v5,k6,v6,k7,v7,k8,v8,k9,v9,k10,v10) local option = compost:AcquireHash(k1,v1,k2,v2,k3,v3,k4,v4,k5,v5,k6,v6,k7,v7,k8,v8,k9,v9,k10,v10) table.insert(dynamicOptions, option) end function PTGuiDropdown:Initialize(level) if not self.Options then return end level = level or 1 local options = self.Options if type(options) == "function" then if self.DynamicOptions then compost:Reclaim(self.DynamicOptions, 1) end self.DynamicOptions = compost:GetTable() dynamicOptions = self.DynamicOptions options(addDynamicOption, level, self.DynamicOptionsArgs) options = dynamicOptions self:BakeOptions(options) end local value = UIDROPDOWNMENU_MENU_VALUE options = level == 1 and options or value.children for _, option in ipairs(options) do if option.initFunc then option:initFunc(self) end UIDropDownMenu_AddButton(option, level) end end -- Similar to normal dropdown initialization. Really only should be used for single level options. -- The initFunc receives a function to add options, use in the style of Compost:AcquireHash -- args are passed into the initFunc function PTGuiDropdown:SetDynamicOptions(initFunc, args) self.Options = initFunc self.DynamicOptionsArgs = args return self end function PTGuiDropdown:SetSimpleOptions(options, createFunc, dropdownText) local optTable = {} for _, option in ipairs(options) do table.insert(optTable, createFunc(option)) end self:SetOptions(optTable) if dropdownText then self:SetText(dropdownText) end return self end -- Sets the options for this dropdown. If elements are added afterwards, it is mandatory that this function is called again. function PTGuiDropdown:SetOptions(options) self.Options = options self:BakeOptions(options) return self end function PTGuiDropdown:BakeOptions(options, parent) for _, opt in ipairs(options) do opt.parent = parent opt.siblings = options opt.value = opt opt.arg1 = opt opt.arg2 = self opt.gui = self if opt.dropdownText or opt.closeOnClick then local func = opt.func opt.func = function(self, gui) if self.dropdownText then gui:SetText(self.dropdownText) end if func then func(self, gui) end if self.closeOnClick then gui:SetToggleState(false) end end end if opt.children then opt.hasArrow = true self:BakeOptions(opt.children, opt) end end end function PTGuiDropdown:SetTextBy(field, value, textField) for _, option in ipairs(self.Options) do if option[field] == value then self:SetText(option[textField or "text"]) return self end end return self end function PTGuiDropdown:SetTextUpdater(updater, noImmediateUpdate) self.TextUpdater = updater if not noImmediateUpdate then self:TextUpdater() end return self end function PTGuiDropdown:UpdateText() if self.TextUpdater then self:TextUpdater() end return self end function PTGuiDropdown:SetText(text) UIDropDownMenu_SetText(text, self:GetHandle()) return self end function PTGuiDropdown:GetText() return UIDropDownMenu_GetText(self:GetHandle()) end local dropdownList1 = _G["DropDownList1"] function PTGuiDropdown:SetTimer(time) dropdownList1.showTimer = time end function PTGuiDropdown:StopCounting() dropdownList1.isCounting = nil end local keepOpenFrame = CreateFrame("Frame", "PTGuiDropdownKeepOpen") local keepOpenDropdown local checkNext local keepOpenScript = function() if GetTime() > checkNext then if not keepOpenDropdown:IsToggledOn() then keepOpenFrame:SetScript("OnUpdate", nil) return end checkNext = GetTime() + 1 dropdownList1.showTimer = 10 dropdownList1.isCounting = nil end end -- Keeps the dropdown open until it is forcibly hidden function PTGuiDropdown:SetKeepOpen(keepOpen) if not self:IsToggledOn() then return self end if keepOpen then checkNext = 0 keepOpenDropdown = self keepOpenFrame:SetScript("OnUpdate", keepOpenScript) else keepOpenFrame:SetScript("OnUpdate", nil) end return self end function PTGuiDropdown:IsToggledOn() return (dropdownList1:IsVisible() ~= nil) and (UIDROPDOWNMENU_OPEN_MENU == self:GetName()) end function PTGuiDropdown:SetToggleState(shown, anchor, xOffset, yOffset) if self:IsToggledOn() ~= shown then ToggleDropDownMenu(1, nil, self:GetHandle(), anchor and anchor:GetName() or nil, xOffset, yOffset) end return self end PTGuiLib.RegisterComponent(PTGuiDropdown)