388 regels
16 KiB
Lua
388 regels
16 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 = 18
|
|
local LAM = LibStub("LibAddonMenu-2.0")
|
|
if not LAM:RegisterWidget("dropdown", widgetVersion) then return end
|
|
|
|
local wm = WINDOW_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 DEFAULT_VISIBLE_ROWS = 10
|
|
local SCROLLABLE_ENTRY_TEMPLATE_HEIGHT = 25 -- same as in zo_combobox.lua
|
|
local CONTENT_PADDING = 24
|
|
local SCROLLBAR_PADDING = 16
|
|
local PADDING = GetMenuPadding() / 2 -- half the amount looks closer to the regular dropdown
|
|
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(parent, control, visibleRows)
|
|
local combobox = control.combobox
|
|
local dropdown = control.dropdown
|
|
self.parent = parent
|
|
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() self:OnShow() end
|
|
local function onHide() self:OnHide() end
|
|
local function doHide() self:DoHide() end
|
|
|
|
ZO_PreHook(dropdown, "ShowDropdownOnMouseUp", onShow)
|
|
ZO_PreHook(dropdown, "HideDropdownInternal", onHide)
|
|
combobox:SetHandler("OnEffectivelyHidden", onHide)
|
|
parent:SetHandler("OnEffectivelyHidden", 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 = {scroll:GetAnchor(0)}
|
|
local anchor2 = {scroll:GetAnchor(1)}
|
|
scroll:ClearAnchors()
|
|
scroll:SetAnchor(anchor1[2], anchor1[3], anchor1[4], anchor1[5] + PADDING, anchor1[6] + PADDING)
|
|
scroll:SetAnchor(anchor2[2], anchor2[3], anchor2[4], anchor2[5] - PADDING, anchor2[6] - PADDING)
|
|
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, 1)
|
|
local dataType2 = ZO_ScrollList_GetDataTypeTable(dropdown.m_scroll, 2)
|
|
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, 2)
|
|
-- 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)
|
|
-- we could also just replace the handlers
|
|
--control:SetHandler("OnMouseEnter", onMouseEnter)
|
|
--control:SetHandler("OnMouseExit", onMouseExit)
|
|
end
|
|
end
|
|
dataType1.setupCallback = SetupEntry
|
|
dataType2.setupCallback = SetupEntry
|
|
|
|
-- adjust dimensions based on entries
|
|
local scrollContent = scroll:GetNamedChild("Contents")
|
|
ZO_PreHook(dropdown, "AddMenuItems", function()
|
|
local width = PADDING * 2 + zo_max(self:GetMaxWidth(), combobox:GetWidth())
|
|
local numItems = #dropdown.m_sortedItems
|
|
local anchorOffset = 0
|
|
if(numItems > self.visibleRows) then
|
|
width = width + CONTENT_PADDING + SCROLLBAR_PADDING
|
|
anchorOffset = -SCROLLBAR_PADDING
|
|
numItems = self.visibleRows
|
|
end
|
|
scrollContent:SetAnchor(BOTTOMRIGHT, nil, nil, anchorOffset)
|
|
local height = PADDING * 2 + numItems * (SCROLLABLE_ENTRY_TEMPLATE_HEIGHT + dropdown.m_spacing) - dropdown.m_spacing + ROUNDING_MARGIN
|
|
dropdown.m_dropdown:SetWidth(width)
|
|
dropdown.m_dropdown:SetHeight(height)
|
|
end)
|
|
end
|
|
|
|
function ScrollableDropdownHelper:OnShow()
|
|
local dropdown = self.dropdown
|
|
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:GetMaxWidth()
|
|
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(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
|