749 lines
29 KiB
Lua
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)
|