Ai
1 Star 0 Fork 0

GameLife/AdvancedInterfaceOptions

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
browser.lua 13.21 KB
一键复制 编辑 原始数据 按行查看 历史
semlar 提交于 2021-11-22 10:02 +08:00 . Fix tracing which addon last modified a cvar
local addonName, addon = ...
local _G = _G
local E = addon:Eve()
-- C_Console.GetAllCommands() does not return the complete list of CVars on login
-- Repopulate the list using UpdateCVarList() when the CVar browser is opened
local CVarList = {}
local function UpdateCVarList()
for i, info in pairs(addon:GetCVars()) do
local cvar = info.command
if addon.hiddenOptions[cvar] then
CVarList[cvar] = addon.hiddenOptions[cvar]
else
CVarList[cvar] = {
description = info.help,
}
end
end
end
-------------------------------------------------------
-- Track cvars set by the interface and other addons
-- hook setcvar early, record to a temporary table and commit to saved vars when we finish loading
local SVLoaded = false -- we can't record any changes until after our own saved vars have loaded
local TempTraces = {} -- [cvar:lower()] = {source, value}
function E:Init()
SVLoaded = true
for cvar, trace in pairs(TempTraces) do -- commit temp vars to sv
local source, value = trace.source, trace.value
local currentValue = GetCVar(cvar)
if value == currentValue then -- only record if the 2 values match, otherwise we probably overwrote it with our own
AdvancedInterfaceOptionsSaved.ModifiedCVars[ cvar ] = source
addon:DontRecordCVar(cvar, value)
end
end
end
local function TraceCVar(cvar, value, ...)
if not addon:CVarExists(cvar) then return end
--[=[
-- Example calls to debugstack(2) as of patch 9.1.5
-- A call to `SetCVar' results in this hook running twice because SetCVar is a wrapper for C_CVar.SetCVar
1) `C_CVar.SetCVar' traces to `SetCVar' in CvarUtil.lua
[string "=[C]"]: in function `SetCVar'
[string "@Interface\SharedXML\CvarUtil.lua"]:13: in function <Interface\SharedXML\CvarUtil.lua:9>
[string "=[C]"]: in function `SetCVar'
[string "@Interface\AddOns\AdvancedInterfaceOptions\browser.lua"]:89: in main chunk
2) `SetCVar' traces to the function that invoked it
[string "=[C]"]: in function `SetCVar'
[string "@Interface\AddOns\AdvancedInterfaceOptions\browser.lua"]:89: in main chunk
]=]
local trace = debugstack(2)
local source, lineNum = trace:match("\"@([^\"]+)\"%]:(%d+)")
if not source then
-- Attempt to pull source out of "in function <file:line>" string
source, lineNum = trace:match("in function <([^:%[>]+):(%d+)>")
if not source then
-- Give up and record entire traceback
source = trace
lineNum = "(unhandled exception)"
end
end
-- Ignore C_CVar.SetCVar hook if it originated from CvarUtil.lua
if source and not source:lower():find("[\\/]sharedxml[\\/]cvarutil%.lua") then
local realValue = GetCVar(cvar) -- the client does some conversions to the original value
if SVLoaded then
AdvancedInterfaceOptionsSaved.ModifiedCVars[ cvar:lower() ] = source .. ':' .. lineNum
addon:DontRecordCVar(cvar, realValue)
else
-- this will still record blame for an addon even if we overwrite their setting
TempTraces[cvar:lower()] = {
source = source .. ':' .. lineNum,
value = realValue,
}
end
end
end
hooksecurefunc('SetCVar', TraceCVar) -- /script SetCVar(cvar, value)
if C_CVar and C_CVar.SetCVar then
hooksecurefunc(C_CVar, 'SetCVar', TraceCVar) -- C_CVar.SetCVar(cvar, value)
end
hooksecurefunc('ConsoleExec', function(msg)
local cmd, cvar, value = msg:match('^(%S+)%s+(%S+)%s*(%S*)')
if cmd then
if cmd:lower() == 'set' then -- /console SET cvar value
TraceCVar(cvar, value)
else -- /console cvar value
TraceCVar(cmd, cvar)
end
end
end)
local SetCVar = function(cvar, value)
addon:SetCVar(cvar, value)
end
-- Create an options panel and insert it into the interface menu
local OptionsPanel = CreateFrame('Frame', nil, InterfaceOptionsFramePanelContainer)
OptionsPanel:Hide()
OptionsPanel:SetAllPoints()
OptionsPanel.name = "CVar Browser"
OptionsPanel.parent = addonName
local Title = OptionsPanel:CreateFontString(nil, 'ARTWORK', 'GameFontNormalLarge')
Title:SetJustifyV('TOP')
Title:SetJustifyH('LEFT')
Title:SetPoint('TOPLEFT', 16, -16)
Title:SetText(OptionsPanel.name)
local SubText = OptionsPanel:CreateFontString(nil, 'ARTWORK', 'GameFontHighlightSmall')
SubText:SetMaxLines(3)
SubText:SetNonSpaceWrap(true)
SubText:SetJustifyV('TOP')
SubText:SetJustifyH('LEFT')
SubText:SetPoint('TOPLEFT', Title, 'BOTTOMLEFT', 0, -8)
SubText:SetPoint('RIGHT', -32, 0)
SubText:SetText('These options allow you to modify various CVars within the game.')
InterfaceOptions_AddCategory(OptionsPanel, addonName)
-- FilterBox should adjust the contents of the list frame based on the input text
-- todo: Display grey "Search" text in the box if it's empty
local FilterBox = CreateFrame('editbox', nil, OptionsPanel, 'InputBoxTemplate')
FilterBox:SetPoint('TOPLEFT', SubText, 'BOTTOMLEFT', 0, -5)
FilterBox:SetPoint('RIGHT', OptionsPanel, 'RIGHT', -10, 0)
FilterBox:SetHeight(20)
FilterBox:SetAutoFocus(false)
FilterBox:ClearFocus()
FilterBox:SetScript('OnEscapePressed', function(self)
self:SetAutoFocus(false) -- Allow focus to clear when escape is pressed
self:ClearFocus()
end)
FilterBox:SetScript('OnEnterPressed', function(self)
self:SetAutoFocus(false) -- Clear focus when enter is pressed because ketho said so
self:ClearFocus()
end)
FilterBox:SetScript('OnEditFocusGained', function(self)
self:SetAutoFocus(true)
self:HighlightText()
end)
local CVarTable = {}
local ListFrame = addon:CreateListFrame(OptionsPanel, 615, 465, {{NAME, 200}, {'Description', 260, 'LEFT'}, {'Value', 100, 'RIGHT'}})
ListFrame:SetPoint('TOP', FilterBox, 'BOTTOM', 0, -20)
ListFrame:SetPoint('BOTTOMLEFT', 4, 6)
ListFrame:SetItems(CVarTable)
ListFrame.Bg:SetAlpha(0.8)
FilterBox:SetMaxLetters(100)
-- Escape special characters for matching a literal string
local function Literalize(str)
return str:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
end
-- Rewrite text pattern to be case-insensitive
local function UnCase(c)
return '[' .. strlower(c) .. strupper(c) .. ']'
end
local FilteredTable = {} -- Filtered version of CVarTable based on search box input
-- Filter displayed items based on value of FilterBox
local function FilterCVarList()
local text = FilterBox:GetText()
if text == '' then
-- set to default list
ListFrame:SetItems(CVarTable)
else
local pattern = Literalize(text):gsub('%a', UnCase)
-- filter based on text
wipe(FilteredTable)
for i = 1, #CVarTable do
local row = CVarTable[i]
for j = 2, #row - 1 do -- start at 2 to skip the hidden value column, not sure if we should include every column in the filter or not
local col = row[j]
-- Color the search query, this is pretty inefficient
local newtext, replacements = col:gsub(pattern, '|cffff0000%1|r')
if replacements > 0 then
local newrow = {row[1], [#row] = row[#row]}
for k = 2, #row - 1 do
newrow[k] = row[k]:gsub(pattern, '|cffff0000%1|r')
end
tinsert(FilteredTable, newrow)
break
end
end
end
ListFrame:SetItems(FilteredTable)
end
end
FilterBox:SetScript('OnTextChanged', FilterCVarList)
-- Returns a rounded integer or float, the default value, and whether it's set to its default value
local function GetPrettyCVar(cvar)
local value, default = GetCVarInfo(cvar)
--if not value then value = '|cff00ff00EROR' end
--if not default then default = '|cff00ff00EROR' end
if not default or not value then return '', false end -- this cvar doesn't exist
local isFloat = strmatch(value or '', '^-?%d+%.%d+$')
if isFloat then
value = format('%.2f', value):gsub("%.?0+$", "")
end
local isDefault = tonumber(value) and tonumber(default) and (value - default == 0) or (value == default)
return value, default, isDefault
end
-- Update CVarTable to reflect current values
local function RefreshCVarList()
wipe(CVarTable)
UpdateCVarList()
-- todo: this needs to be updated every time a cvar changes while the table is visible
for cvar, tbl in pairs(CVarList) do
local value, default, isDefault = GetPrettyCVar(cvar)
if not(type(value) == 'string' and (value:byte(2) == 1 or value:byte(1) == 2)) then -- hack to strip tracking variables and filters from our table, maybe look for a better solution
tinsert(CVarTable, {cvar, cvar, tbl.description or '', isDefault and value or ('|cffff0000' .. value .. '|r')})
end
end
--ListFrame:SetItems(CVarTable)
end
local function FilteredRefresh()
if ListFrame:IsVisible() then
RefreshCVarList()
FilterCVarList()
end
end
ListFrame:HookScript('OnShow', FilteredRefresh)
-- Events
local oSetCVar = SetCVar
function E:PLAYER_LOGIN()
-- todo: this needs to be updated every time a cvar changes while the table is visible
RefreshCVarList()
ListFrame:SetItems(CVarTable)
ListFrame:SortBy(2)
--FilterCVarList()
-- We don't really want the user to be able to do anything else while the input box is open
-- I'd rather make this a child of the input box, but I can't get it to show up above its child
-- todo: show default value around the input box somewhere while it's active
local CVarInputBoxMouseBlocker = CreateFrame('frame', nil, ListFrame)
CVarInputBoxMouseBlocker:SetFrameStrata('FULLSCREEN_DIALOG')
CVarInputBoxMouseBlocker:Hide()
local CVarInputBox = CreateFrame('editbox', nil, CVarInputBoxMouseBlocker, 'InputBoxTemplate')
-- block clicking and cancel on any clicks outside the edit box
CVarInputBoxMouseBlocker:EnableMouse(true)
CVarInputBoxMouseBlocker:SetScript('OnMouseDown', function(self) CVarInputBox:ClearFocus() end)
-- block scrolling
CVarInputBoxMouseBlocker:EnableMouseWheel(true)
CVarInputBoxMouseBlocker:SetScript('OnMouseWheel', function() end)
CVarInputBoxMouseBlocker:SetAllPoints(nil)
local blackout = CVarInputBoxMouseBlocker:CreateTexture(nil, 'BACKGROUND')
blackout:SetAllPoints()
blackout:SetColorTexture(0,0,0,0.2)
CVarInputBox:Hide()
CVarInputBox:SetSize(100, 20)
CVarInputBox:SetJustifyH('RIGHT')
CVarInputBox:SetTextInsets(5, 10, 0, 0)
CVarInputBox:SetScript('OnEscapePressed', function(self)
self:ClearFocus()
self:Hide()
end)
CVarInputBox:SetScript('OnEnterPressed', function(self)
-- todo: I don't like this, change it
oSetCVar(self.cvar, self:GetText() or '')
self:Hide()
FilteredRefresh()
end)
--CVarInputBox:SetScript('OnShow', function(self)
--self:SetFocus()
--end)
CVarInputBox:SetScript('OnHide', function(self)
CVarInputBoxMouseBlocker:Hide()
if self.str then
self.str:Show()
end
end)
CVarInputBox:SetScript('OnEditFocusLost', function(self)
self:Hide()
FilterBox:SetFocus()
end)
function E:PLAYER_REGEN_DISABLED()
if CVarInputBox:IsVisible() then
CVarInputBox:Hide()
end
FilterBox:GetScript('OnEscapePressed')(FilterBox)
end
local LastClickTime = 0 -- Track double clicks on rows
ListFrame:SetScripts({
OnEnter = function(self)
if self.value ~= '' then
GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT")
local cvarTable = CVarList[self.value]
local _, defaultValue = GetCVarInfo(self.value)
GameTooltip:AddLine(cvarTable['prettyName'] or self.value, nil, nil, nil, false)
GameTooltip:AddLine(" ")
if cvarTable['description'] then --and _G[ cvarTable['description'] ] then
GameTooltip:AddLine(cvarTable['description'], 1, 1, 1, true)
end
GameTooltip:AddDoubleLine("Default Value:", defaultValue, 0.2, 1, 0.6, 0.2, 1, 0.6)
local modifiedBy = AdvancedInterfaceOptionsSaved.ModifiedCVars[ self.value:lower() ]
if modifiedBy then
GameTooltip:AddDoubleLine("Last Modified By:", modifiedBy, 1, 0, 0, 1, 0, 0)
end
GameTooltip:Show()
end
self.bg:Show()
end,
OnLeave = function(self)
GameTooltip:Hide()
self.bg:Hide()
end,
OnMouseDown = function(self)
local now = GetTime()
if now - LastClickTime <= 0.2 then
-- display edit box on row with current cvar value
-- save on enter, discard on escape or losing focus
if CVarInputBox.str then
CVarInputBox.str:Show()
end
self.cols[#self.cols]:Hide()
CVarInputBox.str = self.cols[#self.cols]
CVarInputBox.cvar = self.value
CVarInputBox.row = self
CVarInputBox:SetPoint('RIGHT', self)
local value = GetPrettyCVar(self.value)
CVarInputBox:SetText(value or '')
CVarInputBox:HighlightText()
CVarInputBoxMouseBlocker:Show()
CVarInputBox:Show()
CVarInputBox:SetFocus()
else
LastClickTime = now
end
end,
})
end
-- Update browser when a cvar is set while it's open
-- There are at least 4 different ways a cvar can be set:
-- /run SetCVar("cvar", value)
-- /console "cvar" value
-- /console SET "cvar" value (case doesn't matter)
-- Hitting ` and typing SET "cvar" into the console window itself
-- Console is not part of the interface, doesn't fire an event, and I'm not sure that we can hook it
-- These could be more efficient by not refreshing the entire list but that would be more work
hooksecurefunc('SetCVar', FilteredRefresh)
-- should we even bother checking what the console command did?
hooksecurefunc('ConsoleExec', FilteredRefresh)
SlashCmdList.CVAR = function()
if not InCombatLockdown() then
InterfaceOptionsFrame_OpenToCategory(OptionsPanel)
InterfaceOptionsFrame_OpenToCategory(OptionsPanel)
end
end
SLASH_CVAR1 = "/cvar"
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Lua
1
https://gitee.com/addons/AdvancedInterfaceOptions.git
git@gitee.com:addons/AdvancedInterfaceOptions.git
addons
AdvancedInterfaceOptions
AdvancedInterfaceOptions
master

搜索帮助