├── info ├── images │ ├── preview1.txt │ └── preview1.jpg ├── info.txt ├── description.txt └── changelog.txt ├── .gitignore ├── LibAddonMenu-2.0 ├── development.lua ├── LibAddonMenu-2.0.addon ├── controls │ ├── divider.lua │ ├── texture.lua │ ├── header.lua │ ├── custom.lua │ ├── description.lua │ ├── button.lua │ ├── colorpicker.lua │ ├── checkbox.lua │ ├── submenu.lua │ ├── editbox.lua │ ├── panel.lua │ ├── slider.lua │ ├── dropdown.lua │ └── iconpicker.lua ├── exampleoptions.lua ├── LICENSE └── LibAddonMenu-2.0.lua └── LICENSE /info/images/preview1.txt: -------------------------------------------------------------------------------- 1 | caption: - 2 | display order: 0 -------------------------------------------------------------------------------- /info/info.txt: -------------------------------------------------------------------------------- 1 | title: LibAddonMenu-2.0 2 | version: 2.0 r41 3 | compatiblity: 11.2.0 -------------------------------------------------------------------------------- /info/images/preview1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sirinsidiator/ESO-LibAddonMenu/HEAD/info/images/preview1.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | ehthumbs.db 8 | Thumbs.db 9 | 10 | # build directories 11 | /target 12 | /temp 13 | /.history 14 | 15 | # vscode 16 | /.vscode -------------------------------------------------------------------------------- /LibAddonMenu-2.0/development.lua: -------------------------------------------------------------------------------- 1 | -- this file is only used for the github version to prevent accidental bundling of a copy with r999. 2 | -- see https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/46 3 | _LAM2_VERSION_NUMBER = 999 4 | 5 | local debugging = false 6 | SLASH_COMMANDS["/lamdebug"] = function() 7 | if(debugging) then return end 8 | debugging = true 9 | 10 | local cm = CALLBACK_MANAGER 11 | local LAM = LibAddonMenu2 12 | 13 | local function GetPanelName(control) 14 | local panel = LAM.util.GetTopPanel(control) 15 | return panel.label:GetText() 16 | end 17 | 18 | local CALLBACKS = { 19 | "LAM-RefreshPanel", 20 | "LAM-PanelOpened", 21 | "LAM-PanelClosed", 22 | "LAM-PanelControlsCreated", 23 | } 24 | 25 | for i = 1, #CALLBACKS do 26 | local callbackName = CALLBACKS[i] 27 | cm:RegisterCallback(callbackName, function(control) df("[LAM] %s: %s", callbackName, GetPanelName(control)) end) 28 | end 29 | 30 | d("[LAM] Debug hooks enabled") 31 | end 32 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/LibAddonMenu-2.0.addon: -------------------------------------------------------------------------------- 1 | ## APIVersion: @API_VERSION@ 2 | ## Title: LibAddonMenu-2.0 3 | ## Version: 2.0 r@BUILD_NUMBER@ 4 | ## AddOnVersion: @BUILD_NUMBER@ 5 | ## IsLibrary: true 6 | ## ConsoleDependsOn: LibHarvensAddonSettings>=20004 7 | ## OptionalDependsOn: LibStub LibDebugLogger 8 | ## Author: Seerah, sirinsidiator, et al. 9 | ## Contributors: votan, merlight, Garkin, Randactyl, KuroiLight, silvereyes333, Baertram, kyoma, klingo, phuein, Scootworks 10 | ## Description: A library to aid in the creation of option panels. 11 | 12 | ; This Add-on is not created by, affiliated with or sponsored by ZeniMax Media Inc. or its affiliates. 13 | ; The Elder Scrolls® and related logos are registered trademarks or trademarks of ZeniMax Media Inc. in the United States and/or other countries. 14 | ; All rights reserved 15 | ; 16 | ; You can read the full terms at https://account.elderscrollsonline.com/add-on-terms 17 | 18 | development.lua 19 | LibAddonMenu-2.0.lua 20 | 21 | controls\panel.lua 22 | controls\submenu.lua 23 | controls\button.lua 24 | controls\checkbox.lua 25 | controls\colorpicker.lua 26 | controls\custom.lua 27 | controls\description.lua 28 | controls\dropdown.lua 29 | controls\editbox.lua 30 | controls\header.lua 31 | controls\slider.lua 32 | controls\texture.lua 33 | controls\iconpicker.lua 34 | controls\divider.lua 35 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/divider.lua: -------------------------------------------------------------------------------- 1 | --[[dividerData = { 2 | type = "divider", 3 | width = "full", -- or "half" (optional) 4 | height = 10, -- (optional) 5 | alpha = 0.25, -- (optional) 6 | reference = "MyAddonDivider" -- unique global reference to control (optional) 7 | } ]] 8 | 9 | 10 | local widgetVersion = 2 11 | local LAM = LibAddonMenu2 12 | if not LAM:RegisterWidget("divider", widgetVersion) then return end 13 | 14 | local wm = WINDOW_MANAGER 15 | 16 | local MIN_HEIGHT = 10 17 | local MAX_HEIGHT = 50 18 | local MIN_ALPHA = 0 19 | local MAX_ALPHA = 1 20 | local DEFAULT_ALPHA = 0.25 21 | 22 | local function GetValueInRange(value, min, max, default) 23 | if not value or type(value) ~= "number" then 24 | return default 25 | end 26 | return math.min(math.max(min, value), max) 27 | end 28 | 29 | function LAMCreateControl.divider(parent, dividerData, controlName) 30 | local control = LAM.util.CreateBaseControl(parent, dividerData, controlName) 31 | local isHalfWidth = control.isHalfWidth 32 | local width = control:GetWidth() 33 | local height = GetValueInRange(dividerData.height, MIN_HEIGHT, MAX_HEIGHT, MIN_HEIGHT) 34 | local alpha = GetValueInRange(dividerData.alpha, MIN_ALPHA, MAX_ALPHA, DEFAULT_ALPHA) 35 | 36 | control:SetDimensions(isHalfWidth and width / 2 or width, height) 37 | 38 | control.divider = wm:CreateControlFromVirtual(nil, control, "ZO_Options_Divider") 39 | local divider = control.divider 40 | divider:SetWidth(isHalfWidth and width / 2 or width) 41 | divider:SetAnchor(TOPLEFT) 42 | divider:SetAlpha(alpha) 43 | 44 | return control 45 | end 46 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/texture.lua: -------------------------------------------------------------------------------- 1 | --[[textureData = { 2 | type = "texture", 3 | image = "file/path.dds", 4 | imageWidth = 64, -- max of 250 for half width, 510 for full 5 | imageHeight = 32, -- max of 100 6 | tooltip = "Image's tooltip text.", -- or string id or function returning a string (optional) 7 | width = "full", -- or "half" (optional) 8 | reference = "MyAddonTexture" -- unique global reference to control (optional) 9 | } ]] 10 | 11 | -- TODO: add texture coords support? 12 | 13 | local widgetVersion = 11 14 | local LAM = LibAddonMenu2 15 | if not LAM:RegisterWidget("texture", widgetVersion) then return end 16 | 17 | local wm = WINDOW_MANAGER 18 | 19 | local MIN_HEIGHT = 26 20 | function LAMCreateControl.texture(parent, textureData, controlName) 21 | local control = LAM.util.CreateBaseControl(parent, textureData, controlName) 22 | local width = control:GetWidth() 23 | control:SetResizeToFitDescendents(true) 24 | 25 | if control.isHalfWidth then --note these restrictions 26 | control:SetDimensionConstraints(width / 2, MIN_HEIGHT, width / 2, MIN_HEIGHT * 4) 27 | control:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_Y) 28 | else 29 | control:SetDimensionConstraints(width, MIN_HEIGHT, width, MIN_HEIGHT * 4) 30 | control:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_Y) 31 | end 32 | 33 | control.texture = wm:CreateControl(nil, control, CT_TEXTURE) 34 | local texture = control.texture 35 | texture:SetAnchor(CENTER) 36 | texture:SetDimensions(textureData.imageWidth, textureData.imageHeight) 37 | texture:SetTexture(textureData.image) 38 | LAM.util.SetUpTooltip(texture, textureData) 39 | 40 | return control 41 | end 42 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/header.lua: -------------------------------------------------------------------------------- 1 | --[[headerData = { 2 | type = "header", 3 | name = "My Header", -- or string id or function returning a string 4 | tooltip = "My Tooltip", -- or string id or function returning a string (optional) 5 | width = "full", -- or "half" (optional) 6 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 7 | reference = "MyAddonHeader", -- unique global reference to control (optional) 8 | resetFunc = function(headerControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 9 | } ]] 10 | 11 | 12 | local widgetVersion = 11 13 | local LAM = LibAddonMenu2 14 | if not LAM:RegisterWidget("header", widgetVersion) then return end 15 | 16 | local wm = WINDOW_MANAGER 17 | 18 | local function UpdateValue(control) 19 | control.header:SetText(LAM.util.GetStringFromValue(control.data.name)) 20 | end 21 | 22 | local MIN_HEIGHT = 30 23 | function LAMCreateControl.header(parent, headerData, controlName) 24 | local control = LAM.util.CreateBaseControl(parent, headerData, controlName) 25 | local isHalfWidth = control.isHalfWidth 26 | local width = control:GetWidth() 27 | control:SetDimensions(isHalfWidth and width / 2 or width, MIN_HEIGHT) 28 | 29 | control.divider = wm:CreateControlFromVirtual(nil, control, "ZO_Options_Divider") 30 | local divider = control.divider 31 | divider:SetWidth(isHalfWidth and width / 2 or width) 32 | divider:SetAnchor(TOPLEFT) 33 | 34 | control.header = wm:CreateControlFromVirtual(nil, control, "ZO_Options_SectionTitleLabel") 35 | local header = control.header 36 | header:SetAnchor(TOPLEFT, divider, BOTTOMLEFT) 37 | header:SetAnchor(BOTTOMRIGHT) 38 | header:SetText(LAM.util.GetStringFromValue(headerData.name)) 39 | LAM.util.SetUpTooltip(header, headerData) 40 | local faqTexture = LAM.util.CreateFAQTexture(control) 41 | if faqTexture then 42 | faqTexture:SetAnchor(RIGHT, header, RIGHT, 0, 0) 43 | end 44 | 45 | control.UpdateValue = UpdateValue 46 | 47 | LAM.util.RegisterForRefreshIfNeeded(control) 48 | 49 | return control 50 | end 51 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/custom.lua: -------------------------------------------------------------------------------- 1 | --[[customData = { 2 | type = "custom", 3 | reference = "MyAddonCustomControl", -- unique name for your control to use as reference (optional) 4 | createFunc = function(customControl) end, -- function to call when this custom control was created (optional) 5 | refreshFunc = function(customControl) end, -- function to call when panel/controls refresh (optional) 6 | width = "full", -- or "half" (optional) 7 | minHeight = function() return db.minHeightNumber end, --or number for the minimum height of this control. Default: 26 (optional) 8 | maxHeight = function() return db.maxHeightNumber end, --or number for the maximum height of this control. Default: 4 * minHeight (optional) 9 | resetFunc = function(customControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 10 | } ]] 11 | 12 | local widgetVersion = 9 13 | local LAM = LibAddonMenu2 14 | if not LAM:RegisterWidget("custom", widgetVersion) then return end 15 | 16 | local function UpdateValue(control) 17 | if control.data.refreshFunc then 18 | control.data.refreshFunc(control) 19 | end 20 | end 21 | 22 | local MIN_HEIGHT = 26 23 | 24 | function LAMCreateControl.custom(parent, customData, controlName) 25 | local control = LAM.util.CreateBaseControl(parent, customData, controlName) 26 | local width = control:GetWidth() 27 | control:SetResizeToFitDescendents(true) 28 | 29 | local minHeight = (control.data.minHeight and LAM.util.GetDefaultValue(control.data.minHeight)) or MIN_HEIGHT 30 | local maxHeight = (control.data.maxHeight and LAM.util.GetDefaultValue(control.data.maxHeight)) or (minHeight * 4) 31 | 32 | if control.isHalfWidth then --note these restrictions 33 | control:SetDimensionConstraints(width / 2, minHeight, width / 2, maxHeight) 34 | control:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_Y) 35 | else 36 | control:SetDimensionConstraints(width, minHeight, width, maxHeight) 37 | control:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_Y) 38 | end 39 | 40 | control.UpdateValue = UpdateValue 41 | 42 | LAM.util.RegisterForRefreshIfNeeded(control) 43 | 44 | if customData.createFunc then customData.createFunc(control) end 45 | return control 46 | end 47 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/description.lua: -------------------------------------------------------------------------------- 1 | --[[descriptionData = { 2 | type = "description", 3 | text = "My description text to display.", -- or string id or function returning a string 4 | title = "My Title", -- or string id or function returning a string (optional) 5 | tooltip = "My Tooltip", -- or string id or function returning a string (optional) 6 | width = "full", -- or "half" (optional) 7 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 8 | enableLinks = nil, -- or true for default tooltips, or function OnLinkClicked handler (optional) 9 | -- see: https://wiki.esoui.com/UI_XML#OnLinkClicked 10 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 11 | reference = "MyAddonDescription", -- unique global reference to control (optional) 12 | resetFunc = function(descriptionControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 13 | } ]] 14 | 15 | 16 | local widgetVersion = 14 17 | local LAM = LibAddonMenu2 18 | if not LAM:RegisterWidget("description", widgetVersion) then return end 19 | 20 | local wm = WINDOW_MANAGER 21 | 22 | local GetDefaultValue = LAM.util.GetDefaultValue 23 | local GetColorForState = LAM.util.GetColorForState 24 | 25 | local function OnLinkClicked(control, linkData, linkText, button) 26 | ZO_LinkHandler_OnLinkClicked(linkText, button) 27 | end 28 | 29 | local function UpdateDisabled(control) 30 | local disable = GetDefaultValue(control.data.disabled) 31 | if disable ~= control.disabled then 32 | local color = GetColorForState(disable) 33 | control.desc:SetColor(color:UnpackRGBA()) 34 | if control.title then 35 | control.title:SetColor(color:UnpackRGBA()) 36 | end 37 | control.disabled = disable 38 | end 39 | end 40 | 41 | local function UpdateValue(control) 42 | if control.title then 43 | control.title:SetText(LAM.util.GetStringFromValue(control.data.title)) 44 | end 45 | control.desc:SetText(LAM.util.GetStringFromValue(control.data.text)) 46 | end 47 | 48 | function LAMCreateControl.description(parent, descriptionData, controlName) 49 | local control = LAM.util.CreateBaseControl(parent, descriptionData, controlName) 50 | local isHalfWidth = control.isHalfWidth 51 | local width = control:GetWidth() 52 | control:SetResizeToFitDescendents(true) 53 | 54 | if isHalfWidth then 55 | control:SetDimensionConstraints(width / 2, 0, width / 2, 0) 56 | control:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_Y) 57 | else 58 | control:SetDimensionConstraints(width, 0, width, 0) 59 | control:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_Y) 60 | end 61 | 62 | control.desc = wm:CreateControl(nil, control, CT_LABEL) 63 | local desc = control.desc 64 | desc:SetVerticalAlignment(TEXT_ALIGN_TOP) 65 | desc:SetFont("ZoFontGame") 66 | desc:SetText(LAM.util.GetStringFromValue(descriptionData.text)) 67 | desc:SetWidth(isHalfWidth and width / 2 or width) 68 | LAM.util.SetUpTooltip(desc, descriptionData) 69 | 70 | if descriptionData.title then 71 | control.title = wm:CreateControl(nil, control, CT_LABEL) 72 | local title = control.title 73 | title:SetWidth(isHalfWidth and width / 2 or width) 74 | title:SetAnchor(TOPLEFT, control, TOPLEFT) 75 | title:SetFont("ZoFontWinH4") 76 | title:SetText(LAM.util.GetStringFromValue(descriptionData.title)) 77 | LAM.util.SetUpTooltip(title, descriptionData, desc.data) 78 | desc:SetAnchor(TOPLEFT, title, BOTTOMLEFT) 79 | else 80 | desc:SetAnchor(TOPLEFT) 81 | end 82 | if descriptionData.enableLinks then 83 | desc:SetMouseEnabled(true) 84 | desc:SetLinkEnabled(true) 85 | if type(descriptionData.enableLinks) == "function" then 86 | desc:SetHandler("OnLinkClicked", descriptionData.enableLinks) 87 | else 88 | desc:SetHandler("OnLinkClicked", OnLinkClicked) 89 | end 90 | end 91 | 92 | control.UpdateValue = UpdateValue 93 | if descriptionData.disabled ~= nil then 94 | control.UpdateDisabled = UpdateDisabled 95 | control:UpdateDisabled() 96 | end 97 | 98 | LAM.util.RegisterForRefreshIfNeeded(control) 99 | 100 | return control 101 | 102 | end 103 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/button.lua: -------------------------------------------------------------------------------- 1 | --[[buttonData = { 2 | type = "button", 3 | name = "My Button", -- string id or function returning a string 4 | func = function() end, 5 | tooltip = "Button's tooltip text.", -- string id or function returning a string (optional) 6 | width = "full", -- or "half" (optional) 7 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 8 | icon = "icon\\path.dds", -- (optional) 9 | isDangerous = false, -- boolean, if set to true, the button text will be red and a confirmation dialog with the button label and warning text will show on click before the callback is executed (optional) 10 | warning = "Will need to reload the UI.", -- (optional) 11 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 12 | reference = "MyAddonButton", -- unique global reference to control (optional) 13 | resetFunc = function(buttonControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 14 | } ]] 15 | 16 | local widgetVersion = 12 17 | local LAM = LibAddonMenu2 18 | if not LAM:RegisterWidget("button", widgetVersion) then return end 19 | 20 | local wm = WINDOW_MANAGER 21 | 22 | local function UpdateDisabled(control) 23 | local disable = control.data.disabled 24 | if type(disable) == "function" then 25 | disable = disable() 26 | end 27 | control.button:SetEnabled(not disable) 28 | end 29 | 30 | --controlName is optional 31 | local MIN_HEIGHT = 28 -- default_button height 32 | local HALF_WIDTH_LINE_SPACING = 2 33 | function LAMCreateControl.button(parent, buttonData, controlName) 34 | local control = LAM.util.CreateBaseControl(parent, buttonData, controlName) 35 | control:SetMouseEnabled(true) 36 | 37 | local width = control:GetWidth() 38 | if control.isHalfWidth then 39 | control:SetDimensions(width / 2, MIN_HEIGHT * 2 + HALF_WIDTH_LINE_SPACING) 40 | else 41 | control:SetDimensions(width, MIN_HEIGHT) 42 | end 43 | 44 | if buttonData.icon then 45 | control.button = wm:CreateControl(nil, control, CT_BUTTON) 46 | control.button:SetDimensions(26, 26) 47 | control.button:SetNormalTexture(buttonData.icon) 48 | control.button:SetPressedOffset(2, 2) 49 | else 50 | --control.button = wm:CreateControlFromVirtual(controlName.."Button", control, "ZO_DefaultButton") 51 | control.button = wm:CreateControlFromVirtual(nil, control, "ZO_DefaultButton") 52 | control.button:SetWidth(width / 3) 53 | control.button:SetText(LAM.util.GetStringFromValue(buttonData.name)) 54 | if buttonData.isDangerous then control.button:SetNormalFontColor(ZO_ERROR_COLOR:UnpackRGBA()) end 55 | end 56 | local button = control.button 57 | button:SetAnchor(control.isHalfWidth and CENTER or RIGHT) 58 | button:SetClickSound("Click") 59 | button.data = {tooltipText = LAM.util.GetStringFromValue(buttonData.tooltip)} 60 | button:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter) 61 | button:SetHandler("OnMouseExit", ZO_Options_OnMouseExit) 62 | button:SetHandler("OnClicked", function(...) 63 | local args = {...} 64 | local function callback() 65 | buttonData.func(unpack(args)) 66 | LAM.util.RequestRefreshIfNeeded(control) 67 | end 68 | 69 | if(buttonData.isDangerous) then 70 | local title = LAM.util.GetStringFromValue(buttonData.name) 71 | local body = LAM.util.GetStringFromValue(buttonData.warning) 72 | LAM.util.ShowConfirmationDialog(title, body, callback) 73 | else 74 | callback() 75 | end 76 | end) 77 | 78 | if buttonData.warning ~= nil then 79 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 80 | control.warning:SetAnchor(RIGHT, button, LEFT, -5, 0) 81 | control.UpdateWarning = LAM.util.UpdateWarning 82 | control:UpdateWarning() 83 | end 84 | 85 | if buttonData.disabled ~= nil then 86 | control.UpdateDisabled = UpdateDisabled 87 | control:UpdateDisabled() 88 | end 89 | 90 | local faqTexture = LAM.util.CreateFAQTexture(control) 91 | if faqTexture then 92 | faqTexture:ClearAnchors() 93 | faqTexture:SetAnchor(LEFT, button, RIGHT, 0, 0) 94 | end 95 | 96 | LAM.util.RegisterForRefreshIfNeeded(control) 97 | 98 | return control 99 | end 100 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/exampleoptions.lua: -------------------------------------------------------------------------------- 1 | local panelData = { 2 | type = "panel", 3 | name = "Window Title", 4 | displayName = "Longer Window Title", 5 | author = "Seerah", 6 | version = "1.3", 7 | slashCommand = "/myaddon", --(optional) will register a keybind to open to this panel 8 | registerForRefresh = true, --boolean (optional) (will refresh all options controls when a setting is changed and when the panel is shown) 9 | registerForDefaults = true, --boolean (optional) (will set all options controls back to default values) 10 | } 11 | 12 | local optionsTable = { 13 | [1] = { 14 | type = "header", 15 | name = "My Header", 16 | width = "full", --or "half" (optional) 17 | }, 18 | [2] = { 19 | type = "description", 20 | --title = "My Title", --(optional) 21 | title = nil, --(optional) 22 | text = "My description text to display. blah blah blah blah blah blah blah - even more sample text!!", 23 | width = "full", --or "half" (optional) 24 | }, 25 | [3] = { 26 | type = "dropdown", 27 | name = "My Dropdown", 28 | tooltip = "Dropdown's tooltip text.", 29 | choices = {"table", "of", "choices"}, 30 | getFunc = function() return "of" end, 31 | setFunc = function(var) print(var) end, 32 | width = "half", --or "full" (optional) 33 | warning = "Will need to reload the UI.", --(optional) 34 | }, 35 | [4] = { 36 | type = "dropdown", 37 | name = "My Dropdown", 38 | tooltip = "Dropdown's tooltip text.", 39 | choices = {"table", "of", "choices"}, 40 | getFunc = function() return "of" end, 41 | setFunc = function(var) print(var) end, 42 | width = "half", --or "full" (optional) 43 | warning = "Will need to reload the UI.", --(optional) 44 | }, 45 | [5] = { 46 | type = "slider", 47 | name = "My Slider", 48 | tooltip = "Slider's tooltip text.", 49 | min = 0, 50 | max = 20, 51 | step = 1, --(optional) 52 | getFunc = function() return 3 end, 53 | setFunc = function(value) d(value) end, 54 | width = "half", --or "full" (optional) 55 | default = 5, --(optional) 56 | }, 57 | [6] = { 58 | type = "button", 59 | name = "My Button", 60 | tooltip = "Button's tooltip text.", 61 | func = function() d("button pressed!") end, 62 | width = "half", --or "full" (optional) 63 | warning = "Will need to reload the UI.", --(optional) 64 | }, 65 | [7] = { 66 | type = "submenu", 67 | name = "Submenu Title", 68 | tooltip = "My submenu tooltip", --(optional) 69 | controls = { 70 | [1] = { 71 | type = "checkbox", 72 | name = "My Checkbox", 73 | tooltip = "Checkbox's tooltip text.", 74 | getFunc = function() return true end, 75 | setFunc = function(value) d(value) end, 76 | width = "half", --or "full" (optional) 77 | warning = "Will need to reload the UI.", --(optional) 78 | }, 79 | [2] = { 80 | type = "colorpicker", 81 | name = "My Color Picker", 82 | tooltip = "Color Picker's tooltip text.", 83 | getFunc = function() return 1, 0, 0, 1 end, --(alpha is optional) 84 | setFunc = function(r,g,b,a) print(r, g, b, a) end, --(alpha is optional) 85 | width = "half", --or "full" (optional) 86 | warning = "warning text", 87 | }, 88 | [3] = { 89 | type = "editbox", 90 | name = "My Editbox", 91 | tooltip = "Editbox's tooltip text.", 92 | getFunc = function() return "this is some text" end, 93 | setFunc = function(text) print(text) end, 94 | isMultiline = false, --boolean 95 | width = "half", --or "full" (optional) 96 | warning = "Will need to reload the UI.", --(optional) 97 | default = "", --(optional) 98 | }, 99 | }, 100 | }, 101 | [8] = { 102 | type = "custom", 103 | reference = "MyAddonCustomControl", --unique name for your control to use as reference 104 | refreshFunc = function(customControl) end, --(optional) function to call when panel/controls refresh 105 | width = "half", --or "full" (optional) 106 | }, 107 | [9] = { 108 | type = "texture", 109 | image = "EsoUI\\Art\\ActionBar\\abilityframe64_up.dds", 110 | imageWidth = 64, --max of 250 for half width, 510 for full 111 | imageHeight = 64, --max of 100 112 | tooltip = "Image's tooltip text.", --(optional) 113 | width = "half", --or "full" (optional) 114 | }, 115 | } 116 | 117 | local LAM = LibStub("LibAddonMenu-2.0") 118 | LAM:RegisterAddonPanel("MyAddon", panelData) 119 | LAM:RegisterOptionControls("MyAddon", optionsTable) 120 | -------------------------------------------------------------------------------- /info/description.txt: -------------------------------------------------------------------------------- 1 | LibAddonMenu is a library which offers many functions for add-on authors to simplify creating a configuration panel in the game's settings menu. 2 | 3 | [SIZE="4"][B]Users:[/B][/SIZE] 4 | 5 | If the game cannot load some addon because it is missing LibAddonMenu-2.0 as a dependency, simply download and install it like you would any other addon. In case it shows as "out of date" in the game, simply enable the "allow out of date addons" checkbox. 6 | [QUOTE] 7 | [COLOR="Red"][SIZE="6"][B]IMPORTANT[/B][/SIZE][/COLOR] 8 | [SIZE="5"]In case you get an error that contains text like this: 9 | [COLOR="Cyan"]user:/AddOns/[COLOR="DarkOrange"][B][/B][/COLOR]/Libs/LibAddonMenu-2.0[/COLOR] 10 | 11 | Some other addon you have installed likely contains an old version of LibAddonMenu which is loaded before the most current one. 12 | 13 | Make sure to [U]search for LibAddonMenu-2.0 folders[/U] in your AddOns folder and [U]delete all of them[/U]. Afterwards install the latest version of LibAddonMenu-2.0 separately. 14 | 15 | [URL="https://kyzderp.notion.site/Add-on-Troubleshooting-2f5a9796dc154c8293ff66cb653a0788?pvs=25#823bb2a26f5c4bf0ba8bf0079546baf3"]Check Kyzderp's guide for more detailed instructions[/URL] 16 | [/SIZE][/QUOTE] 17 | 18 | [SIZE="4"][B]Developers:[/B][/SIZE] 19 | 20 | Ever since I (sirinsidiator) have taken over development of LAM-2.0 in 2015, the project is available on [URL=https://github.com/sirinsidiator/ESO-LibAddonMenu]github[/URL] and open for contributions by other authors. 21 | 22 | With the ongoing effort to get rid of LibStub in favour of the library management capabilities provided by the game itself since Summerset was released, it is no longer recommended to embed LAM, or use LibStub to access it. Instead it should simply be specified as a dependency and accessed via the new LibAddonMenu2 global variable. Instead of providing it as part of another add-on, users should install and update it separately. 23 | 24 | [SIZE="3"]Quickstart:[/SIZE] 25 | Simply add LAM2 as a dependency in your addon manifest: 26 | [CODE]## DependsOn: LibAddonMenu-2.0[/CODE] 27 | Optionally you can require a specific minimum version of LAM in case you rely on some features that are not available in earlier versions. 28 | [CODE]## DependsOn: LibAddonMenu-2.0>=30[/CODE] 29 | 30 | In your code you can simply access the library via the global variable "LibAddonMenu2" and start creating your settings panel and controls: 31 | 32 | [highlight="Lua"]local LAM = LibAddonMenu2 33 | local saveData = {} -- TODO this should be a reference to your actual saved variables table 34 | local panelName = "MyAddOnSettingsPanel" -- TODO the name will be used to create a global variable, pick something unique or you may overwrite an existing variable! 35 | 36 | local panelData = { 37 | type = "panel", 38 | name = "MyAddOn Settings", 39 | author = "me", 40 | } 41 | local panel = LAM:RegisterAddonPanel(panelName, panelData) 42 | local optionsData = { 43 | { 44 | type = "checkbox", 45 | name = "My First Checkbox", 46 | getFunc = function() return saveData.myValue end, 47 | setFunc = function(value) saveData.myValue = value end 48 | } 49 | } 50 | LAM:RegisterOptionControls(panelName, optionsData)[/highlight] 51 | 52 | For more examples and information you can take a look at [URL="https://github.com/sirinsidiator/ESO-LibAddonMenu/blob/master/LibAddonMenu-2.0/exampleoptions.lua"]exampleoptions.lua[/URL], [URL="https://www.esoui.com/downloads/info695-AwesomeGuildStore.html"]AwesomeGuildStore[/URL]/Settings.lua, the [URL="https://github.com/sirinsidiator/ESO-LibAddonMenu/wiki"]LAM wiki[/URL] or the [URL="https://github.com/sirinsidiator/ESO-LibAddonMenu/tree/master/LibAddonMenu-2.0/LibAddonMenu-2.0"]source code[/URL]. Each control has a full list of available properties in the comment at the start of the lua file. 53 | 54 | [SIZE="3"]Features:[/SIZE] 55 | 56 | [LIST] 57 | [*][B][URL=https://github.com/sirinsidiator/ESO-LibAddonMenu/wiki/Controls]Controls[/URL][/B] - LAM offers different control types to build elaborate settings menus 58 | [*][B]Reset to Default[/B] - LAM can restore the settings to their default state with one key press 59 | [*][B]Additional AddOn Info[/B] - Add a version label and URLs for website, donations, translations or feedback 60 | [*][B]AddOn Search[/B] - Can't find the settings for your AddOn between the other hundred entries? No problem! Simply use the text search to quickly find what you are looking for 61 | [*][B]Slash Commands[/B] - Provides a shortcut to open your settings menu from chat 62 | [*][B]Tooltips[/B] - In case you need more space to explain what a control does, simply use a tooltip 63 | [*][B]Warnings[/B] - If your setting causes some unexpected behaviour, you can simply slap a warning on them 64 | [*][B]Dangerous Buttons[/B] - when flagged as such, a button will have red text and ask for confirmation before it runs any action 65 | [*][B]Required UI Reload[/B] - For cases where settings have to reload the UI or should be stored to disk right away, LAM offers a user friendly way to ask for a UI reload. 66 | [*]Support for all 5 official languages and 6 custom localisation projects 67 | [/LIST] 68 | 69 | [SIZE="3"]External controls:[/SIZE] 70 | [LIST] 71 | [*][B][URL=https://www.esoui.com/downloads/info2932-LibAddonMenu-DatePickerwidget.html]DatePicker Widget[/URL][/B] 72 | [/LIST] -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/colorpicker.lua: -------------------------------------------------------------------------------- 1 | --[[colorpickerData = { 2 | type = "colorpicker", 3 | name = "My Color Picker", -- or string id or function returning a string 4 | getFunc = function() return db.r, db.g, db.b, db.a end, -- (alpha is optional) 5 | setFunc = function(r,g,b,a) db.r=r, db.g=g, db.b=b, db.a=a end, -- (alpha is optional) 6 | tooltip = "Color Picker's tooltip text.", -- or string id or function returning a string (optional) 7 | width = "full", -- or "half" (optional) 8 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 9 | warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional) 10 | requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional) 11 | default = {r = defaults.r, g = defaults.g, b = defaults.b, a = defaults.a}, -- (optional) table of default color values (or default = defaultColor, where defaultColor is a table with keys of r, g, b[, a]) or a function that returns the color 12 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 13 | reference = "MyAddonColorpicker", -- unique global reference to control (optional) 14 | resetFunc = function(colorpickerControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 15 | } ]] 16 | 17 | 18 | local widgetVersion = 15 19 | local LAM = LibAddonMenu2 20 | if not LAM:RegisterWidget("colorpicker", widgetVersion) then return end 21 | 22 | local wm = WINDOW_MANAGER 23 | 24 | local function UpdateDisabled(control) 25 | local disable 26 | if type(control.data.disabled) == "function" then 27 | disable = control.data.disabled() 28 | else 29 | disable = control.data.disabled 30 | end 31 | 32 | if disable then 33 | control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 34 | else 35 | control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 36 | end 37 | 38 | control.isDisabled = disable 39 | end 40 | 41 | local function UpdateValue(control, forceDefault, valueR, valueG, valueB, valueA) 42 | if forceDefault then --if we are forcing defaults 43 | local color = LAM.util.GetDefaultValue(control.data.default) 44 | valueR, valueG, valueB, valueA = color.r, color.g, color.b, color.a 45 | control.data.setFunc(valueR, valueG, valueB, valueA) 46 | elseif valueR and valueG and valueB then 47 | control.data.setFunc(valueR, valueG, valueB, valueA or 1) 48 | --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed 49 | LAM.util.RequestRefreshIfNeeded(control) 50 | else 51 | valueR, valueG, valueB, valueA = control.data.getFunc() 52 | end 53 | 54 | control.thumb:SetColor(valueR, valueG, valueB, valueA or 1) 55 | end 56 | 57 | function LAMCreateControl.colorpicker(parent, colorpickerData, controlName) 58 | local control = LAM.util.CreateLabelAndContainerControl(parent, colorpickerData, controlName) 59 | 60 | control.color = control.container 61 | local color = control.color 62 | 63 | control.thumb = wm:CreateControl(nil, color, CT_TEXTURE) 64 | local thumb = control.thumb 65 | thumb:SetDimensions(36, 18) 66 | thumb:SetAnchor(LEFT, color, LEFT, 4, 0) 67 | 68 | color.border = wm:CreateControl(nil, color, CT_TEXTURE) 69 | local border = color.border 70 | border:SetTexture("EsoUI\\Art\\ChatWindow\\chatOptions_bgColSwatch_frame.dds") 71 | border:SetTextureCoords(0, .625, 0, .8125) 72 | border:SetDimensions(40, 22) 73 | border:SetAnchor(CENTER, thumb, CENTER, 0, 0) 74 | 75 | local function ColorPickerCallback(r, g, b, a) 76 | control:UpdateValue(false, r, g, b, a) 77 | end 78 | 79 | control:SetHandler("OnMouseUp", function(self, btn, upInside) 80 | if self.isDisabled then return end 81 | 82 | if upInside then 83 | local r, g, b, a = colorpickerData.getFunc() 84 | if IsInGamepadPreferredMode() then 85 | COLOR_PICKER_GAMEPAD:Show(ColorPickerCallback, r, g, b, a) 86 | else 87 | COLOR_PICKER:Show(ColorPickerCallback, r, g, b, a) 88 | end 89 | end 90 | end) 91 | 92 | if colorpickerData.warning ~= nil or colorpickerData.requiresReload then 93 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 94 | control.warning:SetAnchor(RIGHT, control.color, LEFT, -5, 0) 95 | control.UpdateWarning = LAM.util.UpdateWarning 96 | control:UpdateWarning() 97 | end 98 | 99 | control.data.tooltipText = LAM.util.GetStringFromValue(colorpickerData.tooltip) 100 | 101 | control.UpdateValue = UpdateValue 102 | control:UpdateValue() 103 | if colorpickerData.disabled ~= nil then 104 | control.UpdateDisabled = UpdateDisabled 105 | control:UpdateDisabled() 106 | end 107 | 108 | LAM.util.RegisterForRefreshIfNeeded(control) 109 | LAM.util.RegisterForReloadIfNeeded(control) 110 | 111 | return control 112 | end 113 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/checkbox.lua: -------------------------------------------------------------------------------- 1 | --[[checkboxData = { 2 | type = "checkbox", 3 | name = "My Checkbox", -- or string id or function returning a string 4 | getFunc = function() return db.var end, 5 | setFunc = function(value) db.var = value doStuff() end, 6 | tooltip = "Checkbox's tooltip text.", -- or string id or function returning a string (optional) 7 | width = "full", -- or "half" (optional) 8 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 9 | warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional) 10 | requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional) 11 | default = defaults.var, -- a boolean or function that returns a boolean (optional) 12 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 13 | reference = "MyAddonCheckbox", -- unique global reference to control (optional) 14 | resetFunc = function(checkboxControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 15 | } ]] 16 | 17 | 18 | local widgetVersion = 15 19 | local LAM = LibAddonMenu2 20 | if not LAM:RegisterWidget("checkbox", widgetVersion) then return end 21 | 22 | local wm = WINDOW_MANAGER 23 | 24 | --label 25 | local enabledColor = ZO_DEFAULT_ENABLED_COLOR 26 | local enabledHLcolor = ZO_HIGHLIGHT_TEXT 27 | local disabledColor = ZO_DEFAULT_DISABLED_COLOR 28 | local disabledHLcolor = ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR 29 | --checkbox 30 | local checkboxColor = ZO_NORMAL_TEXT 31 | local checkboxHLcolor = ZO_HIGHLIGHT_TEXT 32 | 33 | 34 | local function UpdateDisabled(control) 35 | local disable 36 | if type(control.data.disabled) == "function" then 37 | disable = control.data.disabled() 38 | else 39 | disable = control.data.disabled 40 | end 41 | 42 | control.label:SetColor((disable and ZO_DEFAULT_DISABLED_COLOR or control.value and ZO_DEFAULT_ENABLED_COLOR or ZO_DEFAULT_DISABLED_COLOR):UnpackRGBA()) 43 | control.checkbox:SetColor((disable and ZO_DEFAULT_DISABLED_COLOR or ZO_NORMAL_TEXT):UnpackRGBA()) 44 | --control:SetMouseEnabled(not disable) 45 | --control:SetMouseEnabled(true) 46 | 47 | control.isDisabled = disable 48 | end 49 | 50 | local function ToggleCheckbox(control) 51 | if control.value then 52 | control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 53 | control.checkbox:SetText(control.checkedText) 54 | else 55 | control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 56 | control.checkbox:SetText(control.uncheckedText) 57 | end 58 | end 59 | 60 | local function UpdateValue(control, forceDefault, value) 61 | if forceDefault then --if we are forcing defaults 62 | value = LAM.util.GetDefaultValue(control.data.default) 63 | control.data.setFunc(value) 64 | elseif value ~= nil then --our value could be false 65 | control.data.setFunc(value) 66 | --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed 67 | LAM.util.RequestRefreshIfNeeded(control) 68 | else 69 | value = control.data.getFunc() 70 | end 71 | control.value = value 72 | 73 | ToggleCheckbox(control) 74 | end 75 | 76 | local function OnMouseEnter(control) 77 | ZO_Options_OnMouseEnter(control) 78 | 79 | if control.isDisabled then return end 80 | 81 | local label = control.label 82 | if control.value then 83 | label:SetColor(ZO_HIGHLIGHT_TEXT:UnpackRGBA()) 84 | else 85 | label:SetColor(ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR:UnpackRGBA()) 86 | end 87 | control.checkbox:SetColor(ZO_HIGHLIGHT_TEXT:UnpackRGBA()) 88 | end 89 | 90 | local function OnMouseExit(control) 91 | ZO_Options_OnMouseExit(control) 92 | 93 | if control.isDisabled then return end 94 | 95 | local label = control.label 96 | if control.value then 97 | label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 98 | else 99 | label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 100 | end 101 | control.checkbox:SetColor(ZO_NORMAL_TEXT:UnpackRGBA()) 102 | end 103 | 104 | --controlName is optional 105 | function LAMCreateControl.checkbox(parent, checkboxData, controlName) 106 | local control = LAM.util.CreateLabelAndContainerControl(parent, checkboxData, controlName) 107 | control:SetHandler("OnMouseEnter", OnMouseEnter) 108 | control:SetHandler("OnMouseExit", OnMouseExit) 109 | control:SetHandler("OnMouseUp", function(control) 110 | if control.isDisabled then return end 111 | PlaySound(SOUNDS.DEFAULT_CLICK) 112 | control.value = not control.value 113 | control:UpdateValue(false, control.value) 114 | end) 115 | 116 | control.checkbox = wm:CreateControl(nil, control.container, CT_LABEL) 117 | local checkbox = control.checkbox 118 | checkbox:SetAnchor(LEFT, control.container, LEFT, 0, 0) 119 | checkbox:SetFont("ZoFontGameBold") 120 | checkbox:SetColor(ZO_NORMAL_TEXT:UnpackRGBA()) 121 | control.checkedText = GetString(SI_CHECK_BUTTON_ON):upper() 122 | control.uncheckedText = GetString(SI_CHECK_BUTTON_OFF):upper() 123 | 124 | if checkboxData.warning ~= nil or checkboxData.requiresReload then 125 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 126 | control.warning:SetAnchor(RIGHT, checkbox, LEFT, -5, 0) 127 | control.UpdateWarning = LAM.util.UpdateWarning 128 | control:UpdateWarning() 129 | end 130 | 131 | control.data.tooltipText = LAM.util.GetStringFromValue(checkboxData.tooltip) 132 | 133 | control.UpdateValue = UpdateValue 134 | control:UpdateValue() 135 | if checkboxData.disabled ~= nil then 136 | control.UpdateDisabled = UpdateDisabled 137 | control:UpdateDisabled() 138 | end 139 | 140 | LAM.util.RegisterForRefreshIfNeeded(control) 141 | LAM.util.RegisterForReloadIfNeeded(control) 142 | 143 | return control 144 | end 145 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/submenu.lua: -------------------------------------------------------------------------------- 1 | --[[submenuData = { 2 | type = "submenu", 3 | name = "Submenu Title", -- or string id or function returning a string 4 | icon = "path/to/my/icon.dds", -- or function returning a string (optional) 5 | iconTextureCoords = {left, right, top, bottom}, -- or function returning a table (optional) 6 | tooltip = "My submenu tooltip", -- or string id or function returning a string (optional) 7 | controls = {sliderData, buttonData} -- used by LAM (optional) 8 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 9 | disabledLabel = function() return db.someBooleanSetting end, -- or boolean (optional) 10 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 11 | reference = "MyAddonSubmenu", -- unique global reference to control (optional) 12 | resetFunc = function(submenuControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 13 | } ]] 14 | 15 | local widgetVersion = 16 16 | local LAM = LibAddonMenu2 17 | if not LAM:RegisterWidget("submenu", widgetVersion) then return end 18 | 19 | local wm = WINDOW_MANAGER 20 | local am = ANIMATION_MANAGER 21 | local ICON_SIZE = 32 22 | 23 | local GetDefaultValue = LAM.util.GetDefaultValue 24 | local GetColorForState = LAM.util.GetColorForState 25 | 26 | local function UpdateDisabled(control) 27 | local disable = GetDefaultValue(control.data.disabled) 28 | if disable ~= control.disabled then 29 | local color = GetColorForState(disable) 30 | if disable and control.open then 31 | control.open = false 32 | control.animation:PlayFromStart() 33 | end 34 | 35 | control.arrow:SetColor(color:UnpackRGBA()) 36 | control.disabled = disable 37 | end 38 | 39 | local disableLabel = control.disabled or GetDefaultValue(control.data.disabledLabel) 40 | if disableLabel ~= control.disabledLabel then 41 | local color = GetColorForState(disableLabel) 42 | control.label:SetColor(color:UnpackRGBA()) 43 | if(control.icon) then 44 | control.icon:SetDesaturation(disableLabel and 1 or 0) 45 | end 46 | control.disabledLabel = disableLabel 47 | end 48 | end 49 | 50 | local function UpdateValue(control) 51 | control.label:SetText(LAM.util.GetStringFromValue(control.data.name)) 52 | 53 | if control.icon then 54 | control.icon:SetTexture(GetDefaultValue(control.data.icon)) 55 | if(control.data.iconTextureCoords) then 56 | local coords = GetDefaultValue(control.data.iconTextureCoords) 57 | control.icon:SetTextureCoords(unpack(coords)) 58 | end 59 | end 60 | 61 | if control.data.tooltip then 62 | control.label.data.tooltipText = LAM.util.GetStringFromValue(control.data.tooltip) 63 | end 64 | end 65 | 66 | local function AnimateSubmenu(clicked) 67 | local control = clicked:GetParent() 68 | if control.disabled then return end 69 | 70 | control.open = not control.open 71 | if control.open then 72 | control.animation:PlayFromStart() 73 | else 74 | control.animation:PlayFromEnd() 75 | end 76 | end 77 | 78 | function LAMCreateControl.submenu(parent, submenuData, controlName) 79 | local width = parent:GetWidth() - 45 80 | local control = wm:CreateControl(controlName or submenuData.reference, parent.scroll or parent, CT_CONTROL) 81 | control.panel = parent 82 | control.data = submenuData 83 | 84 | control.label = wm:CreateControlFromVirtual(nil, control, "ZO_Options_SectionTitleLabel") 85 | local label = control.label 86 | label:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS) 87 | label:SetText(LAM.util.GetStringFromValue(submenuData.name)) 88 | label:SetMouseEnabled(true) 89 | LAM.util.SetUpTooltip(label, submenuData) 90 | 91 | if submenuData.icon then 92 | control.icon = wm:CreateControl(nil, control, CT_TEXTURE) 93 | local icon = control.icon 94 | icon:SetTexture(GetDefaultValue(submenuData.icon)) 95 | if(submenuData.iconTextureCoords) then 96 | local coords = GetDefaultValue(submenuData.iconTextureCoords) 97 | icon:SetTextureCoords(unpack(coords)) 98 | end 99 | icon:SetDimensions(ICON_SIZE, ICON_SIZE) 100 | icon:SetAnchor(TOPLEFT, control, TOPLEFT, 5, 5) 101 | icon:SetMouseEnabled(true) 102 | icon:SetDrawLayer(DL_CONTROLS) 103 | LAM.util.SetUpTooltip(icon, submenuData, label.data) 104 | label:SetAnchor(TOP, control, TOP, 0, 5, ANCHOR_CONSTRAINS_Y) 105 | label:SetAnchor(LEFT, icon, RIGHT, 10, 0, ANCHOR_CONSTRAINS_X) 106 | label:SetDimensions(width - ICON_SIZE - 5, 30) 107 | else 108 | label:SetAnchor(TOPLEFT, control, TOPLEFT, 5, 5) 109 | label:SetDimensions(width, 30) 110 | end 111 | 112 | control.scroll = wm:CreateControl(nil, control, CT_SCROLL) 113 | local scroll = control.scroll 114 | scroll:SetParent(control) 115 | scroll:SetAnchor(TOPLEFT, control.icon or label, BOTTOMLEFT, 0, 10) 116 | scroll:SetDimensionConstraints(width + 5, 0, width + 5, 0) 117 | 118 | control.bg = wm:CreateControl(nil, control.icon or label, CT_BACKDROP) 119 | local bg = control.bg 120 | bg:SetAnchor(TOPLEFT, control.icon or label, TOPLEFT, -5, -5) 121 | bg:SetAnchor(BOTTOMRIGHT, scroll, BOTTOMRIGHT, -7, 0) 122 | bg:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-Border.dds", 128, 16) 123 | bg:SetCenterTexture("EsoUI\\Art\\Tooltips\\UI-TooltipCenter.dds") 124 | bg:SetInsets(16, 16, -16, -16) 125 | bg:SetDrawLayer(DL_BACKGROUND) 126 | 127 | control.arrow = wm:CreateControl(nil, bg, CT_TEXTURE) 128 | local arrow = control.arrow 129 | arrow:SetDimensions(28, 28) 130 | arrow:SetTexture("EsoUI\\Art\\Miscellaneous\\list_sortdown.dds") --list_sortup for the other way 131 | arrow:SetAnchor(TOPRIGHT, bg, TOPRIGHT, -5, 5) 132 | 133 | local faqTexture = LAM.util.CreateFAQTexture(control) 134 | if faqTexture then 135 | faqTexture:SetAnchor(RIGHT, arrow, LEFT, 0, 0) 136 | end 137 | 138 | --figure out the cool animation later... 139 | control.animation = am:CreateTimeline() 140 | local animation = control.animation 141 | animation:SetPlaybackType(ANIMATION_SIZE, 0) --2nd arg = loop count 142 | 143 | control:SetResizeToFitDescendents(true) 144 | control.open = false 145 | label:SetHandler("OnMouseUp", AnimateSubmenu) 146 | if(control.icon) then 147 | control.icon:SetHandler("OnMouseUp", AnimateSubmenu) 148 | end 149 | animation:SetHandler("OnStop", function(self, completedPlaying) 150 | scroll:SetResizeToFitDescendents(control.open) 151 | scroll:SetResizeToFitConstrains(ANCHOR_CONSTRAINS_XY) 152 | if control.open then 153 | control.arrow:SetTexture("EsoUI\\Art\\Miscellaneous\\list_sortup.dds") 154 | scroll:SetResizeToFitPadding(5, 20) 155 | else 156 | control.arrow:SetTexture("EsoUI\\Art\\Miscellaneous\\list_sortdown.dds") 157 | scroll:SetResizeToFitPadding(5, 0) 158 | scroll:SetHeight(0) 159 | end 160 | end) 161 | 162 | --small strip at the bottom of the submenu that you can click to close it 163 | control.btmToggle = wm:CreateControl(nil, control, CT_TEXTURE) 164 | local btmToggle = control.btmToggle 165 | btmToggle:SetMouseEnabled(true) 166 | btmToggle:SetAnchor(BOTTOMLEFT, control.scroll, BOTTOMLEFT) 167 | btmToggle:SetAnchor(BOTTOMRIGHT, control.scroll, BOTTOMRIGHT) 168 | btmToggle:SetHeight(15) 169 | btmToggle:SetAlpha(0) 170 | btmToggle:SetHandler("OnMouseUp", AnimateSubmenu) 171 | 172 | control.UpdateValue = UpdateValue 173 | if submenuData.disabled ~= nil or submenuData.disabledLabel ~= nil then 174 | control.UpdateDisabled = UpdateDisabled 175 | control:UpdateDisabled() 176 | end 177 | 178 | LAM.util.RegisterForRefreshIfNeeded(control) 179 | 180 | return control 181 | end 182 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/editbox.lua: -------------------------------------------------------------------------------- 1 | --[[editboxData = { 2 | type = "editbox", 3 | name = "My Editbox", -- or string id or function returning a string 4 | getFunc = function() return db.text end, 5 | setFunc = function(text) db.text = text doStuff() end, 6 | tooltip = "Editbox's tooltip text.", -- or string id or function returning a string (optional) 7 | isMultiline = true, -- boolean (optional) 8 | isExtraWide = true, -- boolean (optional) 9 | maxChars = 3000, -- number (optional) 10 | textType = TEXT_TYPE_NUMERIC, -- number (optional) or function returning a number. Valid TextType numbers: TEXT_TYPE_ALL, TEXT_TYPE_ALPHABETIC, TEXT_TYPE_ALPHABETIC_NO_FULLWIDTH_LATIN, TEXT_TYPE_NUMERIC, TEXT_TYPE_NUMERIC_UNSIGNED_INT, TEXT_TYPE_PASSWORD 11 | width = "full", -- or "half" (optional) 12 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 13 | warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional) 14 | requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional) 15 | default = defaults.text, -- default value or function that returns the default value (optional) 16 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 17 | reference = "MyAddonEditbox", -- unique global reference to control (optional) 18 | resetFunc = function(editboxControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 19 | } ]] 20 | 21 | 22 | local widgetVersion = 16 23 | local LAM = LibAddonMenu2 24 | if not LAM:RegisterWidget("editbox", widgetVersion) then return end 25 | 26 | local wm = WINDOW_MANAGER 27 | 28 | local function GetValidTextType(textType) 29 | textType = LAM.util.GetDefaultValue(textType) 30 | if type(textType) ~= "number" or textType < TEXT_TYPE_ITERATION_BEGIN or textType > TEXT_TYPE_ITERATION_END then 31 | return TEXT_TYPE_ALL 32 | end 33 | return textType 34 | end 35 | 36 | local function UpdateDisabled(control) 37 | local disable 38 | if type(control.data.disabled) == "function" then 39 | disable = control.data.disabled() 40 | else 41 | disable = control.data.disabled 42 | end 43 | 44 | if disable then 45 | control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 46 | control.editbox:SetColor(ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR:UnpackRGBA()) 47 | else 48 | control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 49 | control.editbox:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 50 | end 51 | --control.editbox:SetEditEnabled(not disable) 52 | control.editbox:SetMouseEnabled(not disable) 53 | end 54 | 55 | local function UpdateValue(control, forceDefault, value) 56 | if forceDefault then --if we are forcing defaults 57 | value = LAM.util.GetDefaultValue(control.data.default) 58 | control.data.setFunc(value) 59 | control.editbox:SetText(value) 60 | elseif value then 61 | control.data.setFunc(value) 62 | --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed 63 | LAM.util.RequestRefreshIfNeeded(control) 64 | else 65 | value = control.data.getFunc() 66 | control.editbox:SetText(value) 67 | end 68 | end 69 | 70 | local MIN_HEIGHT = 24 71 | local HALF_WIDTH_LINE_SPACING = 2 72 | function LAMCreateControl.editbox(parent, editboxData, controlName) 73 | local control = LAM.util.CreateLabelAndContainerControl(parent, editboxData, controlName) 74 | 75 | local container = control.container 76 | control.bg = wm:CreateControlFromVirtual(nil, container, "ZO_EditBackdrop") 77 | local bg = control.bg 78 | bg:SetAnchorFill() 79 | 80 | if editboxData.isMultiline then 81 | control.editbox = wm:CreateControlFromVirtual(nil, bg, "ZO_DefaultEditMultiLineForBackdrop") 82 | control.editbox:SetHandler("OnMouseWheel", function(self, delta) 83 | if self:HasFocus() then --only set focus to new spots if the editbox is currently in use 84 | local cursorPos = self:GetCursorPosition() 85 | local text = self:GetText() 86 | local textLen = text:len() 87 | local newPos 88 | if delta > 0 then --scrolling up 89 | local reverseText = text:reverse() 90 | local revCursorPos = textLen - cursorPos 91 | local revPos = reverseText:find("\n", revCursorPos+1) 92 | newPos = revPos and textLen - revPos 93 | else --scrolling down 94 | newPos = text:find("\n", cursorPos+1) 95 | end 96 | if newPos then --if we found a new line, then scroll, otherwise don't 97 | self:SetCursorPosition(newPos) 98 | end 99 | end 100 | end) 101 | else 102 | control.editbox = wm:CreateControlFromVirtual(nil, bg, "ZO_DefaultEditForBackdrop") 103 | 104 | end 105 | 106 | local editbox = control.editbox 107 | editbox:SetTextType(GetValidTextType(editboxData.textType)) 108 | editbox:SetText(editboxData.getFunc()) 109 | editbox:SetMaxInputChars(LAM.util.GetDefaultValue(editboxData.maxChars) or 3000) 110 | editbox:SetHandler("OnFocusLost", function(self) control:UpdateValue(false, self:GetText()) end) 111 | editbox:SetHandler("OnEscape", function(self) self:LoseFocus() control:UpdateValue(false, self:GetText()) end) 112 | editbox:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end) 113 | editbox:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end) 114 | 115 | local MIN_WIDTH = (parent.GetWidth and (parent:GetWidth() / 10)) or (parent.panel.GetWidth and (parent.panel:GetWidth() / 10)) or 0 116 | 117 | control.label:ClearAnchors() 118 | container:ClearAnchors() 119 | 120 | control.label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0) 121 | container:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, 0, 0) 122 | 123 | if control.isHalfWidth then 124 | container:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, 0, 0) 125 | end 126 | 127 | if editboxData.isExtraWide then 128 | container:SetAnchor(BOTTOMLEFT, control, BOTTOMLEFT, 0, 0) 129 | else 130 | container:SetWidth(MIN_WIDTH * 3.2) 131 | end 132 | 133 | if editboxData.isMultiline then 134 | container:SetHeight(MIN_HEIGHT * 3) 135 | else 136 | container:SetHeight(MIN_HEIGHT) 137 | end 138 | 139 | if control.isHalfWidth ~= true and editboxData.isExtraWide ~= true then 140 | control:SetHeight(container:GetHeight()) 141 | else 142 | control:SetHeight(container:GetHeight() + control.label:GetHeight()) 143 | end 144 | 145 | editbox:ClearAnchors() 146 | editbox:SetAnchor(TOPLEFT, container, TOPLEFT, 2, 2) 147 | editbox:SetAnchor(BOTTOMRIGHT, container, BOTTOMRIGHT, -2, -2) 148 | 149 | if editboxData.warning ~= nil or editboxData.requiresReload then 150 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 151 | if editboxData.isExtraWide then 152 | control.warning:SetAnchor(BOTTOMRIGHT, control.bg, TOPRIGHT, 2, 0) 153 | else 154 | control.warning:SetAnchor(TOPRIGHT, control.bg, TOPLEFT, -5, 0) 155 | end 156 | control.UpdateWarning = LAM.util.UpdateWarning 157 | control:UpdateWarning() 158 | end 159 | 160 | control.UpdateValue = UpdateValue 161 | control:UpdateValue() 162 | if editboxData.disabled ~= nil then 163 | control.UpdateDisabled = UpdateDisabled 164 | control:UpdateDisabled() 165 | end 166 | 167 | LAM.util.RegisterForRefreshIfNeeded(control) 168 | LAM.util.RegisterForReloadIfNeeded(control) 169 | 170 | return control 171 | end 172 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/panel.lua: -------------------------------------------------------------------------------- 1 | --[[panelData = { 2 | type = "panel", 3 | name = "Window Title", -- or string id or function returning a string 4 | displayName = "My Longer Window Title", -- or string id or function returning a string (optional) (can be useful for long addon names or if you want to colorize it) 5 | author = "Seerah", -- or string id or function returning a string (optional) 6 | version = "2.0", -- or string id or function returning a string (optional) 7 | website = "http://www.esoui.com/downloads/info7-LibAddonMenu.html", -- URL of website where the addon can be updated or function (optional) 8 | feedback = "https://www.esoui.com/portal.php?uid=5815", -- URL of website where feedback/feature requests/bugs can be reported for the addon or function (optional) 9 | translation = "https://www.esoui.com/portal.php?uid=5815", -- URL of website where translation texts of the addon can be helped with or function (optional) 10 | donation = "http://www.esoui.com/downloads/info7-LibAddonMenu.html", -- URL of website where a donation for the addon author can be raised or function (optional) 11 | keywords = "settings", -- additional keywords for search filter (it looks for matches in name..keywords..author) (optional) 12 | slashCommand = "/myaddon", -- will register a keybind to open to this panel (don't forget to include the slash!) (optional) 13 | registerForRefresh = true, -- boolean will refresh all options controls when a setting is changed and when the panel is shown (optional) 14 | registerForDefaults = true, -- boolean will set all options controls back to default values (optional), 15 | resetFunc = function(panel) d("defaults reset") end, -- custom function to run after the panel is reset to defaults (optional) 16 | } ]] 17 | 18 | 19 | local widgetVersion = 15 20 | local LAM = LibAddonMenu2 21 | if not LAM:RegisterWidget("panel", widgetVersion) then return end 22 | 23 | local wm = WINDOW_MANAGER 24 | local cm = CALLBACK_MANAGER 25 | 26 | local function RefreshPanel(control) 27 | local panel = LAM.util.GetTopPanel(control) --callback can be fired by a single control, by the panel showing or by a nested submenu 28 | if LAM.currentAddonPanel ~= panel or not LAM.currentPanelOpened then return end -- we refresh it later when the panel is opened 29 | 30 | local panelControls = panel.controlsToRefresh 31 | 32 | for i = 1, #panelControls do 33 | local updateControl = panelControls[i] 34 | if updateControl ~= control and updateControl.UpdateValue then 35 | updateControl:UpdateValue() 36 | end 37 | if updateControl.UpdateDisabled then 38 | updateControl:UpdateDisabled() 39 | end 40 | if updateControl.UpdateWarning then 41 | updateControl:UpdateWarning() 42 | end 43 | end 44 | end 45 | 46 | local function ForceDefaults(panel) 47 | local panelControls = panel.controlsToRefresh 48 | 49 | for i = 1, #panelControls do 50 | local updateControl = panelControls[i] 51 | local updateControlData = updateControl.data 52 | if updateControl.UpdateValue and updateControlData.default ~= nil then 53 | updateControl:UpdateValue(true) 54 | end 55 | if updateControlData.resetFunc then 56 | updateControlData.resetFunc(updateControl) 57 | end 58 | end 59 | 60 | if panel.data.resetFunc then 61 | panel.data.resetFunc(panel) 62 | end 63 | 64 | cm:FireCallbacks("LAM-RefreshPanel", panel) 65 | end 66 | 67 | local callbackRegistered = false 68 | LAMCreateControl.scrollCount = LAMCreateControl.scrollCount or 1 69 | local SEPARATOR = " - " 70 | local COLORED_SEPARATOR = ZO_WHITE:Colorize(SEPARATOR) 71 | local LINK_COLOR = ZO_ColorDef:New("5959D5") 72 | local LINK_MOUSE_OVER_COLOR = ZO_ColorDef:New("B8B8D3") 73 | local LINK_COLOR_DONATE = ZO_ColorDef:New("FFD700") -- golden 74 | local LINK_MOUSE_OVER_COLOR_DONATE = ZO_ColorDef:New("FFF6CC") 75 | 76 | local function CreateButtonControl(control, label, clickAction, relativeTo) 77 | local button = wm:CreateControl(nil, control, CT_BUTTON) 78 | button:SetClickSound("Click") 79 | button:SetFont(LAM.util.L["PANEL_INFO_FONT"]) 80 | button:SetNormalFontColor(LINK_COLOR:UnpackRGBA()) 81 | button:SetMouseOverFontColor(LINK_MOUSE_OVER_COLOR:UnpackRGBA()) 82 | 83 | local OnClicked 84 | local actionType = type(clickAction) 85 | if actionType == "string" then 86 | OnClicked = function() RequestOpenUnsafeURL(clickAction) end 87 | elseif actionType == "function" then 88 | OnClicked = clickAction 89 | end 90 | button:SetHandler("OnClicked", OnClicked) 91 | 92 | if relativeTo then 93 | button:SetAnchor(TOPLEFT, relativeTo, TOPRIGHT, 0, 0) 94 | button:SetText(COLORED_SEPARATOR .. label) 95 | else 96 | button:SetAnchor(TOPLEFT, control.label, BOTTOMLEFT, 0, -2) 97 | button:SetText(label) 98 | end 99 | button:SetDimensions(button:GetLabelControl():GetTextDimensions()) 100 | 101 | return button 102 | end 103 | 104 | function LAMCreateControl.panel(parent, panelData, controlName) 105 | local control = wm:CreateControl(controlName, parent, CT_CONTROL) 106 | 107 | control.label = wm:CreateControlFromVirtual(nil, control, "ZO_Options_SectionTitleLabel") 108 | local label = control.label 109 | label:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 4) 110 | label:SetText(LAM.util.GetStringFromValue(panelData.displayName or panelData.name)) 111 | 112 | local previousInfoControl 113 | if panelData.author or panelData.version then 114 | control.info = wm:CreateControl(nil, control, CT_LABEL) 115 | local info = control.info 116 | info:SetFont(LAM.util.L["PANEL_INFO_FONT"]) 117 | info:SetAnchor(TOPLEFT, label, BOTTOMLEFT, 0, -2) 118 | 119 | local output = {} 120 | if panelData.author then 121 | output[#output + 1] = zo_strformat(LAM.util.L["AUTHOR"], LAM.util.GetStringFromValue(panelData.author)) 122 | end 123 | if panelData.version then 124 | output[#output + 1] = zo_strformat(LAM.util.L["VERSION"], LAM.util.GetStringFromValue(panelData.version)) 125 | end 126 | info:SetText(table.concat(output, SEPARATOR)) 127 | previousInfoControl = info 128 | end 129 | 130 | if panelData.website then 131 | control.website = CreateButtonControl(control, LAM.util.L["WEBSITE"], panelData.website, previousInfoControl) 132 | previousInfoControl = control.website 133 | end 134 | 135 | if panelData.feedback then 136 | control.feedback = CreateButtonControl(control, LAM.util.L["FEEDBACK"], panelData.feedback, previousInfoControl) 137 | previousInfoControl = control.feedback 138 | end 139 | 140 | if panelData.translation then 141 | control.translation = CreateButtonControl(control, LAM.util.L["TRANSLATION"], panelData.translation, previousInfoControl) 142 | previousInfoControl = control.translation 143 | end 144 | 145 | if panelData.donation then 146 | control.donation = CreateButtonControl(control, LAM.util.L["DONATION"], panelData.donation, previousInfoControl) 147 | local donation = control.donation 148 | previousInfoControl = donation 149 | donation:SetNormalFontColor(LINK_COLOR_DONATE:UnpackRGBA()) 150 | donation:SetMouseOverFontColor(LINK_MOUSE_OVER_COLOR_DONATE:UnpackRGBA()) 151 | end 152 | 153 | control.container = wm:CreateControlFromVirtual("LAMAddonPanelContainer"..LAMCreateControl.scrollCount, control, "ZO_ScrollContainer") 154 | LAMCreateControl.scrollCount = LAMCreateControl.scrollCount + 1 155 | local container = control.container 156 | container:SetAnchor(TOPLEFT, control.info or label, BOTTOMLEFT, 0, 20) 157 | container:SetAnchor(BOTTOMRIGHT, control, BOTTOMRIGHT, -3, -3) 158 | control.scroll = GetControl(control.container, "ScrollChild") 159 | control.scroll:SetResizeToFitPadding(0, 20) 160 | 161 | if panelData.registerForRefresh and not callbackRegistered then --don't want to register our callback more than once 162 | cm:RegisterCallback("LAM-RefreshPanel", RefreshPanel) 163 | callbackRegistered = true 164 | end 165 | 166 | control.ForceDefaults = ForceDefaults 167 | control.RefreshPanel = LAM.util.RequestRefreshIfNeeded 168 | control.data = panelData 169 | control.controlsToRefresh = {} 170 | 171 | return control 172 | end 173 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2015 Ryan Lakanen (Seerah) 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2016 Ryan Lakanen (Seerah) 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/slider.lua: -------------------------------------------------------------------------------- 1 | --[[sliderData = { 2 | type = "slider", 3 | name = "My Slider", -- or string id or function returning a string 4 | getFunc = function() return db.var end, 5 | setFunc = function(value) db.var = value doStuff() end, 6 | min = 0, 7 | max = 20, 8 | step = 1, -- (optional) 9 | clampInput = true, -- boolean, if set to false the input won't clamp to min and max and allow any number instead (optional) 10 | clampFunction = function(value, min, max) return math.max(math.min(value, max), min) end, -- function that is called to clamp the value (optional) 11 | decimals = 0, -- when specified the input value is rounded to the specified number of decimals (optional) 12 | autoSelect = false, -- boolean, automatically select everything in the text input field when it gains focus (optional) 13 | inputLocation = "below", -- or "right", determines where the input field is shown. This should not be used within the addon menu and is for custom sliders (optional) 14 | readOnly = true, -- boolean, you can use the slider, but you can't insert a value manually (optional) 15 | tooltip = "Slider's tooltip text.", -- or string id or function returning a string (optional) 16 | width = "full", -- or "half" (optional) 17 | disabled = function() return db.someBooleanSetting end, --or boolean (optional) 18 | warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional) 19 | requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional) 20 | default = defaults.var, -- default value or function that returns the default value (optional) 21 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 22 | reference = "MyAddonSlider", -- unique global reference to control (optional) 23 | resetFunc = function(sliderControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 24 | } ]] 25 | 26 | local widgetVersion = 16 27 | local LAM = LibAddonMenu2 28 | if not LAM:RegisterWidget("slider", widgetVersion) then return end 29 | 30 | local wm = WINDOW_MANAGER 31 | local strformat = string.format 32 | local SLIDER_HANDLER_NAMESPACE = "LAM2_Slider" 33 | 34 | local function RoundDecimalToPlace(d, place) 35 | return tonumber(strformat("%." .. tostring(place) .. "f", d)) 36 | end 37 | 38 | local function ClampValue(value, min, max) 39 | return math.max(math.min(value, max), min) 40 | end 41 | 42 | local function UpdateDisabled(control) 43 | local disable 44 | if type(control.data.disabled) == "function" then 45 | disable = control.data.disabled() 46 | else 47 | disable = control.data.disabled 48 | end 49 | 50 | control.slider:SetEnabled(not disable) 51 | control.slidervalue:SetEditEnabled(not (control.data.readOnly or disable)) 52 | if disable then 53 | control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 54 | control.minText:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 55 | control.maxText:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 56 | control.slidervalue:SetColor(ZO_DEFAULT_DISABLED_MOUSEOVER_COLOR:UnpackRGBA()) 57 | else 58 | control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 59 | control.minText:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 60 | control.maxText:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 61 | control.slidervalue:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 62 | end 63 | end 64 | 65 | local function UpdateValue(control, forceDefault, value) 66 | if forceDefault then --if we are forcing defaults 67 | value = LAM.util.GetDefaultValue(control.data.default) 68 | control.data.setFunc(value) 69 | elseif value then 70 | if control.data.decimals then 71 | value = RoundDecimalToPlace(value, control.data.decimals) 72 | end 73 | if control.data.clampInput ~= false then 74 | local clamp = control.data.clampFunction or ClampValue 75 | value = clamp(value, control.data.min, control.data.max) 76 | end 77 | control.data.setFunc(value) 78 | --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed 79 | LAM.util.RequestRefreshIfNeeded(control) 80 | else 81 | value = control.data.getFunc() 82 | end 83 | 84 | control.slider:SetValue(value) 85 | control.slidervalue:SetText(value) 86 | end 87 | 88 | local index = 1 89 | function LAMCreateControl.slider(parent, sliderData, controlName) 90 | local control = LAM.util.CreateLabelAndContainerControl(parent, sliderData, controlName) 91 | local isInputOnRight = sliderData.inputLocation == "right" 92 | 93 | --skipping creating the backdrop... Is this the actual slider texture? 94 | control.slider = wm:CreateControl(nil, control.container, CT_SLIDER) 95 | local slider = control.slider 96 | slider:SetAnchor(TOPLEFT) 97 | slider:SetHeight(14) 98 | if(isInputOnRight) then 99 | slider:SetAnchor(TOPRIGHT, nil, nil, -60) 100 | else 101 | slider:SetAnchor(TOPRIGHT) 102 | end 103 | slider:SetMouseEnabled(true) 104 | slider:SetOrientation(ORIENTATION_HORIZONTAL) 105 | --put nil for highlighted texture file path, and what look to be texture coords 106 | slider:SetThumbTexture("EsoUI\\Art\\Miscellaneous\\scrollbox_elevator.dds", "EsoUI\\Art\\Miscellaneous\\scrollbox_elevator_disabled.dds", nil, 8, 16) 107 | local minValue = sliderData.min 108 | local maxValue = sliderData.max 109 | slider:SetMinMax(minValue, maxValue) 110 | slider:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end) 111 | slider:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end) 112 | 113 | slider.bg = wm:CreateControl(nil, slider, CT_BACKDROP) 114 | local bg = slider.bg 115 | bg:SetCenterColor(0, 0, 0) 116 | bg:SetAnchor(TOPLEFT, slider, TOPLEFT, 0, 4) 117 | bg:SetAnchor(BOTTOMRIGHT, slider, BOTTOMRIGHT, 0, -4) 118 | bg:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-SliderBackdrop.dds", 32, 4) 119 | 120 | control.minText = wm:CreateControl(nil, slider, CT_LABEL) 121 | local minText = control.minText 122 | minText:SetFont("ZoFontGameSmall") 123 | minText:SetAnchor(TOPLEFT, slider, BOTTOMLEFT) 124 | minText:SetText(sliderData.min) 125 | 126 | control.maxText = wm:CreateControl(nil, slider, CT_LABEL) 127 | local maxText = control.maxText 128 | maxText:SetFont("ZoFontGameSmall") 129 | maxText:SetAnchor(TOPRIGHT, slider, BOTTOMRIGHT) 130 | maxText:SetText(sliderData.max) 131 | 132 | control.slidervalueBG = wm:CreateControlFromVirtual(nil, slider, "ZO_EditBackdrop") 133 | if(isInputOnRight) then 134 | control.slidervalueBG:SetDimensions(60, 26) 135 | control.slidervalueBG:SetAnchor(LEFT, slider, RIGHT, 5, 0) 136 | else 137 | control.slidervalueBG:SetDimensions(50, 16) 138 | control.slidervalueBG:SetAnchor(TOP, slider, BOTTOM, 0, 0) 139 | end 140 | control.slidervalue = wm:CreateControlFromVirtual(nil, control.slidervalueBG, "ZO_DefaultEditForBackdrop") 141 | local slidervalue = control.slidervalue 142 | slidervalue:ClearAnchors() 143 | slidervalue:SetAnchor(TOPLEFT, control.slidervalueBG, TOPLEFT, 3, 1) 144 | slidervalue:SetAnchor(BOTTOMRIGHT, control.slidervalueBG, BOTTOMRIGHT, -3, -1) 145 | slidervalue:SetTextType(TEXT_TYPE_NUMERIC) 146 | if(isInputOnRight) then 147 | slidervalue:SetFont("ZoFontGameLarge") 148 | else 149 | slidervalue:SetFont("ZoFontGameSmall") 150 | end 151 | 152 | local isHandlingChange = false 153 | local function HandleValueChanged(value) 154 | if isHandlingChange then return end 155 | if sliderData.decimals then 156 | value = RoundDecimalToPlace(value, sliderData.decimals) 157 | end 158 | isHandlingChange = true 159 | slider:SetValue(value) 160 | slidervalue:SetText(value) 161 | isHandlingChange = false 162 | end 163 | 164 | slidervalue:SetHandler("OnEscape", function(self) 165 | HandleValueChanged(sliderData.getFunc()) 166 | self:LoseFocus() 167 | end) 168 | slidervalue:SetHandler("OnEnter", function(self) 169 | self:LoseFocus() 170 | end) 171 | slidervalue:SetHandler("OnFocusLost", function(self) 172 | local value = tonumber(self:GetText()) 173 | control:UpdateValue(false, value) 174 | end) 175 | slidervalue:SetHandler("OnTextChanged", function(self) 176 | local input = self:GetText() 177 | if(#input > 1 and not input:sub(-1):match("[0-9]")) then return end 178 | local value = tonumber(input) 179 | if(value) then 180 | HandleValueChanged(value) 181 | end 182 | end) 183 | if(sliderData.autoSelect) then 184 | ZO_PreHookHandler(slidervalue, "OnFocusGained", function(self) 185 | self:SelectAll() 186 | end) 187 | end 188 | 189 | local range = maxValue - minValue 190 | slider:SetValueStep(sliderData.step or 1) 191 | slider:SetHandler("OnValueChanged", function(self, value, eventReason) 192 | if eventReason == EVENT_REASON_SOFTWARE then return end 193 | HandleValueChanged(value) 194 | end) 195 | slider:SetHandler("OnSliderReleased", function(self, value) 196 | if self:GetEnabled() then 197 | control:UpdateValue(false, value) 198 | end 199 | end) 200 | 201 | local function OnMouseWheel(self, value) 202 | if(not self:GetEnabled()) then return end 203 | local new_value = (tonumber(slidervalue:GetText()) or sliderData.min or 0) + ((sliderData.step or 1) * value) 204 | control:UpdateValue(false, new_value) 205 | end 206 | 207 | local sliderHasFocus = false 208 | local scrollEventInstalled = false 209 | local function UpdateScrollEventHandler() 210 | local needsScrollEvent = sliderHasFocus or slidervalue:HasFocus() 211 | if needsScrollEvent ~= scrollEventInstalled then 212 | local callback = needsScrollEvent and OnMouseWheel or nil 213 | slider:SetHandler("OnMouseWheel", callback, SLIDER_HANDLER_NAMESPACE) 214 | scrollEventInstalled = needsScrollEvent 215 | end 216 | end 217 | 218 | EVENT_MANAGER:RegisterForEvent("LAM_Slider_OnGlobalMouseUp_" .. index, EVENT_GLOBAL_MOUSE_UP, function() 219 | sliderHasFocus = (wm:GetMouseOverControl() == slider) 220 | UpdateScrollEventHandler() 221 | end) 222 | slidervalue:SetHandler("OnFocusGained", UpdateScrollEventHandler, SLIDER_HANDLER_NAMESPACE) 223 | slidervalue:SetHandler("OnFocusLost", UpdateScrollEventHandler, SLIDER_HANDLER_NAMESPACE) 224 | index = index + 1 225 | 226 | if sliderData.warning ~= nil or sliderData.requiresReload then 227 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 228 | control.warning:SetAnchor(RIGHT, slider, LEFT, -5, 0) 229 | control.UpdateWarning = LAM.util.UpdateWarning 230 | control:UpdateWarning() 231 | end 232 | 233 | control.UpdateValue = UpdateValue 234 | control:UpdateValue() 235 | 236 | if sliderData.disabled ~= nil then 237 | control.UpdateDisabled = UpdateDisabled 238 | control:UpdateDisabled() 239 | end 240 | 241 | LAM.util.RegisterForRefreshIfNeeded(control) 242 | LAM.util.RegisterForReloadIfNeeded(control) 243 | 244 | return control 245 | end 246 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/dropdown.lua: -------------------------------------------------------------------------------- 1 | --[[dropdownData = { 2 | type = "dropdown", 3 | name = "My Dropdown", -- or string id or function returning a string 4 | choices = {"table", "of", "choices"}, 5 | choicesValues = {"foo", 2, "three"}, -- if specified, these values will get passed to setFunc instead (optional) 6 | getFunc = function() return db.var end, -- if multiSelect is true the getFunc must return a table 7 | setFunc = function(var) db.var = var doStuff() end, -- if multiSelect is true the setFunc's var must be a table 8 | tooltip = "Dropdown's tooltip text.", -- or string id or function returning a string (optional) 9 | choicesTooltips = {"tooltip 1", "tooltip 2", "tooltip 3"}, -- or array of string ids or array of functions returning a string (optional) 10 | sort = "name-up", -- or "name-down", "numeric-up", "numeric-down", "value-up", "value-down", "numericvalue-up", "numericvalue-down" (optional) - if not provided, list will not be sorted 11 | width = "full", -- or "half" (optional) 12 | scrollable = true, -- boolean or number, if set the dropdown will feature a scroll bar if there are a large amount of choices and limit the visible lines to the specified number or 10 if true is used (optional) 13 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 14 | warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional) 15 | requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional) 16 | default = defaults.var, -- default value or function that returns the default value (optional) 17 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 18 | reference = "MyAddonDropdown", -- unique global reference to control (optional) 19 | resetFunc = function(dropdownControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 20 | multiSelect = false, -- boolean or function returning a boolean. If set to true you can select multiple entries at the list (optional) 21 | multiSelectTextFormatter = SI_COMBO_BOX_DEFAULT_MULTISELECTION_TEXT_FORMATTER, -- or string id or function returning a string. If specified, this will be used with zo_strformat(multiSelectTextFormatter, numSelectedItems) to set the "selected item text". Only incombination with multiSelect = true (optional) 22 | multiSelectNoSelectionText = SI_COMBO_BOX_DEFAULT_NO_SELECTION_TEXT, -- or string id or function returning a string. Only incombination with multiSelect = true (optional) 23 | multiSelectMaxSelections = 5, --Number or function returning a number of the maximum of selectable entries. If not specified there is no max selection. Only incombination with multiSelect = true (optional) 24 | } ]] 25 | 26 | 27 | local widgetVersion = 28 28 | local LAM = LibAddonMenu2 29 | if not LAM:RegisterWidget("dropdown", widgetVersion) then return end 30 | 31 | local GetDefaultValue = LAM.util.GetDefaultValue 32 | 33 | local wm = WINDOW_MANAGER 34 | 35 | local SORT_BY_VALUE = { ["value"] = {} } 36 | local SORT_BY_VALUE_NUMERIC = { ["value"] = { isNumeric = true } } 37 | local SORT_TYPES = { 38 | name = ZO_SORT_BY_NAME, 39 | numeric = ZO_SORT_BY_NAME_NUMERIC, 40 | value = SORT_BY_VALUE, 41 | numericvalue = SORT_BY_VALUE_NUMERIC, 42 | } 43 | local SORT_ORDERS = { 44 | up = ZO_SORT_ORDER_UP, 45 | down = ZO_SORT_ORDER_DOWN, 46 | } 47 | 48 | local DEFAULT_VISIBLE_ROWS = 10 49 | local PADDING_Y = ZO_SCROLLABLE_COMBO_BOX_LIST_PADDING_Y 50 | local ROUNDING_MARGIN = 0.01 -- needed to avoid rare issue with too many anchors processed 51 | 52 | local function UpdateDisabled(control) 53 | local disable 54 | if type(control.data.disabled) == "function" then 55 | disable = control.data.disabled() 56 | else 57 | disable = control.data.disabled 58 | end 59 | 60 | control.dropdown:SetEnabled(not disable) 61 | if disable then 62 | control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 63 | else 64 | control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 65 | end 66 | end 67 | 68 | local function UpdateMultiSelectSelected(control, values) 69 | local data = control.data 70 | assert(values ~= nil, string.format("[LAM2]Dropdown - Values for multiSelect %q are missing", control:GetName())) 71 | 72 | local dropdown = control.dropdown 73 | dropdown.m_selectedItemData = {} 74 | dropdown.m_multiSelectItemData = {} 75 | 76 | local choicesValues = data.choicesValues 77 | local usesChoicesValues = choicesValues ~= nil 78 | 79 | for _, v in ipairs(values) do 80 | local toCompare = v 81 | if usesChoicesValues then 82 | toCompare = choicesValues[v] 83 | end 84 | dropdown:SetSelectedItemByEval(function(entry) 85 | if usesChoicesValues then 86 | return entry.value == toCompare 87 | else 88 | return entry.name == toCompare 89 | end 90 | end, true) 91 | end 92 | dropdown:RefreshSelectedItemText() 93 | end 94 | 95 | local function CallMultiSelectSetFunc(control, values) 96 | local data = control.data 97 | if values == nil then 98 | values = {} 99 | local usesChoicesValues = data.choicesValues ~= nil 100 | for _, entry in ipairs(control.dropdown:GetSelectedItemData()) do 101 | if usesChoicesValues then 102 | values[#values + 1] = entry.value 103 | else 104 | values[#values + 1] = entry.name 105 | end 106 | end 107 | end 108 | data.setFunc(values) 109 | end 110 | 111 | local function UpdateValue(control, forceDefault, value) 112 | local isMultiSelectionEnabled = control.isMultiSelectionEnabled 113 | if forceDefault then --if we are forcing defaults 114 | local value = GetDefaultValue(control.data.default) 115 | if isMultiSelectionEnabled then 116 | value = value or {} 117 | control.data.setFunc(value) 118 | UpdateMultiSelectSelected(control, value) 119 | else 120 | control.data.setFunc(value) 121 | control.dropdown:SetSelectedItem(control.choices[value]) 122 | end 123 | elseif value ~= nil then 124 | if isMultiSelectionEnabled then 125 | --Coming from LAM 2.0 DiscardChangesOnReloadControls? Passing in the saved control.startValue table 126 | if type(value) ~= "table" then value = nil end 127 | CallMultiSelectSetFunc(control, value) 128 | else 129 | control.data.setFunc(value) 130 | end 131 | --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed 132 | LAM.util.RequestRefreshIfNeeded(control) 133 | else 134 | if isMultiSelectionEnabled then 135 | local values = control.data.getFunc() 136 | values = values or {} 137 | UpdateMultiSelectSelected(control, values) 138 | else 139 | value = control.data.getFunc() 140 | control.dropdown:SetSelectedItem(control.choices[value]) 141 | end 142 | end 143 | end 144 | 145 | local function DropdownCallback(control, choiceText, choice) 146 | local updateValue = choice.value 147 | if updateValue == nil then updateValue = choiceText end 148 | choice.control:UpdateValue(false, updateValue) 149 | end 150 | 151 | local function DoShowTooltip(control, tooltip) 152 | local tooltipText = LAM.util.GetStringFromValue(tooltip) 153 | if tooltipText ~= nil and tooltipText ~= "" then 154 | InitializeTooltip(InformationTooltip, control, TOPLEFT, 0, 0, BOTTOMRIGHT) 155 | SetTooltipText(InformationTooltip, tooltipText) 156 | InformationTooltipTopLevel:BringWindowToTop() 157 | end 158 | end 159 | 160 | local function ShowTooltip(control) 161 | DoShowTooltip(control, control.dataEntry.data.tooltip) 162 | end 163 | 164 | local function HideTooltip() 165 | ClearTooltip(InformationTooltip) 166 | end 167 | 168 | local function SetupTooltips(comboBox) 169 | SecurePostHook(ZO_ComboBoxDropdown_Keyboard, "OnEntryMouseEnter", function(comboBoxRowCtrl) 170 | local lComboBox = comboBoxRowCtrl.m_owner 171 | if lComboBox ~= nil and lComboBox == comboBox then 172 | ShowTooltip(comboBoxRowCtrl) 173 | end 174 | end) 175 | 176 | SecurePostHook(ZO_ComboBoxDropdown_Keyboard, "OnEntryMouseExit", function(comboBoxCtrl) 177 | HideTooltip() 178 | end) 179 | end 180 | 181 | local function UpdateChoices(control, choices, choicesValues, choicesTooltips) 182 | control.dropdown:ClearItems() --remove previous choices --(need to call :SetSelectedItem()?) 183 | ZO_ClearTable(control.choices) 184 | 185 | --build new list of choices 186 | local choices = choices or control.data.choices 187 | local choicesValues = choicesValues or control.data.choicesValues 188 | local choicesTooltips = choicesTooltips or control.data.choicesTooltips 189 | 190 | if choicesValues then 191 | assert(#choices == #choicesValues, "choices and choicesValues need to have the same size") 192 | end 193 | 194 | if choicesTooltips then 195 | assert(#choices == #choicesTooltips, "choices and choicesTooltips need to have the same size") 196 | SetupTooltips(control.dropdown) 197 | end 198 | 199 | for i = 1, #choices do 200 | local entry = control.dropdown:CreateItemEntry(choices[i], DropdownCallback) 201 | entry.control = control 202 | if choicesValues then 203 | entry.value = choicesValues[i] 204 | end 205 | if choicesTooltips then 206 | entry.tooltip = choicesTooltips[i] 207 | end 208 | local entryValue = entry.value 209 | if entryValue == nil then entryValue = entry.name end 210 | control.choices[entryValue] = entry.name 211 | 212 | control.dropdown:AddItem(entry, not control.data.sort and ZO_COMBOBOX_SUPRESS_UPDATE) --if sort type/order isn't specified, then don't sort 213 | end 214 | end 215 | 216 | local function GrabSortingInfo(sortInfo) 217 | local t, i = {}, 1 218 | for info in string.gmatch(sortInfo, "([^%-]+)") do 219 | t[i] = info 220 | i = i + 1 221 | end 222 | 223 | return t 224 | end 225 | 226 | --Change the height of the combobox dropdown 227 | local function SetDropdownHeight(control, dropdown, dropdownData) 228 | local entrySpacing = dropdown:GetSpacing() 229 | local numSortedItems = #dropdown.m_sortedItems 230 | local visibleRows, min, max 231 | 232 | local isScrollable = dropdownData.scrollable 233 | visibleRows = type(dropdownData.scrollable) == "number" and dropdownData.scrollable or DEFAULT_VISIBLE_ROWS 234 | --Either scrollable combobox: Show number of entries passed in by the data.scrollable, or use default number of entries (10) 235 | --but if less than default number of entries in the dropdown list, then shrink the max value to the number of entrries! 236 | if numSortedItems < visibleRows then 237 | min = numSortedItems 238 | max = numSortedItems 239 | else 240 | if isScrollable then 241 | min = (DEFAULT_VISIBLE_ROWS < visibleRows and DEFAULT_VISIBLE_ROWS) or visibleRows 242 | max = (DEFAULT_VISIBLE_ROWS > visibleRows and DEFAULT_VISIBLE_ROWS) or visibleRows 243 | else 244 | --Or show all entries if no scrollbar is requested 245 | min = DEFAULT_VISIBLE_ROWS 246 | max = numSortedItems 247 | end 248 | end 249 | 250 | --Entries to actually calculate the height = "number of sorted items" * "template height" + "number of sorted items -1" * spacing (last item got no spacing) 251 | local numEntries = zo_clamp(numSortedItems, min, max) 252 | local entryHeightWithSpacing = ZO_COMBO_BOX_ENTRY_TEMPLATE_HEIGHT + dropdown.m_dropdownObject.spacing 253 | local allItemsHeight = (entryHeightWithSpacing * numEntries) - entrySpacing + (PADDING_Y * 2) + ROUNDING_MARGIN 254 | dropdown:SetHeight(allItemsHeight) 255 | ZO_ScrollList_Commit(dropdown.m_scroll) 256 | 257 | return visibleRows, min, max 258 | end 259 | 260 | local function OnMultiSelectComboBoxMouseUp(control, combobox, button, upInside, alt, shift, ctrl, command) 261 | if button == MOUSE_BUTTON_INDEX_RIGHT and upInside then 262 | ClearMenu() 263 | local lDropdown = ZO_ComboBox_ObjectFromContainer(combobox) 264 | 265 | AddMenuItem(GetString(SI_ITEMFILTERTYPE0), function() 266 | lDropdown.m_multiSelectItemData = {} 267 | local maxSelections = lDropdown.m_maxNumSelections 268 | for index, _ in pairs(lDropdown.m_sortedItems) do 269 | if maxSelections == nil or maxSelections == 0 or maxSelections >= index then 270 | lDropdown:SetSelected(index, true) 271 | end 272 | end 273 | lDropdown:RefreshSelectedItemText() 274 | CallMultiSelectSetFunc(control, nil) 275 | end) 276 | AddMenuItem(GetString(SI_KEEPRESOURCETYPE0), function() 277 | lDropdown:ClearAllSelections() 278 | CallMultiSelectSetFunc(control, nil) 279 | end) 280 | ShowMenu(combobox) 281 | end 282 | end 283 | 284 | 285 | function LAMCreateControl.dropdown(parent, dropdownData, controlName) 286 | local control = LAM.util.CreateLabelAndContainerControl(parent, dropdownData, controlName) 287 | control.choices = {} 288 | 289 | local countControl = parent 290 | local name = parent:GetName() 291 | if not name or #name == 0 then 292 | countControl = LAMCreateControl 293 | name = "LAM" 294 | end 295 | local comboboxCount = (countControl.comboboxCount or 0) + 1 296 | countControl.comboboxCount = comboboxCount 297 | control.combobox = wm:CreateControlFromVirtual(zo_strjoin(nil, name, "Combobox", comboboxCount), control.container, "ZO_ComboBox") 298 | 299 | local combobox = control.combobox 300 | combobox:SetAnchor(TOPLEFT) 301 | combobox:SetDimensions(control.container:GetDimensions()) 302 | combobox:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end) 303 | combobox:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end) 304 | control.dropdown = ZO_ComboBox_ObjectFromContainer(combobox) 305 | local dropdown = control.dropdown 306 | dropdown:SetSortsItems(false) -- need to sort ourselves in order to be able to sort by value 307 | dropdown.m_containerWidth = combobox:GetWidth() -- need to replace it, otherwise the minWidth is wrong 308 | 309 | local isMultiSelectionEnabled = GetDefaultValue(dropdownData.multiSelect) 310 | control.isMultiSelectionEnabled = isMultiSelectionEnabled 311 | 312 | --Multiselection 313 | if isMultiSelectionEnabled == true then 314 | --Add context menu to the multiselect dropdown: Select all / Clear all selections 315 | combobox:SetHandler("OnMouseUp", function(...) OnMultiSelectComboBoxMouseUp(control, ...) end, "LAM2DropdownWidgetOnMouseUp") 316 | 317 | local multiSelectionTextFormatter = GetDefaultValue(dropdownData.multiSelectTextFormatter) or GetString(SI_COMBO_BOX_DEFAULT_MULTISELECTION_TEXT_FORMATTER) 318 | local multiSelectionNoSelectionText = GetDefaultValue(dropdownData.multiSelectNoSelectionText) or GetString(SI_COMBO_BOX_DEFAULT_NO_SELECTION_TEXT) 319 | dropdown:EnableMultiSelect(multiSelectionTextFormatter, multiSelectionNoSelectionText) 320 | 321 | local maxSelections = GetDefaultValue(dropdownData.multiSelectMaxSelections) 322 | if type(maxSelections) == "number" then 323 | dropdown:SetMaxSelections(maxSelections) 324 | end 325 | else 326 | dropdown:DisableMultiSelect() 327 | end 328 | 329 | ZO_PreHook(dropdown, "UpdateItems", function(self) 330 | assert(not self.m_sortsItems, "built-in dropdown sorting was reactivated, sorting is handled by LAM") 331 | if control.m_sortOrder ~= nil and control.m_sortType then 332 | local sortKey = next(control.m_sortType) 333 | local sortFunc = function(item1, item2) return ZO_TableOrderingFunction(item1, item2, sortKey, control.m_sortType, control.m_sortOrder) end 334 | table.sort(self.m_sortedItems, sortFunc) 335 | end 336 | end) 337 | 338 | if dropdownData.sort then 339 | local sortInfo = GrabSortingInfo(dropdownData.sort) 340 | control.m_sortType, control.m_sortOrder = SORT_TYPES[sortInfo[1]], SORT_ORDERS[sortInfo[2]] 341 | elseif dropdownData.choicesValues then 342 | control.m_sortType, control.m_sortOrder = ZO_SORT_ORDER_UP, SORT_BY_VALUE 343 | end 344 | 345 | if dropdownData.warning ~= nil or dropdownData.requiresReload then 346 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 347 | control.warning:SetAnchor(RIGHT, combobox, LEFT, -5, 0) 348 | control.UpdateWarning = LAM.util.UpdateWarning 349 | control:UpdateWarning() 350 | end 351 | 352 | control.SetDropdownHeight = SetDropdownHeight 353 | control.AdjustDimensions = function() end -- no longer needed, but we keep it just in case someone else calls it from outside 354 | control.UpdateChoices = UpdateChoices 355 | control:UpdateChoices(dropdownData.choices, dropdownData.choicesValues) 356 | control.UpdateValue = UpdateValue 357 | control:UpdateValue() 358 | if dropdownData.disabled ~= nil then 359 | control.UpdateDisabled = UpdateDisabled 360 | control:UpdateDisabled() 361 | end 362 | 363 | LAM.util.RegisterForRefreshIfNeeded(control) 364 | LAM.util.RegisterForReloadIfNeeded(control) 365 | 366 | return control 367 | end 368 | -------------------------------------------------------------------------------- /LibAddonMenu-2.0/controls/iconpicker.lua: -------------------------------------------------------------------------------- 1 | --[[iconpickerData = { 2 | type = "iconpicker", 3 | name = "My Icon Picker", -- or string id or function returning a string 4 | choices = {"texture path 1", "texture path 2", "texture path 3"}, 5 | getFunc = function() return db.var end, 6 | setFunc = function(var) db.var = var doStuff() end, 7 | tooltip = "Color Picker's tooltip text.", -- or string id or function returning a string (optional) 8 | choicesTooltips = {"icon tooltip 1", "icon tooltip 2", "icon tooltip 3"}, -- or array of string ids or array of functions returning a string (optional) 9 | maxColumns = 5, -- number of icons in one row (optional) 10 | visibleRows = 4.5, -- number of visible rows (optional) 11 | iconSize = 28, -- size of the icons (optional) 12 | defaultColor = ZO_ColorDef:New("FFFFFF"), -- default color of the icons (optional) 13 | width = "full", --or "half" (optional) 14 | beforeShow = function(control, iconPicker) return preventShow end, -- (optional) 15 | disabled = function() return db.someBooleanSetting end, -- or boolean (optional) 16 | warning = "May cause permanent awesomeness.", -- or string id or function returning a string (optional) 17 | requiresReload = false, -- boolean, if set to true, the warning text will contain a notice that changes are only applied after an UI reload and any change to the value will make the "Apply Settings" button appear on the panel which will reload the UI when pressed (optional) 18 | default = defaults.var, -- default value or function that returns the default value (optional) 19 | helpUrl = "https://www.esoui.com/portal.php?id=218&a=faq", -- a string URL or a function that returns the string URL (optional) 20 | reference = "MyAddonIconPicker", -- unique global reference to control (optional) 21 | resetFunc = function(iconpickerControl) d("defaults reset") end, -- custom function to run after the control is reset to defaults (optional) 22 | } ]] 23 | 24 | local widgetVersion = 11 25 | local LAM = LibAddonMenu2 26 | if not LAM:RegisterWidget("iconpicker", widgetVersion) then return end 27 | 28 | local wm = WINDOW_MANAGER 29 | 30 | local IconPickerMenu = ZO_Object:Subclass() 31 | local iconPicker 32 | LAM.util.GetIconPickerMenu = function() 33 | if not iconPicker then 34 | iconPicker = IconPickerMenu:New("LAMIconPicker") 35 | local sceneFragment = LAM:GetAddonSettingsFragment() 36 | ZO_PreHook(sceneFragment, "OnHidden", function() 37 | if not iconPicker.control:IsHidden() then 38 | iconPicker:Clear() 39 | end 40 | end) 41 | end 42 | return iconPicker 43 | end 44 | 45 | function IconPickerMenu:New(...) 46 | local object = ZO_Object.New(self) 47 | object:Initialize(...) 48 | return object 49 | end 50 | 51 | function IconPickerMenu:Initialize(name) 52 | local control = wm:CreateTopLevelWindow(name) 53 | control:SetDrawTier(DT_HIGH) 54 | control:SetHidden(true) 55 | self.control = control 56 | 57 | local scrollContainer = wm:CreateControlFromVirtual(name .. "ScrollContainer", control, "ZO_ScrollContainer") 58 | -- control:SetDimensions(control.container:GetWidth(), height) -- adjust to icon size / col count 59 | scrollContainer:SetAnchorFill() 60 | ZO_Scroll_SetUseFadeGradient(scrollContainer, false) 61 | ZO_Scroll_SetHideScrollbarOnDisable(scrollContainer, false) 62 | ZO_VerticalScrollbarBase_OnMouseExit(scrollContainer:GetNamedChild("ScrollBar")) -- scrollbar initialization seems to be broken so we force it to update the correct alpha value 63 | local scroll = GetControl(scrollContainer, "ScrollChild") 64 | self.scroll = scroll 65 | self.scrollContainer = scrollContainer 66 | 67 | local bg = wm:CreateControl(nil, scrollContainer, CT_BACKDROP) 68 | bg:SetAnchor(TOPLEFT, scrollContainer, TOPLEFT, 0, -3) 69 | bg:SetAnchor(BOTTOMRIGHT, scrollContainer, BOTTOMRIGHT, 2, 5) 70 | bg:SetEdgeTexture("EsoUI\\Art\\Tooltips\\UI-Border.dds", 128, 16) 71 | bg:SetCenterTexture("EsoUI\\Art\\Tooltips\\UI-TooltipCenter.dds") 72 | bg:SetInsets(16, 16, -16, -16) 73 | 74 | local mungeOverlay = wm:CreateControl(nil, bg, CT_TEXTURE) 75 | mungeOverlay:SetTexture("EsoUI/Art/Tooltips/munge_overlay.dds") 76 | mungeOverlay:SetDrawLevel(1) 77 | mungeOverlay:SetAddressMode(TEX_MODE_WRAP) 78 | mungeOverlay:SetAnchorFill() 79 | 80 | local mouseOver = wm:CreateControl(nil, scrollContainer, CT_TEXTURE) 81 | mouseOver:SetDrawLevel(2) 82 | mouseOver:SetTexture("EsoUI/Art/Buttons/minmax_mouseover.dds") 83 | mouseOver:SetHidden(true) 84 | 85 | local function IconFactory(pool) 86 | local icon = wm:CreateControl(name .. "Entry" .. pool:GetNextControlId(), scroll, CT_TEXTURE) 87 | icon:SetMouseEnabled(true) 88 | icon:SetDrawLevel(3) 89 | icon:SetDrawLayer(DL_CONTROLS) 90 | icon:SetHandler("OnMouseEnter", function() 91 | mouseOver:SetAnchor(TOPLEFT, icon, TOPLEFT, 0, 0) 92 | mouseOver:SetAnchor(BOTTOMRIGHT, icon, BOTTOMRIGHT, 0, 0) 93 | mouseOver:SetHidden(false) 94 | if self.customOnMouseEnter then 95 | self.customOnMouseEnter(icon) 96 | else 97 | self:OnMouseEnter(icon) 98 | end 99 | end) 100 | icon:SetHandler("OnMouseExit", function() 101 | mouseOver:ClearAnchors() 102 | mouseOver:SetHidden(true) 103 | if self.customOnMouseExit then 104 | self.customOnMouseExit(icon) 105 | else 106 | self:OnMouseExit(icon) 107 | end 108 | end) 109 | icon:SetHandler("OnMouseUp", function(control, ...) 110 | PlaySound("Click") 111 | icon.OnSelect(icon, icon.texture) 112 | self:Clear() 113 | end) 114 | return icon 115 | end 116 | 117 | local function ResetFunction(icon) 118 | icon:ClearAnchors() 119 | icon:SetHidden(true) 120 | end 121 | 122 | self.iconPool = ZO_ObjectPool:New(IconFactory, ResetFunction) 123 | self:SetMaxColumns(1) 124 | self.icons = {} 125 | self.color = ZO_DEFAULT_ENABLED_COLOR 126 | 127 | EVENT_MANAGER:RegisterForEvent(name .. "_OnGlobalMouseUp", EVENT_GLOBAL_MOUSE_UP, function() 128 | if self.refCount ~= nil then 129 | local moc = wm:GetMouseOverControl() 130 | if(moc:GetOwningWindow() ~= control) then 131 | self.refCount = self.refCount - 1 132 | if self.refCount <= 0 then 133 | self:Clear() 134 | end 135 | end 136 | end 137 | end) 138 | end 139 | 140 | function IconPickerMenu:OnMouseEnter(icon) 141 | local tooltipText = icon.tooltip and LAM.util.GetStringFromValue(icon.tooltip) 142 | if tooltipText and tooltipText ~= "" then 143 | InitializeTooltip(InformationTooltip, icon, TOPLEFT, 0, 0, BOTTOMRIGHT) 144 | SetTooltipText(InformationTooltip, tooltipText) 145 | InformationTooltipTopLevel:BringWindowToTop() 146 | end 147 | end 148 | 149 | function IconPickerMenu:OnMouseExit(icon) 150 | ClearTooltip(InformationTooltip) 151 | end 152 | 153 | function IconPickerMenu:SetMaxColumns(value) 154 | self.maxCols = value ~= nil and value or 5 155 | end 156 | 157 | local DEFAULT_SIZE = 28 158 | function IconPickerMenu:SetIconSize(value) 159 | local iconSize = DEFAULT_SIZE 160 | if value ~= nil then iconSize = math.max(iconSize, value) end 161 | self.iconSize = iconSize 162 | end 163 | 164 | function IconPickerMenu:SetVisibleRows(value) 165 | self.visibleRows = value ~= nil and value or 4.5 166 | end 167 | 168 | function IconPickerMenu:SetMouseHandlers(onEnter, onExit) 169 | self.customOnMouseEnter = onEnter 170 | self.customOnMouseExit = onExit 171 | end 172 | 173 | function IconPickerMenu:UpdateDimensions() 174 | local iconSize = self.iconSize 175 | local width = iconSize * self.maxCols + 20 176 | local height = iconSize * self.visibleRows 177 | self.control:SetDimensions(width, height) 178 | 179 | local icons = self.icons 180 | for i = 1, #icons do 181 | local icon = icons[i] 182 | icon:SetDimensions(iconSize, iconSize) 183 | end 184 | end 185 | 186 | function IconPickerMenu:UpdateAnchors() 187 | local iconSize = self.iconSize 188 | local col, maxCols = 1, self.maxCols 189 | local previousCol, previousRow 190 | local scroll = self.scroll 191 | local icons = self.icons 192 | 193 | for i = 1, #icons do 194 | local icon = icons[i] 195 | icon:ClearAnchors() 196 | if i == 1 then 197 | icon:SetAnchor(TOPLEFT, scroll, TOPLEFT, 0, 0) 198 | previousRow = icon 199 | elseif col == 1 then 200 | icon:SetAnchor(TOPLEFT, previousRow, BOTTOMLEFT, 0, 0) 201 | previousRow = icon 202 | else 203 | icon:SetAnchor(TOPLEFT, previousCol, TOPRIGHT, 0, 0) 204 | end 205 | previousCol = icon 206 | col = col >= maxCols and 1 or col + 1 207 | end 208 | end 209 | 210 | function IconPickerMenu:Clear() 211 | self.icons = {} 212 | self.iconPool:ReleaseAllObjects() 213 | self.control:SetHidden(true) 214 | self.color = ZO_DEFAULT_ENABLED_COLOR 215 | self.refCount = nil 216 | self.parent = nil 217 | self.customOnMouseEnter = nil 218 | self.customOnMouseExit = nil 219 | end 220 | 221 | function IconPickerMenu:AddIcon(texturePath, callback, tooltip) 222 | local icon, key = self.iconPool:AcquireObject() 223 | icon:SetHidden(false) 224 | icon:SetTexture(texturePath) 225 | icon:SetColor(self.color:UnpackRGBA()) 226 | icon.texture = texturePath 227 | icon.tooltip = tooltip 228 | icon.OnSelect = callback 229 | self.icons[#self.icons + 1] = icon 230 | end 231 | 232 | function IconPickerMenu:Show(parent) 233 | if #self.icons == 0 then return false end 234 | if not self.control:IsHidden() then self:Clear() return false end 235 | self:UpdateDimensions() 236 | self:UpdateAnchors() 237 | 238 | local control = self.control 239 | control:ClearAnchors() 240 | control:SetAnchor(TOPLEFT, parent, BOTTOMLEFT, 0, 8) 241 | control:SetHidden(false) 242 | control:BringWindowToTop() 243 | self.parent = parent 244 | self.refCount = 2 245 | 246 | return true 247 | end 248 | 249 | function IconPickerMenu:SetColor(color) 250 | local icons = self.icons 251 | self.color = color 252 | for i = 1, #icons do 253 | local icon = icons[i] 254 | icon:SetColor(color:UnpackRGBA()) 255 | end 256 | end 257 | 258 | ------------------------------------------------------------- 259 | 260 | local function UpdateChoices(control, choices, choicesTooltips) 261 | local data = control.data 262 | if not choices then 263 | choices, choicesTooltips = data.choices, data.choicesTooltips or {} 264 | end 265 | local addedChoices = {} 266 | 267 | local iconPicker = LAM.util.GetIconPickerMenu() 268 | iconPicker:Clear() 269 | for i = 1, #choices do 270 | local texture = choices[i] 271 | if not addedChoices[texture] then -- remove duplicates 272 | iconPicker:AddIcon(choices[i], function(self, texture) 273 | control.icon:SetTexture(texture) 274 | data.setFunc(texture) 275 | LAM.util.RequestRefreshIfNeeded(control) 276 | end, LAM.util.GetStringFromValue(choicesTooltips[i])) 277 | addedChoices[texture] = true 278 | end 279 | end 280 | end 281 | 282 | local function IsDisabled(control) 283 | if type(control.data.disabled) == "function" then 284 | return control.data.disabled() 285 | else 286 | return control.data.disabled 287 | end 288 | end 289 | 290 | local function SetColor(control, color) 291 | local icon = control.icon 292 | if IsDisabled(control) then 293 | icon:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 294 | else 295 | icon.color = color or control.data.defaultColor or ZO_DEFAULT_ENABLED_COLOR 296 | icon:SetColor(icon.color:UnpackRGBA()) 297 | end 298 | 299 | local iconPicker = LAM.util.GetIconPickerMenu() 300 | if iconPicker.parent == control.container and not iconPicker.control:IsHidden() then 301 | iconPicker:SetColor(icon.color) 302 | end 303 | end 304 | 305 | local function UpdateDisabled(control) 306 | local disable = IsDisabled(control) 307 | 308 | control.dropdown:SetMouseEnabled(not disable) 309 | control.dropdownButton:SetEnabled(not disable) 310 | 311 | local iconPicker = LAM.util.GetIconPickerMenu() 312 | if iconPicker.parent == control.container and not iconPicker.control:IsHidden() then 313 | iconPicker:Clear() 314 | end 315 | 316 | SetColor(control, control.icon.color) 317 | if disable then 318 | control.label:SetColor(ZO_DEFAULT_DISABLED_COLOR:UnpackRGBA()) 319 | else 320 | control.label:SetColor(ZO_DEFAULT_ENABLED_COLOR:UnpackRGBA()) 321 | end 322 | end 323 | 324 | local function UpdateValue(control, forceDefault, value) 325 | if forceDefault then --if we are forcing defaults 326 | value = LAM.util.GetDefaultValue(control.data.default) 327 | control.data.setFunc(value) 328 | control.icon:SetTexture(value) 329 | elseif value then 330 | control.data.setFunc(value) 331 | --after setting this value, let's refresh the others to see if any should be disabled or have their settings changed 332 | LAM.util.RequestRefreshIfNeeded(control) 333 | else 334 | value = control.data.getFunc() 335 | control.icon:SetTexture(value) 336 | end 337 | end 338 | 339 | local MIN_HEIGHT = 26 340 | local HALF_WIDTH_LINE_SPACING = 2 341 | local function SetIconSize(control, size) 342 | local icon = control.icon 343 | icon.size = size 344 | icon:SetDimensions(size, size) 345 | 346 | local height = size + 4 347 | control.dropdown:SetDimensions(size + 20, height) 348 | height = math.max(height, MIN_HEIGHT) 349 | control.container:SetHeight(height) 350 | if control.lineControl then 351 | control.lineControl:SetHeight(MIN_HEIGHT + size + HALF_WIDTH_LINE_SPACING) 352 | else 353 | control:SetHeight(height) 354 | end 355 | 356 | local iconPicker = LAM.util.GetIconPickerMenu() 357 | if iconPicker.parent == control.container and not iconPicker.control:IsHidden() then 358 | iconPicker:SetIconSize(size) 359 | iconPicker:UpdateDimensions() 360 | iconPicker:UpdateAnchors() 361 | end 362 | end 363 | 364 | function LAMCreateControl.iconpicker(parent, iconpickerData, controlName) 365 | local control = LAM.util.CreateLabelAndContainerControl(parent, iconpickerData, controlName) 366 | 367 | local function ShowIconPicker() 368 | local iconPicker = LAM.util.GetIconPickerMenu() 369 | if iconPicker.parent == control.container then 370 | iconPicker:Clear() 371 | else 372 | iconPicker:SetMaxColumns(iconpickerData.maxColumns) 373 | iconPicker:SetVisibleRows(iconpickerData.visibleRows) 374 | iconPicker:SetIconSize(control.icon.size) 375 | UpdateChoices(control) 376 | iconPicker:SetColor(control.icon.color) 377 | if iconpickerData.beforeShow then 378 | if iconpickerData.beforeShow(control, iconPicker) then 379 | iconPicker:Clear() 380 | return 381 | end 382 | end 383 | iconPicker:Show(control.container) 384 | end 385 | end 386 | 387 | local iconSize = iconpickerData.iconSize ~= nil and iconpickerData.iconSize or DEFAULT_SIZE 388 | control.dropdown = wm:CreateControl(nil, control.container, CT_CONTROL) 389 | local dropdown = control.dropdown 390 | dropdown:SetAnchor(LEFT, control.container, LEFT, 0, 0) 391 | dropdown:SetMouseEnabled(true) 392 | dropdown:SetHandler("OnMouseUp", ShowIconPicker) 393 | dropdown:SetHandler("OnMouseEnter", function() ZO_Options_OnMouseEnter(control) end) 394 | dropdown:SetHandler("OnMouseExit", function() ZO_Options_OnMouseExit(control) end) 395 | 396 | control.icon = wm:CreateControl(nil, dropdown, CT_TEXTURE) 397 | local icon = control.icon 398 | icon:SetAnchor(LEFT, dropdown, LEFT, 3, 0) 399 | icon:SetDrawLevel(2) 400 | 401 | local dropdownButton = wm:CreateControlFromVirtual(nil, dropdown, "ZO_DropdownButton") 402 | dropdownButton:SetDimensions(16, 16) 403 | dropdownButton:SetHandler("OnClicked", ShowIconPicker) 404 | dropdownButton:SetAnchor(RIGHT, dropdown, RIGHT, -3, 0) 405 | control.dropdownButton = dropdownButton 406 | 407 | control.bg = wm:CreateControl(nil, dropdown, CT_BACKDROP) 408 | local bg = control.bg 409 | bg:SetAnchor(TOPLEFT, dropdown, TOPLEFT, 0, -3) 410 | bg:SetAnchor(BOTTOMRIGHT, dropdown, BOTTOMRIGHT, 2, 5) 411 | bg:SetEdgeTexture("EsoUI/Art/Tooltips/UI-Border.dds", 128, 16) 412 | bg:SetCenterTexture("EsoUI/Art/Tooltips/UI-TooltipCenter.dds") 413 | bg:SetInsets(16, 16, -16, -16) 414 | local mungeOverlay = wm:CreateControl(nil, bg, CT_TEXTURE) 415 | mungeOverlay:SetTexture("EsoUI/Art/Tooltips/munge_overlay.dds") 416 | mungeOverlay:SetDrawLevel(1) 417 | mungeOverlay:SetAddressMode(TEX_MODE_WRAP) 418 | mungeOverlay:SetAnchorFill() 419 | 420 | if iconpickerData.warning ~= nil or iconpickerData.requiresReload then 421 | control.warning = wm:CreateControlFromVirtual(nil, control, "ZO_Options_WarningIcon") 422 | control.warning:SetAnchor(RIGHT, control.container, LEFT, -5, 0) 423 | control.UpdateWarning = LAM.util.UpdateWarning 424 | control:UpdateWarning() 425 | end 426 | 427 | control.UpdateChoices = UpdateChoices 428 | control.UpdateValue = UpdateValue 429 | control:UpdateValue() 430 | control.SetColor = SetColor 431 | control:SetColor() 432 | control.SetIconSize = SetIconSize 433 | control:SetIconSize(iconSize) 434 | 435 | if iconpickerData.disabled ~= nil then 436 | control.UpdateDisabled = UpdateDisabled 437 | control:UpdateDisabled() 438 | end 439 | 440 | LAM.util.RegisterForRefreshIfNeeded(control) 441 | LAM.util.RegisterForReloadIfNeeded(control) 442 | 443 | return control 444 | end 445 | -------------------------------------------------------------------------------- /info/changelog.txt: -------------------------------------------------------------------------------- 1 | 2.0 r41 2 | - added "maxChars" to HAS conversion for console ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/153]#153[/url], thanks M0RGaming) 3 | - fixed dropdown width not getting calculated correctly 4 | - fixed dropdown height no longer being restricted after opening it for the second time 5 | - updated for Seasons of the Worm Cult Part 2 6 | 7 | 2.0 r40 8 | - added (temporary) LAM to HAS conversion for console ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/149]#149[/url], thanks Dolgubon) 9 | 10 | 2.0 r39 11 | - added "compatibility" for console 12 | [INDENT]- this just makes it so that there are no errors in console flow - actual menu generation will be subject of a future v3[/INDENT] 13 | - fixed click sound no longer working in addon list ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/147]#147[/url], thanks DakJaniels) 14 | - updated Chinese translation ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/144]#144[/url], thanks Jacko9et) 15 | - updated for Seasons of the Worm Cult Part 1 16 | 17 | 2.0 r38 18 | - fixed submenus only opening once after Update 45 ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/145]#145[/url], thanks Baertram) 19 | - added new callback "LAM-PanelControlsCreated" which gets called right before a panel is shown for the first time 20 | - updated for Fallen Banners 21 | 22 | 2.0 r37 23 | - added Turkish and Ukrainian translations ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/137]#137[/url], thanks Sharlikran) 24 | - fixed multi-select dropdowns not showing selected entries correctly ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/140]#140[/url], thanks MycroftJr) 25 | - fixed dropdown choice tooltips not working correctly (thanks Calamath) 26 | - updated for Gold Road 27 | 28 | 2.0 r36 29 | - added multiselect feature to dropdown control ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/135]#135[/url], thanks Baertram) 30 | - fixed anchor constraint warnings in the interface.log ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/136]#136[/url], thanks DakJaniels) 31 | - fixed a bug which could lead to some controls not getting created in some rare cases 32 | - updated for Scions of Ithelia 33 | 34 | 2.0 r35 35 | - added "resetFunc" to each control type which gets called while resetting a panel to default values ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/130]#130[/url], thanks Baertram) 36 | - added workaround for dropdown menus getting cut off when used inside submenus 37 | - updated for Secret of the Telvanni 38 | 39 | 2.0 r34 40 | - added tooltips for header and description controls ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/129]#129[/url], thanks remosito) 41 | - fixed old icons not being hidden when choices are updated on the icon picker (thanks Gandalf) 42 | - updated for High Isle 43 | 44 | 2.0 r33 45 | - fixed dropdown widget choicesValues not accepting boolean "false" ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/127]#127[/url], thanks Baertram) 46 | - switched to a new build system 47 | - updated for Ascending Tide 48 | 49 | 2.0 r32 50 | - updated folder structure ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/119]#119[/url]) 51 | - added "createFunc", "minHeight" and "maxHeight" properties to custom control ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/123]#123[/url], thanks Baertram) 52 | 53 | 2.0 r31 54 | - fixed iconpicker showing an empty tooltip when no choicesTooltips are set ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/111]#111[/url], thanks Scootworks) 55 | - fixed slider mouse wheel interactions ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/115]#115[/url]) 56 | - fixed translated texts not showing in the official Russian localization ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/118]#118[/url], thanks andy.s) 57 | - improved dropdown choice tooltip code compatibility ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/115]#115[/url]) 58 | - added "helpUrl" property for many control types ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/109]#109[/url], thanks Baertram) 59 | - added "textType" and "maxChars" properties for editbox ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/110]#110[/url], thanks Scootworks) 60 | - added "readOnly" property for slider ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/112]#112[/url], thanks Scootworks) 61 | - removed embedded copy of LibStub ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/116]#116[/url]) 62 | - updated Japanese translation ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/113]#113[/url], thanks Calamath) 63 | - updated for Greymoor 64 | 65 | 2.0 r30 66 | - updated Korean translation (thanks whya5448) 67 | - added "enableLinks" property to description control ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/102]#102[/url], thanks silvereyes333) 68 | - updated for Dragonhold 69 | 70 | 2.0 r29 71 | - fixed a rare error when a panel refresh is triggered by an addon before LAM is fully initialized ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/98]#98[/url]) 72 | - fixed SetHandler warning showing when a scrollable dropdown is used ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/97]#97[/url]) 73 | - improved SetHandler warning message to show the panel title instead of the internal name and in addition log to LibDebugLogger for easy access to a stack trace ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/99]#99[/url]) 74 | - improved comments in control files ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/100]#100[/url], thanks Phuein) 75 | - adjusted ReloadUI warning color to match the color of the warning in the ingame video settings ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/101]#101[/url], thanks Phuein) 76 | 77 | 2.0 r28 78 | - fixed color picker throwing errors in gamepad mode ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/94]#94[/url], thanks Gandalf) 79 | - added global variable "LibAddonMenu2" for direct access without using LibStub ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/95]#95[/url]) 80 | - added IsLibrary directive to manifest ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/93]#93[/url]) 81 | - added warning message when an addon is setting the "OnShow", "OnEffectivelyShown", "OnHide" or "OnEffectivelyHidden" handler on a panel ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/92]#92[/url]) 82 | [INDENT]- use the callbacks "LAM-PanelControlsCreated", "LAM-PanelOpened" and "LAM-PanelClosed" instead[/INDENT] 83 | - updated Brazilian translation (thanks FelipeS11) 84 | 85 | 2.0 r27 86 | - fixed scrollable dropdown not working correctly ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/83]#83[/url]) 87 | - fixed disabled sliders changing value in some situations when clicked 88 | - fixed panel not refreshing on open when it was already selected ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/82]#82[/url]) 89 | - added RefreshPanel function to panel control ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/84]#84[/url]) 90 | [INDENT]- the panel control is returned by RegisterAddonPanel[/INDENT] 91 | - added "translation", "feedback" and "donation" properties to panel ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/88]#88[/url], thanks Baertram) 92 | [INDENT]- all three (and also the "website" property) accept a function or a string[/INDENT] 93 | - added "disabled" and "disabledLabel" property for submenus ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/86]#86[/url], [url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/90]#90[/url], thanks klingo) 94 | - added "icon" and "iconTextureCoords" property for submenus ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/91]#91[/url]) 95 | - added "disabled" property for descriptions ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/89]#89[/url], thanks klingo) 96 | - added "clampFunction" property for slider controls ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/85]#85[/url]) 97 | [INDENT]- the function receives the value, min and max as arguments and has to return a clamped value[/INDENT] 98 | - added optional support for LibDebugLogger 99 | [INDENT]- in case it is loaded, it logs the full error when control creation failed[/INDENT] 100 | - updated LibStub to r5 101 | 102 | 2.0 r26 103 | - fixed error when loading LAM on an unsupported locale 104 | - added Korean translation (thanks p.walker) 105 | - added Brazilian translation (thanks mlsevero) 106 | 107 | 2.0 r25 108 | - fixed tooltips not working for entries in scrollable dropdown controls ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/78]#78[/url], thanks kyoma) 109 | - fixed standalone LAM not loading as expected when LAM is bundled with the manifest included ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/81]#81[/url]) 110 | - fixed slashcommands not opening the correct panel on first attempt after UI load ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/79]#79[/url]) 111 | - fixed an error when opening the addon settings menu after Clockwork City update ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/80]#80[/url], thanks Gandalf) 112 | 113 | 2.0 r24 114 | - added scrollable property for dropdown controls ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/71]#71[/url], [url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/75]#75[/url], thanks kyoma) 115 | - added Italian translation ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/70]#70[/url], thanks JohnnyKing94) 116 | - added Polish translation ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/73]#73[/url], [url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/74]#74[/url], thanks EmiruTegryfon) 117 | - updated Spanish translation (thanks TERAB1T) 118 | - updated Russian translation (thanks Morganlefai and Kwisatz) 119 | - fixed debug code not accepting functions for widget names ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/72]#72[/url], thanks kyoma) 120 | 121 | 2.0 r23 122 | - added Chinese translation ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/64]#64[/url], thanks bssthu) 123 | - added tooltips for dropdown menu entries ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/42]#42[/url]) 124 | - added support for separate values for dropdown menu entries ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/65]#65[/url]) 125 | - added keybind for reset to defaults button ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/68]#68[/url]) 126 | - added requireReload property for input controls ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/47]#47[/url]) 127 | - fixed support for nested submenus ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/61]#61[/url], thanks Baertram) 128 | - fixed alpha and height not working on divider control ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/69]#69[/url]) 129 | 130 | 2.0 r22 131 | - fixed mouse exit events for sliders and textures ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/52]#52[/url], thanks silvereyes333) 132 | - fixed decimal input on sliders ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/54]#54[/url]) 133 | - fixed icon picker not retaining color when disabled is toggled ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/58]#58[/url]) 134 | - fixed slider accepting mouse wheel input while being disabled ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/60]#60[/url]) 135 | - added support for nested submenus ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/53]#53[/url]) 136 | - added new divider widget ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/56]#56[/url], thanks silvereyes333) 137 | - added new function "UpdateWarning" to controls which allows to refresh the warning text ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/55]#55[/url], thanks silvereyes333) 138 | - added new property "website" to panels which will render a button in the panel to open the specified addon URL ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/57]#57[/url]) 139 | - updated localization ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/59]#59[/url], thanks everyone who helped with it) 140 | 141 | 2.0 r21 142 | - fixed panel creation starting more than once when switching between different addon panels quickly ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/40]#40[/url]) 143 | - fixed LAM.util getting wiped with each version load causing errors for many players ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/44]#44[/url]) 144 | - fixed disabled controls not having the correct label color in some cases ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/41]#41[/url]) 145 | - fixed controls not updating their own disabled state when their value changes ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/51]#51[/url]) 146 | - added Japanese translation (thanks k0ta0uchi) ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/45]#45[/url]) 147 | - added isDangerous flag for button controls ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/50]#50[/url]) 148 | [INDENT]- when set to true it changes the text color of the button to red and opens a dialog which shows the label and the warning text before running the callback[/INDENT] 149 | - added new options for sliders and fixed some bugs ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/49]#49[/url]) 150 | [INDENT]- autoSelect (boolean): when set to true it makes the input field select all text when it gains focus 151 | - inputLocation (string): setting it to "right" will move the input field to the right side of the slider and make it slightly bigger. For aesthetic reasons this should only be used in custom panels and not in the addon menu 152 | - clampInput (boolean): true by default and if set to false it allows the input values of the slider to exceed the min and max value[/INDENT] 153 | - for other internal code changes take a look at the [URL="https://github.com/sirinsidiator/ESO-LibAddonMenu/commits/master"]git history[/URL] 154 | 155 | 2.0 r20 156 | - fixed empty panels not firing LAM-PanelControlsCreated ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/32]#32[/url]) 157 | - removed height constraint of 2500 from submenus ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/34]#34[/url]) 158 | - added two new callbacks LAM-PanelOpened and LAM-PanelClosed. Both pass the panel as their sole argument ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/27]#27[/url]) 159 | - 'default' can now be a function in addition to a static value ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/23]#23[/url]) 160 | - all labels (name, tooltip, warning, etc.) can now be a string id or function in addition to a static string ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/issues/22]#22[/url]) 161 | - updated LibStub to r4 162 | 163 | 2.0 r19 164 | - made icon picker choicesTooltips array optional 165 | - added support for custom panel objects without a GetWidth method (partially fixes [url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/26]#26[/url]) 166 | - fixed controls not refreshing correctly when they are initialized with a boolean "false" on the disabled property ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/35]#35[/url], thanks Randactyl) 167 | - removed height constraint on the description control ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/36]#36[/url], thanks KuroiLight) 168 | - added "isExtraWide" property to editboxes, allowing them to utilize more space ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/37]#37[/url], thanks KuroiLight) 169 | - added "decimals" property to sliders to allow rounding values to x decimals ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/38]#38[/url], implements [url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/21]#21[/url], thanks KuroiLight) 170 | - added mousewheel support for sliders ([url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/39]#39[/url], implements [url=https://github.com/sirinsidiator/ESO-LibAddonMenu/pull/30]#30[/url], thanks KuroiLight) 171 | 172 | 2.0 r18 173 | - major overhaul of the addon menu style (thanks votan & merlight) 174 | [INDENT][COLOR="Gray"]- NOTE: the menu is now a bit wider than before, if you created custom elements you might need to update them accordingly[/COLOR][/INDENT] 175 | - added search box to addon list (thanks votan & merlight) 176 | - new icon picker widget 177 | - removed micro freeze when opening a menu with many options for the first time 178 | - changed tooltip property to accept functions that return a string (thanks Ayantir) 179 | - changed the label on the defaults button and menu to avoid a grammar mistake in the french localization (thanks Ayantir) 180 | - updated LibStub to r3 (support for '.' in minor version string, e.g. "17.5") 181 | 182 | 2.0 r17 183 | - updated for changes in 100011 184 | - fixed OpenToPanel function 185 | - fixed possible error with combobox names 186 | - half width control no longer have a fixed height and instead scale automatically now 187 | - changed controls to no longer use top level windows 188 | - fixed problems with the loading order and added warning if an old version gets initialized first 189 | A big thank you to everyone who helped making these changes, especially votan, merlight and Garkin! 190 | 191 | 2.0 r16 192 | - updated for changes in 100010 193 | - thanks to Garkin for alerting me of changes needed and for testing on the test server 194 | - Spanish support added, translation provided by Luisen75 for their Spanish project 195 | 196 | 2.0 r14 197 | - fixed bug where the LAM-RefreshPanel callback was being registered with CALLBACK_MANAGER multiple times 198 | - fixed highlighting of entries in the game Settings menu (Addon Settings now properly highlights and other entries go back to normal) 199 | 200 | 2.0 r13 201 | - one last bug ran out from anunder the dresser - I smashed it hopefully! 202 | 203 | 2.0 r12 204 | - fix one bug another shows up... 205 | 206 | 2.0 r11 207 | - don't overwrite widgets list if table already exists (in case an external lib or addon registers a new widget type) 208 | - headers, descriptions, submenus and custom widgets now have the ability to update their text when the panel and other controls refresh (simply change the name/text in the controlData table) 209 | - custom controls now have the ability to refresh with other controls and your panel - there is a new optional field in the data table called refreshFunc (when the panel refreshes, this function will be called) 210 | 211 | 2.0 r10 212 | - fixed display of warning icon for dropdown controls 213 | - update LibStub.lua 214 | 215 | 2.0 r9 216 | - added Russian locale support for RuESO project 217 | - fixed anchoring issue with addon list (addon names are now properly in the scroll frame, so the few of you with tons installed should have no issue any longer) 218 | - added ability to close submenus from the bottom of the submenu (there is a small strip along the bottom of the submenu that is clickable) 219 | - edited each control to better support custom-created UIs via LAM and the parent passed through to the create functions 220 | 221 | 2.0 r8 222 | - changed border texture around panel and addon list 223 | - expanded maximum size of submenus from 1500 to 2500 224 | 225 | 2.0 r7 226 | - shortened game menu entry for French and German localizations (so the text doesn't get cut off) 227 | - fixed checkbox label coloring bug (when a checkbox that is set to "off" is re-enabled by another setting) 228 | - fixed multi-line editbox bug (where text didn't display) 229 | - added mousewheel scrolling for multi-line editboxes 230 | 231 | 2.0 r6 232 | - added "LAM-PanelControlsCreated" callback when you panel has been shown for the first time and your controls have now been created 233 | - fixed duplicate Addon Settings panels when you have a newer version of LAM overwriting an older version 234 | - finished localizing stuff that wasn't localized yet 235 | - added "sort" field to dropdown control 236 | 237 | 2.0 r5 238 | - fix RefreshPanel function so that all controls now update 239 | - add RefreshPanel call to ForceDefaults function 240 | 241 | 2.0 r4 242 | - fix for me being an idiot. Sorry guys >< 243 | 244 | 2.0 r3 245 | - fixed checkboxes making a sound when just refreshing 246 | - fixed error when the lib is loaded standalone, but no addons are registered with it 247 | - fixed error when LAM updates itself to a newer version (won't try to create two of the same frame) 248 | 249 | 2.0 r2 250 | - LAM-2.0 is now released! See http://www.esoui.com/portal.php?&id=5&pageid=10 for a list of differences between LAM1 and LAM2, as well as a guide for usage and the library's docs 251 | 252 | ----------------- 253 | 1.0 r8 254 | - updated APIVersion to 100004 255 | - changed submenu so scroll bar doesn't overlap contents 256 | - submenu should hopefully no longer occasionally show up behind the options panel 257 | 258 | 1.0 r7 259 | - the defaults button now properly hides for each panel (Note: the keybind still works, I can't seem to get rid of that, but at least the prompt is hidden now) 260 | - LAM now supports sub menus! See the description page for docs on usage 261 | 262 | 1.0 r6 263 | - copy/paste fail when changing the name of an arg. Description titles will no longer hide from you. 264 | 265 | 1.0 r5 266 | - exposed the widgets created via return 267 | 268 | 1.0 r4 269 | -new widget: Description 270 | 271 | 1.0 r3 272 | -fixed error with color picker in new patch 273 | 274 | 1.0 r2 275 | -fixed bug when more than one addon panel is created -------------------------------------------------------------------------------- /LibAddonMenu-2.0/LibAddonMenu-2.0.lua: -------------------------------------------------------------------------------- 1 | -- LibAddonMenu-2.0 & its files © Ryan Lakanen (Seerah) -- 2 | -- Distributed under The Artistic License 2.0 (see LICENSE) -- 3 | ------------------------------------------------------------------ 4 | 5 | 6 | local MAJOR, MINOR = "LibAddonMenu-2.0", _LAM2_VERSION_NUMBER or -1 7 | 8 | local lam 9 | if(not LibStub) then 10 | lam = {} 11 | else 12 | -- Optionally register LAM with LibStub 13 | lam = LibStub:NewLibrary(MAJOR, MINOR) 14 | if not lam then 15 | return --the same or newer version of this lib is already loaded into memory 16 | end 17 | end 18 | LibAddonMenu2 = lam 19 | 20 | local messages = {} 21 | local MESSAGE_PREFIX = "[LAM2] " 22 | local function PrintLater(msg) 23 | if CHAT_SYSTEM.primaryContainer then 24 | d(MESSAGE_PREFIX .. msg) 25 | else 26 | messages[#messages + 1] = msg 27 | end 28 | end 29 | 30 | local function FlushMessages() 31 | for i = 1, #messages do 32 | d(MESSAGE_PREFIX .. messages[i]) 33 | end 34 | messages = {} 35 | end 36 | 37 | local logger 38 | if LibDebugLogger then 39 | logger = LibDebugLogger(MAJOR) 40 | else 41 | local function noop() end 42 | logger = setmetatable({}, { __index = function() return noop end }) 43 | end 44 | 45 | if LAMSettingsPanelCreated and not LAMCompatibilityWarning then 46 | PrintLater("An old version of LibAddonMenu with compatibility issues was detected. For more information on how to proceed search for LibAddonMenu on esoui.com") 47 | LAMCompatibilityWarning = true 48 | end 49 | 50 | --UPVALUES-- 51 | local wm = WINDOW_MANAGER 52 | local em = EVENT_MANAGER 53 | local sm = SCENE_MANAGER 54 | local cm = CALLBACK_MANAGER 55 | local tconcat = table.concat 56 | local tinsert = table.insert 57 | 58 | local MIN_HEIGHT = 26 59 | local HALF_WIDTH_LINE_SPACING = 2 60 | local OPTIONS_CREATION_RUNNING = 1 61 | local OPTIONS_CREATED = 2 62 | local LAM_CONFIRM_DIALOG = "LAM_CONFIRM_DIALOG" 63 | local LAM_DEFAULTS_DIALOG = "LAM_DEFAULTS" 64 | local LAM_RELOAD_DIALOG = "LAM_RELOAD_DIALOG" 65 | 66 | local addonsForList = {} 67 | local addonToOptionsMap = {} 68 | local optionsState = {} 69 | lam.widgets = lam.widgets or {} 70 | local widgets = lam.widgets 71 | lam.util = lam.util or {} 72 | local util = lam.util 73 | lam.controlsForReload = lam.controlsForReload or {} 74 | local controlsForReload = lam.controlsForReload 75 | 76 | local function GetDefaultValue(default) 77 | if type(default) == "function" then 78 | return default() 79 | end 80 | return default 81 | end 82 | 83 | local function GetStringFromValue(value) 84 | if type(value) == "function" then 85 | return value() 86 | elseif type(value) == "number" then 87 | return GetString(value) 88 | end 89 | return value 90 | end 91 | 92 | local FAQ_ICON_COLOR = ZO_ColorDef:New("FFFFFF") -- white 93 | local FAQ_ICON_MOUSE_OVER_COLOR = ZO_ColorDef:New("B8B8D3") -- dark-blue/white 94 | local FAQ_ICON_MOUSE_OVER_ALPHA = 1 95 | local FAQ_ICON_MOUSE_EXIT_ALPHA = 0.4 96 | local FAQ_ICON_SIZE = 23 97 | local FAQ_ICON_TOOTIP_TEMPLATE = "%s: %s" 98 | 99 | local function GetColorForState(disabled) 100 | return disabled and ZO_DEFAULT_DISABLED_COLOR or ZO_DEFAULT_ENABLED_COLOR 101 | end 102 | 103 | local function CreateFAQTexture(control) 104 | local controlData = control.data 105 | if not control or not controlData then logger:Warn("CreateFAQTexture - missing or invalid control") return end 106 | local helpUrl = controlData and GetStringFromValue(controlData.helpUrl) 107 | if not helpUrl or helpUrl == "" then return end 108 | 109 | local faqControl = wm:CreateControl(nil, control, CT_TEXTURE) 110 | control.faqControl = faqControl 111 | 112 | faqControl:SetDrawLayer(DL_OVERLAY) 113 | faqControl:SetTexture("EsoUI\\Art\\miscellaneous\\help_icon.dds") 114 | faqControl:SetDimensions(FAQ_ICON_SIZE, FAQ_ICON_SIZE) 115 | faqControl:SetColor(FAQ_ICON_COLOR:UnpackRGBA()) 116 | faqControl:SetAlpha(FAQ_ICON_MOUSE_EXIT_ALPHA) 117 | faqControl:SetHidden(false) 118 | 119 | faqControl.data = faqControl.data or {} 120 | faqControl.data.helpUrl = helpUrl 121 | faqControl.data.tooltipText = FAQ_ICON_TOOTIP_TEMPLATE:format(util.L.WEBSITE, helpUrl) 122 | 123 | faqControl:SetMouseEnabled(true) 124 | local function onMouseExitFAQ(ctrl, ...) 125 | ZO_Options_OnMouseExit(ctrl) 126 | ctrl:SetColor(FAQ_ICON_COLOR:UnpackRGBA()) 127 | ctrl:SetAlpha(FAQ_ICON_MOUSE_EXIT_ALPHA) 128 | end 129 | faqControl:SetHandler("OnMouseUp", function(self, button, upInside) 130 | if button == MOUSE_BUTTON_INDEX_LEFT and upInside then 131 | --As the parent control's OnMouseExit won't be called because of the popup "open website": 132 | --We hide the faq texture ourself 133 | onMouseExitFAQ(self, true) 134 | RequestOpenUnsafeURL(helpUrl) 135 | end 136 | end) 137 | faqControl:SetHandler("OnMouseEnter", function(self) 138 | ZO_Options_OnMouseEnter(self) 139 | self:SetColor(FAQ_ICON_MOUSE_OVER_COLOR:UnpackRGBA()) --light blue 140 | self:SetAlpha(FAQ_ICON_MOUSE_OVER_ALPHA) 141 | end, "LAM2_FAQTexture_OnMouseEnter") 142 | faqControl:SetHandler("OnMouseExit", onMouseExitFAQ, "LAM2_FAQTexture_OnMouseExit") 143 | 144 | return faqControl 145 | end 146 | 147 | local function CreateBaseControl(parent, controlData, controlName) 148 | local control = wm:CreateControl(controlName or controlData.reference, parent.scroll or parent, CT_CONTROL) 149 | control.panel = parent.panel or parent -- if this is in a submenu, panel is the submenu's parent 150 | control.data = controlData 151 | 152 | control.isHalfWidth = controlData.width == "half" 153 | local width = 510 -- set default width in case a custom parent object is passed 154 | if control.panel.GetWidth ~= nil then width = control.panel:GetWidth() - 60 end 155 | control:SetWidth(width) 156 | return control 157 | end 158 | 159 | local function CreateLabelAndContainerControl(parent, controlData, controlName) 160 | local control = CreateBaseControl(parent, controlData, controlName) 161 | local width = control:GetWidth() 162 | 163 | local container = wm:CreateControl(nil, control, CT_CONTROL) 164 | container:SetDimensions(width / 3, MIN_HEIGHT) 165 | control.container = container 166 | 167 | local labelContainer 168 | local faqTexture = CreateFAQTexture(control) 169 | if faqTexture then 170 | labelContainer = wm:CreateControl(nil, control, CT_CONTROL) 171 | labelContainer:SetHeight(MIN_HEIGHT) 172 | control.labelContainer = container 173 | end 174 | 175 | local label = wm:CreateControl(nil, labelContainer or control, CT_LABEL) 176 | label:SetFont("ZoFontWinH4") 177 | label:SetHeight(MIN_HEIGHT) 178 | label:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS) 179 | label:SetText(GetStringFromValue(controlData.name)) 180 | control.label = label 181 | 182 | local labelAnchorTarget = labelContainer or label 183 | if control.isHalfWidth then 184 | control:SetDimensions(width / 2, MIN_HEIGHT * 2 + HALF_WIDTH_LINE_SPACING) 185 | labelAnchorTarget:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0) 186 | labelAnchorTarget:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 0) 187 | container:SetAnchor(TOPRIGHT, labelAnchorTarget, BOTTOMRIGHT, 0, HALF_WIDTH_LINE_SPACING) 188 | else 189 | control:SetDimensions(width, MIN_HEIGHT) 190 | container:SetAnchor(TOPRIGHT, control, TOPRIGHT, 0, 0) 191 | labelAnchorTarget:SetAnchor(TOPLEFT, control, TOPLEFT, 0, 0) 192 | labelAnchorTarget:SetAnchor(TOPRIGHT, container, TOPLEFT, 5, 0) 193 | end 194 | 195 | if faqTexture then 196 | faqTexture:ClearAnchors() 197 | faqTexture:SetAnchor(LEFT, label, RIGHT, 5, -1) 198 | faqTexture:SetParent(labelContainer) 199 | label:SetAnchor(LEFT, labelContainer, LEFT) 200 | label:SetDimensionConstraints(0, 0, labelContainer:GetWidth() - faqTexture:GetWidth(), 0) 201 | end 202 | 203 | control.data.tooltipText = GetStringFromValue(control.data.tooltip) 204 | control:SetMouseEnabled(true) 205 | control:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter) 206 | control:SetHandler("OnMouseExit", ZO_Options_OnMouseExit) 207 | return control 208 | end 209 | 210 | local function SetUpTooltip(control, data, tooltipData) 211 | if not data.tooltip then return end 212 | control:SetMouseEnabled(true) 213 | control.data = tooltipData or {tooltipText = util.GetStringFromValue(data.tooltip)} 214 | control:SetHandler("OnMouseEnter", ZO_Options_OnMouseEnter) 215 | control:SetHandler("OnMouseExit", ZO_Options_OnMouseExit) 216 | end 217 | 218 | local function GetTopPanel(panel) 219 | while panel.panel and panel.panel ~= panel do 220 | panel = panel.panel 221 | end 222 | return panel 223 | end 224 | 225 | local function IsSame(objA, objB) 226 | if #objA ~= #objB then return false end 227 | for i = 1, #objA do 228 | if objA[i] ~= objB[i] then return false end 229 | end 230 | return true 231 | end 232 | 233 | local function RefreshReloadUIButton() 234 | lam.requiresReload = false 235 | 236 | for i = 1, #controlsForReload do 237 | local reloadControl = controlsForReload[i] 238 | if not IsSame(reloadControl.startValue, {reloadControl.data.getFunc()}) then 239 | lam.requiresReload = true 240 | break 241 | end 242 | end 243 | 244 | if lam.applyButton then 245 | lam.applyButton:SetHidden(not lam.requiresReload) 246 | end 247 | end 248 | 249 | local function RequestRefreshIfNeeded(control) 250 | -- if our parent window wants to refresh controls, then fire the callback 251 | local panel = GetTopPanel(control) 252 | local panelData = panel.data 253 | if panelData.registerForRefresh then 254 | cm:FireCallbacks("LAM-RefreshPanel", control) 255 | end 256 | RefreshReloadUIButton() 257 | end 258 | 259 | local function RegisterForRefreshIfNeeded(control) 260 | -- if our parent window wants to refresh controls, then add this to the list 261 | local panel = GetTopPanel(control.panel) 262 | local panelData = panel.data 263 | if panelData.registerForRefresh or panelData.registerForDefaults then 264 | tinsert(panel.controlsToRefresh or {}, control) -- prevent errors on custom panels 265 | end 266 | end 267 | 268 | local function RegisterForReloadIfNeeded(control) 269 | if control.data.requiresReload then 270 | tinsert(controlsForReload, control) 271 | control.startValue = {control.data.getFunc()} 272 | end 273 | end 274 | 275 | local function GetConfirmDialog() 276 | if(not ESO_Dialogs[LAM_CONFIRM_DIALOG]) then 277 | ESO_Dialogs[LAM_CONFIRM_DIALOG] = { 278 | canQueue = true, 279 | title = { 280 | text = "", 281 | }, 282 | mainText = { 283 | text = "", 284 | }, 285 | buttons = { 286 | [1] = { 287 | text = SI_DIALOG_CONFIRM, 288 | callback = function(dialog) end, 289 | }, 290 | [2] = { 291 | text = SI_DIALOG_CANCEL, 292 | } 293 | } 294 | } 295 | end 296 | return ESO_Dialogs[LAM_CONFIRM_DIALOG] 297 | end 298 | 299 | local function ShowConfirmationDialog(title, body, callback) 300 | local dialog = GetConfirmDialog() 301 | dialog.title.text = title 302 | dialog.mainText.text = body 303 | dialog.buttons[1].callback = callback 304 | ZO_Dialogs_ShowDialog(LAM_CONFIRM_DIALOG) 305 | end 306 | 307 | local function GetDefaultsDialog() 308 | if(not ESO_Dialogs[LAM_DEFAULTS_DIALOG]) then 309 | ESO_Dialogs[LAM_DEFAULTS_DIALOG] = { 310 | canQueue = true, 311 | title = { 312 | text = SI_INTERFACE_OPTIONS_RESET_TO_DEFAULT_TOOLTIP, 313 | }, 314 | mainText = { 315 | text = SI_OPTIONS_RESET_PROMPT, 316 | }, 317 | buttons = { 318 | [1] = { 319 | text = SI_OPTIONS_RESET, 320 | callback = function(dialog) end, 321 | }, 322 | [2] = { 323 | text = SI_DIALOG_CANCEL, 324 | } 325 | } 326 | } 327 | end 328 | return ESO_Dialogs[LAM_DEFAULTS_DIALOG] 329 | end 330 | 331 | local function ShowDefaultsDialog(panel) 332 | local dialog = GetDefaultsDialog() 333 | dialog.buttons[1].callback = function() 334 | panel:ForceDefaults() 335 | RefreshReloadUIButton() 336 | end 337 | ZO_Dialogs_ShowDialog(LAM_DEFAULTS_DIALOG) 338 | end 339 | 340 | local function DiscardChangesOnReloadControls() 341 | for i = 1, #controlsForReload do 342 | local reloadControl = controlsForReload[i] 343 | if not IsSame(reloadControl.startValue, {reloadControl.data.getFunc()}) then 344 | reloadControl:UpdateValue(false, unpack(reloadControl.startValue)) 345 | end 346 | end 347 | lam.requiresReload = false 348 | lam.applyButton:SetHidden(true) 349 | end 350 | 351 | local function StorePanelForReopening() 352 | local saveData = ZO_Ingame_SavedVariables["LAM"] or {} 353 | saveData.reopenPanel = lam.currentAddonPanel:GetName() 354 | ZO_Ingame_SavedVariables["LAM"] = saveData 355 | end 356 | 357 | local function RetrievePanelForReopening() 358 | local saveData = ZO_Ingame_SavedVariables["LAM"] 359 | if(saveData) then 360 | ZO_Ingame_SavedVariables["LAM"] = nil 361 | return _G[saveData.reopenPanel] 362 | end 363 | end 364 | 365 | local function HandleReloadUIPressed() 366 | StorePanelForReopening() 367 | ReloadUI() 368 | end 369 | 370 | local function HandleLoadDefaultsPressed() 371 | ShowDefaultsDialog(lam.currentAddonPanel) 372 | end 373 | 374 | local function GetReloadDialog() 375 | if(not ESO_Dialogs[LAM_RELOAD_DIALOG]) then 376 | ESO_Dialogs[LAM_RELOAD_DIALOG] = { 377 | canQueue = true, 378 | title = { 379 | text = util.L["RELOAD_DIALOG_TITLE"], 380 | }, 381 | mainText = { 382 | text = util.L["RELOAD_DIALOG_TEXT"], 383 | }, 384 | buttons = { 385 | [1] = { 386 | text = util.L["RELOAD_DIALOG_RELOAD_BUTTON"], 387 | callback = function() ReloadUI() end, 388 | }, 389 | [2] = { 390 | text = util.L["RELOAD_DIALOG_DISCARD_BUTTON"], 391 | callback = DiscardChangesOnReloadControls, 392 | } 393 | }, 394 | noChoiceCallback = DiscardChangesOnReloadControls, 395 | } 396 | end 397 | return ESO_Dialogs[LAM_CONFIRM_DIALOG] 398 | end 399 | 400 | local function ShowReloadDialogIfNeeded() 401 | if lam.requiresReload then 402 | local dialog = GetReloadDialog() 403 | ZO_Dialogs_ShowDialog(LAM_RELOAD_DIALOG) 404 | end 405 | end 406 | 407 | local function UpdateWarning(control) 408 | local warning 409 | if control.data.warning ~= nil then 410 | warning = util.GetStringFromValue(control.data.warning) 411 | end 412 | 413 | if control.data.requiresReload then 414 | if not warning then 415 | warning = string.format("%s", util.L["RELOAD_UI_WARNING"]) 416 | else 417 | warning = string.format("%s\n\n%s", warning, util.L["RELOAD_UI_WARNING"]) 418 | end 419 | end 420 | 421 | if not warning then 422 | control.warning:SetHidden(true) 423 | else 424 | control.warning.data = {tooltipText = warning} 425 | control.warning:SetHidden(false) 426 | end 427 | end 428 | 429 | local localization = { 430 | en = { 431 | PANEL_NAME = "Addons", 432 | AUTHOR = string.format("%s: <>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Author: <>" 433 | VERSION = "Version: <>", 434 | WEBSITE = "Visit Website", 435 | FEEDBACK = "Feedback", 436 | TRANSLATION = "Translation", 437 | DONATION = "Donate", 438 | PANEL_INFO_FONT = "$(CHAT_FONT)|14|soft-shadow-thin", 439 | RELOAD_UI_WARNING = "Changes to this setting require a UI reload in order to take effect.", 440 | RELOAD_DIALOG_TITLE = "UI Reload Required", 441 | RELOAD_DIALOG_TEXT = "Some changes require a UI reload in order to take effect. Do you want to reload now or discard the changes?", 442 | RELOAD_DIALOG_RELOAD_BUTTON = "Reload", 443 | RELOAD_DIALOG_DISCARD_BUTTON = "Discard", 444 | }, 445 | it = { -- provided by JohnnyKing 446 | PANEL_NAME = "Addon", 447 | VERSION = "Versione: <>", 448 | WEBSITE = "Visita il Sitoweb", 449 | FEEDBACK = "Feedback", 450 | TRANSLATION = "Traduzione", 451 | DONATION = "Donare", 452 | RELOAD_UI_WARNING = "Cambiare questa impostazione richiede un Ricarica UI al fine che faccia effetto.", 453 | RELOAD_DIALOG_TITLE = "Ricarica UI richiesto", 454 | 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?", 455 | RELOAD_DIALOG_RELOAD_BUTTON = "Ricarica", 456 | RELOAD_DIALOG_DISCARD_BUTTON = "Annulla", 457 | }, 458 | fr = { -- provided by Ayantir 459 | PANEL_NAME = "Extensions", 460 | WEBSITE = "Visiter le site Web", 461 | FEEDBACK = "Réaction", 462 | TRANSLATION = "Traduction", 463 | DONATION = "Donner", 464 | RELOAD_UI_WARNING = "La modification de ce paramètre requiert un rechargement de l'UI pour qu'il soit pris en compte.", 465 | RELOAD_DIALOG_TITLE = "Reload UI requis", 466 | 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 ?", 467 | RELOAD_DIALOG_RELOAD_BUTTON = "Recharger", 468 | RELOAD_DIALOG_DISCARD_BUTTON = "Annuler", 469 | }, 470 | de = { -- provided by sirinsidiator 471 | PANEL_NAME = "Erweiterungen", 472 | WEBSITE = "Webseite besuchen", 473 | FEEDBACK = "Feedback", 474 | TRANSLATION = "Übersetzung", 475 | DONATION = "Spende", 476 | RELOAD_UI_WARNING = "Änderungen an dieser Option werden erst übernommen nachdem die Benutzeroberfläche neu geladen wird.", 477 | RELOAD_DIALOG_TITLE = "Neuladen benötigt", 478 | 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?", 479 | RELOAD_DIALOG_RELOAD_BUTTON = "Neu laden", 480 | RELOAD_DIALOG_DISCARD_BUTTON = "Verwerfen", 481 | }, 482 | ru = { -- provided by TERAB1T, updated by andy.s 483 | PANEL_NAME = "Дополнения", 484 | VERSION = "Версия: <>", 485 | WEBSITE = "Посетить сайт", 486 | FEEDBACK = "Отзыв", 487 | TRANSLATION = "Перевод", 488 | DONATION = "Жертвовать", 489 | RELOAD_UI_WARNING = "Для применения этой настройки необходима перезагрузка интерфейса.", 490 | RELOAD_DIALOG_TITLE = "Необходима перезагрузка интерфейса", 491 | RELOAD_DIALOG_TEXT = "Для применения некоторых изменений необходима перезагрузка интерфейса. Перезагрузить интерфейс сейчас или отменить изменения?", 492 | RELOAD_DIALOG_RELOAD_BUTTON = "Перезагрузить", 493 | RELOAD_DIALOG_DISCARD_BUTTON = "Отменить изменения", 494 | }, 495 | es = { -- provided by Morganlefai, checked by Kwisatz 496 | PANEL_NAME = "Configuración", 497 | VERSION = "Versión: <>", 498 | WEBSITE = "Visita la página web", 499 | FEEDBACK = "Reaccion", 500 | TRANSLATION = "Traducción", 501 | DONATION = "Donar", 502 | RELOAD_UI_WARNING = "Cambiar este ajuste recargará la interfaz del usuario.", 503 | RELOAD_DIALOG_TITLE = "Requiere recargar la interfaz", 504 | RELOAD_DIALOG_TEXT = "Algunos cambios requieren recargar la interfaz para poder aplicarse. Quieres aplicar los cambios y recargar la interfaz?", 505 | RELOAD_DIALOG_RELOAD_BUTTON = "Recargar", 506 | RELOAD_DIALOG_DISCARD_BUTTON = "Cancelar", 507 | }, 508 | jp = { -- provided by k0ta0uchi, updated by Calamath 509 | PANEL_NAME = "アドオン設定", 510 | WEBSITE = "ウェブサイトを見る", 511 | FEEDBACK = "フィードバック", 512 | TRANSLATION = "訳書", 513 | DONATION = "寄付", 514 | RELOAD_UI_WARNING = "この設定変更を有効にするには、UIのリロードが必要です。", 515 | RELOAD_DIALOG_TITLE = "UIのリロードが必要", 516 | RELOAD_DIALOG_TEXT = "一部の変更を有効にするには、UIのリロードが必要です。 今すぐリロードしますか、それとも変更内容を破棄しますか?", 517 | RELOAD_DIALOG_RELOAD_BUTTON = "リロード", 518 | RELOAD_DIALOG_DISCARD_BUTTON = "破棄", 519 | }, 520 | zh = { -- provided by Jacko9et 521 | PANEL_NAME = GetString(SI_GAME_MENU_ADDONS), 522 | AUTHOR = string.format("%s: <>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Author: <>" 523 | VERSION = "版本: <>", 524 | WEBSITE = "访问网站", 525 | FEEDBACK = GetString(SI_CUSTOMER_SERVICE_SUBMIT_FEEDBACK), 526 | TRANSLATION = GetString(SI_ENCHANTING_TRANSLATION_HEADER), 527 | DONATION = "捐赠", 528 | PANEL_INFO_FONT = "$(CHAT_FONT)|14|soft-shadow-thin", 529 | RELOAD_UI_WARNING = "对此设置的更改需要重新加载界面才能生效。", 530 | RELOAD_DIALOG_TITLE = "需要重新加载界面", 531 | RELOAD_DIALOG_TEXT = "某些更改需要重新加载界面才能生效。您想现在重新加载还是放弃更改?", 532 | RELOAD_DIALOG_RELOAD_BUTTON = GetString(SI_ADDON_MANAGER_RELOAD), 533 | RELOAD_DIALOG_DISCARD_BUTTON = GetString(SI_CHAMPION_SYSTEM_DISCARD_CHANGES), 534 | }, 535 | pl = { -- provided by EmiruTegryfon 536 | PANEL_NAME = "Dodatki", 537 | VERSION = "Wersja: <>", 538 | WEBSITE = "Odwiedź stronę", 539 | RELOAD_UI_WARNING = "Zmiany będą widoczne po ponownym załadowaniu UI.", 540 | RELOAD_DIALOG_TITLE = "Wymagane przeładowanie UI", 541 | RELOAD_DIALOG_TEXT = "Niektóre zmiany wymagają ponownego załadowania UI. Czy chcesz teraz ponownie załadować, czy porzucić zmiany?", 542 | RELOAD_DIALOG_RELOAD_BUTTON = "Przeładuj", 543 | RELOAD_DIALOG_DISCARD_BUTTON = "Porzuć", 544 | }, 545 | br = { -- provided by mlsevero & FelipeS11 546 | PANEL_NAME = "Addons", 547 | AUTHOR = string.format("%s: <>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Autor: <>" 548 | VERSION = "Versão: <>", 549 | WEBSITE = "Visite o Website", 550 | FEEDBACK = "Feedback", 551 | TRANSLATION = "Tradução", 552 | DONATION = "Doação", 553 | RELOAD_UI_WARNING = "Mudanças nessa configuração requerem o recarregamento da UI para ter efeito.", 554 | RELOAD_DIALOG_TITLE = "Recarregamento da UI requerida", 555 | RELOAD_DIALOG_TEXT = "Algumas mudanças requerem o recarregamento da UI para ter efeito. Você deseja recarregar agora ou descartar as mudanças?", 556 | RELOAD_DIALOG_RELOAD_BUTTON = "Recarregar", 557 | RELOAD_DIALOG_DISCARD_BUTTON = "Descartar", 558 | }, 559 | tr = { 560 | PANEL_NAME= "Eklentiler", 561 | AUTHOR = string.format("%s: <>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Yazar: <>" 562 | VERSION = "Sürüm: <>", 563 | WEBSITE = "Web Sitesini Ziyaret Edin", 564 | FEEDBACK = "Geri bildirim", 565 | TRANSLATION = "Çeviri", 566 | DONATION = "Bağış", 567 | PANEL_INFO_FONT = "$(CHAT_FONT)|14|soft-shadow-thin", 568 | RELOAD_UI_WARNING = "Bu ayarda yapılan değişikliklerin etkili olması için kullanıcı arayüzünün yeniden yüklenmesi gerekir.", 569 | RELOAD_DIALOG_TITLE = "Kullanıcı Arayüzünün Yeniden Yüklenmesi Gerekli", 570 | RELOAD_DIALOG_TEXT = "Bazı değişikliklerin etkili olması için kullanıcı arayüzünün yeniden yüklenmesi gerekir. Şimdi yeniden yüklemek mi yoksa değişiklikleri iptal etmek mi istiyorsunuz?", 571 | RELOAD_DIALOG_RELOAD_BUTTON = "Yeniden Yükle", 572 | RELOAD_DIALOG_DISCARD_BUTTON = "İptal et", 573 | }, 574 | ua = { 575 | PANEL_NAME = "Додатки", 576 | AUTHOR = string.format("%s: <>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Автор: <>" 577 | VERSION = "Версія: <>", 578 | WEBSITE = "Відвідати вебсайт", 579 | FEEDBACK = "Відгук", 580 | TRANSLATION = "Переклад", 581 | DONATION = "Підтримати", 582 | PANEL_INFO_FONT = "$(CHAT_FONT)|14|soft-shadow-thin", 583 | RELOAD_UI_WARNING = "Зміни цього параметра потребують перезавантаження інтерфейсу, аби вони набули чинності.", 584 | RELOAD_DIALOG_TITLE = "Необхідне перезавантаження інтерфейсу", 585 | RELOAD_DIALOG_TEXT = "Деякі зміни потребують перезавантаження інтерфейсу, аби вони набули чинності. Перезавантажити зараз чи бажаєте скасувати зміни?", 586 | RELOAD_DIALOG_RELOAD_BUTTON = "Перезавантажити", 587 | RELOAD_DIALOG_DISCARD_BUTTON = "Скасувати", 588 | }, 589 | } 590 | 591 | do 592 | local EsoKR = EsoKR 593 | if EsoKR and EsoKR:isKorean() then 594 | util.L = ZO_ShallowTableCopy({ -- provided by whya5448 595 | PANEL_NAME = EsoKR:E("애드온"), 596 | AUTHOR = string.format("%s: <>", GetString(SI_ADDON_MANAGER_AUTHOR)), -- "Author: <>" 597 | VERSION = EsoKR:E("버전: <>"), 598 | WEBSITE = EsoKR:E("웹사이트 방문"), 599 | FEEDBACK = EsoKR:E("피드백"), 600 | TRANSLATION = EsoKR:E("번역"), 601 | DONATION = EsoKR:E("기부"), 602 | PANEL_INFO_FONT = "EsoKR/fonts/Univers57.otf|14|soft-shadow-thin", 603 | RELOAD_UI_WARNING = EsoKR:E("이 설정을 변경하면 효과를 적용하기위해 UI 새로고침이 필요합니다."), 604 | RELOAD_DIALOG_TITLE = EsoKR:E("UI 새로고침 필요"), 605 | RELOAD_DIALOG_TEXT = EsoKR:E("변경된 설정 중 UI 새로고침을 필요로하는 사항이 있습니다. 지금 새로고침하시겠습니까? 아니면 변경을 취소하시겠습니까?"), 606 | RELOAD_DIALOG_RELOAD_BUTTON = EsoKR:E("새로고침"), 607 | RELOAD_DIALOG_DISCARD_BUTTON = EsoKR:E("변경취소"), 608 | }, localization["en"]) 609 | else 610 | util.L = ZO_ShallowTableCopy(localization[GetCVar("Language.2")] or {}, localization["en"]) 611 | end 612 | end 613 | 614 | util.GetTooltipText = GetStringFromValue -- deprecated, use util.GetStringFromValue instead 615 | util.GetStringFromValue = GetStringFromValue 616 | util.GetDefaultValue = GetDefaultValue 617 | util.GetColorForState = GetColorForState 618 | util.CreateBaseControl = CreateBaseControl 619 | util.CreateLabelAndContainerControl = CreateLabelAndContainerControl 620 | util.SetUpTooltip = SetUpTooltip 621 | util.RequestRefreshIfNeeded = RequestRefreshIfNeeded 622 | util.RegisterForRefreshIfNeeded = RegisterForRefreshIfNeeded 623 | util.RegisterForReloadIfNeeded = RegisterForReloadIfNeeded 624 | util.GetTopPanel = GetTopPanel 625 | util.ShowConfirmationDialog = ShowConfirmationDialog 626 | util.UpdateWarning = UpdateWarning 627 | util.CreateFAQTexture = CreateFAQTexture 628 | 629 | local ADDON_DATA_TYPE = 1 630 | local RESELECTING_DURING_REBUILD = true 631 | local USER_REQUESTED_OPEN = true 632 | 633 | 634 | --INTERNAL FUNCTION 635 | --scrolls ZO_ScrollList `list` to move the row corresponding to `data` 636 | -- into view (does nothing if there is no such row in the list) 637 | --unlike ZO_ScrollList_ScrollDataIntoView, this function accounts for 638 | -- fading near the list's edges - it avoids the fading area by scrolling 639 | -- a little further than the ZO function 640 | local function ScrollDataIntoView(list, data) 641 | local targetIndex = data.sortIndex 642 | if not targetIndex then return end 643 | 644 | local scrollMin, scrollMax = list.scrollbar:GetMinMax() 645 | local scrollTop = list.scrollbar:GetValue() 646 | local controlHeight = list.uniformControlHeight or list.controlHeight 647 | local targetMin = controlHeight * (targetIndex - 1) - 64 648 | -- subtracting 64 ain't arbitrary, it's the maximum fading height 649 | -- (libraries/zo_templates/scrolltemplates.lua/UpdateScrollFade) 650 | 651 | if targetMin < scrollTop then 652 | ZO_ScrollList_ScrollAbsolute(list, zo_max(targetMin, scrollMin)) 653 | else 654 | local listHeight = ZO_ScrollList_GetHeight(list) 655 | local targetMax = controlHeight * targetIndex + 64 - listHeight 656 | 657 | if targetMax > scrollTop then 658 | ZO_ScrollList_ScrollAbsolute(list, zo_min(targetMax, scrollMax)) 659 | end 660 | end 661 | end 662 | 663 | 664 | --INTERNAL FUNCTION 665 | --constructs a string pattern from the text in `searchEdit` control 666 | -- * metacharacters are escaped, losing their special meaning 667 | -- * whitespace matches anything (including empty substring) 668 | --if there is nothing but whitespace, returns nil 669 | --otherwise returns a filter function, which takes a `data` table argument 670 | -- and returns true iff `data.filterText` matches the pattern 671 | local function GetSearchFilterFunc(searchEdit) 672 | local text = searchEdit:GetText():lower() 673 | local pattern = text:match("(%S+.-)%s*$") 674 | 675 | if not pattern then -- nothing but whitespace 676 | return nil 677 | end 678 | 679 | -- escape metacharacters, e.g. "ESO-Datenbank.de" => "ESO%-Datenbank%.de" 680 | pattern = pattern:gsub("[-*+?^$().[%]%%]", "%%%0") 681 | 682 | -- replace whitespace with "match shortest anything" 683 | pattern = pattern:gsub("%s+", ".-") 684 | 685 | return function(data) 686 | return data.filterText:lower():find(pattern) ~= nil 687 | end 688 | end 689 | 690 | 691 | --INTERNAL FUNCTION 692 | --populates `addonList` with entries from `addonsForList` 693 | -- addonList = ZO_ScrollList control 694 | -- filter = [optional] function(data) 695 | local function PopulateAddonList(addonList, filter) 696 | local entryList = ZO_ScrollList_GetDataList(addonList) 697 | local numEntries = 0 698 | local selectedData = nil 699 | local selectionIsFinal = false 700 | 701 | ZO_ScrollList_Clear(addonList) 702 | 703 | for i, data in ipairs(addonsForList) do 704 | if not filter or filter(data) then 705 | local dataEntry = ZO_ScrollList_CreateDataEntry(ADDON_DATA_TYPE, data) 706 | numEntries = numEntries + 1 707 | data.sortIndex = numEntries 708 | entryList[numEntries] = dataEntry 709 | -- select the first panel passing the filter, or the currently 710 | -- shown panel, but only if it passes the filter as well 711 | if selectedData == nil or data.panel == lam.pendingAddonPanel or data.panel == lam.currentAddonPanel then 712 | if not selectionIsFinal then 713 | selectedData = data 714 | end 715 | if data.panel == lam.pendingAddonPanel then 716 | lam.pendingAddonPanel = nil 717 | selectionIsFinal = true 718 | end 719 | end 720 | else 721 | data.sortIndex = nil 722 | end 723 | end 724 | 725 | ZO_ScrollList_Commit(addonList) 726 | 727 | if selectedData then 728 | if selectedData.panel == lam.currentAddonPanel then 729 | ZO_ScrollList_SelectData(addonList, selectedData, nil, RESELECTING_DURING_REBUILD) 730 | else 731 | ZO_ScrollList_SelectData(addonList, selectedData, nil) 732 | end 733 | ScrollDataIntoView(addonList, selectedData) 734 | end 735 | end 736 | 737 | 738 | --METHOD: REGISTER WIDGET-- 739 | --each widget has its version checked before loading, 740 | --so we only have the most recent one in memory 741 | --Usage: 742 | -- widgetType = "string"; the type of widget being registered 743 | -- widgetVersion = integer; the widget's version number 744 | LAMCreateControl = LAMCreateControl or {} 745 | local lamcc = LAMCreateControl 746 | 747 | function lam:RegisterWidget(widgetType, widgetVersion) 748 | if widgets[widgetType] and widgets[widgetType] >= widgetVersion then 749 | return false 750 | else 751 | widgets[widgetType] = widgetVersion 752 | return true 753 | end 754 | end 755 | 756 | -- INTERNAL METHOD: hijacks the handlers for the actions in the OptionsWindow layer if not already done 757 | local function InitKeybindActions() 758 | if not lam.keybindsInitialized then 759 | lam.keybindsInitialized = true 760 | ZO_PreHook(KEYBOARD_OPTIONS, "ApplySettings", function() 761 | if lam.currentPanelOpened then 762 | if not lam.applyButton:IsHidden() then 763 | HandleReloadUIPressed() 764 | end 765 | return true 766 | end 767 | end) 768 | ZO_PreHook("ZO_Dialogs_ShowDialog", function(dialogName) 769 | if lam.currentPanelOpened and dialogName == "OPTIONS_RESET_TO_DEFAULTS" then 770 | if not lam.defaultButton:IsHidden() then 771 | HandleLoadDefaultsPressed() 772 | end 773 | return true 774 | end 775 | end) 776 | end 777 | end 778 | 779 | -- INTERNAL METHOD: fires the LAM-PanelOpened callback if not already done 780 | local function OpenCurrentPanel() 781 | if lam.currentAddonPanel and not lam.currentPanelOpened then 782 | lam.currentPanelOpened = true 783 | lam.defaultButton:SetHidden(not lam.currentAddonPanel.data.registerForDefaults) 784 | cm:FireCallbacks("LAM-PanelOpened", lam.currentAddonPanel) 785 | end 786 | end 787 | 788 | -- INTERNAL METHOD: fires the LAM-PanelClosed callback if not already done 789 | local function CloseCurrentPanel() 790 | if lam.currentAddonPanel and lam.currentPanelOpened then 791 | lam.currentPanelOpened = false 792 | cm:FireCallbacks("LAM-PanelClosed", lam.currentAddonPanel) 793 | end 794 | end 795 | 796 | --METHOD: OPEN TO ADDON PANEL-- 797 | --opens to a specific addon's option panel 798 | --Usage: 799 | -- panel = userdata; the panel returned by the :RegisterOptionsPanel method 800 | local locSettings = GetString(SI_GAME_MENU_SETTINGS) 801 | function lam:OpenToPanel(panel) 802 | 803 | -- find and select the panel's row in addon list 804 | 805 | local addonList = lam.addonList 806 | local selectedData = nil 807 | 808 | for _, addonData in ipairs(addonsForList) do 809 | if addonData.panel == panel then 810 | selectedData = addonData 811 | ScrollDataIntoView(addonList, selectedData) 812 | lam.pendingAddonPanel = addonData.panel 813 | break 814 | end 815 | end 816 | 817 | ZO_ScrollList_SelectData(addonList, selectedData) 818 | ZO_ScrollList_RefreshVisible(addonList, selectedData) 819 | 820 | local srchEdit = LAMAddonSettingsWindow:GetNamedChild("SearchFilterEdit") 821 | srchEdit:Clear() 822 | 823 | -- note that ZO_ScrollList doesn't require `selectedData` to be actually 824 | -- present in the list, and that the list will only be populated once LAM 825 | -- "Addon Settings" menu entry is selected for the first time 826 | 827 | local function openAddonSettingsMenu() 828 | local gameMenu = ZO_GameMenu_InGame.gameMenu 829 | local settingsMenu = gameMenu.headerControls[locSettings] 830 | 831 | if settingsMenu then -- an instance of ZO_TreeNode 832 | local children = settingsMenu:GetChildren() 833 | for i = 1, (children and #children or 0) do 834 | local childNode = children[i] 835 | local data = childNode:GetData() 836 | if data and data.id == lam.panelId then 837 | -- found LAM "Addon Settings" node, yay! 838 | childNode:GetTree():SelectNode(childNode) 839 | break 840 | end 841 | end 842 | end 843 | end 844 | 845 | if sm:GetScene("gameMenuInGame"):GetState() == SCENE_SHOWN then 846 | openAddonSettingsMenu() 847 | else 848 | sm:CallWhen("gameMenuInGame", SCENE_SHOWN, openAddonSettingsMenu) 849 | sm:Show("gameMenuInGame") 850 | end 851 | end 852 | 853 | local TwinOptionsContainer_Index = 0 854 | local function TwinOptionsContainer(parent, leftWidget, rightWidget) 855 | TwinOptionsContainer_Index = TwinOptionsContainer_Index + 1 856 | local cParent = parent.scroll or parent 857 | local panel = parent.panel or cParent 858 | local container = wm:CreateControl("$(parent)TwinContainer" .. tostring(TwinOptionsContainer_Index), 859 | cParent, CT_CONTROL) 860 | container:SetResizeToFitDescendents(true) 861 | container:SetAnchor(select(2, leftWidget:GetAnchor(0) )) 862 | 863 | leftWidget:ClearAnchors() 864 | leftWidget:SetAnchor(TOPLEFT, container, TOPLEFT) 865 | rightWidget:SetAnchor(TOPLEFT, leftWidget, TOPRIGHT, 5, 0) 866 | 867 | leftWidget:SetWidth( leftWidget:GetWidth() - 2.5 ) -- fixes bad alignment with 'full' controls 868 | rightWidget:SetWidth( rightWidget:GetWidth() - 2.5 ) 869 | 870 | leftWidget:SetParent(container) 871 | rightWidget:SetParent(container) 872 | 873 | container.data = {type = "container"} 874 | container.panel = panel 875 | return container 876 | end 877 | 878 | --INTERNAL FUNCTION 879 | --creates controls when options panel is first shown 880 | --controls anchoring of these controls in the panel 881 | local function CreateOptionsControls(panel) 882 | local addonID = panel:GetName() 883 | if(optionsState[addonID] == OPTIONS_CREATED) then 884 | return false 885 | elseif(optionsState[addonID] == OPTIONS_CREATION_RUNNING) then 886 | return true 887 | end 888 | optionsState[addonID] = OPTIONS_CREATION_RUNNING 889 | 890 | local function CreationFinished() 891 | optionsState[addonID] = OPTIONS_CREATED 892 | cm:FireCallbacks("LAM-PanelControlsCreated", panel) 893 | OpenCurrentPanel() 894 | end 895 | 896 | cm:FireCallbacks("LAM-BeforePanelControlsCreated", panel) 897 | local optionsTable = addonToOptionsMap[addonID] 898 | if optionsTable then 899 | local function CreateAndAnchorWidget(parent, widgetData, offsetX, offsetY, anchorTarget, wasHalf) 900 | local widget 901 | local status, err = pcall(function() widget = LAMCreateControl[widgetData.type](parent, widgetData) end) 902 | if not status then 903 | return err or true, offsetY, anchorTarget, wasHalf 904 | else 905 | local isHalf = (widgetData.width == "half") 906 | if not anchorTarget then -- the first widget in a panel is just placed in the top left corner 907 | widget:SetAnchor(TOPLEFT) 908 | anchorTarget = widget 909 | 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 910 | widget.lineControl = anchorTarget 911 | isHalf = false 912 | offsetY = 0 913 | anchorTarget = TwinOptionsContainer(parent, anchorTarget, widget) 914 | else -- otherwise we just put it below the previous one normally 915 | widget:SetAnchor(TOPLEFT, anchorTarget, BOTTOMLEFT, 0, 15) 916 | offsetY = 0 917 | anchorTarget = widget 918 | end 919 | return false, offsetY, anchorTarget, isHalf 920 | end 921 | end 922 | 923 | local THROTTLE_TIMEOUT, THROTTLE_COUNT = 10, 20 924 | local fifo = {} 925 | local anchorOffset, lastAddedControl, wasHalf 926 | local CreateWidgetsInPanel, err 927 | 928 | local function PrepareForNextPanel() 929 | anchorOffset, lastAddedControl, wasHalf = 0, nil, false 930 | end 931 | 932 | local function SetupCreationCalls(parent, widgetDataTable) 933 | fifo[#fifo + 1] = PrepareForNextPanel 934 | local count = #widgetDataTable 935 | for i = 1, zo_ceil(count / THROTTLE_COUNT) do 936 | fifo[#fifo + 1] = function() 937 | local startIndex = (i - 1) * THROTTLE_COUNT + 1 938 | local endIndex = zo_min(i * THROTTLE_COUNT, count) 939 | CreateWidgetsInPanel(parent, widgetDataTable, startIndex, endIndex) 940 | end 941 | end 942 | return count ~= NonContiguousCount(widgetDataTable) 943 | end 944 | 945 | CreateWidgetsInPanel = function(parent, widgetDataTable, startIndex, endIndex) 946 | for i=startIndex,endIndex do 947 | local widgetData = widgetDataTable[i] 948 | if not widgetData then 949 | PrintLater("Skipped creation of missing entry in the settings menu of " .. addonID .. ".") 950 | else 951 | local widgetType = widgetData.type 952 | local offsetX = 0 953 | local isSubmenu = (widgetType == "submenu") 954 | if isSubmenu then 955 | wasHalf = false 956 | offsetX = 5 957 | end 958 | 959 | err, anchorOffset, lastAddedControl, wasHalf = CreateAndAnchorWidget(parent, widgetData, offsetX, anchorOffset, lastAddedControl, wasHalf) 960 | if err then 961 | PrintLater(("Could not create %s '%s' of %s."):format(widgetData.type, GetStringFromValue(widgetData.name or "unnamed"), addonID)) 962 | logger:Error(err) 963 | end 964 | 965 | if isSubmenu then 966 | if SetupCreationCalls(lastAddedControl, widgetData.controls) then 967 | PrintLater(("The sub menu '%s' of %s is missing some entries."):format(GetStringFromValue(widgetData.name or "unnamed"), addonID)) 968 | end 969 | end 970 | end 971 | end 972 | end 973 | 974 | local function DoCreateSettings() 975 | if #fifo > 0 then 976 | local nextCall = table.remove(fifo, 1) 977 | nextCall() 978 | if(nextCall == PrepareForNextPanel) then 979 | DoCreateSettings() 980 | else 981 | zo_callLater(DoCreateSettings, THROTTLE_TIMEOUT) 982 | end 983 | else 984 | CreationFinished() 985 | end 986 | end 987 | 988 | if SetupCreationCalls(panel, optionsTable) then 989 | PrintLater(("The settings menu of %s is missing some entries."):format(addonID)) 990 | end 991 | DoCreateSettings() 992 | else 993 | CreationFinished() 994 | end 995 | 996 | return true 997 | end 998 | 999 | --INTERNAL FUNCTION 1000 | --handles switching between panels 1001 | local function ToggleAddonPanels(panel) --called in OnShow of newly shown panel 1002 | local currentlySelected = lam.currentAddonPanel 1003 | if currentlySelected and currentlySelected ~= panel then 1004 | currentlySelected:SetHidden(true) 1005 | CloseCurrentPanel() 1006 | end 1007 | lam.currentAddonPanel = panel 1008 | 1009 | -- refresh visible rows to reflect panel IsHidden status 1010 | ZO_ScrollList_RefreshVisible(lam.addonList) 1011 | 1012 | if not CreateOptionsControls(panel) then 1013 | OpenCurrentPanel() 1014 | end 1015 | 1016 | cm:FireCallbacks("LAM-RefreshPanel", panel) 1017 | end 1018 | 1019 | local CheckSafetyAndInitialize 1020 | local function ShowSetHandlerWarning(panel, handler) 1021 | local hint 1022 | if(handler == "OnShow" or handler == "OnEffectivelyShown") then 1023 | hint = "'LAM-PanelControlsCreated' or 'LAM-PanelOpened'" 1024 | elseif(handler == "OnHide" or handler == "OnEffectivelyHidden") then 1025 | hint = "'LAM-PanelClosed'" 1026 | end 1027 | 1028 | if hint then 1029 | 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) 1030 | PrintLater(message) 1031 | logger:Warn(message) 1032 | end 1033 | end 1034 | 1035 | --METHOD: REGISTER ADDON PANEL 1036 | --registers your addon with LibAddonMenu and creates a panel 1037 | --Usage: 1038 | -- addonID = "string"; unique ID which will be the global name of your panel 1039 | -- panelData = table; data object for your panel - see controls\panel.lua 1040 | function lam:RegisterAddonPanel(addonID, panelData) 1041 | CheckSafetyAndInitialize(addonID) 1042 | if IsConsoleUI() then 1043 | lam:registerConsoleAddonPanel(addonID, panelData) 1044 | return 1045 | end 1046 | local container = lam:GetAddonPanelContainer() 1047 | local panel = lamcc.panel(container, panelData, addonID) --addonID==global name of panel 1048 | panel:SetHidden(true) 1049 | panel:SetAnchorFill(container) 1050 | panel:SetHandler("OnEffectivelyShown", ToggleAddonPanels) 1051 | ZO_PreHook(panel, "SetHandler", ShowSetHandlerWarning) 1052 | 1053 | local function stripMarkup(str) 1054 | return str:gsub("|[Cc]%x%x%x%x%x%x", ""):gsub("|[Rr]", "") 1055 | end 1056 | 1057 | local filterParts = {panelData.name, nil, nil} 1058 | -- append keywords and author separately, the may be nil 1059 | filterParts[#filterParts + 1] = panelData.keywords 1060 | filterParts[#filterParts + 1] = panelData.author 1061 | 1062 | local addonData = { 1063 | panel = panel, 1064 | name = stripMarkup(panelData.name), 1065 | filterText = stripMarkup(tconcat(filterParts, "\t")):lower(), 1066 | } 1067 | 1068 | tinsert(addonsForList, addonData) 1069 | 1070 | if panelData.slashCommand then 1071 | SLASH_COMMANDS[panelData.slashCommand] = function() 1072 | lam:OpenToPanel(panel) 1073 | end 1074 | end 1075 | 1076 | return panel --return for authors creating options manually 1077 | end 1078 | 1079 | 1080 | --METHOD: REGISTER OPTION CONTROLS 1081 | --registers the options you want shown for your addon 1082 | --these are stored in a table where each key-value pair is the order 1083 | --of the options in the panel and the data for that control, respectively 1084 | --see exampleoptions.lua for an example 1085 | --see controls\.lua for each widget type 1086 | --Usage: 1087 | -- addonID = "string"; the same string passed to :RegisterAddonPanel 1088 | -- optionsTable = table; the table containing all of the options controls and their data 1089 | function lam:RegisterOptionControls(addonID, optionsTable) --optionsTable = {sliderData, buttonData, etc} 1090 | if IsConsoleUI() then 1091 | lam:registerConsoleOptionControls(addonID, optionsTable) 1092 | end 1093 | addonToOptionsMap[addonID] = optionsTable 1094 | end 1095 | 1096 | --INTERNAL FUNCTION 1097 | --creates LAM's Addon Settings entry in ZO_GameMenu 1098 | local function CreateAddonSettingsMenuEntry() 1099 | if IsConsoleUI() then return end 1100 | local panelData = { 1101 | id = KEYBOARD_OPTIONS.currentPanelId, 1102 | name = util.L["PANEL_NAME"], 1103 | } 1104 | 1105 | KEYBOARD_OPTIONS.currentPanelId = panelData.id + 1 1106 | KEYBOARD_OPTIONS.panelNames[panelData.id] = panelData.name 1107 | KEYBOARD_OPTIONS.controlTable[panelData.id] = {} 1108 | 1109 | lam.panelId = panelData.id 1110 | 1111 | local addonListSorted = false 1112 | 1113 | function panelData.callback() 1114 | sm:AddFragment(lam:GetAddonSettingsFragment()) 1115 | KEYBOARD_OPTIONS:ChangePanels(lam.panelId) 1116 | 1117 | local title = LAMAddonSettingsWindow:GetNamedChild("Title") 1118 | title:SetText(panelData.name) 1119 | 1120 | if not addonListSorted and #addonsForList > 0 then 1121 | local searchEdit = LAMAddonSettingsWindow:GetNamedChild("SearchFilterEdit") 1122 | --we're about to show our list for the first time - let's sort it 1123 | table.sort(addonsForList, function(a, b) return a.name < b.name end) 1124 | PopulateAddonList(lam.addonList, GetSearchFilterFunc(searchEdit)) 1125 | addonListSorted = true 1126 | end 1127 | end 1128 | 1129 | function panelData.unselectedCallback() 1130 | sm:RemoveFragment(lam:GetAddonSettingsFragment()) 1131 | if SetCameraOptionsPreviewModeEnabled then -- available since API version 100011 1132 | SetCameraOptionsPreviewModeEnabled(false) 1133 | end 1134 | end 1135 | 1136 | ZO_GameMenu_AddSettingPanel(panelData) 1137 | end 1138 | 1139 | 1140 | --INTERNAL FUNCTION 1141 | --creates the left-hand menu in LAM's window 1142 | local function CreateAddonList(name, parent) 1143 | local addonList = wm:CreateControlFromVirtual(name, parent, "ZO_ScrollList") 1144 | 1145 | local function addonListRow_OnMouseDown(control, button) 1146 | if button == 1 then 1147 | local data = ZO_ScrollList_GetData(control) 1148 | ZO_ScrollList_SelectData(addonList, data, control) 1149 | end 1150 | end 1151 | 1152 | local function addonListRow_OnMouseEnter(control) 1153 | ZO_ScrollList_MouseEnter(addonList, control) 1154 | end 1155 | 1156 | local function addonListRow_OnMouseExit(control) 1157 | ZO_ScrollList_MouseExit(addonList, control) 1158 | end 1159 | 1160 | local function addonListRow_Select(previouslySelectedData, selectedData, reselectingDuringRebuild) 1161 | if not reselectingDuringRebuild then 1162 | if previouslySelectedData then 1163 | previouslySelectedData.panel:SetHidden(true) 1164 | end 1165 | if selectedData then 1166 | selectedData.panel:SetHidden(false) 1167 | PlaySound(SOUNDS.TREE_SUBCATEGORY_CLICK) 1168 | end 1169 | end 1170 | end 1171 | 1172 | local function addonListRow_Setup(control, data) 1173 | control:SetText(data.name) 1174 | control:SetSelected(not data.panel:IsHidden()) 1175 | end 1176 | 1177 | ZO_ScrollList_AddDataType(addonList, ADDON_DATA_TYPE, "ZO_SelectableLabel", 28, addonListRow_Setup) 1178 | -- I don't know how to make highlights clear properly; they often 1179 | -- get stuck and after a while the list is full of highlighted rows 1180 | --ZO_ScrollList_EnableHighlight(addonList, "ZO_ThinListHighlight") 1181 | ZO_ScrollList_EnableSelection(addonList, "ZO_ThinListHighlight", addonListRow_Select) 1182 | 1183 | local addonDataType = ZO_ScrollList_GetDataTypeTable(addonList, ADDON_DATA_TYPE) 1184 | local addonListRow_CreateRaw = addonDataType.pool.m_Factory 1185 | 1186 | local function addonListRow_Create(pool) 1187 | local control = addonListRow_CreateRaw(pool) 1188 | control:SetHandler("OnMouseDown", addonListRow_OnMouseDown) 1189 | --control:SetHandler("OnMouseEnter", addonListRow_OnMouseEnter) 1190 | --control:SetHandler("OnMouseExit", addonListRow_OnMouseExit) 1191 | control:SetHeight(28) 1192 | control:SetFont("ZoFontHeader") 1193 | control:SetHorizontalAlignment(TEXT_ALIGN_LEFT) 1194 | control:SetVerticalAlignment(TEXT_ALIGN_CENTER) 1195 | control:SetWrapMode(TEXT_WRAP_MODE_ELLIPSIS) 1196 | return control 1197 | end 1198 | 1199 | addonDataType.pool.m_Factory = addonListRow_Create 1200 | 1201 | return addonList 1202 | end 1203 | 1204 | 1205 | --INTERNAL FUNCTION 1206 | local function CreateSearchFilterBox(name, parent) 1207 | local boxControl = wm:CreateControl(name, parent, CT_CONTROL) 1208 | 1209 | local srchButton = wm:CreateControl("$(parent)Button", boxControl, CT_BUTTON) 1210 | srchButton:SetDimensions(32, 32) 1211 | srchButton:SetAnchor(LEFT, nil, LEFT, 2, 0) 1212 | srchButton:SetNormalTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_up.dds") 1213 | srchButton:SetPressedTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_down.dds") 1214 | srchButton:SetMouseOverTexture("EsoUI/Art/LFG/LFG_tabIcon_groupTools_over.dds") 1215 | 1216 | local srchEdit = wm:CreateControlFromVirtual("$(parent)Edit", boxControl, "ZO_DefaultEdit") 1217 | srchEdit:SetAnchor(LEFT, srchButton, RIGHT, 4, 1) 1218 | srchEdit:SetAnchor(RIGHT, nil, RIGHT, -4, 1) 1219 | srchEdit:SetColor(ZO_NORMAL_TEXT:UnpackRGBA()) 1220 | 1221 | local srchBg = wm:CreateControl("$(parent)Bg", boxControl, CT_BACKDROP) 1222 | srchBg:SetAnchorFill() 1223 | srchBg:SetAlpha(0) 1224 | srchBg:SetCenterColor(0, 0, 0, 0.5) 1225 | srchBg:SetEdgeColor(ZO_DISABLED_TEXT:UnpackRGBA()) 1226 | srchBg:SetEdgeTexture("", 1, 1, 0, 0) 1227 | 1228 | -- search backdrop should appear whenever you hover over either 1229 | -- the magnifying glass button or the edit field (which is only 1230 | -- visible when it contains some text), and also while the edit 1231 | -- field has keyboard focus 1232 | 1233 | local srchActive = false 1234 | local srchHover = false 1235 | 1236 | local function srchBgUpdateAlpha() 1237 | if srchActive or srchEdit:HasFocus() then 1238 | srchBg:SetAlpha(srchHover and 0.8 or 0.6) 1239 | else 1240 | srchBg:SetAlpha(srchHover and 0.6 or 0.0) 1241 | end 1242 | end 1243 | 1244 | local function srchMouseEnter(control) 1245 | srchHover = true 1246 | srchBgUpdateAlpha() 1247 | end 1248 | 1249 | local function srchMouseExit(control) 1250 | srchHover = false 1251 | srchBgUpdateAlpha() 1252 | end 1253 | 1254 | boxControl:SetMouseEnabled(true) 1255 | boxControl:SetHitInsets(1, 1, -1, -1) 1256 | boxControl:SetHandler("OnMouseEnter", srchMouseEnter) 1257 | boxControl:SetHandler("OnMouseExit", srchMouseExit) 1258 | 1259 | srchButton:SetHandler("OnMouseEnter", srchMouseEnter) 1260 | srchButton:SetHandler("OnMouseExit", srchMouseExit) 1261 | 1262 | local focusLostTime = 0 1263 | 1264 | srchButton:SetHandler("OnClicked", function(self) 1265 | srchEdit:Clear() 1266 | if GetFrameTimeMilliseconds() - focusLostTime < 100 then 1267 | -- re-focus the edit box if it lost focus due to this 1268 | -- button click (note that this handler may run a few 1269 | -- frames later) 1270 | srchEdit:TakeFocus() 1271 | end 1272 | end) 1273 | 1274 | srchEdit:SetHandler("OnMouseEnter", srchMouseEnter) 1275 | srchEdit:SetHandler("OnMouseExit", srchMouseExit) 1276 | srchEdit:SetHandler("OnFocusGained", srchBgUpdateAlpha) 1277 | 1278 | srchEdit:SetHandler("OnFocusLost", function() 1279 | focusLostTime = GetFrameTimeMilliseconds() 1280 | srchBgUpdateAlpha() 1281 | end) 1282 | 1283 | srchEdit:SetHandler("OnEscape", function(self) 1284 | self:Clear() 1285 | self:LoseFocus() 1286 | end) 1287 | 1288 | srchEdit:SetHandler("OnTextChanged", function(self) 1289 | local filterFunc = GetSearchFilterFunc(self) 1290 | if filterFunc then 1291 | srchActive = true 1292 | srchBg:SetEdgeColor(ZO_SECOND_CONTRAST_TEXT:UnpackRGBA()) 1293 | srchButton:SetState(BSTATE_PRESSED) 1294 | else 1295 | srchActive = false 1296 | srchBg:SetEdgeColor(ZO_DISABLED_TEXT:UnpackRGBA()) 1297 | srchButton:SetState(BSTATE_NORMAL) 1298 | end 1299 | srchBgUpdateAlpha() 1300 | PopulateAddonList(lam.addonList, filterFunc) 1301 | PlaySound(SOUNDS.SPINNER_DOWN) 1302 | end) 1303 | 1304 | return boxControl 1305 | end 1306 | 1307 | 1308 | --INTERNAL FUNCTION 1309 | --creates LAM's Addon Settings top-level window 1310 | local function CreateAddonSettingsWindow() 1311 | local tlw = wm:CreateTopLevelWindow("LAMAddonSettingsWindow") 1312 | tlw:SetHidden(true) 1313 | tlw:SetDimensions(1010, 914) -- same height as ZO_OptionsWindow 1314 | 1315 | ZO_ReanchorControlForLeftSidePanel(tlw) 1316 | 1317 | -- create black background for the window (mimic ZO_RightFootPrintBackground) 1318 | 1319 | local bgLeft = wm:CreateControl("$(parent)BackgroundLeft", tlw, CT_TEXTURE) 1320 | bgLeft:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_left.dds") 1321 | bgLeft:SetDimensions(1024, 1024) 1322 | bgLeft:SetAnchor(TOPLEFT, nil, TOPLEFT) 1323 | bgLeft:SetDrawLayer(DL_BACKGROUND) 1324 | bgLeft:SetExcludeFromResizeToFitExtents(true) 1325 | 1326 | local bgRight = wm:CreateControl("$(parent)BackgroundRight", tlw, CT_TEXTURE) 1327 | bgRight:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_right.dds") 1328 | bgRight:SetDimensions(64, 1024) 1329 | bgRight:SetAnchor(TOPLEFT, bgLeft, TOPRIGHT) 1330 | bgRight:SetDrawLayer(DL_BACKGROUND) 1331 | bgRight:SetExcludeFromResizeToFitExtents(true) 1332 | 1333 | -- create gray background for addon list (mimic ZO_TreeUnderlay) 1334 | 1335 | local underlayLeft = wm:CreateControl("$(parent)UnderlayLeft", tlw, CT_TEXTURE) 1336 | underlayLeft:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_indexArea_left.dds") 1337 | underlayLeft:SetDimensions(256, 1024) 1338 | underlayLeft:SetAnchor(TOPLEFT, bgLeft, TOPLEFT) 1339 | underlayLeft:SetDrawLayer(DL_BACKGROUND) 1340 | underlayLeft:SetExcludeFromResizeToFitExtents(true) 1341 | 1342 | local underlayRight = wm:CreateControl("$(parent)UnderlayRight", tlw, CT_TEXTURE) 1343 | underlayRight:SetTexture("EsoUI/Art/Miscellaneous/centerscreen_indexArea_right.dds") 1344 | underlayRight:SetDimensions(128, 1024) 1345 | underlayRight:SetAnchor(TOPLEFT, underlayLeft, TOPRIGHT) 1346 | underlayRight:SetDrawLayer(DL_BACKGROUND) 1347 | underlayRight:SetExcludeFromResizeToFitExtents(true) 1348 | 1349 | -- create title bar (mimic ZO_OptionsWindow) 1350 | 1351 | local title = wm:CreateControl("$(parent)Title", tlw, CT_LABEL) 1352 | title:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 70) 1353 | title:SetFont("ZoFontWinH1") 1354 | title:SetModifyTextType(MODIFY_TEXT_TYPE_UPPERCASE) 1355 | 1356 | local divider = wm:CreateControlFromVirtual("$(parent)Divider", tlw, "ZO_Options_Divider") 1357 | divider:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 108) 1358 | 1359 | -- create search filter box 1360 | 1361 | local srchBox = CreateSearchFilterBox("$(parent)SearchFilter", tlw) 1362 | srchBox:SetAnchor(TOPLEFT, nil, TOPLEFT, 63, 120) 1363 | srchBox:SetDimensions(260, 30) 1364 | 1365 | -- create scrollable addon list 1366 | 1367 | local addonList = CreateAddonList("$(parent)AddonList", tlw) 1368 | addonList:SetAnchor(TOPLEFT, nil, TOPLEFT, 65, 160) 1369 | addonList:SetDimensions(285, 665) 1370 | 1371 | lam.addonList = addonList -- for easy access from elsewhere 1372 | 1373 | -- create container for option panels 1374 | 1375 | local panelContainer = wm:CreateControl("$(parent)PanelContainer", tlw, CT_CONTROL) 1376 | panelContainer:SetAnchor(TOPLEFT, nil, TOPLEFT, 365, 120) 1377 | panelContainer:SetDimensions(645, 675) 1378 | 1379 | local defaultButton = wm:CreateControlFromVirtual("$(parent)ResetToDefaultButton", tlw, "ZO_DialogButton") 1380 | ZO_KeybindButtonTemplate_Setup(defaultButton, "OPTIONS_LOAD_DEFAULTS", HandleLoadDefaultsPressed, GetString(SI_OPTIONS_DEFAULTS)) 1381 | defaultButton:SetAnchor(TOPLEFT, panelContainer, BOTTOMLEFT, 0, 2) 1382 | lam.defaultButton = defaultButton 1383 | 1384 | local applyButton = wm:CreateControlFromVirtual("$(parent)ApplyButton", tlw, "ZO_DialogButton") 1385 | ZO_KeybindButtonTemplate_Setup(applyButton, "OPTIONS_APPLY_CHANGES", HandleReloadUIPressed, GetString(SI_ADDON_MANAGER_RELOAD)) 1386 | applyButton:SetAnchor(TOPRIGHT, panelContainer, BOTTOMRIGHT, 0, 2) 1387 | applyButton:SetHidden(true) 1388 | lam.applyButton = applyButton 1389 | 1390 | return tlw 1391 | end 1392 | 1393 | 1394 | --INITIALIZING 1395 | local safeToInitialize = false 1396 | local hasInitialized = false 1397 | 1398 | local eventHandle = table.concat({MAJOR, MINOR}, "r") 1399 | local function OnLoad(_, addonName) 1400 | -- wait for the first loaded event 1401 | em:UnregisterForEvent(eventHandle, EVENT_ADD_ON_LOADED) 1402 | safeToInitialize = true 1403 | end 1404 | em:RegisterForEvent(eventHandle, EVENT_ADD_ON_LOADED, OnLoad) 1405 | 1406 | local function OnActivated(_, initial) 1407 | em:UnregisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED) 1408 | FlushMessages() 1409 | 1410 | local reopenPanel = RetrievePanelForReopening() 1411 | if not initial and reopenPanel then 1412 | lam:OpenToPanel(reopenPanel) 1413 | end 1414 | end 1415 | em:RegisterForEvent(eventHandle, EVENT_PLAYER_ACTIVATED, OnActivated) 1416 | 1417 | function CheckSafetyAndInitialize(addonID) 1418 | if not safeToInitialize then 1419 | local msg = string.format("The panel with id '%s' was registered before addon loading has completed. This might break the AddOn Settings menu.", addonID) 1420 | PrintLater(msg) 1421 | end 1422 | if not hasInitialized then 1423 | hasInitialized = true 1424 | end 1425 | end 1426 | 1427 | 1428 | --TODO documentation 1429 | function lam:GetAddonPanelContainer() 1430 | local fragment = lam:GetAddonSettingsFragment() 1431 | local window = fragment:GetControl() 1432 | return window:GetNamedChild("PanelContainer") 1433 | end 1434 | 1435 | 1436 | --TODO documentation 1437 | function lam:GetAddonSettingsFragment() 1438 | assert(hasInitialized or safeToInitialize) 1439 | if not LAMAddonSettingsFragment then 1440 | local window = CreateAddonSettingsWindow() 1441 | LAMAddonSettingsFragment = ZO_FadeSceneFragment:New(window, true, 100) 1442 | LAMAddonSettingsFragment:RegisterCallback("StateChange", function(oldState, newState) 1443 | if(newState == SCENE_FRAGMENT_SHOWN) then 1444 | InitKeybindActions() 1445 | PushActionLayerByName("OptionsWindow") 1446 | OpenCurrentPanel() 1447 | elseif(newState == SCENE_FRAGMENT_HIDDEN) then 1448 | CloseCurrentPanel() 1449 | RemoveActionLayerByName("OptionsWindow") 1450 | ShowReloadDialogIfNeeded() 1451 | end 1452 | end) 1453 | CreateAddonSettingsMenuEntry() 1454 | end 1455 | return LAMAddonSettingsFragment 1456 | end 1457 | 1458 | 1459 | ------------------------------------------ 1460 | -- Temporary LAM to LHAS console converter 1461 | ------------------------------------------ 1462 | 1463 | local function addToControlTable(newOption, t) 1464 | t.indexed[#t.indexed + 1 ] = newOption 1465 | if newOption.label then 1466 | t.nameMap[newOption.label] = newOption 1467 | end 1468 | newOption.conversionIndex = #t.indexed 1469 | end 1470 | local function LAMtoHASDropdownConverter(option, controlTable) 1471 | local newOption = { 1472 | type = LibHarvensAddonSettings.ST_DROPDOWN, 1473 | label = option.name, 1474 | default = option.default, 1475 | -- setFunction = option.setFunc, 1476 | getFunction = option.getFunc, 1477 | tooltip = option.tooltip, 1478 | disable = option.disabled, 1479 | } 1480 | 1481 | newOption.setFunction = function(combobox, name, item) option.setFunc(item.data) end 1482 | 1483 | local items = {} 1484 | local labelMap = {} 1485 | if not option.choicesValues then 1486 | option.choicesValues = option.choices 1487 | end 1488 | for i = 1, # option.choices do 1489 | items[i] = {name = option.choices[i], data = option.choicesValues[i]} 1490 | if option.choicesValues[i] then 1491 | labelMap[items[i].data] = items[i].name 1492 | end 1493 | end 1494 | newOption.items = items 1495 | newOption.getFunction = function() return labelMap[option.getFunc()] end 1496 | addToControlTable(newOption, controlTable) 1497 | end 1498 | 1499 | local function LamtoHASSubmenuConverter(optionsTable, controlTable) 1500 | local newOption = { 1501 | type = LibHarvensAddonSettings.ST_SECTION, 1502 | label = optionsTable.name, 1503 | } 1504 | addToControlTable(newOption, controlTable) 1505 | return lam:convertLamOptionsToHasTable(optionsTable.controls, controlTable) 1506 | end 1507 | 1508 | local function LamToHASDescriptionConverter(entry, controlTable) 1509 | 1510 | local newOption = { 1511 | type = LibHarvensAddonSettings.ST_LABEL, 1512 | label = entry.title, 1513 | default = entry.default, 1514 | tooltip = entry.tooltip, 1515 | } 1516 | addToControlTable(newOption, controlTable) 1517 | newOption = { 1518 | type = LibHarvensAddonSettings.ST_LABEL, 1519 | label = entry.text, 1520 | default = entry.default, 1521 | tooltip = entry.tooltip, 1522 | } 1523 | addToControlTable(newOption, controlTable) 1524 | end 1525 | 1526 | local function LamToHASDividerConverter(entry, controlTable) 1527 | newOption = { 1528 | type = LibHarvensAddonSettings.ST_SECTION, 1529 | label = nil, 1530 | } 1531 | addToControlTable(newOption, controlTable) 1532 | end 1533 | 1534 | function lam:convertLamOptionsToHasTable(optionsTable, controlTable) 1535 | local LAMtoHAS = { 1536 | slider = LibHarvensAddonSettings.ST_SLIDER, 1537 | header = LibHarvensAddonSettings.ST_SECTION, 1538 | checkbox = LibHarvensAddonSettings.ST_CHECKBOX, 1539 | colorpicker = LibHarvensAddonSettings.ST_COLOR, 1540 | button = LibHarvensAddonSettings.ST_BUTTON, 1541 | editbox = LibHarvensAddonSettings.ST_EDIT, 1542 | } 1543 | local LAMtoHASSpecial = { 1544 | dropdown = LAMtoHASDropdownConverter, 1545 | submenu = LamtoHASSubmenuConverter, 1546 | description = LamToHASDescriptionConverter, 1547 | divider = LamToHASDividerConverter, 1548 | } 1549 | local controlTable = controlTable or { 1550 | indexed = {}, 1551 | nameMap = {}, 1552 | } 1553 | 1554 | -- LAMHASMissing = {} 1555 | 1556 | for i, entry in ipairs(optionsTable) do 1557 | local newType = LAMtoHAS[entry.type] 1558 | if newType and not entry.isPCOnly then 1559 | local newOption = { 1560 | type = newType, 1561 | label = entry.name, 1562 | default = entry.default, 1563 | setFunction = entry.setFunc, 1564 | getFunction = entry.getFunc, 1565 | tooltip = entry.tooltip, 1566 | min = entry.min, 1567 | max = entry.max, 1568 | step = entry.step, 1569 | disable = entry.disabled, 1570 | clickHandler = entry.func, 1571 | buttonText = entry.name, 1572 | maxChars = entry.maxChars, 1573 | } 1574 | addToControlTable(newOption, controlTable) 1575 | -- settings:AddSetting(newOption) 1576 | elseif LAMtoHASSpecial[entry.type] then 1577 | LAMtoHASSpecial[entry.type](entry, controlTable) 1578 | else 1579 | -- LHAS does not have an equivalent for: 1580 | -- iconPicker 1581 | -- texture 1582 | end 1583 | end 1584 | return controlTable 1585 | end 1586 | 1587 | lam.LHASConversion = {} 1588 | lam.LHASConversion.settingTables = {} 1589 | lam.LHASConversion.optionControls = {} 1590 | 1591 | function lam:registerConsoleAddonPanel(addonID, panelData) 1592 | if IsConsoleUI() then 1593 | local LHA = LibHarvensAddonSettings 1594 | local options = { 1595 | allowDefaults = panelData.registerForDefaults, --will allow users to reset the settings to default values 1596 | allowRefresh = panelData.registerForRefresh, --if this is true, when one of settings is changed, all other settings will be checked for state change (disable/enable) 1597 | defaultsFunction = panelData.resetFunc, 1598 | } 1599 | 1600 | local settings = LHA:AddAddon(panelData.name, options) 1601 | lam.LHASConversion.settingTables[addonID] = settings 1602 | end 1603 | end 1604 | 1605 | function lam:registerConsoleOptionControls(addonID, optionsTable) 1606 | if not IsConsoleUI() then 1607 | return 1608 | end 1609 | if lam.LHASConversion.settingTables[addonID] then 1610 | local settings = lam.LHASConversion.settingTables[addonID] 1611 | local LHA = LibHarvensAddonSettings 1612 | lam.LHASConversion.optionControls [addonID]= lam:convertLamOptionsToHasTable(optionsTable) 1613 | local controlTable = lam.LHASConversion.optionControls[addonID] 1614 | 1615 | for i = 1, #controlTable.indexed do 1616 | settings:AddSetting(controlTable.indexed[i]) 1617 | end 1618 | end 1619 | end 1620 | --------------------------------------------------------------------------------