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.

487 lines
15 KiB

--[[ Menu:header
Documentation for the [Menu](Menu) object.
Created with [LibDropDown:NewMenu()](LibDropDown#libdropdownnewmenuparent-name).
--]]
local lib = LibStub('LibDropDown')
local function OnShow(self)
-- gather some size data
local padding = self.parent.padding
local spacing = self.parent.spacing
local minWidth = self.parent.minWidth
local maxWidth = self.parent.maxWidth
local width = 0
local height = -spacing
for _, Line in next, self.lines do
if(Line:IsShown()) then
local lineWidth
if(Line.Text:IsShown()) then
lineWidth = Line.Text:GetWidth()
else
lineWidth = Line:GetTextWidth()
end
if(Line.Radio:IsShown()) then
lineWidth = lineWidth + Line.Radio:GetWidth() + padding
end
lineWidth = math.max(lineWidth, minWidth)
if(maxWidth) then
lineWidth = math.min(lineWidth, maxWidth)
end
if(lineWidth > width) then
width = lineWidth
end
height = height + (16 + spacing)
end
end
-- make sure all lines are the same width
for _, Line in next, self.lines do
Line:SetWidth(width)
end
-- resize the entire frame
self:SetSize(width, height)
self.Backdrop:SetSize(width + padding * 2, height + padding * 2)
-- positioning
self:ClearAllPoints()
if(self.parent == self) then
-- this is the first menu
if(self.parent.anchorCursor) then
local x, y = GetCursorPosition()
local scale = UIParent:GetScale()
self:SetPoint('TOP', UIParent, 'BOTTOMLEFT', x / scale, (y / scale) + 8) -- 8 to let the cursor end up on the first line
else
self:SetPoint(unpack(self.parent.anchor))
end
else
-- submenu
self:SetPoint('TOPLEFT', self:GetParent(), 'TOPRIGHT', self.parent.gap, 0)
end
end
local menuMixin = {}
--[[ Menu:Toggle()
Toggles the dropdown menu, closing all others (see [LibDropDown:CloseAll()](LibDropDown#libdropdowncloseall)).
--]]
function menuMixin:Toggle()
-- hide everything first
lib:CloseAll(self)
-- toggle this
self:SetShown(not self:IsShown())
end
local dummyFontInstance = CreateFont('LibDropDownDummyFontObject')
--[[ Menu:UpdateLine(_index, data_)
Update a line with the given index with the supplied data.
* `index`: menu line index _(integer)_
* `data`: line data _(table)_ (see [Menu:AddLine(_data_)](Menu#menuaddlinedata))
--]]
function menuMixin:UpdateLine(index, data)
local Line = self.lines[index]
if(not Line) then
Line = lib:CreateLine(self)
Line:SetPoint('TOPLEFT', 0, -((16 + self.parent.spacing) * (index - 1)))
table.insert(self.lines, Line)
end
Line:Reset()
Line.func = data.func
Line.args = data.args
Line.tooltip = data.tooltip
Line.tooltipTitle = data.tooltipTitle
Line.keepShown = data.keepShown
local fontName, fontSize, fontFlags
if(data.font) then
fontName = data.font
fontSize = data.fontSize or 12
fontFlags = data.fontFlags
elseif(data.disabled) then
dummyFontInstance:SetFontObject(data.fontObjectDisabled or self.parent.disabledFont)
fontName, fontSize, fontFlags = dummyFontInstance:GetFont()
else
dummyFontInstance:SetFontObject(data.fontObjectDisabled or self.parent.disabledFont)
fontName, fontSize, fontFlags = dummyFontInstance:GetFont()
end
if(data.isSpacer) then
Line.Spacer:Show()
Line:EnableMouse(false)
elseif(data.isTitle) then
local text = data.text
assert(text and type(text) == 'string', 'Missing required data "text"')
Line.Text:SetText(text)
Line:EnableMouse(false)
Line:SetNormalFontObject(self.parent.titleFont)
else
Line:EnableMouse(true)
local text = data.text
assert(text and type(text) == 'string', 'Missing required data "text"')
Line.Text:SetFont(fontName, fontSize, fontFlags)
Line.Text:SetText(text)
Line:SetTexture(data.texture, data.textureColor)
if(data.icon) then
local width = data.iconWidth or 16
local height = data.iconHeight or 16
local fileWidth = data.iconFileWidth or width
local fileHeight = data.iconFileHeight or height
local left, right, top, bottom = 0, 1, 0, 1
if(data.iconTexCoords) then
left, right, top, bottom = unpack(data.iconTexCoords)
end
Line:SetIcon(data.icon, fileWidth, fileHeight, width, height, left, right, top, bottom)
end
if(data.atlas) then
local atlas = C_Texture.GetAtlasInfo(data.atlas)
assert(atlas and (atlas.filename or atlas.file), 'No atlas \'' .. data.atlas .. '\' exists')
local width = data.atlasWidth
local height = data.atlasHeight
if(not height) then
-- default to line height
height = 16
end
if(not width) then
-- keeping aspect ratio of the atlas
width = (atlas.width / atlas.height) * height
end
local x = data.atlasOffsetX or data.atlasOffset or 0
local y = data.atlasOffsetY or data.atlasOffset or 0
Line:SetAtlas(data.atlas, height, width, x, y)
end
if(data.disabled) then
Line:Disable()
Line:SetMotionScriptsWhileDisabled(not not data.forceMotion)
else
Line:Enable()
end
if(data.menu) then
Line.Expand:Show()
else
if(data.isColorPicker) then
local r, g, b, a = data.colorR, data.colorG, data.colorB, data.colorOpacity
local callback = data.colorPickerCallback
assert(r and type(r) == 'number', 'Missing required data "colorR"')
assert(g and type(g) == 'number', 'Missing required data "colorG"')
assert(b and type(b) == 'number', 'Missing required data "colorB"')
assert(callback and type(callback) == 'function', 'Missing required data "colorPickerCallback"')
if(not Line.colors) then
Line.colors = CreateColor(r, g, b, a)
else
Line.colors:SetRGBA(r, g, b, a)
end
Line.colorPickerCallback = callback
Line.ColorSwatch.Swatch:SetVertexColor(r, g, b, a or 1)
Line.ColorSwatch:Show()
else
if(data.checked ~= nil) then
Line.Radio:Show()
if(type(data.checked) == 'function') then
Line.checked = data.checked
Line.isRadio = data.isRadio
else
if(data.isRadio) then
Line:SetRadioState(not not data.checked)
else
Line:SetCheckedState(not not data.checked)
end
end
if self.parent.checkButtonAlignment == 'LEFT' then
Line.Radio:ClearAllPoints()
Line.Radio:SetPoint('LEFT', Line)
Line.Text:ClearAllPoints()
Line.Text:SetPoint('LEFT', Line.Radio, 'RIGHT')
else
Line.Radio:ClearAllPoints()
Line.Radio:SetPoint('RIGHT')
Line.Text:ClearAllPoints()
Line.Text:SetPoint('LEFT')
end
end
end
end
-- TODO: slider
-- TODO: editbox
-- TODO: scrolling
end
Line:Show()
return Line
end
--[[ Menu:AddLines(_..._)
See [Menu:AddLine(_data_)](Menu#menuaddlinedata), this one does the exact same thing, except
this one can add more than one line at a time.
* `...`: One or more tables containing line information.
--]]
function menuMixin:AddLines(...)
for index = 1, select('#', ...) do
self:AddLine(select(index, ...))
end
end
--[[ Menu:AddLine(_data_)
Adds a line using the given data to the menu menu.
Everything™ is optional, some are exclusive with others.
* `data`:
* `text`: Text to show on the line _(string)_
* `isTitle`: Turns the `text` into a title _(boolean)_
* `isSpacer`: Turns the line into a spacer _(boolean)_
* `func`: Function to execute when clicking the line _(function)_
Arguments passed: `button`, `args` (unpacked).
* `keepShown`: Keeps the dropdown shown after clicking the line _(boolean)_
* `args`: Table of arguments to pass through to the click function _(table)_
* `tooltip`: Tooltip contents _(string)_
* `tooltipTitle`: Tooltip title _(string)_
* `tooltipWhileDisabled`: Enable tooltips while disabled _(boolean)_
* `checked`: Show or hide a checkbox _(boolean/function)_
* `isRadio`: Turns the checkbox into a radio button _(boolean)_
* `isColorPicker`: Adds a color picker to the line _(boolean)_
* `colorR`: Red color channel, 0-1 _(number)_
* `colorG`: Green color channel, 0-1 _(number)_
* `colorB`: Blue color channel, 0-1 _(number)_
* `colorOpacity`: Alpha channel, 0-1 _(number)_
* `colorPickerCallback`: Callback function for the color picker _(function)_
Arguments passed: `color`, see [SharedXML\Util.lua's ColorMixin](https://www.townlong-yak.com/framexml/live/go/ColorMixin).
* `icon`: Texture path for the icon to embed into the start of `text` _(string)_
* `iconTexCoords`: Texture coordinates for cropping the `icon` _(array)_
* `iconWidth`: Width of the displayed `icon` _(number)_
* `iconHeight`: Height of the displayed `icon` _(number)_
* `iconFileWidth`: File width of the `icon` _(number)_
* `iconFileHeight`: File height of the `icon` _(number)_
* `atlas`: Atlas to embed into the start of `text` _(string)_
* `atlasWidth`: Width of the displayed `atlas` _(number)_
* `atlasHeight`: Height of the displayed `atlas` _(number)_
* `atlasOffsetX`: Horizontal offset for `atlas` _(number)_
* `atlasOffsetY`: Vertical offset for `atlas` _(number)_
* `atlasOffset`: Common offset for both axis for `atlas` _(number)_
* `disabled`: Disables the whole line _(boolean)_
* `texture`: Sets background texture that spans the line _(string)_
* `textureColor`: Sets the color of the background texture _([ColorMixin object](https://www.townlong-yak.com/framexml/live/go/ColorMixin))_
* `font`: Font to use for the line _(string)_
* `fontSize`: Font size to use for the line, requires `font` to be set _(number)_
* `fontFlags`: Font flags to use for the line, requires `font` to be set _(string)_
* `fontObject`: Font object to use for the line _(string/[FontInstance](http://wowprogramming.com/docs/widgets/FontInstance))_
* `menu`: Sub-menu for the current menu line _(array)_
This needs to contain one or more tables of `data` (all of the above) in an
indexed array. Can be chained.
#### Notes
The following are exclusive options, only one can be used at a time:
* `isSpacer`
* `isTitle`
* `menu`
* `isColorPicker`
* `checked`
* `font`
* `fontObject`
--]]
function menuMixin:AddLine(data)
if(not self.data) then
self.data = {}
end
table.insert(self.data, data)
local Line = self:UpdateLine(#self.data, data)
if(data.menu) then
local Menu = lib:NewMenu(Line)
Menu:AddLines(unpack(data.menu))
if(#data.menu == 0) then
error('Sub-menu created but no entries found.')
end
table.insert(self.menus, Menu)
Line.Menu = Menu
else
Line.Menu = nil
end
end
--[[ Menu:RemoveLine(_index_)
Removes a specific line by index.
- `index`: Number between 1 and [Menu:NumLines()](Menu#menunumlines)
--]]
function menuMixin:RemoveLine(index)
assert(index >= 1 and index <= self:NumLines(), 'index out of scope')
table.remove(self.data, index)
self.lines[index]:Hide()
end
--[[ Menu:ClearLines()
Removes all lines in the menu.
--]]
function menuMixin:ClearLines()
if(self.data) then
table.wipe(self.data)
for _, Line in next, self.lines do
Line:Hide()
end
end
end
--[[ Menu:NumLines()
Returns the number of lines in the menu.
--]]
function menuMixin:NumLines()
return #self.lines
end
--[[ Menu:SetStyle(_name_)
Sets the active style for all menus related to this one.
- `name`: Name of registered style (see [LibDropDown:RegisterStyle](LibDropDown#libdropdownregisterstyle))
--]]
function menuMixin:SetStyle(name)
if(not name) then
name = self.parent.style or 'DEFAULT'
elseif(not lib.styles[name]) then
error('Style "' .. name .. '" does not exist.')
end
self.parent.style = name
local data = lib.styles[name]
self.parent.spacing = data.spacing or 0
self.parent.padding = data.padding or 10
self.parent.minWidth = data.minWidth or 100
self.parent.maxWidth = data.maxWidth
self.parent.gap = data.gap or 10
self.parent.normalFont = data.normalFont or 'GameFontHighlightSmallLeft'
self.parent.highlightFont = data.highlightFont or self.parent.normalFont
self.parent.disabledFont = data.disabledFont or 'GameFontDisableSmallLeft'
self.parent.titleFont = data.titleFont or 'GameFontNormal'
self.parent.radioTexture = data.radioTexture or [[Interface\Common\UI-DropDownRadioChecks]]
self.parent.highlightTexture = data.highlightTexture or [[Interface\QuestFrame\UI-QuestTitleHighlight]]
self.parent.expandTexture = data.expandTexture or [[Interface\ChatFrame\ChatFrameExpandArrow]]
self.parent.checkButtonAlignment = data.checkButtonAlignment or 'RIGHT'
self.Backdrop:SetBackdrop(data.backdrop)
self.Backdrop:SetBackdropColor((data.backdropColor or HIGHLIGHT_FONT_COLOR):GetRGBA())
self.Backdrop:SetBackdropBorderColor((data.backdropBorderColor or HIGHLIGHT_FONT_COLOR):GetRGBA())
end
--[[ Menu:GetStyle()
Returns the name of the active style for the menu (and child menus).
--]]
function menuMixin:GetStyle()
return self.parent.style
end
--[[ Menu:SetAnchor(_point, anchor, relativePoint, x, y_)
Replaces the default anchor with a custom one.
Exact same parameters as in [Widgets:SetPoint](http://wowprogramming.com/docs/widgets/Region/SetPoint), read that documentation instead.
--]]
function menuMixin:SetAnchor(point, anchor, relativePoint, x, y)
self.parent.anchor[1] = point
self.parent.anchor[2] = anchor
self.parent.anchor[3] = relativePoint
self.parent.anchor[4] = x
self.parent.anchor[5] = y
end
--[[ Menu:GetAnchor()
Returns the point data for the registered anchor (see [Widgets:GetPoint](http://wowprogramming.com/docs/widgets/Region/GetPoint)).
--]]
function menuMixin:GetAnchor()
return unpack(self.parent.anchor)
end
--[[ Menu:SetAnchorCursor(_state_)
Allows the anchor to be overridden and places the menu on the cursor.
* `state`: Enables/disables cursor anchoring _(boolean)_
--]]
function menuMixin:SetAnchorCursor(state)
self.parent.anchorCursor = state
end
--[[ Menu:IsAnchorCursor()
Returns the boolean state of whether the menu should be anchored to the cursor or not.
--]]
function menuMixin:IsAnchorCursor()
return self.parent.anchorCursor
end
--[[ Menu:SetCheckAlignment(_alignment_)
Sets the alignment of check/radio buttons within the menu.
* `alignment`: Either "LEFT" or "RIGHT" (default) _(string)_
--]]
function menuMixin:SetCheckAlignment(alignment)
if alignment == 'LEFT' or alignment == 'RIGHT' then
self.parent.checkButtonAlignment = alignment
end
end
--[[ LibDropDown:NewMenu(_parent_, _name_)
Creates and returns a new, empty dropdown [Menu](Menu).
* `parent`: Frame for parenting. _(frame/string)_
* `name`: Global name for the menu. Falls back to `parent` name with suffix. _(string)_
--]]
function lib:NewMenu(parent, name)
assert(parent, 'A menu requires a given parent')
if(type(parent) == 'string') then
parent = _G[parent]
end
local Menu = Mixin(CreateFrame('Button', (name or parent:GetDebugName() .. 'Menu'), parent), menuMixin, CallbackRegistryMixin)
Menu:Hide()
Menu:EnableMouse(true)
Menu:SetClampedToScreen(true)
Menu:SetScript('OnShow', OnShow)
local Backdrop = CreateFrame('Frame', '$parentBackdrop', Menu, 'BackdropTemplate')
Backdrop:SetPoint('CENTER')
Menu.Backdrop = Backdrop
Menu.parent = parent.parent or Menu
Menu.lines = {}
Menu.menus = {}
Menu:SetStyle()
table.insert(UIMenus, Menu:GetDebugName())
Menu.anchor = {'TOP', Menu:GetParent(), 'BOTTOM', 0, -12} -- 8, 22
Menu.anchorCursor = false
Menu:SetClampRectInsets(-20, 20, 20, -20)
lib.dropdowns[Menu] = true
return Menu
end