1249 lines
47 KiB
Lua
1249 lines
47 KiB
Lua
-- LibAddonMenu-2.0 & its files © Ryan Lakanen (Seerah) --
|
|
-- Distributed under The Artistic License 2.0 (see LICENSE) --
|
|
------------------------------------------------------------------
|
|
|
|
|
|
--Register LAM with LibStub
|
|
local MAJOR, MINOR = "LibAddonMenu-2.0", 26
|
|
local lam, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
|
if not lam then return end --the same or newer version of this lib is already loaded into memory
|
|
|
|
local messages = {}
|
|
local MESSAGE_PREFIX = "[LAM2] "
|
|
local function PrintLater(msg)
|
|
if CHAT_SYSTEM.primaryContainer then
|
|
d(MESSAGE_PREFIX .. msg)
|
|
else
|
|
messages[#messages + 1] = msg
|
|
end
|
|
end
|
|
|
|
local function FlushMessages()
|
|
for i = 1, #messages do
|
|
d(MESSAGE_PREFIX .. messages[i])
|
|
end
|
|
messages = {}
|
|
end
|
|
|
|
if LAMSettingsPanelCreated and not LAMCompatibilityWarning then
|
|
PrintLater("An old version of LibAddonMenu with compatibility issues was detected. For more information on how to proceed search for LibAddonMenu on esoui.com")
|
|
LAMCompatibilityWarning = true
|
|
end
|
|
|
|
--UPVALUES--
|
|
local wm = WINDOW_MANAGER
|
|
local em = EVENT_MANAGER
|
|
local sm = SCENE_MANAGER
|
|
local cm = CALLBACK_MANAGER
|
|
local tconcat = table.concat
|
|
local tinsert = table.insert
|
|
|
|
local MIN_HEIGHT = 26
|
|
local HALF_WIDTH_LINE_SPACING = 2
|
|
local OPTIONS_CREATION_RUNNING = 1
|
|
local OPTIONS_CREATED = 2
|
|
local LAM_CONFIRM_DIALOG = "LAM_CONFIRM_DIALOG"
|
|
local LAM_DEFAULTS_DIALOG = "LAM_DEFAULTS"
|
|
local LAM_RELOAD_DIALOG = "LAM_RELOAD_DIALOG"
|
|
|
|
local addonsForList = {}
|
|
local addonToOptionsMap = {}
|
|
local optionsState = {}
|
|
lam.widgets = lam.widgets or {}
|
|
local widgets = lam.widgets
|
|
lam.util = lam.util or {}
|
|
local util = lam.util
|
|
lam.controlsForReload = lam.controlsForReload or {}
|
|
local controlsForReload = lam.controlsForReload
|
|
|
|
local function GetDefaultValue(default)
|
|
if type(default) == "function" then
|
|
return default()
|
|
end
|
|
return default
|
|
end
|
|
|
|
local function GetStringFromValue(value)
|
|
if type(value) == "function" then
|
|
return value()
|
|
elseif type(value) == "number" then
|
|
return GetString(value)
|
|
end
|
|
return value
|
|
end
|
|
|
|
local function CreateBaseControl(parent, controlData, controlName)
|
|
local control = wm:CreateControl(controlName or controlData.reference, parent.scroll or parent, CT_CONTROL)
|
|
control.panel = parent.panel or parent -- if this is in a submenu, panel is the submenu's parent
|
|
control.data = controlData
|
|
|
|
control.isHalfWidth = controlData.width == "half"
|
|
local width = 510 -- set default width in case a custom parent object is passed
|
|
if control.panel.GetWidth ~= nil then width = control.panel:GetWidth() - 60 end
|
|
control:SetWidth(width)
|
|
return control
|
|
end
|
|
|
|
local function CreateLabelAndContainerControl(parent, controlData, controlName)
|
|
local control = CreateBaseControl(parent, controlData, controlName)
|
|
local width = control:GetWidth()
|
|
|
|
local container = wm:CreateControl(nil, control, CT_CONTROL)
|
|
container:SetDimensions(width / 3, MIN_HEIGHT)
|
|
control.container = container
|
|
|
|
local label = wm:CreateControl(nil, control, CT_LABEL)
|
|
label:SetFont("ZoFontWinH4")
|
|
label:SetHeight(MIN_HEIGHT)
|
|
label:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
|
|
label:SetText(GetStringFromValue(controlData.name))
|
|
control.label = label
|
|
|
|
if control.isHalfWidth then
|
|
control:SetDimensions(width / 2, MIN_HEIGHT * 2 + HALF_WIDTH_LINE_SPACING)
|
|
label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0)
|
|
label:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 0)
|
|
container:SetAnchor(TOPRIGHT, control.label, BOTTOMRIGHT, 0, HALF_WIDTH_LINE_SPACING)
|
|
else
|
|
control:SetDimensions(width, MIN_HEIGHT)
|
|
container:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 0)
|
|
label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0)
|
|
label:SetAnchor(TOPRIGHT, container, TOPLEFT, 5, 0)
|
|
end
|
|
|
|
control.data.tooltipText = GetStringFromValue(control.data.tooltip)
|
|
control:SetMouseEnabled(true)
|
|
control:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter)
|
|
control:SetHandler("OnMouseExit", ZO_Options_OnMouseExit)
|
|
return control
|
|
end
|
|
|
|
local function GetTopPanel(panel)
|
|
while panel.panel and panel.panel ~= panel do
|
|
panel = panel.panel
|
|
end
|
|
return panel
|
|
end
|
|
|
|
local function IsSame(objA, objB)
|
|
if #objA ~= #objB then return false end
|
|
for i = 1, #objA do
|
|
if objA[i] ~= objB[i] then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function RefreshReloadUIButton()
|
|
lam.requiresReload = false
|
|
|
|
for i = 1, #controlsForReload do
|
|
local reloadControl = controlsForReload[i]
|
|
if not IsSame(reloadControl.startValue, {reloadControl.data.getFunc()}) then
|
|
lam.requiresReload = true
|
|
break
|
|
end
|
|
end
|
|
|
|
lam.applyButton:SetHidden(not lam.requiresReload)
|
|
end
|
|
|
|
local function RequestRefreshIfNeeded(control)
|
|
-- if our parent window wants to refresh controls, then fire the callback
|
|
local panel = GetTopPanel(control.panel)
|
|
local panelData = panel.data
|
|
if panelData.registerForRefresh then
|
|
cm:FireCallbacks("LAM-RefreshPanel", control)
|
|
end
|
|
RefreshReloadUIButton()
|
|
end
|
|
|
|
local function RegisterForRefreshIfNeeded(control)
|
|
-- if our parent window wants to refresh controls, then add this to the list
|
|
local panel = GetTopPanel(control.panel)
|
|
local panelData = panel.data
|
|
if panelData.registerForRefresh or panelData.registerForDefaults then
|
|
tinsert(panel.controlsToRefresh or {}, control) -- prevent errors on custom panels
|
|
end
|
|
end
|
|
|
|
local function RegisterForReloadIfNeeded(control)
|
|
if control.data.requiresReload then
|
|
tinsert(controlsForReload, control)
|
|
control.startValue = {control.data.getFunc()}
|
|
end
|
|
end
|
|
|
|
local function GetConfirmDialog()
|
|
if(not ESO_Dialogs[LAM_CONFIRM_DIALOG]) then
|
|
ESO_Dialogs[LAM_CONFIRM_DIALOG] = {
|
|
canQueue = true,
|
|
title = {
|
|
text = "",
|
|
},
|
|
mainText = {
|
|
text = "",
|
|
},
|
|
buttons = {
|
|
[1] = {
|
|
text = SI_DIALOG_CONFIRM,
|
|
callback = function(dialog) end,
|
|
},
|
|
[2] = {
|
|
text = SI_DIALOG_CANCEL,
|
|
}
|
|
}
|
|
}
|
|
end
|
|
return ESO_Dialogs[LAM_CONFIRM_DIALOG]
|
|
end
|
|
|
|
local function ShowConfirmationDialog(title, body, callback)
|
|
local dialog = GetConfirmDialog()
|
|
dialog.title.text = title
|
|
dialog.mainText.text = body
|
|
dialog.buttons[1].callback = callback
|
|
ZO_Dialogs_ShowDialog(LAM_CONFIRM_DIALOG)
|
|
end
|
|
|
|
local function GetDefaultsDialog()
|
|
if(not ESO_Dialogs[LAM_DEFAULTS_DIALOG]) then
|
|
ESO_Dialogs[LAM_DEFAULTS_DIALOG] = {
|
|
canQueue = true,
|
|
title = {
|
|
text = SI_INTERFACE_OPTIONS_RESET_TO_DEFAULT_TOOLTIP,
|
|
},
|
|
mainText = {
|
|
text = SI_OPTIONS_RESET_PROMPT,
|
|
},
|
|
buttons = {
|
|
[1] = {
|
|
text = SI_OPTIONS_RESET,
|
|
callback = function(dialog) end,
|
|
},
|
|
[2] = {
|
|
text = SI_DIALOG_CANCEL,
|
|
}
|
|
}
|
|
}
|
|
end
|
|
return ESO_Dialogs[LAM_DEFAULTS_DIALOG]
|
|
end
|
|
|
|
local function ShowDefaultsDialog(panel)
|
|
local dialog = GetDefaultsDialog()
|
|
dialog.buttons[1].callback = function()
|
|
panel:ForceDefaults()
|
|
RefreshReloadUIButton()
|
|
end
|
|
ZO_Dialogs_ShowDialog(LAM_DEFAULTS_DIALOG)
|
|
end
|
|
|
|
local function DiscardChangesOnReloadControls()
|
|
for i = 1, #controlsForReload do
|
|
local reloadControl = controlsForReload[i]
|
|
if not IsSame(reloadControl.startValue, {reloadControl.data.getFunc()}) then
|
|
reloadControl:UpdateValue(false, unpack(reloadControl.startValue))
|
|
end
|
|
end
|
|
lam.requiresReload = false
|
|
lam.applyButton:SetHidden(true)
|
|
end
|
|
|
|
local function StorePanelForReopening()
|
|
local saveData = ZO_Ingame_SavedVariables["LAM"] or {}
|
|
saveData.reopenPanel = lam.currentAddonPanel:GetName()
|
|
ZO_Ingame_SavedVariables["LAM"] = saveData
|
|
end
|
|
|
|
local function RetrievePanelForReopening()
|
|
local saveData = ZO_Ingame_SavedVariables["LAM"]
|
|
if(saveData) then
|
|
ZO_Ingame_SavedVariables["LAM"] = nil
|
|
return _G[saveData.reopenPanel]
|
|
end
|
|
end
|
|
|
|
local function HandleReloadUIPressed()
|
|
StorePanelForReopening()
|
|
ReloadUI()
|
|
end
|
|
|
|
local function HandleLoadDefaultsPressed()
|
|
ShowDefaultsDialog(lam.currentAddonPanel)
|
|
end
|
|
|
|
local function GetReloadDialog()
|
|
if(not ESO_Dialogs[LAM_RELOAD_DIALOG]) then
|
|
ESO_Dialogs[LAM_RELOAD_DIALOG] = {
|
|
canQueue = true,
|
|
title = {
|
|
text = util.L["RELOAD_DIALOG_TITLE"],
|
|
},
|
|
mainText = {
|
|
text = util.L["RELOAD_DIALOG_TEXT"],
|
|
},
|
|
buttons = {
|
|
[1] = {
|
|
text = util.L["RELOAD_DIALOG_RELOAD_BUTTON"],
|
|
callback = function() ReloadUI() end,
|
|
},
|
|
[2] = {
|
|
text = util.L["RELOAD_DIALOG_DISCARD_BUTTON"],
|
|
callback = DiscardChangesOnReloadControls,
|
|
}
|
|
},
|
|
noChoiceCallback = DiscardChangesOnReloadControls,
|
|
}
|
|
end
|
|
return ESO_Dialogs[LAM_CONFIRM_DIALOG]
|
|
end
|
|
|
|
local function ShowReloadDialogIfNeeded()
|
|
if lam.requiresReload then
|
|
local dialog = GetReloadDialog()
|
|
ZO_Dialogs_ShowDialog(LAM_RELOAD_DIALOG)
|
|
end
|
|
end
|
|
|
|
local function UpdateWarning(control)
|
|
local warning
|
|
if control.data.warning ~= nil then
|
|
warning = util.GetStringFromValue(control.data.warning)
|
|
end
|
|
|
|
if control.data.requiresReload then
|
|
if not warning then
|
|
warning = string.format("|cff0000%s", util.L["RELOAD_UI_WARNING"])
|
|
else
|
|
warning = string.format("%s\n\n|cff0000%s", warning, util.L["RELOAD_UI_WARNING"])
|
|
end
|
|
end
|
|
|
|
if not warning then
|
|
control.warning:SetHidden(true)
|
|
else
|
|
control.warning.data = {tooltipText = warning}
|
|
control.warning:SetHidden(false)
|
|
end
|
|
end
|
|
|
|
local localization = {
|
|
en = {
|
|
PANEL_NAME = "Addons",
|
|
AUTHOR = string.format("%s: <<X:1>>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Author: <<X:1>>"
|
|
VERSION = "Version: <<X:1>>",
|
|
WEBSITE = "Visit Website",
|
|
PANEL_INFO_FONT = "$(CHAT_FONT)|14|soft-shadow-thin",
|
|
RELOAD_UI_WARNING = "Changes to this setting require an UI reload in order to take effect.",
|
|
RELOAD_DIALOG_TITLE = "UI Reload required",
|
|
RELOAD_DIALOG_TEXT = "Some changes require an UI reload in order to take effect. Do you want to reload now or discard the changes?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Reload",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Discard",
|
|
},
|
|
it = { -- provided by JohnnyKing
|
|
PANEL_NAME = "Addon",
|
|
VERSION = "Versione: <<X:1>>",
|
|
WEBSITE = "Visita il Sitoweb",
|
|
RELOAD_UI_WARNING = "Cambiare questa impostazione richiede un Ricarica UI al fine che faccia effetto.",
|
|
RELOAD_DIALOG_TITLE = "Ricarica UI richiesto",
|
|
RELOAD_DIALOG_TEXT = "Alcune modifiche richiedono un Ricarica UI al fine che facciano effetto. Sei sicuro di voler ricaricare ora o di voler annullare le modifiche?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Ricarica",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Annulla",
|
|
},
|
|
fr = { -- provided by Ayantir
|
|
PANEL_NAME = "Extensions",
|
|
WEBSITE = "Visiter le site Web",
|
|
RELOAD_UI_WARNING = "La modification de ce paramètre requiert un rechargement de l'UI pour qu'il soit pris en compte.",
|
|
RELOAD_DIALOG_TITLE = "Reload UI requis",
|
|
RELOAD_DIALOG_TEXT = "Certaines modifications requièrent un rechargement de l'UI pour qu'ils soient pris en compte. Souhaitez-vous recharger l'interface maintenant ou annuler les modifications ?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Recharger",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Annuler",
|
|
},
|
|
de = { -- provided by sirinsidiator
|
|
PANEL_NAME = "Erweiterungen",
|
|
WEBSITE = "Webseite besuchen",
|
|
RELOAD_UI_WARNING = "Änderungen an dieser Option werden erst übernommen nachdem die Benutzeroberfläche neu geladen wird.",
|
|
RELOAD_DIALOG_TITLE = "Neuladen benötigt",
|
|
RELOAD_DIALOG_TEXT = "Einige Änderungen werden erst übernommen nachdem die Benutzeroberfläche neu geladen wird. Wollt Ihr sie jetzt neu laden oder die Änderungen verwerfen?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Neu laden",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Verwerfen",
|
|
},
|
|
ru = { -- provided by TERAB1T
|
|
PANEL_NAME = "Дополнения",
|
|
VERSION = "Версия: <<X:1>>",
|
|
WEBSITE = "Посетить сайт",
|
|
PANEL_INFO_FONT = "RuESO/fonts/Univers57.otf|14|soft-shadow-thin",
|
|
RELOAD_UI_WARNING = "Для применения этой настройки необходима перезагрузка интерфейса.",
|
|
RELOAD_DIALOG_TITLE = "Необходима перезагрузка интерфейса",
|
|
RELOAD_DIALOG_TEXT = "Для применения некоторых изменений необходима перезагрузка интерфейса. Перезагрузить интерфейс сейчас или отменить изменения?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Перезагрузить",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Отменить изменения",
|
|
},
|
|
es = { -- provided by Morganlefai, checked by Kwisatz
|
|
PANEL_NAME = "Configuración",
|
|
VERSION = "Versión: <<X:1>>",
|
|
WEBSITE = "Visita la página web",
|
|
RELOAD_UI_WARNING = "Cambiar este ajuste recargará la interfaz del usuario.",
|
|
RELOAD_DIALOG_TITLE = "Requiere recargar la interfaz",
|
|
RELOAD_DIALOG_TEXT = "Algunos cambios requieren recargar la interfaz para poder aplicarse. Quieres aplicar los cambios y recargar la interfaz?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Recargar",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Cancelar",
|
|
},
|
|
jp = { -- provided by k0ta0uchi
|
|
PANEL_NAME = "アドオン設定",
|
|
WEBSITE = "ウェブサイトを見る",
|
|
},
|
|
zh = { -- provided by bssthu
|
|
PANEL_NAME = "插件",
|
|
VERSION = "版本: <<X:1>>",
|
|
WEBSITE = "访问网站",
|
|
PANEL_INFO_FONT = "EsoZh/fonts/univers57.otf|14|soft-shadow-thin",
|
|
},
|
|
pl = { -- provided by EmiruTegryfon
|
|
PANEL_NAME = "Dodatki",
|
|
VERSION = "Wersja: <<X:1>>",
|
|
WEBSITE = "Odwiedź stronę",
|
|
RELOAD_UI_WARNING = "Zmiany będą widoczne po ponownym załadowaniu UI.",
|
|
RELOAD_DIALOG_TITLE = "Wymagane przeładowanie UI",
|
|
RELOAD_DIALOG_TEXT = "Niektóre zmiany wymagają ponownego załadowania UI. Czy chcesz teraz ponownie załadować, czy porzucić zmiany?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Przeładuj",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Porzuć",
|
|
},
|
|
kr = { -- provided by p.walker
|
|
PANEL_NAME = "蝠盜蠨",
|
|
VERSION = "纄訄: <<X:1>>",
|
|
WEBSITE = "裹芬襴钸 縩紸",
|
|
PANEL_INFO_FONT = "EsoKR/fonts/Univers57.otf|14|soft-shadow-thin",
|
|
RELOAD_UI_WARNING = "襴 茤訕襄 绀溽靘籴 風滼筼 訁袩靘瀰褄靴 UI 苈穜滠遨襴 靄袔革瓈瓤.",
|
|
RELOAD_DIALOG_TITLE = "UI 苈穜滠遨 靄袔",
|
|
RELOAD_DIALOG_TEXT = "绀溽瘜 茤訕 謑 UI 苈穜滠遨襄 靄袔穜靘璔 芬靭襴 覈蒵瓈瓤. 诀瀈 苈穜滠遨靘蓜溠蒵瓈灌? 蝄瓈籴 绀溽襄 迨莌靘蓜溠蒵瓈灌?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "苈穜滠遨",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "绀溽迨莌",
|
|
},
|
|
br = { -- provided by mlsevero
|
|
PANEL_NAME = "Addons",
|
|
AUTHOR = string.format("%s: <<X:1>>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Autor: <<X:1>>"
|
|
VERSION = "Versão: <<X:1>>",
|
|
WEBSITE = "Visite o Website",
|
|
RELOAD_UI_WARNING = "Mudanças nessa configuração requer a releitura da UI para ter efeito.",
|
|
RELOAD_DIALOG_TITLE = "Releitura da UI requerida",
|
|
RELOAD_DIALOG_TEXT = "Algumas mudanças requerem a releitura da UI para ter efeito. Você deseja reler agora ou descartar as mudanças?",
|
|
RELOAD_DIALOG_RELOAD_BUTTON = "Relê",
|
|
RELOAD_DIALOG_DISCARD_BUTTON = "Descarta",
|
|
},
|
|
}
|
|
|
|
util.L = ZO_ShallowTableCopy(localization[GetCVar("Language.2")] or {}, localization["en"])
|
|
util.GetTooltipText = GetStringFromValue -- deprecated, use util.GetStringFromValue instead
|
|
util.GetStringFromValue = GetStringFromValue
|
|
util.GetDefaultValue = GetDefaultValue
|
|
util.CreateBaseControl = CreateBaseControl
|
|
util.CreateLabelAndContainerControl = CreateLabelAndContainerControl
|
|
util.RequestRefreshIfNeeded = RequestRefreshIfNeeded
|
|
util.RegisterForRefreshIfNeeded = RegisterForRefreshIfNeeded
|
|
util.RegisterForReloadIfNeeded = RegisterForReloadIfNeeded
|
|
util.GetTopPanel = GetTopPanel
|
|
util.ShowConfirmationDialog = ShowConfirmationDialog
|
|
util.UpdateWarning = UpdateWarning
|
|
|
|
local ADDON_DATA_TYPE = 1
|
|
local RESELECTING_DURING_REBUILD = true
|
|
local USER_REQUESTED_OPEN = true
|
|
|
|
|
|
--INTERNAL FUNCTION
|
|
--scrolls ZO_ScrollList `list` to move the row corresponding to `data`
|
|
-- into view (does nothing if there is no such row in the list)
|
|
--unlike ZO_ScrollList_ScrollDataIntoView, this function accounts for
|
|
-- fading near the list's edges - it avoids the fading area by scrolling
|
|
-- a little further than the ZO function
|
|
local function ScrollDataIntoView(list, data)
|
|
local targetIndex = data.sortIndex
|
|
if not targetIndex then return end
|
|
|
|
local scrollMin, scrollMax = list.scrollbar:GetMinMax()
|
|
local scrollTop = list.scrollbar:GetValue()
|
|
local controlHeight = list.uniformControlHeight or list.controlHeight
|
|
local targetMin = controlHeight * (targetIndex - 1) - 64
|
|
-- subtracting 64 ain't arbitrary, it's the maximum fading height
|
|
-- (libraries/zo_templates/scrolltemplates.lua/UpdateScrollFade)
|
|
|
|
if targetMin < scrollTop then
|
|
ZO_ScrollList_ScrollAbsolute(list, zo_max(targetMin, scrollMin))
|
|
else
|
|
local listHeight = ZO_ScrollList_GetHeight(list)
|
|
local targetMax = controlHeight * targetIndex + 64 - listHeight
|
|
|
|
if targetMax > scrollTop then
|
|
ZO_ScrollList_ScrollAbsolute(list, zo_min(targetMax, scrollMax))
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--INTERNAL FUNCTION
|
|
--constructs a string pattern from the text in `searchEdit` control
|
|
-- * metacharacters are escaped, losing their special meaning
|
|
-- * whitespace matches anything (including empty substring)
|
|
--if there is nothing but whitespace, returns nil
|
|
--otherwise returns a filter function, which takes a `data` table argument
|
|
-- and returns true iff `data.filterText` matches the pattern
|
|
local function GetSearchFilterFunc(searchEdit)
|
|
local text = searchEdit:GetText():lower()
|
|
local pattern = text:match("(%S+.-)%s*$")
|
|
|
|
if not pattern then -- nothing but whitespace
|
|
return nil
|
|
end
|
|
|
|
-- escape metacharacters, e.g. "ESO-Datenbank.de" => "ESO%-Datenbank%.de"
|
|
pattern = pattern:gsub("[-*+?^$().[%]%%]", "%%%0")
|
|
|
|
-- replace whitespace with "match shortest anything"
|
|
pattern = pattern:gsub("%s+", ".-")
|
|
|
|
return function(data)
|
|
return data.filterText:lower():find(pattern) ~= nil
|
|
end
|
|
end
|
|
|
|
|
|
--INTERNAL FUNCTION
|
|
--populates `addonList` with entries from `addonsForList`
|
|
-- addonList = ZO_ScrollList control
|
|
-- filter = [optional] function(data)
|
|
local function PopulateAddonList(addonList, filter)
|
|
local entryList = ZO_ScrollList_GetDataList(addonList)
|
|
local numEntries = 0
|
|
local selectedData = nil
|
|
local selectionIsFinal = false
|
|
|
|
ZO_ScrollList_Clear(addonList)
|
|
|
|
for i, data in ipairs(addonsForList) do
|
|
if not filter or filter(data) then
|
|
local dataEntry = ZO_ScrollList_CreateDataEntry(ADDON_DATA_TYPE, data)
|
|
numEntries = numEntries + 1
|
|
data.sortIndex = numEntries
|
|
entryList[numEntries] = dataEntry
|
|
-- select the first panel passing the filter, or the currently
|
|
-- shown panel, but only if it passes the filter as well
|
|
if selectedData == nil or data.panel == lam.pendingAddonPanel or data.panel == lam.currentAddonPanel then
|
|
if not selectionIsFinal then
|
|
selectedData = data
|
|
end
|
|
if data.panel == lam.pendingAddonPanel then
|
|
lam.pendingAddonPanel = nil
|
|
selectionIsFinal = true
|
|
end
|
|
end
|
|
else
|
|
data.sortIndex = nil
|
|
end
|
|
end
|
|
|
|
ZO_ScrollList_Commit(addonList)
|
|
|
|
if selectedData then
|
|
if selectedData.panel == lam.currentAddonPanel then
|
|
ZO_ScrollList_SelectData(addonList, selectedData, nil, RESELECTING_DURING_REBUILD)
|
|
else
|
|
ZO_ScrollList_SelectData(addonList, selectedData, nil)
|
|
end
|
|
ScrollDataIntoView(addonList, selectedData)
|
|
end
|
|
end
|
|
|
|
|
|
--METHOD: REGISTER WIDGET--
|
|
--each widget has its version checked before loading,
|
|
--so we only have the most recent one in memory
|
|
--Usage:
|
|
-- widgetType = "string"; the type of widget being registered
|
|
-- widgetVersion = integer; the widget's version number
|
|
LAMCreateControl = LAMCreateControl or {}
|
|
local lamcc = LAMCreateControl
|
|
|
|
function lam:RegisterWidget(widgetType, widgetVersion)
|
|
if widgets[widgetType] and widgets[widgetType] >= widgetVersion then
|
|
return false
|
|
else
|
|
widgets[widgetType] = widgetVersion
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- INTERNAL METHOD: hijacks the handlers for the actions in the OptionsWindow layer if not already done
|
|
local function InitKeybindActions()
|
|
if not lam.keybindsInitialized then
|
|
lam.keybindsInitialized = true
|
|
ZO_PreHook(KEYBOARD_OPTIONS, "ApplySettings", function()
|
|
if lam.currentPanelOpened then
|
|
if not lam.applyButton:IsHidden() then
|
|
HandleReloadUIPressed()
|
|
end
|
|
return true
|
|
end
|
|
end)
|
|
ZO_PreHook("ZO_Dialogs_ShowDialog", function(dialogName)
|
|
if lam.currentPanelOpened and dialogName == "OPTIONS_RESET_TO_DEFAULTS" then
|
|
if not lam.defaultButton:IsHidden() then
|
|
HandleLoadDefaultsPressed()
|
|
end
|
|
return true
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- INTERNAL METHOD: fires the LAM-PanelOpened callback if not already done
|
|
local function OpenCurrentPanel()
|
|
if lam.currentAddonPanel and not lam.currentPanelOpened then
|
|
lam.currentPanelOpened = true
|
|
lam.defaultButton:SetHidden(not lam.currentAddonPanel.data.registerForDefaults)
|
|
cm:FireCallbacks("LAM-PanelOpened", lam.currentAddonPanel)
|
|
end
|
|
end
|
|
|
|
-- INTERNAL METHOD: fires the LAM-PanelClosed callback if not already done
|
|
local function CloseCurrentPanel()
|
|
if lam.currentAddonPanel and lam.currentPanelOpened then
|
|
lam.currentPanelOpened = false
|
|
cm:FireCallbacks("LAM-PanelClosed", lam.currentAddonPanel)
|
|
end
|
|
end
|
|
|
|
--METHOD: OPEN TO ADDON PANEL--
|
|
--opens to a specific addon's option panel
|
|
--Usage:
|
|
-- panel = userdata; the panel returned by the :RegisterOptionsPanel method
|
|
local locSettings = GetString(SI_GAME_MENU_SETTINGS)
|
|
function lam:OpenToPanel(panel)
|
|
|
|
-- find and select the panel's row in addon list
|
|
|
|
local addonList = lam.addonList
|
|
local selectedData = nil
|
|
|
|
for _, addonData in ipairs(addonsForList) do
|
|
if addonData.panel == panel then
|
|
selectedData = addonData
|
|
ScrollDataIntoView(addonList, selectedData)
|
|
lam.pendingAddonPanel = addonData.panel
|
|
break
|
|
end
|
|
end
|
|
|
|
ZO_ScrollList_SelectData(addonList, selectedData)
|
|
ZO_ScrollList_RefreshVisible(addonList, selectedData)
|
|
|
|
local srchEdit = LAMAddonSettingsWindow:GetNamedChild("SearchFilterEdit")
|
|
srchEdit:Clear()
|
|
|
|
-- note that ZO_ScrollList doesn't require `selectedData` to be actually
|
|
-- present in the list, and that the list will only be populated once LAM
|
|
-- "Addon Settings" menu entry is selected for the first time
|
|
|
|
local function openAddonSettingsMenu()
|
|
local gameMenu = ZO_GameMenu_InGame.gameMenu
|
|
local settingsMenu = gameMenu.headerControls[locSettings]
|
|
|
|
if settingsMenu then -- an instance of ZO_TreeNode
|
|
local children = settingsMenu:GetChildren()
|
|
for i = 1, (children and #children or 0) do
|
|
local childNode = children[i]
|
|
local data = childNode:GetData()
|
|
if data and data.id == lam.panelId then
|
|
-- found LAM "Addon Settings" node, yay!
|
|
childNode:GetTree():SelectNode(childNode)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if sm:GetScene("gameMenuInGame"):GetState() == SCENE_SHOWN then
|
|
openAddonSettingsMenu()
|
|
else
|
|
sm:CallWhen("gameMenuInGame", SCENE_SHOWN, openAddonSettingsMenu)
|
|
sm:Show("gameMenuInGame")
|
|
end
|
|
end
|
|
|
|
local TwinOptionsContainer_Index = 0
|
|
local function TwinOptionsContainer(parent, leftWidget, rightWidget)
|
|
TwinOptionsContainer_Index = TwinOptionsContainer_Index + 1
|
|
local cParent = parent.scroll or parent
|
|
local panel = parent.panel or cParent
|
|
local container = wm:CreateControl("$(parent)TwinContainer" .. tostring(TwinOptionsContainer_Index),
|
|
cParent, CT_CONTROL)
|
|
container:SetResizeToFitDescendents(true)
|
|
container:SetAnchor(select(2, leftWidget:GetAnchor(0) ))
|
|
|
|
leftWidget:ClearAnchors()
|
|
leftWidget:SetAnchor(TOPLEFT, container, TOPLEFT)
|
|
rightWidget:SetAnchor(TOPLEFT, leftWidget, TOPRIGHT, 5, 0)
|
|
|
|
leftWidget:SetWidth( leftWidget:GetWidth() - 2.5 ) -- fixes bad alignment with 'full' controls
|
|
rightWidget:SetWidth( rightWidget:GetWidth() - 2.5 )
|
|
|
|
leftWidget:SetParent(container)
|
|
rightWidget:SetParent(container)
|
|
|
|
container.data = {type = "container"}
|
|
container.panel = panel
|
|
return container
|
|
end
|
|
|
|
--INTERNAL FUNCTION
|
|
--creates controls when options panel is first shown
|
|
--controls anchoring of these controls in the panel
|
|
local function CreateOptionsControls(panel)
|
|
local addonID = panel:GetName()
|
|
if(optionsState[addonID] == OPTIONS_CREATED) then
|
|
return false
|
|
elseif(optionsState[addonID] == OPTIONS_CREATION_RUNNING) then
|
|
return true
|
|
end
|
|
optionsState[addonID] = OPTIONS_CREATION_RUNNING
|
|
|
|
local function CreationFinished()
|
|
optionsState[addonID] = OPTIONS_CREATED
|
|
cm:FireCallbacks("LAM-PanelControlsCreated", panel)
|
|
OpenCurrentPanel()
|
|
end
|
|
|
|
local optionsTable = addonToOptionsMap[addonID]
|
|
if optionsTable then
|
|
local function CreateAndAnchorWidget(parent, widgetData, offsetX, offsetY, anchorTarget, wasHalf)
|
|
local widget
|
|
local status, err = pcall(function() widget = LAMCreateControl[widgetData.type](parent, widgetData) end)
|
|
if not status then
|
|
return err or true, offsetY, anchorTarget, wasHalf
|
|
else
|
|
local isHalf = (widgetData.width == "half")
|
|
if not anchorTarget then -- the first widget in a panel is just placed in the top left corner
|
|
widget:SetAnchor(TOPLEFT)
|
|
anchorTarget = widget
|
|
elseif wasHalf and isHalf then -- when the previous widget was only half width and this one is too, we place it on the right side
|
|
widget.lineControl = anchorTarget
|
|
isHalf = false
|
|
offsetY = 0
|
|
anchorTarget = TwinOptionsContainer(parent, anchorTarget, widget)
|
|
else -- otherwise we just put it below the previous one normally
|
|
widget:SetAnchor(TOPLEFT, anchorTarget, BOTTOMLEFT, 0, 15)
|
|
offsetY = 0
|
|
anchorTarget = widget
|
|
end
|
|
return false, offsetY, anchorTarget, isHalf
|
|
end
|
|
end
|
|
|
|
local THROTTLE_TIMEOUT, THROTTLE_COUNT = 10, 20
|
|
local fifo = {}
|
|
local anchorOffset, lastAddedControl, wasHalf
|
|
local CreateWidgetsInPanel, err
|
|
|
|
local function PrepareForNextPanel()
|
|
anchorOffset, lastAddedControl, wasHalf = 0, nil, false
|
|
end
|
|
|
|
local function SetupCreationCalls(parent, widgetDataTable)
|
|
fifo[#fifo + 1] = PrepareForNextPanel
|
|
local count = #widgetDataTable
|
|
for i = 1, count, THROTTLE_COUNT do
|
|
fifo[#fifo + 1] = function()
|
|
CreateWidgetsInPanel(parent, widgetDataTable, i, zo_min(i + THROTTLE_COUNT - 1, count))
|
|
end
|
|
end
|
|
return count ~= NonContiguousCount(widgetDataTable)
|
|
end
|
|
|
|
CreateWidgetsInPanel = function(parent, widgetDataTable, startIndex, endIndex)
|
|
for i=startIndex,endIndex do
|
|
local widgetData = widgetDataTable[i]
|
|
if not widgetData then
|
|
PrintLater("Skipped creation of missing entry in the settings menu of " .. addonID .. ".")
|
|
else
|
|
local widgetType = widgetData.type
|
|
local offsetX = 0
|
|
local isSubmenu = (widgetType == "submenu")
|
|
if isSubmenu then
|
|
wasHalf = false
|
|
offsetX = 5
|
|
end
|
|
|
|
err, anchorOffset, lastAddedControl, wasHalf = CreateAndAnchorWidget(parent, widgetData, offsetX, anchorOffset, lastAddedControl, wasHalf)
|
|
if err then
|
|
PrintLater(("Could not create %s '%s' of %s."):format(widgetData.type, GetStringFromValue(widgetData.name or "unnamed"), addonID))
|
|
end
|
|
|
|
if isSubmenu then
|
|
if SetupCreationCalls(lastAddedControl, widgetData.controls) then
|
|
PrintLater(("The sub menu '%s' of %s is missing some entries."):format(GetStringFromValue(widgetData.name or "unnamed"), addonID))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function DoCreateSettings()
|
|
if #fifo > 0 then
|
|
local nextCall = table.remove(fifo, 1)
|
|
nextCall()
|
|
if(nextCall == PrepareForNextPanel) then
|
|
DoCreateSettings()
|
|
else
|
|
zo_callLater(DoCreateSettings, THROTTLE_TIMEOUT)
|
|
end
|
|
else
|
|
CreationFinished()
|
|
end
|
|
end
|
|
|
|
if SetupCreationCalls(panel, optionsTable) then
|
|
PrintLater(("The settings menu of %s is missing some entries."):format(addonID))
|
|
end
|
|
DoCreateSettings()
|
|
else
|
|
CreationFinished()
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--INTERNAL FUNCTION
|
|
--handles switching between panels
|
|
local function ToggleAddonPanels(panel) --called in OnShow of newly shown panel
|
|
local currentlySelected = lam.currentAddonPanel
|
|
if currentlySelected and currentlySelected ~= panel then
|
|
currentlySelected:SetHidden(true)
|
|
CloseCurrentPanel()
|
|
end
|
|
lam.currentAddonPanel = panel
|
|
|
|
-- refresh visible rows to reflect panel IsHidden status
|
|
ZO_ScrollList_RefreshVisible(lam.addonList)
|
|
|
|
if not CreateOptionsControls(panel) then
|
|
OpenCurrentPanel()
|
|
end
|
|
|
|
cm:FireCallbacks("LAM-RefreshPanel", panel)
|
|
end
|
|
|
|
local CheckSafetyAndInitialize
|
|
|
|
--METHOD: REGISTER ADDON PANEL
|
|
--registers your addon with LibAddonMenu and creates a panel
|
|
--Usage:
|
|
-- addonID = "string"; unique ID which will be the global name of your panel
|
|
-- panelData = table; data object for your panel - see controls\panel.lua
|
|
function lam:RegisterAddonPanel(addonID, panelData)
|
|
CheckSafetyAndInitialize(addonID)
|
|
local container = lam:GetAddonPanelContainer()
|
|
local panel = lamcc.panel(container, panelData, addonID) --addonID==global name of panel
|
|
panel:SetHidden(true)
|
|
panel:SetAnchorFill(container)
|
|
panel:SetHandler("OnShow", ToggleAddonPanels)
|
|
|
|
local function stripMarkup(str)
|
|
return str:gsub("|[Cc]%x%x%x%x%x%x", ""):gsub("|[Rr]", "")
|
|
end
|
|
|
|
local filterParts = {panelData.name, nil, nil}
|
|
-- append keywords and author separately, the may be nil
|
|
filterParts[#filterParts + 1] = panelData.keywords
|
|
filterParts[#filterParts + 1] = panelData.author
|
|
|
|
local addonData = {
|
|
panel = panel,
|
|
name = stripMarkup(panelData.name),
|
|
filterText = stripMarkup(tconcat(filterParts, "\t")):lower(),
|
|
}
|
|
|
|
tinsert(addonsForList, addonData)
|
|
|
|
if panelData.slashCommand then
|
|
SLASH_COMMANDS[panelData.slashCommand] = function()
|
|
lam:OpenToPanel(panel)
|
|
end
|
|
end
|
|
|
|
return panel --return for authors creating options manually
|
|
end
|
|
|
|
|
|
--METHOD: REGISTER OPTION CONTROLS
|
|
--registers the options you want shown for your addon
|
|
--these are stored in a table where each key-value pair is the order
|
|
--of the options in the panel and the data for that control, respectively
|
|
--see exampleoptions.lua for an example
|
|
--see controls\<widget>.lua for each widget type
|
|
--Usage:
|
|
-- addonID = "string"; the same string passed to :RegisterAddonPanel
|
|
-- optionsTable = table; the table containing all of the options controls and their data
|
|
function lam:RegisterOptionControls(addonID, optionsTable) --optionsTable = {sliderData, buttonData, etc}
|
|
addonToOptionsMap[addonID] = optionsTable
|
|
end
|
|
|
|
--INTERNAL FUNCTION
|
|
--creates LAM's Addon Settings entry in ZO_GameMenu
|
|
local function CreateAddonSettingsMenuEntry()
|
|
local panelData = {
|
|
id = KEYBOARD_OPTIONS.currentPanelId,
|
|
name = util.L["PANEL_NAME"],
|
|
}
|
|
|
|
KEYBOARD_OPTIONS.currentPanelId = panelData.id + 1
|
|
KEYBOARD_OPTIONS.panelNames[panelData.id] = panelData.name
|
|
|
|
lam.panelId = panelData.id
|
|
|
|
local addonListSorted = false
|
|
|
|
function panelData.callback()
|
|
sm:AddFragment(lam:GetAddonSettingsFragment())
|
|
KEYBOARD_OPTIONS:ChangePanels(lam.panelId)
|
|
|
|
local title = LAMAddonSettingsWindow:GetNamedChild("Title")
|
|
title:SetText(panelData.name)
|
|
|
|
if not addonListSorted and #addonsForList > 0 then
|
|
local searchEdit = LAMAddonSettingsWindow:GetNamedChild("SearchFilterEdit")
|
|
--we're about to show our list for the first time - let's sort it
|
|
table.sort(addonsForList, function(a, b) return a.name < b.name end)
|
|
PopulateAddonList(lam.addonList, GetSearchFilterFunc(searchEdit))
|
|
addonListSorted = true
|
|
end
|
|
end
|
|
|
|
function panelData.unselectedCallback()
|
|
sm:RemoveFragment(lam:GetAddonSettingsFragment())
|
|
if SetCameraOptionsPreviewModeEnabled then -- available since API version 100011
|
|
SetCameraOptionsPreviewModeEnabled(false)
|
|
end
|
|
end
|
|
|
|
ZO_GameMenu_AddSettingPanel(panelData)
|
|
end
|
|
|
|
|
|
--INTERNAL FUNCTION
|
|
--creates the left-hand menu in LAM's window
|
|
local function CreateAddonList(name, parent)
|
|
local addonList = wm:CreateControlFromVirtual(name, parent, "ZO_ScrollList")
|
|
|
|
local function addonListRow_OnMouseDown(control, button)
|
|
if button == 1 then
|
|
local data = ZO_ScrollList_GetData(control)
|
|
ZO_ScrollList_SelectData(addonList, data, control)
|
|
end
|
|
end
|
|
|
|
local function addonListRow_OnMouseEnter(control)
|
|
ZO_ScrollList_MouseEnter(addonList, control)
|
|
end
|
|
|
|
local function addonListRow_OnMouseExit(control)
|
|
ZO_ScrollList_MouseExit(addonList, control)
|
|
end
|
|
|
|
local function addonListRow_Select(previouslySelectedData, selectedData, reselectingDuringRebuild)
|
|
if not reselectingDuringRebuild then
|
|
if previouslySelectedData then
|
|
previouslySelectedData.panel:SetHidden(true)
|
|
end
|
|
if selectedData then
|
|
selectedData.panel:SetHidden(false)
|
|
PlaySound(SOUNDS.MENU_SUBCATEGORY_SELECTION)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function addonListRow_Setup(control, data)
|
|
control:SetText(data.name)
|
|
control:SetSelected(not data.panel:IsHidden())
|
|
end
|
|
|
|
ZO_ScrollList_AddDataType(addonList, ADDON_DATA_TYPE, "ZO_SelectableLabel", 28, addonListRow_Setup)
|
|
-- I don't know how to make highlights clear properly; they often
|
|
-- get stuck and after a while the list is full of highlighted rows
|
|
--ZO_ScrollList_EnableHighlight(addonList, "ZO_ThinListHighlight")
|
|
ZO_ScrollList_EnableSelection(addonList, "ZO_ThinListHighlight", addonListRow_Select)
|
|
|
|
local addonDataType = ZO_ScrollList_GetDataTypeTable(addonList, ADDON_DATA_TYPE)
|
|
local addonListRow_CreateRaw = addonDataType.pool.m_Factory
|
|
|
|
local function addonListRow_Create(pool)
|
|
local control = addonListRow_CreateRaw(pool)
|
|
control:SetHandler("OnMouseDown", addonListRow_OnMouseDown)
|
|
--control:SetHandler("OnMouseEnter", addonListRow_OnMouseEnter)
|
|
--control:SetHandler("OnMouseExit", addonListRow_OnMouseExit)
|
|
control:SetHeight(28)
|
|
control:SetFont("ZoFontHeader")
|
|
control:SetHorizontalAlignment(TEXT_ALIGN_LEFT)
|
|
control:SetVerticalAlignment(TEXT_ALIGN_CENTER)
|
|
control:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS)
|
|
return control
|
|
end
|
|
|
|
addonDataType.pool.m_Factory = addonListRow_Create
|
|
|
|
return addonList
|
|
end
|
|
|
|
|
|
--INTERNAL FUNCTION
|
|
local function CreateSearchFilterBox(name, parent)
|
|
local boxControl = wm:CreateControl(name, parent, CT_CONTROL)
|
|
|
|
local srchButton = wm:CreateControl("$(parent)Button", boxControl, CT_BUTTON)
|
|
srchButton:SetDimensions(32, 32)
|
|
srchButton:SetAnchor(LEFT, nil, LEFT, 2, 0)
|
|
srchButton:SetNormalTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_up.dds")
|
|
srchButton:SetPressedTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_down.dds")
|
|
srchButton:SetMouseOverTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_over.dds")
|
|
|
|
local srchEdit = wm:CreateControlFromVirtual("$(parent)Edit", boxControl, "ZO_DefaultEdit")
|
|
srchEdit:SetAnchor(LEFT, srchButton, RIGHT, 4, 1)
|
|
srchEdit:SetAnchor(RIGHT, nil, RIGHT, -4, 1)
|
|
srchEdit:SetColor(ZO_NORMAL_TEXT:UnpackRGBA())
|
|
|
|
local srchBg = wm:CreateControl("$(parent)Bg", boxControl, CT_BACKDROP)
|
|
srchBg:SetAnchorFill()
|
|
srchBg:SetAlpha(0)
|
|
srchBg:SetCenterColor(0, 0, 0, 0.5)
|
|
srchBg:SetEdgeColor(ZO_DISABLED_TEXT:UnpackRGBA())
|
|
srchBg:SetEdgeTexture("", 1, 1, 0, 0)
|
|
|
|
-- search backdrop should appear whenever you hover over either
|
|
-- the magnifying glass button or the edit field (which is only
|
|
-- visible when it contains some text), and also while the edit
|
|
-- field has keyboard focus
|
|
|
|
local srchActive = false
|
|
local srchHover = false
|
|
|
|
local function srchBgUpdateAlpha()
|
|
if srchActive or srchEdit:HasFocus() then
|
|
srchBg:SetAlpha(srchHover and 0.8 or 0.6)
|
|
else
|
|
srchBg:SetAlpha(srchHover and 0.6 or 0.0)
|
|
end
|
|
end
|
|
|
|
local function srchMouseEnter(control)
|
|
srchHover = true
|
|
srchBgUpdateAlpha()
|
|
end
|
|
|
|
local function srchMouseExit(control)
|
|
srchHover = false
|
|
srchBgUpdateAlpha()
|
|
end
|
|
|
|
boxControl:SetMouseEnabled(true)
|
|
boxControl:SetHitInsets(1, 1, -1, -1)
|
|
boxControl:SetHandler("OnMouseEnter", srchMouseEnter)
|
|
boxControl:SetHandler("OnMouseExit", srchMouseExit)
|
|
|
|
srchButton:SetHandler("OnMouseEnter", srchMouseEnter)
|
|
srchButton:SetHandler("OnMouseExit", srchMouseExit)
|
|
|
|
local focusLostTime = 0
|
|
|
|
srchButton:SetHandler("OnClicked", function(self)
|
|
srchEdit:Clear()
|
|
if GetFrameTimeMilliseconds() - focusLostTime < 100 then
|
|
-- re-focus the edit box if it lost focus due to this
|
|
-- button click (note that this handler may run a few
|
|
-- frames later)
|
|
srchEdit:TakeFocus()
|
|
end
|
|
end)
|
|
|
|
srchEdit:SetHandler("OnMouseEnter", srchMouseEnter)
|
|
srchEdit:SetHandler("OnMouseExit", srchMouseExit)
|
|
srchEdit:SetHandler("OnFocusGained", srchBgUpdateAlpha)
|
|
|
|
srchEdit:SetHandler("OnFocusLost", function()
|
|
focusLostTime = GetFrameTimeMilliseconds()
|
|
srchBgUpdateAlpha()
|
|
end)
|
|
|
|
srchEdit:SetHandler("OnEscape", function(self)
|
|
self:Clear()
|
|
self:LoseFocus()
|
|
end)
|
|
|
|
srchEdit:SetHandler("OnTextChanged", function(self)
|
|
local filterFunc = GetSearchFilterFunc(self)
|
|
if filterFunc then
|
|
srchActive = true
|
|
srchBg:SetEdgeColor(ZO_SECOND_CONTRAST_TEXT:UnpackRGBA())
|
|
srchButton:SetState(BSTATE_PRESSED)
|
|
else
|
|
srchActive = false
|
|
srchBg:SetEdgeColor(ZO_DISABLED_TEXT:UnpackRGBA())
|
|
srchButton:SetState(BSTATE_NORMAL)
|
|
end
|
|
srchBgUpdateAlpha()
|
|
PopulateAddonList(lam.addonList, filterFunc)
|
|
PlaySound(SOUNDS.SPINNER_DOWN)
|
|
end)
|
|
|
|
return boxControl
|
|
end
|
|
|
|
|
|
--INTERNAL FUNCTION
|
|
--creates LAM's Addon Settings top-level window
|
|
local function CreateAddonSettingsWindow()
|
|
local tlw = wm:CreateTopLevelWindow("LAMAddonSettingsWindow")
|
|
tlw:SetHidden(true)
|
|
tlw:SetDimensions(1010, 914) -- same height as ZO_OptionsWindow
|
|
|
|
ZO_ReanchorControlForLeftSidePanel(tlw)
|
|
|
|
-- create black background for the window (mimic ZO_RightFootPrintBackground)
|
|
|
|
local bgLeft = wm:CreateControl("$(parent)BackgroundLeft", tlw, CT_TEXTURE)
|
|
bgLeft:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_left.dds")
|
|
bgLeft:SetDimensions(1024, 1024)
|
|
bgLeft:SetAnchor(TOPLEFT, nil, TOPLEFT)
|
|
bgLeft:SetDrawLayer(DL_BACKGROUND)
|
|
bgLeft:SetExcludeFromResizeToFitExtents(true)
|
|
|
|
local bgRight = wm:CreateControl("$(parent)BackgroundRight", tlw, CT_TEXTURE)
|
|
bgRight:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_right.dds")
|
|
bgRight:SetDimensions(64, 1024)
|
|
bgRight:SetAnchor(TOPLEFT, bgLeft, TOPRIGHT)
|
|
bgRight:SetDrawLayer(DL_BACKGROUND)
|
|
bgRight:SetExcludeFromResizeToFitExtents(true)
|
|
|
|
-- create gray background for addon list (mimic ZO_TreeUnderlay)
|
|
|
|
local underlayLeft = wm:CreateControl("$(parent)UnderlayLeft", tlw, CT_TEXTURE)
|
|
underlayLeft:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_indexArea_left.dds")
|
|
underlayLeft:SetDimensions(256, 1024)
|
|
underlayLeft:SetAnchor(TOPLEFT, bgLeft, TOPLEFT)
|
|
underlayLeft:SetDrawLayer(DL_BACKGROUND)
|
|
underlayLeft:SetExcludeFromResizeToFitExtents(true)
|
|
|
|
local underlayRight = wm:CreateControl("$(parent)UnderlayRight", tlw, CT_TEXTURE)
|
|
underlayRight:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_indexArea_right.dds")
|
|
underlayRight:SetDimensions(128, 1024)
|
|
underlayRight:SetAnchor(TOPLEFT, underlayLeft, TOPRIGHT)
|
|
underlayRight:SetDrawLayer(DL_BACKGROUND)
|
|
underlayRight:SetExcludeFromResizeToFitExtents(true)
|
|
|
|
-- create title bar (mimic ZO_OptionsWindow)
|
|
|
|
local title = wm:CreateControl("$(parent)Title", tlw, CT_LABEL)
|
|
title:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 70)
|
|
title:SetFont("ZoFontWinH1")
|
|
title:SetModifyTextType(MODIFY_TEXT_TYPE_UPPERCASE)
|
|
|
|
local divider = wm:CreateControlFromVirtual("$(parent)Divider", tlw, "ZO_Options_Divider")
|
|
divider:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 108)
|
|
|
|
-- create search filter box
|
|
|
|
local srchBox = CreateSearchFilterBox("$(parent)SearchFilter", tlw)
|
|
srchBox:SetAnchor(TOPLEFT, nil, TOPLEFT, 63, 120)
|
|
srchBox:SetDimensions(260, 30)
|
|
|
|
-- create scrollable addon list
|
|
|
|
local addonList = CreateAddonList("$(parent)AddonList", tlw)
|
|
addonList:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 160)
|
|
addonList:SetDimensions(285, 665)
|
|
|
|
lam.addonList = addonList -- for easy access from elsewhere
|
|
|
|
-- create container for option panels
|
|
|
|
local panelContainer = wm:CreateControl("$(parent)PanelContainer", tlw, CT_CONTROL)
|
|
panelContainer:SetAnchor(TOPLEFT, nil, TOPLEFT, 365, 120)
|
|
panelContainer:SetDimensions(645, 675)
|
|
|
|
local defaultButton = wm:CreateControlFromVirtual("$(parent)ResetToDefaultButton", tlw, "ZO_DialogButton")
|
|
ZO_KeybindButtonTemplate_Setup(defaultButton, "OPTIONS_LOAD_DEFAULTS", HandleLoadDefaultsPressed, GetString(SI_OPTIONS_DEFAULTS))
|
|
defaultButton:SetAnchor(TOPLEFT, panelContainer, BOTTOMLEFT, 0, 2)
|
|
lam.defaultButton = defaultButton
|
|
|
|
local applyButton = wm:CreateControlFromVirtual("$(parent)ApplyButton", tlw, "ZO_DialogButton")
|
|
ZO_KeybindButtonTemplate_Setup(applyButton, "OPTIONS_APPLY_CHANGES", HandleReloadUIPressed, GetString(SI_ADDON_MANAGER_RELOAD))
|
|
applyButton:SetAnchor(TOPRIGHT, panelContainer, BOTTOMRIGHT, 0, 2)
|
|
applyButton:SetHidden(true)
|
|
lam.applyButton = applyButton
|
|
|
|
return tlw
|
|
end
|
|
|
|
|
|
--INITIALIZING
|
|
local safeToInitialize = false
|
|
local hasInitialized = false
|
|
|
|
local eventHandle = table.concat({MAJOR, MINOR}, "r")
|
|
local function OnLoad(_, addonName)
|
|
-- wait for the first loaded event
|
|
em:UnregisterForEvent(eventHandle, EVENT_ADD_ON_LOADED)
|
|
safeToInitialize = true
|
|
end
|
|
em:RegisterForEvent(eventHandle, EVENT_ADD_ON_LOADED, OnLoad)
|
|
|
|
local function OnActivated(_, initial)
|
|
em:UnregisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED)
|
|
FlushMessages()
|
|
|
|
local reopenPanel = RetrievePanelForReopening()
|
|
if not initial and reopenPanel then
|
|
lam:OpenToPanel(reopenPanel)
|
|
end
|
|
end
|
|
em:RegisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED, OnActivated)
|
|
|
|
function CheckSafetyAndInitialize(addonID)
|
|
if not safeToInitialize then
|
|
local msg = string.format("The panel with id '%s' was registered before addon loading has completed. This might break the AddOn Settings menu.", addonID)
|
|
PrintLater(msg)
|
|
end
|
|
if not hasInitialized then
|
|
hasInitialized = true
|
|
end
|
|
end
|
|
|
|
|
|
--TODO documentation
|
|
function lam:GetAddonPanelContainer()
|
|
local fragment = lam:GetAddonSettingsFragment()
|
|
local window = fragment:GetControl()
|
|
return window:GetNamedChild("PanelContainer")
|
|
end
|
|
|
|
|
|
--TODO documentation
|
|
function lam:GetAddonSettingsFragment()
|
|
assert(hasInitialized or safeToInitialize)
|
|
if not LAMAddonSettingsFragment then
|
|
local window = CreateAddonSettingsWindow()
|
|
LAMAddonSettingsFragment = ZO_FadeSceneFragment:New(window, true, 100)
|
|
LAMAddonSettingsFragment:RegisterCallback("StateChange", function(oldState, newState)
|
|
if(newState == SCENE_FRAGMENT_SHOWN) then
|
|
InitKeybindActions()
|
|
PushActionLayerByName("OptionsWindow")
|
|
OpenCurrentPanel()
|
|
elseif(newState == SCENE_FRAGMENT_HIDDEN) then
|
|
CloseCurrentPanel()
|
|
RemoveActionLayerByName("OptionsWindow")
|
|
ShowReloadDialogIfNeeded()
|
|
end
|
|
end)
|
|
CreateAddonSettingsMenuEntry()
|
|
end
|
|
return LAMAddonSettingsFragment
|
|
end
|