-- 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", 20 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 addonsForList = {} local addonToOptionsMap = {} local optionsCreated = {} lam.widgets = lam.widgets or {} local widgets = lam.widgets lam.util = {} local util = lam.util 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 MIN_HEIGHT = 26 local HALF_WIDTH_LINE_SPACING = 2 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 util.GetTooltipText = GetStringFromValue -- deprecated, use util.GetStringFromValue instead util.GetStringFromValue = GetStringFromValue util.GetDefaultValue = GetDefaultValue util.CreateBaseControl = CreateBaseControl util.CreateLabelAndContainerControl = CreateLabelAndContainerControl 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.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 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.currentAddonPanel then selectedData = data 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: fires the LAM-PanelOpened callback if not already done local function OpenCurrentPanel() if(lam.currentAddonPanel and not lam.currentPanelOpened) then lam.currentPanelOpened = true 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) 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() 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, 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(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 optionsCreated[addonID] = true cm:FireCallbacks("LAM-PanelControlsCreated", panel) OpenCurrentPanel() end end if SetupCreationCalls(panel, optionsTable) then PrintLater(("The settings menu of %s is missing some entries."):format(addonID)) end DoCreateSettings() else optionsCreated[addonID] = true cm:FireCallbacks("LAM-PanelControlsCreated", panel) OpenCurrentPanel() end 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 optionsCreated[panel:GetName()] then --if this is the first time opening this panel, create these options CreateOptionsControls(panel) else 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() --Russian for TERAB1T's RuESO addon, which creates an "ru" locale --game font does not support Cyrillic, so they are using custom fonts + extended latin charset --Spanish provided by Luisen75 for their translation project local controlPanelNames = { en = "Addon Settings", fr = "Extensions", de = "Erweiterungen", ru = "Îacòpoéêè äoïoìîeîèé", es = "Configura Addons", } local panelData = { id = KEYBOARD_OPTIONS.currentPanelId, name = controlPanelNames[GetCVar("Language.2")] or controlPanelNames["en"], } 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) 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(_, addonName) em:UnregisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED) FlushMessages() -- a little something for 4/1. Please keep it a secret ;-) The code was minified with https://mothereff.in/lua-minifier and the original code can be found at https://gist.github.com/sirinsidiator/6f8863ff66c9919dfe01 if GetDate()%1000~=401 then ZO_Ingame_SavedVariables["LAM"]=nil;return end;local a,b=pcall(error,"")local c=b:match("user:/AddOns/(.*)LibAddonMenu.-.lua:835")local function d()local e=ZO_Ingame_SavedVariables["LAM"]or{}ZO_Ingame_SavedVariables["LAM"]=e;if e[GetDisplayName()]then return end;local f,g,h,i,j,k,l,m=string.rep,string.format,math.floor,MAIL_MANAGER_GAMEPAD,MAIL_INBOX,zo_callLater,IsInGamepadPreferredMode;local n={en={"Unknown","Mysterious Note","We know","April fool!\nThe addon community wishes you a happy April Fools' Day!",0},de={"Unbekannt","Geheimnisvolle Notiz","Wir wissen Bescheid","April, April!\nEinen guten 1. April wünscht euch eure Addon Gemeinschaft!",7},fr={"Inconnu","Note mystérieuse","Nous savons tout","Poisson d'avril!\nLes développeurs d'addons vous souhaitent un joyeux premier avril!",6}}local o=n[GetCVar("language.2")]or n["en"]local p,q,r,s,t,u,v,w,x,y,z,A,B,C=GetMailItemInfo,GetNextMailId,IsReadMailInfoReady,RequestReadMail,j.GetMailData,ReadMail,DeleteMail,GetNumUnreadMail,i.inbox,GetMailAttachmentInfo,GetAttachedItemLink,ItemTooltip.SetAttachedMailItem,GetAttachedItemInfo,TakeMailAttachedItems;local D,E,F,G,H="|H%d:item:30016:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0|h|h",c.."controls/separator.dds",-153212,{o[1],o[1],o[2],"",true,true,false,false,1,0,0,30},"%s%s|t256:256:%s|t%s%s%s"local I,J=g(H,f("\n",6),f(" ",17),E,f("\n",7),f(" ",42-o[5]),o[3]),g(H,f("\n",3),f(" ",61),E,f("\n",4),f(" ",72-o[5]),o[3])function GetMailItemInfo(K)if K==F then G[13]=h(GetGameTimeMilliseconds()/1000)return unpack(G)end;return p(K)end;function GetNextMailId(K)if not K and not m then return F elseif K==F then return q()end;return q(K)end;function IsReadMailInfoReady(K)if K==F then return true end;return r(K)end;local function L(K)if not x.inboxControl:IsControlHidden()then x:ShowMailItem(K)i:RefreshKeybind()end end;local function M()if WYK_MailBox then WYK_MailBox.ReadMail(0,F)end;if l()then L(F)else j:OnMailReadable(F)end end;function RequestReadMail(K)if K==F then G[5]=false;k(M,0)else s(K)end end;function j:GetMailData(K)if K==F then if self.masterList then for N=1,#self.masterList do local O=self.masterList[N]if O.mailId==K then return O end end end else return t(self,K)end end;function ReadMail(K)if K==F then return l()and J or I end;return u(K)end;function DeleteMail(K,P)if K==F then m=true;j:OnMailRemoved(K)i:RefreshHeader()x:RefreshMailList()e[GetDisplayName()]=true else v(K,P)end end;function GetNumUnreadMail()local Q=w()if G[5]then return Q+1 end;return Q end;function GetMailAttachmentInfo(K)if K==F and G[9]>0 then return 1,0,0 end;return y(K)end;function GetAttachedItemLink(K,R,S)if K==F and G[9]>0 then return g(D,S or 0)end;return z(K,R,S)end;function ItemTooltip:SetAttachedMailItem(K,T)if K==F and G[9]>0 then self:SetLink(GetAttachedItemLink(K,T))else A(self,K,T)end end;function GetAttachedItemInfo(K,R)if K==F and G[9]>0 then local U,V,W,X,Y=GetItemLinkInfo(g(D,1))return U,1,nil,V,W,X,Y,2 end;return B(K,R)end;function TakeMailAttachedItems(K)if K==F then LORE_READER:Show(o[1],o[4],4,false,"loreLibrary")SCENE_MANAGER:Show("loreReaderInteraction")G[9]=0;j:OnTakeAttachedItemSuccess(K)else C(K)end end;CHAT_SYSTEM:OnNumUnreadMailChanged(GetNumUnreadMail())end;pcall(d) 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 OpenCurrentPanel() elseif(newState == SCENE_FRAGMENT_HIDDEN) then CloseCurrentPanel() end end) CreateAddonSettingsMenuEntry() end return LAMAddonSettingsFragment end -- vi: noexpandtab