Files
2026-10-24 00:00:00 +00:00

749 lines
29 KiB
Lua

-- VimWowSettings: one Vanilla-sized options window for VimWow and its side
-- addons (VimFinder, VimBag, VimMap). Each addon registers a tab along the
-- bottom of the window: its pages of options and its key bindings, kept in
-- the addon's own SavedVariables table. Moved out of VimWow/settings_ui.lua
-- (0.1.84) so each addon carries its settings without VimWow.
--
-- VimWowSettings:Register({
-- id = "VimBag", name = "Bags", order = 200,
-- db = function() return VimBagDB end,
-- pages = {{name = "General", options = {...}}},
-- bindings = {{action = "VIMBAGOPEN", defaults = {"CTRL-B"}}},
-- onChange = function(key) end,
-- })
-- onChange runs, for every tab, after a checkbox, choice or binding changes
-- and when a key capture starts or stops; a slider only writes db().
-- local first, second = VimWowSettings:BindingKeys("VimBag", action, defaults)
--
-- Option format: {key, label, help} is a checkbox; slider = {low, high,
-- step} (with an optional format) is a slider; checked/unchecked names the
-- checkbox's two values; choices cycle through values. A page without
-- options lists the tab's bindings; a tab with bindings and no such page
-- gets one named Bindings. db().bindings[action] holds {key 1, key 2} and
-- db().disabledBindings[action] marks an action turned off.
-- disabled = function() ... end greys an option out while it returns true.
-- A page with draw = function(content) ... end draws itself: the window
-- gives it an empty frame below the page buttons, hidden with the page,
-- and calls draw each time the page renders (VimAddons' tab, 0.1.4).
--
-- VimWowSettings' own tab, General, keeps the settings every addon reads
-- (VimWowSettingsDB): High contrast, VimWowSettings:HighContrast().
VimWowSettings = VimWowSettings or {}
local UI = VimWowSettings
UI.addons = UI.addons or {}
local function button(parent, label, width, height, x, y, handler)
local control = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
control:SetWidth(width)
control:SetHeight(height)
control:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
control:SetText(label)
control:SetScript("OnClick", handler)
return control
end
local function label(parent, value, x, y)
local text = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
text:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
text:SetText(value)
return text
end
local function record(id)
for _, entry in ipairs(UI.addons) do
if entry.id == id then return entry end
end
end
local function store(entry)
local db = entry and entry.spec.db and entry.spec.db()
return db or {}
end
-- The shown tab's SavedVariables table.
local function db()
return store(UI.current)
end
-- Every addon hears of a change: a capture blocks all their keys, and a new
-- binding or option may be any addon's.
local function changed(key)
for _, entry in ipairs(UI.addons) do
if entry.spec.onChange then entry.spec.onChange(key) end
end
end
local function sortAddons()
table.sort(UI.addons, function(a, b)
local left, right = a.spec.order or 1000, b.spec.order or 1000
if left ~= right then return left < right end
return a.id < b.id
end)
end
function UI:Register(spec)
if type(spec) ~= "table" or not spec.id or not spec.name
or type(spec.db) ~= "function" then
error("VimWowSettings:Register needs id, name and db")
end
local entry = record(spec.id)
if not entry then
entry = {id = spec.id, page = 1, bindingPage = 1, rowCache = {}}
table.insert(self.addons, entry)
end
entry.spec = spec
entry.pages = spec.pages or {}
entry.catalog = spec.bindings or {}
local listsBindings = false
for _, page in ipairs(entry.pages) do
-- if not page.options then listsBindings = true end
if not page.options and not page.draw then listsBindings = true end
end
if table.getn(entry.catalog) > 0 and not listsBindings then
table.insert(entry.pages, {name = "Bindings"})
end
sortAddons()
-- The window opens on the first tab (VimWow's when it is loaded) until
-- it is shown.
if not (self.frame and self.frame:IsVisible()) then self:Use(self.addons[1]) end
if self.frame then self:BuildAddonTabs() end
return entry
end
-- An addon's keys for an action: none when turned off, else the keys set
-- in its options, else the defaults.
function UI:BindingKeys(id, action, defaults)
local saved = store(record(id))
if saved.disabledBindings and saved.disabledBindings[action] then
return nil, nil
end
local assigned = saved.bindings and saved.bindings[action]
if assigned and assigned[1] then return assigned[1], assigned[2] end
defaults = defaults or {}
return defaults[1], defaults[2]
end
-- Shows `entry`'s tab. pages, tabs, page, bindingPage and rowCache always
-- describe the shown tab.
function UI:Use(entry)
if not entry then return end
local old = self.current
if old then old.page, old.bindingPage = self.page or old.page, self.bindingPage or old.bindingPage end
self.current = entry
self.pages = entry.pages
self.bindings = entry.catalog
self.page = entry.page or 1
self.bindingPage = entry.bindingPage or 1
self.rowCache = entry.rowCache
self.tabs = entry.pageButtons or {}
end
local function suffix(text)
return function(value) return tostring(value) .. text end
end
UI.Suffix = suffix
local function settingHandler(option)
return function()
if option.disabled and option.disabled() then UI:Render(); return end
local saved = db()
if option.choices then
local position = 0
for i, candidate in ipairs(option.choices) do
if candidate == saved[option.key] then
position = i; break
end
end
-- saved[option.key] = option.choices[position
-- % table.getn(option.choices) + 1]
-- Lua 5.0 has no % operator (ClassicAPI transpiles it); the
-- position is never negative, so math.mod gives the same.
saved[option.key] = option.choices[math.mod(position,
table.getn(option.choices)) + 1]
-- A two-value checkbox switches between its named values.
elseif option.checked then
saved[option.key] = saved[option.key] == option.checked
and option.unchecked or option.checked
else
saved[option.key] = not saved[option.key]
end
changed(option.key)
UI:Render()
end
end
-- A slider shows its value with the option's format, or as a multiple.
local function sliderText(option, value)
if option.format then return option.format(value) end
return value .. "x"
end
-- Sliders store their value rounded to the slider step.
local function sliderHandler(option, slider)
return function()
local step = option.slider[3]
local value = math.floor(slider:GetValue() / step + 0.5) * step
local saved = db()
if saved[option.key] == value then return end
saved[option.key] = value
if slider.valueText then
slider.valueText:SetText(sliderText(option, value))
end
-- The camera keys read the speed each time one is pressed.
end
end
local function pageHandler(index)
return function()
UI.page = index
UI:Render()
end
end
local function addonHandler(index)
return function()
UI:Use(UI.addons[index])
UI:Render()
end
end
local function bindingHandler(method, spec, slot)
return function() UI[method](UI, spec, slot) end
end
function UI:Effective(spec)
return self:BindingKeys(self.current.id, spec.action, spec.defaults)
end
function UI:StopCapture()
self.captureAction = nil
self.captureSlot = nil
self.pendingKey = nil
VimWowBindingsCapturing = false
if self.capture then self.capture:EnableKeyboard(false); self.capture:Hide() end
changed()
end
function UI:StartCapture(spec, slot)
self.captureAction = spec.action
self.captureSpec = spec
self.captureSlot = slot
self.pendingKey = nil
VimWowBindingsCapturing = true
self.captureMessage:SetText("Press a key for "
.. (getglobal("BINDING_NAME_" .. spec.action) or spec.action)
.. ". Esc cancels.")
self.capture:Show()
self.capture:EnableKeyboard(true)
changed()
end
local function modifiedKey(key)
if not key or key == "LSHIFT" or key == "RSHIFT"
or key == "LCTRL" or key == "RCTRL"
or key == "LALT" or key == "RALT"
or key == "SHIFT" or key == "CTRL" or key == "ALT" then return end
local prefix = ""
if IsControlKeyDown and IsControlKeyDown() then prefix = prefix .. "CTRL-" end
if IsAltKeyDown and IsAltKeyDown() then prefix = prefix .. "ALT-" end
if IsShiftKeyDown and IsShiftKeyDown() then prefix = prefix .. "SHIFT-" end
return prefix .. string.upper(key)
end
-- ------------------------------------------------------------ stock keys
-- A client without ClassicAPI.dll has no override bindings. The side
-- addons then take their window's keys through a capture frame, and their
-- open key is a game key binding (their Bindings.xml).
-- A key written as this window writes a pressed one: CTRL-, ALT-, SHIFT-,
-- in that order, whatever order `key` names them in.
function UI.NormalKey(key)
if type(key) ~= "string" or key == "" then return end
local held, rest = {}, key
while true do
local _, _, modifier, tail = string.find(rest, "^(%u+)%-(.+)$")
if modifier == "CTRL" or modifier == "ALT" or modifier == "SHIFT" then
held[modifier] = true
rest = tail
else
break
end
end
local prefix = ""
if held.CTRL then prefix = prefix .. "CTRL-" end
if held.ALT then prefix = prefix .. "ALT-" end
if held.SHIFT then prefix = prefix .. "SHIFT-" end
return prefix .. string.upper(rest)
end
-- A capture frame: SetKeys({[key] = button}) gives it those keys. While it
-- has any, it takes the whole keyboard (the game's own keys wait until it
-- gives the keyboard back) and a key it has clicks its button.
function UI:NewKeyCapture(name)
local capture = CreateFrame("Frame", name, UIParent)
capture:SetFrameStrata("TOOLTIP")
capture:EnableKeyboard(false)
capture.keys = {}
capture:SetScript("OnKeyDown", function(_, raw)
local key = modifiedKey(raw or arg1)
local target = key and capture.keys[key]
if target then target:Click() end
end)
function capture:SetKeys(keys)
local normal, any = {}, false
for key, target in pairs(keys or {}) do
local written = UI.NormalKey(key)
if written and target then
normal[written] = target
any = true
end
end
self.keys = normal
self:EnableKeyboard(any)
end
return capture
end
-- Puts an addon's open keys on the game's key bindings, to its Bindings.xml
-- command `action`: a key is taken only while no other binding has it, and
-- a key no longer wanted is given back. `held` (kept in the addon's saved
-- variables) lists the keys bound this way. Call it only once the client's
-- own bindings have loaded (after PLAYER_ENTERING_WORLD).
function UI.ClaimGameKeys(held, action, first, second)
if type(SetBinding) ~= "function" or type(GetBindingAction) ~= "function" then
return
end
local wanted = {}
if first then wanted[first] = true end
if second then wanted[second] = true end
local changed = false
for key in pairs(held) do
if not wanted[key] then
if GetBindingAction(key) == action then
SetBinding(key)
changed = true
end
held[key] = nil
end
end
for key in pairs(wanted) do
local owner = GetBindingAction(key)
if owner == nil or owner == "" then
SetBinding(key, action)
changed = true
held[key] = true
elseif owner == action then
held[key] = true
end
end
if changed and type(SaveBindings) == "function" then
SaveBindings(type(GetCurrentBindingSet) == "function"
and GetCurrentBindingSet() or 1)
end
end
function UI:CaptureKey(raw)
if raw == "ESCAPE" then self:StopCapture(); self:Render(); return end
local key = modifiedKey(raw)
if not key or not self.captureAction then return end
local owner = GetBindingAction and GetBindingAction(key) or nil
if owner == "" or owner == "UNKNOWN" then owner = nil end
if owner and self.pendingKey ~= key then
self.pendingKey = key
self.captureMessage:SetText(key .. " is bound to " .. owner
.. ". Press it again to use it for " .. self.current.spec.name
.. " while active; the game binding stays intact. Esc cancels.")
return
end
local saved = db()
saved.bindings = saved.bindings or {}
-- An action never set starts from the keys it has now, so setting key 2
-- keeps key 1.
local pair = saved.bindings[self.captureAction]
or {self:Effective(self.captureSpec)}
pair[self.captureSlot] = key
-- BindingKeys reads a saved pair only when key 1 is set, so a repeated
-- key and a lone key 2 both end up as key 1.
if pair[1] == pair[2] then pair[2] = nil end
if not pair[1] then pair[1], pair[2] = pair[2], nil end
saved.bindings[self.captureAction] = pair
if saved.disabledBindings then
saved.disabledBindings[self.captureAction] = nil
end
self:StopCapture()
self:Render()
end
function UI:ResetBinding(spec)
local saved = db()
if saved.bindings then saved.bindings[spec.action] = nil end
if saved.disabledBindings then saved.disabledBindings[spec.action] = nil end
changed()
self:Render()
end
function UI:DisableBinding(spec)
self:ResetBinding(spec)
local saved = db()
saved.disabledBindings = saved.disabledBindings or {}
saved.disabledBindings[spec.action] = true
changed()
self:Render()
end
function UI:ClearRows()
for _, entry in ipairs(self.addons) do
for _, row in pairs(entry.rowCache or {}) do row:Hide() end
for _, tab in ipairs(entry.pageButtons or {}) do tab:Hide() end
end
end
-- The page buttons along the top of a tab.
function UI:BuildPageButtons(entry)
if entry.pageButtons then return end
entry.pageButtons = {}
for index, page in ipairs(entry.pages) do
local tab = button(self.frame, page.name, 104, 26,
23 + (index - 1) * 110, -55, pageHandler(index))
tab:Hide()
table.insert(entry.pageButtons, tab)
end
if entry == self.current then self.tabs = entry.pageButtons end
end
-- One bottom tab per addon, as on the character window; [ and ] switch
-- them.
function UI:BuildAddonTabs()
local count = table.getn(self.addons)
self.addonTabs = self.addonTabs or {}
for index = 1, count do
local tab = self.addonTabs[index]
if not tab then
tab = CreateFrame("Button", "VimWowSettingsFrameTab" .. index,
self.frame, "CharacterFrameTabButtonTemplate")
tab:SetID(index)
if index == 1 then
tab:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 15, 7)
else
tab:SetPoint("LEFT", self.addonTabs[index - 1], "RIGHT", -16, 0)
end
self.addonTabs[index] = tab
end
tab:SetText(self.addons[index].spec.name)
tab:SetScript("OnClick", addonHandler(index))
if PanelTemplates_TabResize then PanelTemplates_TabResize(0, tab) end
tab:Show()
end
for index = count + 1, table.getn(self.addonTabs) do
self.addonTabs[index]:Hide()
end
self.frame.numTabs = count
for _, entry in ipairs(self.addons) do self:BuildPageButtons(entry) end
end
function UI:Render()
if not self.frame or not self.current then return end
local entry = self.current
entry.page, entry.bindingPage = self.page, self.bindingPage
local spec = entry.spec
self.statusLabel:SetText(spec.status and spec.status() or "")
self.credit:SetText(spec.credit or "")
for index, other in ipairs(self.addons) do
if other == entry and PanelTemplates_SetTab then
PanelTemplates_SetTab(self.frame, index)
end
end
self:ClearRows()
for index, tab in ipairs(self.tabs) do
tab:Show()
if index == self.page then tab:LockHighlight()
else tab:UnlockHighlight() end
end
local saved = db()
local catalog = self.bindings
local extra = spec.bindingsButton
-- A page that draws itself gets its own frame.
local shown = self.pages[self.page]
if shown.draw then
self.pageLabel:SetText(shown.name)
self.prev:Hide(); self.next:Hide(); self.suggestions:Hide()
local cacheKey = "draw:" .. self.page
local content = self.rowCache[cacheKey]
if not content then
content = CreateFrame("Frame", nil, self.frame)
content:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 24, -91)
content:SetWidth(654); content:SetHeight(405)
self.rowCache[cacheKey] = content
end
content:Show()
shown.draw(content)
-- The page without options is the bindings list.
-- if not self.pages[self.page].options then
elseif not shown.options then
local start = (self.bindingPage - 1) * 8 + 1
for n = 0, 7 do
local binding = catalog[start + n]
if binding then
local cacheKey = "binding:" .. (start + n)
local row = self.rowCache[cacheKey]
if not row then
row = CreateFrame("Frame", nil, self.frame)
row:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 24,
-91 - n * 49)
row:SetWidth(654); row:SetHeight(45)
label(row, getglobal("BINDING_NAME_" .. binding.action)
or binding.action, 0, -6)
row.first = button(row, "", 112, 22, 318, -2,
bindingHandler("StartCapture", binding, 1))
row.second = button(row, "", 112, 22, 434, -2,
bindingHandler("StartCapture", binding, 2))
button(row, "Default", 55, 22, 550, -2,
bindingHandler("ResetBinding", binding))
button(row, "Off", 42, 22, 608, -2,
bindingHandler("DisableBinding", binding))
self.rowCache[cacheKey] = row
end
local first, second = self:Effective(binding)
local disabled = saved.disabledBindings
and saved.disabledBindings[binding.action]
row.first:SetText(disabled and "Off" or first or "Set key 1")
row.second:SetText(disabled and "Off" or second or "Set key 2")
row:Show()
end
end
self.pageLabel:SetText("Bindings " .. self.bindingPage .. "/"
.. math.max(1, math.ceil(table.getn(catalog) / 8)))
self.prev:Show(); self.next:Show()
if extra then
self.suggestions:SetText(extra.label)
self.suggestions:Show()
else
self.suggestions:Hide()
end
else
self.pageLabel:SetText(self.pages[self.page].name)
self.prev:Hide(); self.next:Hide(); self.suggestions:Hide()
for index, option in ipairs(self.pages[self.page].options) do
local cacheKey = "setting:" .. self.page .. ":" .. index
local row = self.rowCache[cacheKey]
if not row then
row = CreateFrame("Frame", nil, self.frame)
-- Seven rows fit above the page label.
row:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 42,
-105 - (index - 1) * 56)
row:SetWidth(610); row:SetHeight(48)
-- A checkbox sits left of its label, as in the game's options.
local checkbox = not option.slider and not option.choices
local textX = checkbox and 34 or 0
-- label(row, option.label, textX, -4)
row.label = label(row, option.label, textX, -4)
local help = row:CreateFontString(nil, "OVERLAY",
"GameFontHighlightSmall")
help:SetPoint("TOPLEFT", row, "TOPLEFT", textX, -24)
help:SetText(option.help)
if option.slider then
-- local name = "VimWowSettingSlider" .. option.key
-- VimWow's slider names stay as they were; other tabs
-- add their id.
local name = "VimWowSettingSlider"
.. (entry.id == "VimWow" and "" or entry.id) .. option.key
local slider = CreateFrame("Slider", name, row,
"OptionsSliderTemplate")
slider:SetWidth(170); slider:SetHeight(17)
slider:SetPoint("TOPLEFT", row, "TOPLEFT", 400, -10)
slider:SetMinMaxValues(option.slider[1], option.slider[2])
slider:SetValueStep(option.slider[3])
-- Keyboard steps use the option's step.
slider.vimwowStep = option.slider[3]
local low = getglobal(name .. "Low")
local high = getglobal(name .. "High")
if low then low:SetText(sliderText(option, option.slider[1])) end
if high then high:SetText(sliderText(option, option.slider[2])) end
slider.valueText = getglobal(name .. "Text")
slider:SetScript("OnValueChanged",
sliderHandler(option, slider))
row.slider = slider
elseif option.choices then
row.valueButton = button(row, "", 110, 27, 440, -8,
settingHandler(option))
else
local check = CreateFrame("CheckButton",
"VimWowSettingCheck" .. (entry.id == "VimWow" and "" or entry.id)
.. option.key, row,
"OptionsCheckButtonTemplate")
check:SetWidth(26); check:SetHeight(26)
check:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
local toggle = settingHandler(option)
check:SetScript("OnClick", function()
toggle()
if type(PlaySound) == "function" then
PlaySound(check:GetChecked()
and "igMainMenuOptionCheckBoxOn"
or "igMainMenuOptionCheckBoxOff")
end
end)
row.check = check
end
self.rowCache[cacheKey] = row
end
local value = saved[option.key]
if row.slider then
row.slider:SetValue(value)
if row.slider.valueText then
row.slider.valueText:SetText(sliderText(option, value))
end
elseif row.check then
local checked
if option.checked then checked = value == option.checked
else checked = value and true or false end
row.check:SetChecked(checked and 1 or nil)
else
local caption
if option.choices then
caption = option.captions and option.captions[value]
or option.format and option.format(value)
or tostring(value)
else
caption = value and "On" or "Off"
end
row.valueButton:SetText(caption)
end
-- An option another one decides for greys out.
local off = option.disabled and option.disabled() and true or false
local control = row.check or row.valueButton
if control then
if off then control:Disable() else control:Enable() end
end
if off then row.label:SetTextColor(0.5, 0.5, 0.5)
else row.label:SetTextColor(1, 0.82, 0) end
row:Show()
end
end
end
function UI:Initialize()
if self.frame then return end
local frame = CreateFrame("Frame", "VimWowSettingsFrame", UIParent)
frame:SetWidth(700); frame:SetHeight(555)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:SetFrameStrata("DIALOG")
frame:EnableMouse(true)
frame:SetBackdrop({bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true, tileSize = 32, edgeSize = 32,
insets = {left = 11, right = 12, top = 12, bottom = 11}})
label(frame, "VimWow Options", 24, -20)
local credit = frame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
credit:SetPoint("TOPLEFT", frame, "TOPLEFT", 24, -38)
self.credit = credit
-- A close cross like other panels; keyboard navigation skips it, since
-- Esc closes the window.
local close = CreateFrame("Button", "VimWowSettingsFrameCloseButton", frame,
"UIPanelCloseButton")
close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5)
close:SetScript("OnClick", function() self:Hide() end)
self.frame = frame
self.statusLabel = label(frame, "", 33, -530)
self.pageLabel = label(frame, "", 33, -512)
self.prev = button(frame, "Previous", 80, 24, 490, -503,
function() self.bindingPage = math.max(1, self.bindingPage - 1)
self:Render() end)
self.next = button(frame, "Next", 80, 24, 582, -503,
function()
self.bindingPage = math.min(math.max(1,
math.ceil(table.getn(self.bindings) / 8)), self.bindingPage + 1)
self:Render()
end)
-- A tab's own button on its bindings page (VimWow: the game keys its
-- keys cover). Options closes first, so a popup is not hidden behind it.
self.suggestions = button(frame, "", 150, 24, 330, -503,
function()
local extra = self.current and self.current.spec.bindingsButton
self:Hide()
if extra and extra.onClick then extra.onClick() end
end)
local capture = CreateFrame("Frame", "VimWowBindingCapture", UIParent)
capture:SetAllPoints(UIParent)
capture:SetFrameStrata("FULLSCREEN_DIALOG")
capture:EnableMouse(true)
capture:SetBackdrop({bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background"})
capture:SetBackdropColor(0, 0, 0, 0.8)
self.captureMessage = label(capture, "", 100, -200)
capture:SetScript("OnKeyDown", function(_, key)
self:CaptureKey(key or arg1)
end)
capture:EnableKeyboard(false)
capture:Hide()
self.capture = capture
frame:SetScript("OnHide", function() self:StopCapture() end)
frame:Hide()
self:BuildAddonTabs()
if self.current then self.tabs = self.current.pageButtons end
UISpecialFrames = UISpecialFrames or {}
table.insert(UISpecialFrames, "VimWowSettingsFrame")
end
-- Opens the window, on addon `id`'s tab when given.
function UI:Show(id)
self:Initialize()
if id and record(id) then self:Use(record(id)) end
self.frame:Show()
self:Render()
end
function UI:Hide()
if self.frame then self.frame:Hide() end
end
-- ------------------------------------------------------------ shared
-- Settings every Vim addon reads, on VimWowSettings' own tab (the user,
-- 2026-10-14). High contrast: the 1.12 client has no accessibility or
-- contrast setting to follow, so this is the one; VimQuest's quest areas
-- turn blue with it.
function UI:HighContrast()
return VimWowSettingsDB ~= nil and VimWowSettingsDB.highContrast == true
end
UI:Register({
id = "VimWowSettings", name = "General", order = 10000,
db = function()
VimWowSettingsDB = VimWowSettingsDB or {}
return VimWowSettingsDB
end,
pages = {{name = "Accessibility", options = {
{key = "highContrast", label = "High contrast",
help = "Colours that stand out more on the map's parchment: VimQuest's quest areas turn blue."},
}}},
})
local menuInstaller = CreateFrame("Frame", "VimWowMenuInstaller", UIParent)
menuInstaller:RegisterEvent("PLAYER_LOGIN")
menuInstaller:SetScript("OnEvent", function()
UI.menuReadyAt = GetTime() + 0.3
end)
menuInstaller:SetScript("OnUpdate", function()
if UI.menuInstalled or not UI.menuReadyAt
or GetTime() < UI.menuReadyAt then return end
if QuestieOcto and QuestieOcto.GameMenu
and not QuestieOcto.GameMenu.installed then return end
if not GameMenuFrame or not GameMenuButtonUIOptions
or not GameMenuButtonKeybindings then return end
local anchor = GameMenuButtonQuestieOctoOptions
or GameMenuButtonAdvancedOptions or GameMenuButtonUIOptions
local item = CreateFrame("Button", "GameMenuButtonVimWowOptions",
GameMenuFrame, "GameMenuButtonTemplate")
item:SetText("VimWow Options")
item:SetPoint("TOP", anchor, "BOTTOM", 0, -1)
item:SetScript("OnClick", function()
if HideUIPanel then HideUIPanel(GameMenuFrame)
else GameMenuFrame:Hide() end
UI:Show()
end)
GameMenuButtonKeybindings:ClearAllPoints()
GameMenuButtonKeybindings:SetPoint("TOP", item, "BOTTOM", 0, -1)
GameMenuFrame:SetHeight(GameMenuFrame:GetHeight() + 32)
UI.menuInstalled = true
menuInstaller:SetScript("OnUpdate", nil)
end)