elderscrolls-addon-achievem.../Libs/LibAddonMenu-2.0/controls/dropdown.lua

433 lines
17 KiB
Lua

--[[dropdownData = {
type = "dropdown",
name = "My Dropdown", -- or string id or function returning a string
choices = {"table", "of", "choices"},
choicesValues = {"foo", 2, "three"}, -- if specified, these values will get passed to setFunc instead (optional)
getFunc = function() return db.var end,
setFunc = function(var) db.var = var doStuff() end,
tooltip = "Dropdown's tooltip text.", -- or string id or function returning a string (optional)
choicesTooltips = {"tooltip 1", "tooltip 2", "tooltip 3"}, -- or array of string ids or array of functions returning a string (optional)
sort = "name-up", -- or "name-down", "numeric-up", "numeric-down", "value-up", "value-down", "numericvalue-up", "numericvalue-down" (optional) - if not provided, list will not be sorted
width = "full", -- or "half" (optional)
scrollable = true, -- boolean or number, if set the dropdown will feature a scroll bar if there are a large amount of choices and limit the visible lines to the specified number or 10 if true is used (optional)
disabled = function() return db.someBooleanSetting end, -- or boolean (optional)
warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional)
requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional)
default = defaults.var, -- default value or function that returns the default value (optional)
reference = "MyAddonDropdown" -- unique global reference to control (optional)
} ]]
local widgetVersion = 20
local LAM = LibStub("LibAddonMenu-2.0")
if not LAM:RegisterWidget("dropdown", widgetVersion) then return end
local wm = WINDOW_MANAGER
local cm = CALLBACK_MANAGER
local SORT_BY_VALUE = { ["value"] = {} }
local SORT_BY_VALUE_NUMERIC = { ["value"] = { isNumeric = true } }
local SORT_TYPES = {
name = ZO_SORT_BY_NAME,
numeric = ZO_SORT_BY_NAME_NUMERIC,
value = SORT_BY_VALUE,
numericvalue = SORT_BY_VALUE_NUMERIC,
}
local SORT_ORDERS = {
up = ZO_SORT_ORDER_UP,
down = ZO_SORT_ORDER_DOWN,
}
local function UpdateDisabled(control)
local disable
if type(control.data.disabled) == "function" then
disable = control.data.disabled()
else
disable = control.data.disabled
end
control.dropdown:SetEnabled(not disable)
if disable then
control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA())
else
control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA())
end
end
local function UpdateValue(control, forceDefault, value)
if forceDefault then --if we are forcing defaults
value = LAM.util.GetDefaultValue(control.data.default)
control.data.setFunc(value)
control.dropdown:SetSelectedItem(control.choices[value])
elseif value then
control.data.setFunc(value)
--after setting this value, let's refresh the others to see if any should be disabled or have their settings changed
LAM.util.RequestRefreshIfNeeded(control)
else
value = control.data.getFunc()
control.dropdown:SetSelectedItem(control.choices[value])
end
end
local function DropdownCallback(control, choiceText, choice)
choice.control:UpdateValue(false, choice.value or choiceText)
end
local function SetupTooltips(comboBox, choicesTooltips)
local function ShowTooltip(control)
InitializeTooltip(InformationTooltip, control, TOPLEFT, 0, 0, BOTTOMRIGHT)
SetTooltipText(InformationTooltip, LAM.util.GetStringFromValue(control.tooltip))
InformationTooltipTopLevel:BringWindowToTop()
end
local function HideTooltip(control)
ClearTooltip(InformationTooltip)
end
-- allow for tooltips on the drop down entries
local originalShow = comboBox.ShowDropdownInternal
comboBox.ShowDropdownInternal = function(comboBox)
originalShow(comboBox)
local entries = ZO_Menu.items
for i = 1, #entries do
local entry = entries[i]
local control = entries[i].item
control.tooltip = choicesTooltips[i]
entry.onMouseEnter = control:GetHandler("OnMouseEnter")
entry.onMouseExit = control:GetHandler("OnMouseExit")
ZO_PreHookHandler(control, "OnMouseEnter", ShowTooltip)
ZO_PreHookHandler(control, "OnMouseExit", HideTooltip)
end
end
local originalHide = comboBox.HideDropdownInternal
comboBox.HideDropdownInternal = function(self)
local entries = ZO_Menu.items
for i = 1, #entries do
local entry = entries[i]
local control = entries[i].item
control:SetHandler("OnMouseEnter", entry.onMouseEnter)
control:SetHandler("OnMouseExit", entry.onMouseExit)
control.tooltip = nil
end
originalHide(self)
end
end
local function UpdateChoices(control, choices, choicesValues, choicesTooltips)
control.dropdown:ClearItems() --remove previous choices --(need to call :SetSelectedItem()?)
ZO_ClearTable(control.choices)
--build new list of choices
local choices = choices or control.data.choices
local choicesValues = choicesValues or control.data.choicesValues
local choicesTooltips = choicesTooltips or control.data.choicesTooltips
if choicesValues then
assert(#choices == #choicesValues, "choices and choicesValues need to have the same size")
end
if choicesTooltips then
assert(#choices == #choicesTooltips, "choices and choicesTooltips need to have the same size")
if not control.scrollHelper then -- only do this for non-scrollable
SetupTooltips(control.dropdown, choicesTooltips)
end
end
for i = 1, #choices do
local entry = control.dropdown:CreateItemEntry(choices[i], DropdownCallback)
entry.control = control
if choicesValues then
entry.value = choicesValues[i]
end
if choicesTooltips and control.scrollHelper then
entry.tooltip = choicesTooltips[i]
end
control.choices[entry.value or entry.name] = entry.name
control.dropdown:AddItem(entry, not control.data.sort and ZO_COMBOBOX_SUPRESS_UPDATE) --if sort type/order isn't specified, then don't sort
end
end
local function GrabSortingInfo(sortInfo)
local t, i = {}, 1
for info in string.gmatch(sortInfo, "([^%-]+)") do
t[i] = info
i = i + 1
end
return t
end
local ENTRY_ID = 1
local LAST_ENTRY_ID = 2
local OFFSET_X_INDEX = 4
local DEFAULT_VISIBLE_ROWS = 10
local SCROLLABLE_ENTRY_TEMPLATE_HEIGHT = ZO_SCROLLABLE_ENTRY_TEMPLATE_HEIGHT
local SCROLLBAR_PADDING = ZO_SCROLL_BAR_WIDTH
local PADDING_X = GetMenuPadding()
local PADDING_Y = ZO_SCROLLABLE_COMBO_BOX_LIST_PADDING_Y
local LABEL_OFFSET_X = 2
local CONTENT_PADDING = PADDING_X * 4
local ROUNDING_MARGIN = 0.01 -- needed to avoid rare issue with too many anchors processed
local ScrollableDropdownHelper = ZO_Object:Subclass()
function ScrollableDropdownHelper:New(...)
local object = ZO_Object.New(self)
object:Initialize(...)
return object
end
function ScrollableDropdownHelper:Initialize(panel, control, visibleRows)
local combobox = control.combobox
local dropdown = control.dropdown
self.panel = panel
self.control = control
self.combobox = combobox
self.dropdown = dropdown
self.visibleRows = visibleRows
-- clear anchors so we can adjust the width dynamically
dropdown.m_dropdown:ClearAnchors()
dropdown.m_dropdown:SetAnchor(TOPLEFT, combobox, BOTTOMLEFT)
-- handle dropdown or settingsmenu opening/closing
local function onShow() return self:OnShow() end
local function onHide() self:OnHide() end
local function doHide(closedPanel)
if closedPanel == panel then self:DoHide() end
end
ZO_PreHook(dropdown, "ShowDropdownOnMouseUp", onShow)
ZO_PreHook(dropdown, "HideDropdownInternal", onHide)
combobox:SetHandler("OnEffectivelyHidden", onHide)
cm:RegisterCallback("LAM-PanelClosed", doHide)
-- dont fade entries near the edges
local scrollList = dropdown.m_scroll
scrollList.selectionTemplate = nil
scrollList.highlightTemplate = nil
ZO_ScrollList_EnableSelection(scrollList, "ZO_SelectionHighlight")
ZO_ScrollList_EnableHighlight(scrollList, "ZO_SelectionHighlight")
ZO_Scroll_SetUseFadeGradient(scrollList, false)
-- adjust scroll content anchor to mimic menu padding
local scroll = dropdown.m_dropdown:GetNamedChild("Scroll")
local anchor1 = {select(2, scroll:GetAnchor(0))}
local anchor2 = {select(2, scroll:GetAnchor(1))}
anchor1[OFFSET_X_INDEX] = PADDING_X - LABEL_OFFSET_X
anchor2[OFFSET_X_INDEX] = -anchor1[OFFSET_X_INDEX]
scroll:ClearAnchors()
scroll:SetAnchor(unpack(anchor1))
scroll:SetAnchor(unpack(anchor2))
ZO_ScrollList_Commit(scrollList)
-- hook mouse enter/exit
local function onMouseEnter(control) self:OnMouseEnter(control) end
local function onMouseExit(control) self:OnMouseExit(control) end
-- adjust row setup to mimic the highlight padding
local dataType1 = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, ENTRY_ID)
local dataType2 = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, LAST_ENTRY_ID)
local oSetup = dataType1.setupCallback -- both types have the same setup function
local function SetupEntry(control, data, list)
oSetup(control, data, list)
control.m_label:SetAnchor(LEFT, nil, nil, LABEL_OFFSET_X)
control.m_label:SetAnchor(RIGHT, nil, nil, -LABEL_OFFSET_X)
-- no need to store old ones since we have full ownership of our dropdown controls
if not control.hookedMouseHandlers then --only do it once per control
control.hookedMouseHandlers = true
ZO_PreHookHandler(control, "OnMouseEnter", onMouseEnter)
ZO_PreHookHandler(control, "OnMouseExit", onMouseExit)
end
end
dataType1.setupCallback = SetupEntry
dataType2.setupCallback = SetupEntry
-- adjust dimensions based on entries
local scrollContent = scroll:GetNamedChild("Contents")
dropdown.AddMenuItems = ScrollableDropdownHelper.AddMenuItems
dropdown.AdjustDimensions = function()
local numItems = #dropdown.m_sortedItems
local contentWidth = self:CalculateContentWidth() + CONTENT_PADDING
local anchorOffset = 0
if(numItems > self.visibleRows) then
numItems = self.visibleRows
contentWidth = contentWidth + SCROLLBAR_PADDING
anchorOffset = -SCROLLBAR_PADDING
end
local width = zo_max(contentWidth, dropdown.m_container:GetWidth())
local height = dropdown:GetEntryTemplateHeightWithSpacing() * numItems - dropdown.m_spacing + (PADDING_Y * 2) + ROUNDING_MARGIN
dropdown.m_dropdown:SetWidth(width)
dropdown.m_dropdown:SetHeight(height)
ZO_ScrollList_SetHeight(dropdown.m_scroll, height)
scrollContent:SetAnchor(BOTTOMRIGHT, nil, nil, anchorOffset)
end
end
local function CreateScrollableComboBoxEntry(self, item, index, isLast)
item.m_index = index
item.m_owner = self
local entryType = isLast and LAST_ENTRY_ID or ENTRY_ID
local entry = ZO_ScrollList_CreateDataEntry(entryType, item)
return entry
end
function ScrollableDropdownHelper.AddMenuItems(self) -- self refers to the ZO_ScrollableComboBox here
ZO_ScrollList_Clear(self.m_scroll)
local numItems = #self.m_sortedItems
local dataList = ZO_ScrollList_GetDataList(self.m_scroll)
for i = 1, numItems do
local item = self.m_sortedItems[i]
local entry = CreateScrollableComboBoxEntry(self, item, i, i == numItems)
table.insert(dataList, entry)
end
self:AdjustDimensions()
ZO_ScrollList_Commit(self.m_scroll)
end
function ScrollableDropdownHelper:OnShow()
local dropdown = self.dropdown
-- don't show if there are no entries
if #dropdown.m_sortedItems == 0 then return true end
if dropdown.m_lastParent ~= ZO_Menus then
dropdown.m_lastParent = dropdown.m_dropdown:GetParent()
dropdown.m_dropdown:SetParent(ZO_Menus)
ZO_Menus:BringWindowToTop()
end
end
function ScrollableDropdownHelper:OnHide()
local dropdown = self.dropdown
if dropdown.m_lastParent then
dropdown.m_dropdown:SetParent(dropdown.m_lastParent)
dropdown.m_lastParent = nil
end
end
function ScrollableDropdownHelper:DoHide()
local dropdown = self.dropdown
if dropdown:IsDropdownVisible() then
dropdown:HideDropdown()
end
end
function ScrollableDropdownHelper:CalculateContentWidth()
local dropdown = self.dropdown
local dataType = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, 1)
local dummy = dataType.pool:AcquireObject()
dataType.setupCallback(dummy, {
m_owner = dropdown,
name = "Dummy"
}, dropdown)
local maxWidth = 0
local label = dummy.m_label
local entries = dropdown.m_sortedItems
local numItems = #entries
for index = 1, numItems do
label:SetText(entries[index].name)
local width = label:GetTextWidth()
if (width > maxWidth) then
maxWidth = width
end
end
dataType.pool:ReleaseObject(dummy.key)
return maxWidth
end
function ScrollableDropdownHelper:OnMouseEnter(control)
-- call original code if we replace instead of hook the handler
--ZO_ScrollableComboBox_Entry_OnMouseEnter(control)
-- show tooltip
if control.m_data.tooltip then
InitializeTooltip(InformationTooltip, control, TOPLEFT, 0, 0, BOTTOMRIGHT)
SetTooltipText(InformationTooltip, LAM.util.GetStringFromValue(control.m_data.tooltip))
InformationTooltipTopLevel:BringWindowToTop()
end
end
function ScrollableDropdownHelper:OnMouseExit(control)
-- call original code if we replace instead of hook the handler
--ZO_ScrollableComboBox_Entry_OnMouseExit(control)
-- hide tooltip
if control.m_data.tooltip then
ClearTooltip(InformationTooltip)
end
end
function LAMCreateControl.dropdown(parent, dropdownData, controlName)
local control = LAM.util.CreateLabelAndContainerControl(parent, dropdownData, controlName)
control.choices = {}
local countControl = parent
local name = parent:GetName()
if not name or #name == 0 then
countControl = LAMCreateControl
name = "LAM"
end
local comboboxCount = (countControl.comboboxCount or 0) + 1
countControl.comboboxCount = comboboxCount
control.combobox = wm:CreateControlFromVirtual(zo_strjoin(nil, name, "Combobox", comboboxCount), control.container, dropdownData.scrollable and "ZO_ScrollableComboBox" or "ZO_ComboBox")
local combobox = control.combobox
combobox:SetAnchor(TOPLEFT)
combobox:SetDimensions(control.container:GetDimensions())
combobox:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end)
combobox:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end)
control.dropdown = ZO_ComboBox_ObjectFromContainer(combobox)
local dropdown = control.dropdown
dropdown:SetSortsItems(false) -- need to sort ourselves in order to be able to sort by value
if dropdownData.scrollable then
local visibleRows = type(dropdownData.scrollable) == "number" and dropdownData.scrollable or DEFAULT_VISIBLE_ROWS
control.scrollHelper = ScrollableDropdownHelper:New(LAM.util.GetTopPanel(parent), control, visibleRows)
end
ZO_PreHook(dropdown, "UpdateItems", function(self)
assert(not self.m_sortsItems, "built-in dropdown sorting was reactivated, sorting is handled by LAM")
if control.m_sortOrder ~= nil and control.m_sortType then
local sortKey = next(control.m_sortType)
local sortFunc = function(item1, item2) return ZO_TableOrderingFunction(item1, item2, sortKey, control.m_sortType, control.m_sortOrder) end
table.sort(self.m_sortedItems, sortFunc)
end
end)
if dropdownData.sort then
local sortInfo = GrabSortingInfo(dropdownData.sort)
control.m_sortType, control.m_sortOrder = SORT_TYPES[sortInfo[1]], SORT_ORDERS[sortInfo[2]]
elseif dropdownData.choicesValues then
control.m_sortType, control.m_sortOrder = ZO_SORT_ORDER_UP, SORT_BY_VALUE
end
if dropdownData.warning ~= nil or dropdownData.requiresReload then
control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon")
control.warning:SetAnchor(RIGHT, combobox, LEFT, -5, 0)
control.UpdateWarning = LAM.util.UpdateWarning
control:UpdateWarning()
end
control.UpdateChoices = UpdateChoices
control:UpdateChoices(dropdownData.choices, dropdownData.choicesValues)
control.UpdateValue = UpdateValue
control:UpdateValue()
if dropdownData.disabled ~= nil then
control.UpdateDisabled = UpdateDisabled
control:UpdateDisabled()
end
LAM.util.RegisterForRefreshIfNeeded(control)
LAM.util.RegisterForReloadIfNeeded(control)
return control
end