2019-02-10 14:12:44 +00:00
-- LibAddonMenu-2.0 & its files © Ryan Lakanen (Seerah) --
-- Distributed under The Artistic License 2.0 (see LICENSE) --
2016-10-10 19:48:47 +00:00
------------------------------------------------------------------
--Register LAM with LibStub
2019-10-26 20:14:39 +00:00
local MAJOR , MINOR = " LibAddonMenu-2.0 " , 30
2016-10-10 19:48:47 +00:00
local lam , oldminor = LibStub : NewLibrary ( MAJOR , MINOR )
2019-02-10 14:12:44 +00:00
if not lam then return end --the same or newer version of this lib is already loaded into memory
2019-10-26 20:14:39 +00:00
LibAddonMenu2 = lam
2016-10-10 19:48:47 +00:00
local messages = { }
local MESSAGE_PREFIX = " [LAM2] "
local function PrintLater ( msg )
2019-02-10 14:12:44 +00:00
if CHAT_SYSTEM.primaryContainer then
d ( MESSAGE_PREFIX .. msg )
else
messages [ # messages + 1 ] = msg
end
2016-10-10 19:48:47 +00:00
end
local function FlushMessages ( )
2019-02-10 14:12:44 +00:00
for i = 1 , # messages do
d ( MESSAGE_PREFIX .. messages [ i ] )
end
messages = { }
2016-10-10 19:48:47 +00:00
end
2019-10-26 20:14:39 +00:00
local logger
if LibDebugLogger then
logger = LibDebugLogger ( MAJOR )
end
2016-10-10 19:48:47 +00:00
if LAMSettingsPanelCreated and not LAMCompatibilityWarning then
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
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
2019-02-10 14:12:44 +00:00
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 "
2016-10-10 19:48:47 +00:00
local addonsForList = { }
local addonToOptionsMap = { }
2019-02-10 14:12:44 +00:00
local optionsState = { }
2016-10-10 19:48:47 +00:00
lam.widgets = lam.widgets or { }
local widgets = lam.widgets
2019-02-10 14:12:44 +00:00
lam.util = lam.util or { }
2016-10-10 19:48:47 +00:00
local util = lam.util
2019-02-10 14:12:44 +00:00
lam.controlsForReload = lam.controlsForReload or { }
local controlsForReload = lam.controlsForReload
local function GetDefaultValue ( default )
if type ( default ) == " function " then
return default ( )
end
return default
end
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
local function GetStringFromValue ( value )
if type ( value ) == " function " then
return value ( )
elseif type ( value ) == " number " then
return GetString ( value )
end
return value
2016-10-10 19:48:47 +00:00
end
2019-10-26 20:14:39 +00:00
local function GetColorForState ( disabled )
return disabled and ZO_DEFAULT_DISABLED_COLOR or ZO_DEFAULT_ENABLED_COLOR
end
2016-10-10 19:48:47 +00:00
local function CreateBaseControl ( parent , controlData , controlName )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
local function CreateLabelAndContainerControl ( parent , controlData , controlName )
2019-02-10 14:12:44 +00:00
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
2019-10-26 20:14:39 +00:00
if lam.applyButton then
lam.applyButton : SetHidden ( not lam.requiresReload )
end
2019-02-10 14:12:44 +00:00
end
local function RequestRefreshIfNeeded ( control )
-- if our parent window wants to refresh controls, then fire the callback
2019-10-26 20:14:39 +00:00
local panel = GetTopPanel ( control )
2019-02-10 14:12:44 +00:00
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
2019-10-26 20:14:39 +00:00
warning = string.format ( " %s " , util.L [ " RELOAD_UI_WARNING " ] )
2019-02-10 14:12:44 +00:00
else
2019-10-26 20:14:39 +00:00
warning = string.format ( " %s \n \n %s " , warning , util.L [ " RELOAD_UI_WARNING " ] )
2019-02-10 14:12:44 +00:00
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 " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " Feedback " ,
TRANSLATION = " Translation " ,
DONATION = " Donate " ,
2019-02-10 14:12:44 +00:00
PANEL_INFO_FONT = " $(CHAT_FONT)|14|soft-shadow-thin " ,
2019-10-26 20:14:39 +00:00
RELOAD_UI_WARNING = " Changes to this setting require a UI reload in order to take effect. " ,
RELOAD_DIALOG_TITLE = " UI Reload Required " ,
RELOAD_DIALOG_TEXT = " Some changes require a UI reload in order to take effect. Do you want to reload now or discard the changes? " ,
2019-02-10 14:12:44 +00:00
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 " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " Feedback " ,
TRANSLATION = " Traduzione " ,
DONATION = " Donare " ,
2019-02-10 14:12:44 +00:00
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 " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " Réaction " ,
TRANSLATION = " Traduction " ,
DONATION = " Donner " ,
2019-02-10 14:12:44 +00:00
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 " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " Feedback " ,
TRANSLATION = " Übersetzung " ,
DONATION = " Spende " ,
2019-02-10 14:12:44 +00:00
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 = " Посетить сайт " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " отзыв " ,
TRANSLATION = " Перевод " ,
DONATION = " жертвовать " ,
2019-02-10 14:12:44 +00:00
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 " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " Reaccion " ,
TRANSLATION = " Traducción " ,
DONATION = " Donar " ,
2019-02-10 14:12:44 +00:00
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 = " ウェブサイトを見る " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " フィードバック " ,
TRANSLATION = " 訳書 " ,
DONATION = " 寄贈する " ,
2019-02-10 14:12:44 +00:00
} ,
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ć " ,
} ,
2019-10-26 20:14:39 +00:00
br = { -- provided by mlsevero & FelipeS11
2019-02-10 14:12:44 +00:00
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 " ,
2019-10-26 20:14:39 +00:00
FEEDBACK = " Feedback " ,
TRANSLATION = " Tradução " ,
DONATION = " Doação " ,
RELOAD_UI_WARNING = " Mudanças nessa configuração requerem o recarregamento da UI para ter efeito. " ,
RELOAD_DIALOG_TITLE = " Recarregamento da UI requerida " ,
RELOAD_DIALOG_TEXT = " Algumas mudanças requerem o recarregamento da UI para ter efeito. Você deseja recarregar agora ou descartar as mudanças? " ,
RELOAD_DIALOG_RELOAD_BUTTON = " Recarregar " ,
RELOAD_DIALOG_DISCARD_BUTTON = " Descartar " ,
2019-02-10 14:12:44 +00:00
} ,
}
2019-10-26 20:14:39 +00:00
do
local EsoKR = EsoKR
if EsoKR and EsoKR : isKorean ( ) then
util.L = ZO_ShallowTableCopy ( { -- provided by whya5448
PANEL_NAME = EsoKR : E ( " 애드온 " ) ,
AUTHOR = string.format ( " %s: <<X:1>> " , GetString ( SI_ADDON_MANAGER_AUTHOR ) ) , -- "Author: <<X:1>>"
VERSION = EsoKR : E ( " 버전: <<X:1>> " ) ,
WEBSITE = EsoKR : E ( " 웹사이트 방문 " ) ,
FEEDBACK = EsoKR : E ( " 피드백 " ) ,
TRANSLATION = EsoKR : E ( " 번역 " ) ,
DONATION = EsoKR : E ( " 기부 " ) ,
PANEL_INFO_FONT = " EsoKR/fonts/Univers57.otf|14|soft-shadow-thin " ,
RELOAD_UI_WARNING = EsoKR : E ( " 이 설정을 변경하면 효과를 적용하기위해 UI 새로고침이 필요합니다. " ) ,
RELOAD_DIALOG_TITLE = EsoKR : E ( " UI 새로고침 필요 " ) ,
RELOAD_DIALOG_TEXT = EsoKR : E ( " 변경된 설정 중 UI 새로고침을 필요로하는 사항이 있습니다. 지금 새로고침하시겠습니까? 아니면 변경을 취소하시겠습니까? " ) ,
RELOAD_DIALOG_RELOAD_BUTTON = EsoKR : E ( " 새로고침 " ) ,
RELOAD_DIALOG_DISCARD_BUTTON = EsoKR : E ( " 변경취소 " ) ,
} , localization [ " en " ] )
else
util.L = ZO_ShallowTableCopy ( localization [ GetCVar ( " Language.2 " ) ] or { } , localization [ " en " ] )
end
end
2019-02-10 14:12:44 +00:00
util.GetTooltipText = GetStringFromValue -- deprecated, use util.GetStringFromValue instead
util.GetStringFromValue = GetStringFromValue
util.GetDefaultValue = GetDefaultValue
2019-10-26 20:14:39 +00:00
util.GetColorForState = GetColorForState
2016-10-10 19:48:47 +00:00
util.CreateBaseControl = CreateBaseControl
util.CreateLabelAndContainerControl = CreateLabelAndContainerControl
2019-02-10 14:12:44 +00:00
util.RequestRefreshIfNeeded = RequestRefreshIfNeeded
util.RegisterForRefreshIfNeeded = RegisterForRefreshIfNeeded
util.RegisterForReloadIfNeeded = RegisterForReloadIfNeeded
util.GetTopPanel = GetTopPanel
util.ShowConfirmationDialog = ShowConfirmationDialog
util.UpdateWarning = UpdateWarning
2016-10-10 19:48:47 +00:00
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`
2019-02-10 14:12:44 +00:00
-- into view (does nothing if there is no such row in the list)
2016-10-10 19:48:47 +00:00
--unlike ZO_ScrollList_ScrollDataIntoView, this function accounts for
2019-02-10 14:12:44 +00:00
-- fading near the list's edges - it avoids the fading area by scrolling
-- a little further than the ZO function
2016-10-10 19:48:47 +00:00
local function ScrollDataIntoView ( list , data )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
--constructs a string pattern from the text in `searchEdit` control
2019-02-10 14:12:44 +00:00
-- * metacharacters are escaped, losing their special meaning
-- * whitespace matches anything (including empty substring)
2016-10-10 19:48:47 +00:00
--if there is nothing but whitespace, returns nil
--otherwise returns a filter function, which takes a `data` table argument
2019-02-10 14:12:44 +00:00
-- and returns true iff `data.filterText` matches the pattern
2016-10-10 19:48:47 +00:00
local function GetSearchFilterFunc ( searchEdit )
2019-02-10 14:12:44 +00:00
local text = searchEdit : GetText ( ) : lower ( )
local pattern = text : match ( " (%S+.-)%s*$ " )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
if not pattern then -- nothing but whitespace
return nil
end
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- escape metacharacters, e.g. "ESO-Datenbank.de" => "ESO%-Datenbank%.de"
pattern = pattern : gsub ( " [-*+?^$().[%]%%] " , " %%%0 " )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- replace whitespace with "match shortest anything"
pattern = pattern : gsub ( " %s+ " , " .- " )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
return function ( data )
return data.filterText : lower ( ) : find ( pattern ) ~= nil
end
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
--populates `addonList` with entries from `addonsForList`
2019-02-10 14:12:44 +00:00
-- addonList = ZO_ScrollList control
-- filter = [optional] function(data)
2016-10-10 19:48:47 +00:00
local function PopulateAddonList ( addonList , filter )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--METHOD: REGISTER WIDGET--
--each widget has its version checked before loading,
--so we only have the most recent one in memory
--Usage:
2019-02-10 14:12:44 +00:00
-- widgetType = "string"; the type of widget being registered
-- widgetVersion = integer; the widget's version number
2016-10-10 19:48:47 +00:00
LAMCreateControl = LAMCreateControl or { }
local lamcc = LAMCreateControl
function lam : RegisterWidget ( widgetType , widgetVersion )
2019-02-10 14:12:44 +00:00
if widgets [ widgetType ] and widgets [ widgetType ] >= widgetVersion then
return false
else
widgets [ widgetType ] = widgetVersion
return true
end
2016-10-10 19:48:47 +00:00
end
2019-02-10 14:12:44 +00:00
-- 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
2016-10-10 19:48:47 +00:00
--METHOD: OPEN TO ADDON PANEL--
--opens to a specific addon's option panel
--Usage:
2019-02-10 14:12:44 +00:00
-- panel = userdata; the panel returned by the :RegisterOptionsPanel method
2016-10-10 19:48:47 +00:00
local locSettings = GetString ( SI_GAME_MENU_SETTINGS )
function lam : OpenToPanel ( panel )
2019-02-10 14:12:44 +00:00
-- 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
2016-10-10 19:48:47 +00:00
end
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
--INTERNAL FUNCTION
--creates controls when options panel is first shown
--controls anchoring of these controls in the panel
local function CreateOptionsControls ( panel )
2019-02-10 14:12:44 +00:00
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 ) )
2019-10-26 20:14:39 +00:00
if logger then
logger : Error ( err )
end
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
--handles switching between panels
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
end
local CheckSafetyAndInitialize
2019-10-26 20:14:39 +00:00
local function ShowSetHandlerWarning ( panel , handler )
local hint
if ( handler == " OnShow " or handler == " OnEffectivelyShown " ) then
hint = " 'LAM-PanelControlsCreated' or 'LAM-PanelOpened' "
elseif ( handler == " OnHide " or handler == " OnEffectivelyHidden " ) then
hint = " 'LAM-PanelClosed' "
end
if hint then
local message = ( " Setting a handler on a panel is not recommended. Use the global callback %s instead. (%s on %s) " ) : format ( hint , handler , panel.data . name )
PrintLater ( message )
if logger then
logger : Warn ( message )
end
end
end
2016-10-10 19:48:47 +00:00
--METHOD: REGISTER ADDON PANEL
--registers your addon with LibAddonMenu and creates a panel
--Usage:
2019-02-10 14:12:44 +00:00
-- addonID = "string"; unique ID which will be the global name of your panel
-- panelData = table; data object for your panel - see controls\panel.lua
2016-10-10 19:48:47 +00:00
function lam : RegisterAddonPanel ( addonID , panelData )
2019-02-10 14:12:44 +00:00
CheckSafetyAndInitialize ( addonID )
local container = lam : GetAddonPanelContainer ( )
local panel = lamcc.panel ( container , panelData , addonID ) --addonID==global name of panel
panel : SetHidden ( true )
panel : SetAnchorFill ( container )
2019-10-26 20:14:39 +00:00
panel : SetHandler ( " OnEffectivelyShown " , ToggleAddonPanels )
ZO_PreHook ( panel , " SetHandler " , ShowSetHandlerWarning )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
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:
2019-02-10 14:12:44 +00:00
-- 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
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
--creates LAM's Addon Settings entry in ZO_GameMenu
local function CreateAddonSettingsMenuEntry ( )
2019-02-10 14:12:44 +00:00
local panelData = {
id = KEYBOARD_OPTIONS.currentPanelId ,
name = util.L [ " PANEL_NAME " ] ,
}
KEYBOARD_OPTIONS.currentPanelId = panelData.id + 1
KEYBOARD_OPTIONS.panelNames [ panelData.id ] = panelData.name
2019-10-26 20:14:39 +00:00
KEYBOARD_OPTIONS.controlTable [ panelData.id ] = { }
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
--creates the left-hand menu in LAM's window
local function CreateAddonList ( name , parent )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
local function CreateSearchFilterBox ( name , parent )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--INTERNAL FUNCTION
--creates LAM's Addon Settings top-level window
local function CreateAddonSettingsWindow ( )
2019-02-10 14:12:44 +00:00
local tlw = wm : CreateTopLevelWindow ( " LAMAddonSettingsWindow " )
tlw : SetHidden ( true )
tlw : SetDimensions ( 1010 , 914 ) -- same height as ZO_OptionsWindow
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
ZO_ReanchorControlForLeftSidePanel ( tlw )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- create black background for the window (mimic ZO_RightFootPrintBackground)
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- create gray background for addon list (mimic ZO_TreeUnderlay)
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- create title bar (mimic ZO_OptionsWindow)
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
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 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
local divider = wm : CreateControlFromVirtual ( " $(parent)Divider " , tlw , " ZO_Options_Divider " )
divider : SetAnchor ( TOPLEFT , nil , TOPLEFT , 65 , 108 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- create search filter box
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
local srchBox = CreateSearchFilterBox ( " $(parent)SearchFilter " , tlw )
srchBox : SetAnchor ( TOPLEFT , nil , TOPLEFT , 63 , 120 )
srchBox : SetDimensions ( 260 , 30 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- create scrollable addon list
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
local addonList = CreateAddonList ( " $(parent)AddonList " , tlw )
addonList : SetAnchor ( TOPLEFT , nil , TOPLEFT , 65 , 160 )
addonList : SetDimensions ( 285 , 665 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
lam.addonList = addonList -- for easy access from elsewhere
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
-- create container for option panels
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
local panelContainer = wm : CreateControl ( " $(parent)PanelContainer " , tlw , CT_CONTROL )
panelContainer : SetAnchor ( TOPLEFT , nil , TOPLEFT , 365 , 120 )
panelContainer : SetDimensions ( 645 , 675 )
2016-10-10 19:48:47 +00:00
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--INITIALIZING
local safeToInitialize = false
local hasInitialized = false
local eventHandle = table.concat ( { MAJOR , MINOR } , " r " )
local function OnLoad ( _ , addonName )
2019-02-10 14:12:44 +00:00
-- wait for the first loaded event
em : UnregisterForEvent ( eventHandle , EVENT_ADD_ON_LOADED )
safeToInitialize = true
2016-10-10 19:48:47 +00:00
end
em : RegisterForEvent ( eventHandle , EVENT_ADD_ON_LOADED , OnLoad )
2019-02-10 14:12:44 +00:00
local function OnActivated ( _ , initial )
em : UnregisterForEvent ( eventHandle , EVENT_PLAYER_ACTIVATED )
FlushMessages ( )
local reopenPanel = RetrievePanelForReopening ( )
if not initial and reopenPanel then
lam : OpenToPanel ( reopenPanel )
end
2016-10-10 19:48:47 +00:00
end
em : RegisterForEvent ( eventHandle , EVENT_PLAYER_ACTIVATED , OnActivated )
function CheckSafetyAndInitialize ( addonID )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end
--TODO documentation
function lam : GetAddonPanelContainer ( )
2019-02-10 14:12:44 +00:00
local fragment = lam : GetAddonSettingsFragment ( )
local window = fragment : GetControl ( )
return window : GetNamedChild ( " PanelContainer " )
2016-10-10 19:48:47 +00:00
end
--TODO documentation
function lam : GetAddonSettingsFragment ( )
2019-02-10 14:12:44 +00:00
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
2016-10-10 19:48:47 +00:00
end