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.

875 lines
34 KiB

local MAJOR, MINOR = "LibDropDownExtension-1.0", 3
assert(LibStub, MAJOR .. " requires LibStub")
---@class DropDownList : Button
---@field public dropdown CustomDropDown
---@field public GetID fun(): number
---@field public maxWidth number
---@class LibDropDownExtension
---@field public _callbacks CustomDropDownCallback[]
---@field public _cdropdowns table<DropDownList, CustomDropDown>
---@field public _separatorTable CustomDropDownOption[]
---@field public _hooked table<DropDownList, boolean>
---@field public _Broadcast fun(self: LibDropDownExtension, event: LibDropDownExtensionEvent, dropdown: DropDownList)
---@field public Option table<string, CustomDropDownOption> `LibDropDownExtension.Option.Separator` `LibDropDownExtension.Option.Space`
---@field public RegisterEvent fun(self: LibDropDownExtension, events: string, func: LibDropDownExtensionCallback, levels?: number|boolean, data?: table): boolean `LibDropDownExtension:RegisterEvent(events, func[, levels[, data]])` where func is later called as `func(dropdown, event, options, level, data)` and the return boolean if true will append the options to the dropdown, otherwise false will ignore appending our options to the dropdown.
---@field public UnregisterEvent fun(self: LibDropDownExtension, events: string, func: LibDropDownExtensionCallback, levels?: number|boolean): boolean `LibDropDownExtension:UnregisterEvent(events, func[, levels])`
---@alias LibDropDownExtensionEvent "OnShow"|"OnHide"
---@alias LibDropDownExtensionCallback fun(dropdown: CustomDropDown, event: LibDropDownExtensionEvent, options: CustomDropDownOption[], level: number, data?: table): CustomDropDownOption[]?
---@type LibDropDownExtension?, number?
local Lib, LibPrevMinor = LibStub:NewLibrary(MAJOR, MINOR) ---@diagnostic disable-line: assign-type-mismatch
if not Lib then return end
---@class CustomDropDownOptionIconInfo
---@field public tCoordLeft number
---@field public tCoordRight number
---@field public tCoordTop number
---@field public tCoordBottom number
---@field public tSizeX number
---@field public tSizeY number
---@field public tFitDropDownSizeX boolean
---@field public disablecolor? string
-- copy pasta more or less from UIDropDownMenu.lua about UIDropDownMenu_CreateInfo()
---@class CustomDropDownOption : CustomDropDownOptionIconInfo
---@field public text string @The text of the button
---@field public value any @The value that UIDROPDOWNMENU_MENU_VALUE is set to when the button is clicked
---@field public func fun(button: CustomDropDownButton, arg1: any, arg2: any, checked: boolean) @The function that is called when you click the button. Called as `func(button, option.arg1, option.arg2, option.checkedEval)`
---@field public checked boolean|fun(self: CustomDropDownOption): boolean? @Check the button if true or function returns true
---@field public checkedEval boolean @The final value of the checked state (in case a function and such, use this in your click handler)
---@field public isNotRadio boolean @Check the button uses radial image if false check box image if true
---@field public isTitle boolean @If it's a title the button is disabled and the font color is set to yellow
---@field public disabled boolean @Disable the button and show an invisible button that still traps the mouseover event so menu doesn't time out
---@field public tooltipWhileDisabled boolean @Show the tooltip, even when the button is disabled.
---@field public hasArrow boolean @Show the expand arrow for multilevel menus
---@field public hasColorSwatch boolean @Show color swatch or not, for color selection
---@field public r number @Red color value of the color swatch [1-255]
---@field public g number @Green color value of the color swatch [1-255]
---@field public b number @Blue color value of the color swatch [1-255]
---@field public colorCode string @"|cAARRGGBB" embedded hex value of the button text color. Only used when button is enabled
---@field public swatchFunc function @Function called by the color picker on color change
---@field public hasOpacity boolean @Show the opacity slider on the colorpicker frame
---@field public opacity number @Percentatge of the opacity, 1.0 is fully shown, 0 is transparent [0.0-1.0]
---@field public opacityFunc function @Function called by the opacity slider when you change its value
---@field public cancelFunc function @Function called by the colorpicker when you click the cancel button (it takes the previous values as its argument)
---@field public notClickable boolean @Disable the button and color the font white
---@field public notCheckable boolean @Shrink the size of the buttons and don't display a check box
---@field public owner table @Dropdown frame that "owns" the current dropdownlist
---@field public keepShownOnClick boolean @Don't hide the dropdownlist after a button is clicked
---@field public tooltipTitle string @Title of the tooltip shown on mouseover
---@field public tooltipText string @Text of the tooltip shown on mouseover
---@field public tooltipOnButton boolean @Show the tooltip attached to the button instead of as a Newbie tooltip.
---@field public noTooltipWhileEnabled boolean
---@field public justifyH string @Justify button text like "LEFT", "CENTER", "RIGHT"
---@field public arg1 any @This is the first argument used by .func
---@field public arg2 any @This is the second argument used by .func
---@field public fontObject table @font object replacement for Normal and Highlight
---@field public menuTable table @This contains an array of info tables to be displayed as a child menu
---@field public noClickSound boolean @Set to 1 to suppress the sound when clicking the button. The sound only plays if .func is set.
---@field public padding number @Number of pixels to pad the text on the right side
---@field public leftPadding number @Number of pixels to pad the button on the left side
---@field public minWidth number @Minimum width for this line
---@field public customFrame table @Allows this button to be a completely custom frame, should inherit from UIDropDownCustomMenuEntryTemplate and override appropriate methods.
---@field public icon string|number @An icon for the button.
---@field public mouseOverIcon string|number @An override icon when a button is moused over.
---@field public dist number
---@field public isUninteractable boolean
---@field public iconOnly boolean
---@field public iconInfo CustomDropDownOptionIconInfo
---@field public customCheckIconAtlas string
---@field public customCheckIconTexture string|number
---@field public customUncheckIconAtlas string
---@field public customUncheckIconTexture string|number
---@field public show? fun()
---@class CustomDropDownCallback
---@field public events table<LibDropDownExtensionEvent, number|boolean>
---@field public func LibDropDownExtensionCallback
---@field public options CustomDropDownOption[]
---@field public data table
Lib._callbacks = Lib._callbacks or {}
local callbacks = Lib._callbacks
---@class CustomDropDownButtonIconTexture : Texture
---@field public tFitDropDownSizeX? number
---@class CustomDropDownButton : Button
---@field public option CustomDropDownOption
---@field public order number
---@field public invisibleButton Button
---@field public highlight Texture
---@field public normalText FontString
---@field public iconTexture CustomDropDownButtonIconTexture
---@field public expandArrow Button
---@field public check Texture
---@field public uncheck Texture
---@field public colorSwatch Button
---@field public colorSwatchNormalTexture Texture
---@field public mouseOverIcon string?
---@field public tooltipOnButton boolean?
---@field public tooltipTitle string?
---@field public tooltipWhileDisabled boolean?
---@field public tooltipInstruction string?
---@field public tooltipText string?
---@field public tooltipWarning string?
---@field public colorSwatchBg Texture?
---@class CustomDropDown : DropDownList
---@field public options CustomDropDownOption[]
---@field public buttons CustomDropDownButton[]
Lib._cdropdowns = Lib._cdropdowns or {}
local cdropdowns = Lib._cdropdowns
---@param self CustomDropDownButton
local function CustomDropDownButton_OnClick(self)
local option = self.option
if not option then
return
end
local cdropdown = self:GetParent() ---@type Frame
---@diagnostic disable-next-line: assign-type-mismatch
local checked = option.checked ---@type boolean
if type(checked) == "function" then
checked = checked(self)
end
if option.keepShownOnClick and not option.notCheckable then
if checked then
checked = false
self.check:Hide()
self.uncheck:Show()
else
checked = true
self.check:Show()
self.uncheck:Hide()
end
end
if type(option.checkedEval) ~= "function" then
option.checkedEval = checked
end
if type(option.func) == "function" then
option.func(self, option.arg1, option.arg2, checked)
end
if not option.noClickSound then
PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON)
end
if not option.keepShownOnClick then
cdropdown:GetParent():Hide() -- CloseDropDownMenus()
end
end
---@param self CustomDropDownButton
local function CustomDropDownButton_OnEnter(self)
local option = self.option
if not option then
return
end
---@diagnostic disable-next-line: assign-type-mismatch
local cdropdown = self:GetParent() ---@type CustomDropDown
if option.hasArrow then
-- open next dropdown level and we don't support that
else
CloseDropDownMenus(cdropdown:GetID() + 1)
end
self.highlight:Show()
local shownTooltip
if self.normalText:IsTruncated() then
if not shownTooltip then
shownTooltip = true
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip_SetTitle(GameTooltip, self.normalText:GetText())
else
GameTooltip:AddLine(self.normalText:GetText(), 1, 1, 1, false)
end
GameTooltip:Show()
end
if option.tooltipTitle and not option.noTooltipWhileEnabled then
if option.tooltipOnButton then
if not shownTooltip then
shownTooltip = true
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip_SetTitle(GameTooltip, option.tooltipTitle)
else
GameTooltip:AddLine(option.tooltipTitle, 1, 1, 1, false)
end
if option.tooltipText then
GameTooltip_AddNormalLine(GameTooltip, option.tooltipText, true)
end
GameTooltip:Show()
end
end
if option.mouseOverIcon then
self.iconTexture:SetTexture(self.mouseOverIcon)
self.iconTexture:Show()
end
end
---@param self CustomDropDownButton
local function CustomDropDownButton_OnLeave(self)
self.highlight:Hide()
GameTooltip:Hide()
local option = self.option
if option.mouseOverIcon then
if option.icon then
self.iconTexture:SetTexture(option.icon)
else
self.iconTexture:Hide()
end
end
end
---@param self CustomDropDownButton
local function CustomDropDownButton_OnEnable(self)
self.invisibleButton:Hide()
end
---@param self CustomDropDownButton
local function CustomDropDownButton_OnDisable(self)
self.invisibleButton:Show()
end
local function CustomDropDownButton_InvisibleButton_OnEnter(self)
local button = self:GetParent() ---@type CustomDropDownButton
---@diagnostic disable-next-line: assign-type-mismatch
local cdropdown = button:GetParent() ---@type CustomDropDown
CloseDropDownMenus(cdropdown:GetID() + 1)
if not button.tooltipOnButton or (not button.tooltipTitle and not button.tooltipWhileDisabled) then
return
end
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip_SetTitle(GameTooltip, button.tooltipTitle)
if button.tooltipInstruction then
GameTooltip_AddInstructionLine(GameTooltip, button.tooltipInstruction)
end
if button.tooltipText then
GameTooltip_AddNormalLine(GameTooltip, button.tooltipText, true)
end
if button.tooltipWarning then
GameTooltip_AddColoredLine(GameTooltip, button.tooltipWarning, RED_FONT_COLOR, true)
end
GameTooltip:Show()
end
local function CustomDropDownButton_InvisibleButton_OnLeave(self)
GameTooltip:Hide()
end
local function CustomDropDownButton_ColorSwatch_OnClick(self)
local button = self:GetParent() ---@type CustomDropDownButton
CloseMenus()
UIDropDownMenuButton_OpenColorPicker(button)
end
local function CustomDropDownButton_ColorSwatch_OnEnter(self)
local button = self:GetParent() ---@type CustomDropDownButton
---@diagnostic disable-next-line: assign-type-mismatch
local cdropdown = button:GetParent() ---@type CustomDropDown
CloseDropDownMenus(cdropdown:GetID() + 1)
button.colorSwatchBg:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
end
local function CustomDropDownButton_ColorSwatch_OnLeave(self)
local button = self:GetParent() ---@type CustomDropDownButton
button.colorSwatchBg:SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
end
---@param cdropdown CustomDropDown
---@param button CustomDropDownButton?
---@return CustomDropDownButton button
local function NewCustomDropDownButton(cdropdown, button)
local index = #cdropdown.buttons + 1
---@diagnostic disable-next-line: cast-local-type
button = button or CreateFrame("Button", cdropdown:GetName() .. "Button" .. index, cdropdown, "UIDropDownMenuButtonTemplate")
button.order = nil
button.option = nil
button:SetID(index)
button:SetFrameLevel(cdropdown:GetFrameLevel() + 2)
button:SetScript("OnClick", CustomDropDownButton_OnClick)
button:SetScript("OnEnter", CustomDropDownButton_OnEnter)
button:SetScript("OnLeave", CustomDropDownButton_OnLeave)
button:SetScript("OnEnable", CustomDropDownButton_OnEnable)
button:SetScript("OnDisable", CustomDropDownButton_OnDisable)
local buttonName = button:GetName()
button.invisibleButton = _G[buttonName .. "InvisibleButton"]
button.invisibleButton:SetScript("OnEnter", CustomDropDownButton_InvisibleButton_OnEnter)
button.invisibleButton:SetScript("OnLeave", CustomDropDownButton_InvisibleButton_OnLeave)
button.highlight = _G[buttonName .. "Highlight"]
button.normalText = _G[buttonName .. "NormalText"]
button.normalText:ClearAllPoints()
button.normalText:SetPoint("LEFT")
button.normalText:SetPoint("RIGHT")
button.normalText:SetWordWrap(false) ---@diagnostic disable-line: redundant-parameter
button.normalText:SetNonSpaceWrap(false)
button.iconTexture = _G[buttonName .. "Icon"]
button.expandArrow = _G[buttonName .. "ExpandArrow"]
button.expandArrow:SetScript("OnMouseDown", nil) ---@diagnostic disable-line: param-type-mismatch
button.expandArrow:SetScript("OnEnter", nil) ---@diagnostic disable-line: param-type-mismatch
button.check = _G[buttonName .. "Check"]
button.uncheck = _G[buttonName .. "UnCheck"]
button.colorSwatch = _G[buttonName .. "ColorSwatch"]
button.colorSwatchBg = _G[buttonName .. "ColorSwatchSwatchBg"]
button.colorSwatchNormalTexture = _G[buttonName .. "ColorSwatchNormalTexture"]
button.colorSwatch:SetScript("OnClick", CustomDropDownButton_ColorSwatch_OnClick)
button.colorSwatch:SetScript("OnEnter", CustomDropDownButton_ColorSwatch_OnEnter)
button.colorSwatch:SetScript("OnLeave", CustomDropDownButton_ColorSwatch_OnLeave)
---@diagnostic disable-next-line: return-type-mismatch
return button
end
---@param cdropdown CustomDropDown
local function CustomDropDown_OnShow(cdropdown)
---@type DropDownList
local parent = cdropdown:GetParent() ---@diagnostic disable-line: assign-type-mismatch
local maxWidth = parent.maxWidth
local width, height = parent:GetWidth(), 32
for i = 1, #cdropdown.buttons do
local button = cdropdown.buttons[i]
if button:IsShown() then
button:SetWidth(maxWidth)
height = height + button:GetHeight()
end
end
cdropdown:SetHeight(height)
end
---@param widget Region
local function Hide(widget)
widget:SetAlpha(0)
widget:Hide()
widget.Show = widget.Hide
end
---@param dropdown DropDownList
local function NewCustomDropDown(dropdown)
---@type CustomDropDown
local cdropdown = CreateFrame("Button", "LibDropDownExtensionCustomDropDown_" .. tostring(dropdown), dropdown, "UIDropDownListTemplate") ---@diagnostic disable-line: assign-type-mismatch
cdropdown:SetID(dropdown:GetID())
cdropdown.options = {}
cdropdown.buttons = {}
do
local cdropdownName = cdropdown:GetName()
Hide(_G[cdropdownName .. "Backdrop"])
Hide(_G[cdropdownName .. "MenuBackdrop"])
cdropdown:SetFrameStrata(dropdown:GetFrameStrata())
cdropdown:SetFrameLevel(dropdown:GetFrameLevel() + 1)
cdropdown:SetScript("OnClick", nil) ---@diagnostic disable-line: param-type-mismatch
cdropdown:SetScript("OnUpdate", nil) ---@diagnostic disable-line: param-type-mismatch
cdropdown:SetScript("OnShow", CustomDropDown_OnShow)
cdropdown:SetScript("OnHide", nil) ---@diagnostic disable-line: param-type-mismatch
for i = 1, UIDROPDOWNMENU_MAXBUTTONS do
---@type CustomDropDownButton
local button = _G[cdropdown:GetName() .. "Button" .. i]
if not button then
break
end
button = NewCustomDropDownButton(cdropdown, button)
cdropdown.buttons[i] = button
end
end
return cdropdown
end
---@param a CustomDropDownButton
---@param b CustomDropDownButton
local function SortDropDownButtons(a, b)
return a.order < b.order
end
---@param cdropdown CustomDropDown
local function ClearDropDown(cdropdown)
for i = 1, #cdropdown.buttons do
local button = cdropdown.buttons[i]
button.option = nil
end
table.wipe(cdropdown.options)
end
---@param cdropdown CustomDropDown
---@param options CustomDropDownOption[]
---@param orderOffset number
local function AppendDropDown(cdropdown, options, orderOffset)
---@type table<CustomDropDownButton, boolean?>
local available = {}
for i = 1, #cdropdown.buttons do
local button = cdropdown.buttons[i]
if not button.option then
available[button] = true
end
end
for i = 1, #options do
local option = options[i]
---@type CustomDropDownButton
local button = next(available)
if not button then
button = NewCustomDropDownButton(cdropdown)
cdropdown.buttons[#cdropdown.buttons + 1] = button
else
available[button] = nil
end
button.order = orderOffset + i
button.option = option
cdropdown.options[#cdropdown.options + 1] = option
end
end
---@param button CustomDropDownButton
local function RefreshButton(button)
local option = button.option
local icon = button.iconTexture
local invisibleButton = button.invisibleButton
button:SetDisabledFontObject(GameFontDisableSmallLeft)
button:Enable()
invisibleButton:Hide()
if option.notClickable then
option.disabled = true
button:SetDisabledFontObject(GameFontHighlightSmallLeft)
end
if option.isTitle then
option.disabled = true
button:SetDisabledFontObject(GameFontNormalSmallLeft)
end
if option.disabled then
button:Disable()
invisibleButton:Show()
option.colorCode = nil
end
if option.disablecolor then
option.colorCode = option.disablecolor
end
if option.text then
if option.colorCode then
button:SetText(option.colorCode .. option.text .. "|r")
else
button:SetText(option.text)
end
if option.icon or option.mouseOverIcon then
icon:ClearAllPoints()
icon:SetPoint("RIGHT")
icon:SetSize(16, 16)
icon:SetTexture(option.icon or option.mouseOverIcon)
if option.tCoordLeft then
icon:SetTexCoord(option.tCoordLeft, option.tCoordRight, option.tCoordTop, option.tCoordBottom)
else
icon:SetTexCoord(0, 1, 0, 1)
end
icon:Show()
else
icon:Hide()
end
if option.fontObject then
button:SetNormalFontObject(option.fontObject)
button:SetHighlightFontObject(option.fontObject)
else
button:SetNormalFontObject(GameFontHighlightSmallLeft)
button:SetHighlightFontObject(GameFontHighlightSmallLeft)
end
else
button:SetText("")
icon:Hide()
end
if option.iconInfo then
icon.tFitDropDownSizeX = option.iconInfo.tFitDropDownSizeX
else
icon.tFitDropDownSizeX = nil
end
if option.iconOnly and option.icon then
icon:ClearAllPoints()
icon:SetPoint("LEFT")
icon:SetWidth(option.iconInfo and option.iconInfo.tSizeX or 16)
icon:SetHeight(option.iconInfo and option.iconInfo.tSizeY or 16)
icon:SetTexture(option.icon)
if option.iconInfo and option.iconInfo.tCoordLeft then
icon:SetTexCoord(option.iconInfo.tCoordLeft, option.iconInfo.tCoordRight, option.iconInfo.tCoordTop, option.iconInfo.tCoordBottom)
else
icon:SetTexCoord(0, 1, 0, 1)
end
icon:Show()
end
local expandArrow = button.expandArrow
expandArrow:SetShown(option.hasArrow)
expandArrow:SetEnabled(not option.disabled)
if option.iconOnly then
icon:SetPoint("LEFT")
icon:SetPoint("RIGHT", -5, 0)
end
--[=[
local xPos = 5
local displayInfo = button.normalText ---@type FontString|Texture
if option.iconOnly then
displayInfo = icon
end
displayInfo:ClearAllPoints()
if option.notCheckable then
if option.justifyH and option.justifyH == "CENTER" then
displayInfo:SetPoint("CENTER", button, "CENTER", -7, 0)
else
displayInfo:SetPoint("LEFT", button, "LEFT", 0, 0)
end
xPos = xPos + 10
else
displayInfo:SetPoint("LEFT", button, "LEFT", 20, 0)
xPos = xPos + 12
end
local frame = UIDROPDOWNMENU_OPEN_MENU
if frame and frame.displayMode == "MENU" then
if not option.notCheckable then
xPos = xPos - 6
end
end
frame = frame or UIDROPDOWNMENU_INIT_MENU
if option.leftPadding then
xPos = xPos + option.leftPadding
end
displayInfo:SetPoint("TOPLEFT", button, "TOPLEFT", xPos, 0)
--]=]
if not option.notCheckable then
local check = button.check
local uncheck = button.uncheck
if option.disabled then
check:SetDesaturated(true)
check:SetAlpha(0.5)
uncheck:SetDesaturated(true)
uncheck:SetAlpha(0.5)
else
check:SetDesaturated(false)
check:SetAlpha(1)
uncheck:SetDesaturated(false)
uncheck:SetAlpha(1)
end
if option.customCheckIconAtlas or option.customCheckIconTexture then
check:SetTexCoord(0, 1, 0, 1)
uncheck:SetTexCoord(0, 1, 0, 1)
if option.customCheckIconAtlas then
check:SetAtlas(option.customCheckIconAtlas)
uncheck:SetAtlas(option.customUncheckIconAtlas or option.customCheckIconAtlas)
else
check:SetTexture(option.customCheckIconTexture)
uncheck:SetTexture(option.customUncheckIconTexture or option.customCheckIconTexture)
end
elseif option.isNotRadio then
check:SetTexCoord(0, 0.5, 0, 0.5)
check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks")
uncheck:SetTexCoord(0.5, 1, 0, 0.5)
uncheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks")
else
check:SetTexCoord(0, 0.5, 0.5, 1)
check:SetTexture("Interface\\Common\\UI-DropDownRadioChecks")
uncheck:SetTexCoord(0.5, 1, 0.5, 1)
uncheck:SetTexture("Interface\\Common\\UI-DropDownRadioChecks")
end
---@diagnostic disable-next-line: assign-type-mismatch
local checked = option.checked ---@type boolean
if type(checked) == "function" then
checked = checked(button)
end
if checked then
button:LockHighlight()
check:Show()
uncheck:Hide()
else
button:UnlockHighlight()
check:Hide()
uncheck:Show()
end
else
button.check:Hide()
button.uncheck:Hide()
end
local colorSwatch = button.colorSwatch
if option.hasColorSwatch then
button.colorSwatchNormalTexture:SetVertexColor(option.r, option.g, option.b)
colorSwatch:Show()
else
colorSwatch:Hide()
end
end
---@param cdropdown CustomDropDown
local function RefreshButtons(cdropdown)
local lastButton ---@type CustomDropDownButton?
for i = 1, #cdropdown.buttons do
local button = cdropdown.buttons[i]
if not button.option then
button.order = 1000000
end
end
table.sort(cdropdown.buttons, SortDropDownButtons)
for i = 1, #cdropdown.buttons do
local button = cdropdown.buttons[i]
if button.option then
button:ClearAllPoints()
if lastButton then
button:SetPoint("TOPLEFT", lastButton, "BOTTOMLEFT", 0, 0)
else
button:SetPoint("TOPLEFT", cdropdown, "TOPLEFT", 15, -17)
end
RefreshButton(button)
button:Show()
lastButton = button
else
button:Hide()
end
end
local numOptions = #cdropdown.options
if numOptions > 0 then
---@diagnostic disable-next-line: assign-type-mismatch
local parent = cdropdown:GetParent() ---@type Frame
parent:SetHeight(parent:GetHeight() + 16 * numOptions)
cdropdown:ClearAllPoints()
cdropdown:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 0, 0)
cdropdown:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
cdropdown:Show()
else
cdropdown:Hide()
end
end
---@param dropdown DropDownList
local function GetCustomDropDown(dropdown)
local cdropdown = cdropdowns[dropdown]
if not cdropdown then
cdropdown = NewCustomDropDown(dropdown)
cdropdowns[dropdown] = cdropdown
end
return cdropdown
end
---@param option CustomDropDownOption
local function IsOptionValid(option)
return type(option) == "table"
end
---@param options1 CustomDropDownOption[]
---@param options2 CustomDropDownOption[]
local function CopyOptions(options1, options2)
table.wipe(options2)
local index = 0
for i = 1, #options1 do
local option = options1[i]
if IsOptionValid(option) then
index = index + 1
options2[index] = option
end
end
end
---@param options CustomDropDownOption[]
local function RemoveInvalidOptions(options)
for i = #options, 1, -1 do
local option = options[i]
if not IsOptionValid(option) then
table.remove(options, i)
end
end
end
---@param event LibDropDownExtensionEvent
---@param dropdown DropDownList
function Lib:_Broadcast(event, dropdown)
local level = dropdown:GetID()
local cdropdown = GetCustomDropDown(dropdown)
local shownSeparator
ClearDropDown(cdropdown)
for i = 1, #callbacks do
local callback = callbacks[i]
local callbackLevel = callback.events[event]
if callbackLevel == true or callbackLevel == level then
local status, retval = pcall(callback.func, dropdown.dropdown, event, callback.options, level, callback.data)
if status and retval then
if not shownSeparator and callback.options[1] then
shownSeparator = true
AppendDropDown(cdropdown, Lib._separatorTable, 0)
end
if type(retval) == "table" and retval ~= callback.options then
CopyOptions(retval, callback.options)
else
RemoveInvalidOptions(callback.options)
end
AppendDropDown(cdropdown, callback.options, i * 100)
end
end
end
RefreshButtons(cdropdown)
end
---@param func LibDropDownExtensionCallback
---@return CustomDropDownCallback?, number?
local function GetCallbackForFunc(func)
for i = 1, #callbacks do
local callback = callbacks[i]
if callback.func == func then
return callback, i
end
end
end
---@param self DropDownList
local function DropDown_OnShow(self)
Lib:_Broadcast("OnShow", self)
end
---@param self DropDownList
local function DropDown_OnHide(self)
Lib:_Broadcast("OnHide", self)
end
Lib._hooked = Lib._hooked or {}
for i = 1, 3 do
local dropDownList = _G[format("DropDownList%d", i)] ---@type DropDownList?
if dropDownList and not Lib._hooked[dropDownList] then
Lib._hooked[dropDownList] = true
dropDownList:HookScript("OnShow", DropDown_OnShow)
dropDownList:HookScript("OnHide", DropDown_OnHide)
end
end
Lib.Option = Lib.Option or {}
Lib.Option.Separator = Lib.Option.Separator or {
hasArrow = false,
dist = 0,
isTitle = true,
isUninteractable = true,
notCheckable = true,
iconOnly = true,
icon = "Interface\\Common\\UI-TooltipDivider-Transparent",
tCoordLeft = 0,
tCoordRight = 1,
tCoordTop = 0,
tCoordBottom = 1,
tSizeX = 0,
tSizeY = 8,
tFitDropDownSizeX = true,
iconInfo = {
tCoordLeft = 0,
tCoordRight = 1,
tCoordTop = 0,
tCoordBottom = 1,
tSizeX = 0,
tSizeY = 8,
tFitDropDownSizeX = true
}
}
Lib.Option.Space = Lib.Option.Space or {
hasArrow = false,
dist = 0,
isTitle = true,
isUninteractable = true,
notCheckable = true
}
Lib._separatorTable = Lib._separatorTable or { Lib.Option.Separator }
---@param events string
---@param func LibDropDownExtensionCallback
---@param levels (number|boolean)?
---@param data table?
---@return boolean success
function Lib:RegisterEvent(events, func, levels, data)
assert(type(events) == "string" and type(func) == "function", "LibDropDownExtension:RegisterEvent(events, func[, levels][, data]) requires events to be a string and func a function. levels is an optional number 1, 2 or nil for any level.")
local callback = GetCallbackForFunc(func)
for _, event in ipairs({strsplit(" ", events)}) do
if not callback then
---@type CustomDropDownCallback
callback = {
events = {},
func = func,
options = {},
data = type(data) == "table" and data or {},
}
callbacks[#callbacks + 1] = callback
end
callback.events[event] = levels or true
end
return callback ~= nil
end
---@param events string
---@param func LibDropDownExtensionCallback
---@param levels (number|boolean)?
---@return boolean success
function Lib:UnregisterEvent(events, func, levels)
assert(type(events) == "string" and type(func) == "function", "LibDropDownExtension:UnregisterEvent(events, func) requires events to be a string and func a function.")
local callback, index = GetCallbackForFunc(func)
if not callback then
return false
end
for _, event in ipairs({strsplit(" ", events)}) do
callback.events[event] = levels
end
if not next(callback.events) then
table.remove(callbacks, index)
end
return true
end
-- DEBUG:
--[[
print(..., MAJOR, LibPrevMinor, "->", MINOR)
Lib:RegisterEvent("OnShow", function(dropdown, event, options, level, data)
if event == "OnShow" then
options[1] = { text = "A1 | Level " .. level .. " | Random " .. data.test .. " | A1", tooltipOnButton = true, tooltipTitle = "Custom Tooltip Title", tooltipText = "Custom Tooltip Text" }
options[2] = { text = "A2 | Level " .. level .. " | Random " .. data.test .. " | A2" }
options[3] = { text = "A3 | Level " .. level .. " | Random " .. data.test .. " | A3" }
return true
else
wipe(options)
end
end, true, { test = random(100000000, 999999999) })
Lib:RegisterEvent("OnShow", function(dropdown, event, options, level, data)
if event == "OnShow" then
options[1] = { text = "B1 | Level " .. level .. " | Random " .. data.test .. " | B1" }
options[2] = { text = "B2 | Level " .. level .. " | Random " .. data.test .. " | B2" }
options[3] = { text = "B3 | Level " .. level .. " | Random " .. data.test .. " | B3" }
return true
else
wipe(options)
end
end, true, { test = random(100000000, 999999999) })
Lib:RegisterEvent("OnShow", function(dropdown, event, options, level, data)
if event == "OnShow" then
options[1] = { text = "C1 | Level " .. level .. " | Random " .. data.test .. " | C1" }
options[2] = { text = "C2 | Level " .. level .. " | Random " .. data.test .. " | C2" }
options[3] = { text = "C3 | Level " .. level .. " | Random " .. data.test .. " | C3" }
return true
else
wipe(options)
end
end, true, { test = random(100000000, 999999999) })
--]]