639 lines
28 KiB
Lua
639 lines
28 KiB
Lua
-- BulwarkFrame - options.lua
|
|
-- Standalone options panel (no Blizzard InterfaceOptions integration on 1.12). Built lazily on
|
|
-- first open, repopulated from BulwarkFrameDB on show. Opened by the minimap button's left click
|
|
-- and by /bulwark options. WoW-API file (parse-checked only).
|
|
--
|
|
-- Widget wiring uses the 1.12 templates: UICheckButtonTemplate (GetChecked() -> 1/nil) and
|
|
-- OptionsSliderTemplate (SetMinMaxValues BEFORE SetValue, else it clamps to 0). Colour swatches
|
|
-- drive ColorPickerFrame the way pfUI does -- func/cancelFunc plus ShowUIPanel.
|
|
|
|
BulwarkFrame = BulwarkFrame or {}
|
|
local BF = BulwarkFrame
|
|
|
|
local PANEL_W = 430
|
|
-- Content width of one column: panel minus both outer margins minus the gutter, halved.
|
|
local COL_W = (PANEL_W - 48 - 12) / 2
|
|
local panel = nil
|
|
local cbIndex, slIndex, swIndex, ebIndex = 0, 0, 0, 0
|
|
local refreshers = {}
|
|
|
|
local function db() return BulwarkFrameDB end
|
|
|
|
-- 1.12: a FontString created WITHOUT a font object has no font, and FontString:SetText() then
|
|
-- throws "Font not set" -- which aborts the rest of the panel build. Every CreateFontString here
|
|
-- therefore passes an inherit template, and this only swaps in the pfUI face afterwards.
|
|
local function ApplyFont(fs, size)
|
|
if not fs then return end
|
|
if pfUI and pfUI.font_default then
|
|
fs:SetFont(pfUI.font_default, size or 12)
|
|
else
|
|
fs:SetFontObject(GameFontNormalSmall)
|
|
end
|
|
end
|
|
|
|
-- Extends a widget's hover area across the whole row, so the tooltip also appears over its label
|
|
-- rather than only over the 22px box itself. 1.12 FontStrings take no mouse events, so this is an
|
|
-- invisible button laid over the row; clicking it forwards to the real widget, which also makes
|
|
-- the label clickable -- the behaviour anyone expects from a labelled checkbox.
|
|
local function AddRowHover(widget, tip, onClick)
|
|
if not widget then return end
|
|
local hover = CreateFrame("Button", nil, widget:GetParent())
|
|
hover:SetPoint("TOPLEFT", widget, "TOPLEFT", 0, 0)
|
|
hover:SetWidth(COL_W)
|
|
hover:SetHeight(widget:GetHeight() or 22)
|
|
hover:SetFrameLevel((widget:GetFrameLevel() or 1) + 1)
|
|
hover:SetScript("OnClick", onClick)
|
|
return hover
|
|
end
|
|
|
|
local function AddTooltip(widget, text)
|
|
if not widget or not text then return end
|
|
widget.bfTip = text
|
|
local oldEnter = widget:GetScript("OnEnter")
|
|
local oldLeave = widget:GetScript("OnLeave")
|
|
widget:SetScript("OnEnter", function()
|
|
if oldEnter then oldEnter() end
|
|
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
|
GameTooltip:SetText(this.bfTip, 1, 1, 1, 1, 1)
|
|
GameTooltip:Show()
|
|
end)
|
|
widget:SetScript("OnLeave", function()
|
|
if oldLeave then oldLeave() end
|
|
GameTooltip:Hide()
|
|
end)
|
|
end
|
|
|
|
-- Any change to a display option has to reach the frame immediately -- an options panel whose
|
|
-- effect only shows after /reload trains you to distrust it.
|
|
local function Applied()
|
|
BF.ApplyLayout()
|
|
BF.RequestUpdate()
|
|
end
|
|
|
|
-- ---- widget factories -------------------------------------------------------------------
|
|
|
|
local function CreateCheckbox(parent, label, tip, getter, setter)
|
|
cbIndex = cbIndex + 1
|
|
local name = "BulwarkFrameOptCheck" .. cbIndex
|
|
local cb = CreateFrame("CheckButton", name, parent, "UICheckButtonTemplate")
|
|
cb:SetWidth(22)
|
|
cb:SetHeight(22)
|
|
local lbl = getglobal(name .. "Text")
|
|
if lbl then lbl:SetText(label); ApplyFont(lbl) end
|
|
cb.bfSet = setter
|
|
cb.bfLabel = label -- scratch field for the offline stub; the real client's own label
|
|
-- text lives in the $parentText template region instead.
|
|
cb:SetScript("OnClick", function()
|
|
this.bfSet(this:GetChecked() == 1)
|
|
Applied()
|
|
end)
|
|
AddTooltip(cb, tip)
|
|
AddTooltip(AddRowHover(cb, tip, function()
|
|
cb:SetChecked(not (cb:GetChecked() == 1) and 1 or nil)
|
|
cb.bfSet(cb:GetChecked() == 1)
|
|
Applied()
|
|
end), tip)
|
|
if pfUI and pfUI.api and pfUI.api.SkinCheckbox then pfUI.api.SkinCheckbox(cb) end
|
|
table.insert(refreshers, function() cb:SetChecked(getter() and 1 or nil) end)
|
|
return cb
|
|
end
|
|
|
|
local function CreateSlider(parent, label, minVal, maxVal, step, fmt, tip, getter, setter)
|
|
slIndex = slIndex + 1
|
|
local name = "BulwarkFrameOptSlider" .. slIndex
|
|
local sl = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
|
|
sl:SetWidth(COL_W)
|
|
sl:SetHeight(16)
|
|
sl:SetMinMaxValues(minVal, maxVal)
|
|
sl:SetValueStep(step)
|
|
local low, high, txt = getglobal(name .. "Low"), getglobal(name .. "High"), getglobal(name .. "Text")
|
|
if low then low:SetText(tostring(minVal)); ApplyFont(low, 10) end
|
|
if high then high:SetText(tostring(maxVal)); ApplyFont(high, 10) end
|
|
if txt then
|
|
txt:ClearAllPoints()
|
|
txt:SetPoint("BOTTOMLEFT", sl, "TOPLEFT", 0, 2)
|
|
txt:SetJustifyH("LEFT")
|
|
ApplyFont(txt, 11)
|
|
end
|
|
sl.bfFmt = fmt
|
|
sl.bfText = txt
|
|
sl.bfSet = setter
|
|
sl:SetScript("OnValueChanged", function()
|
|
local v = this:GetValue()
|
|
if this.bfText then this.bfText:SetText(string.format(this.bfFmt, v)) end
|
|
this.bfSet(v)
|
|
Applied()
|
|
end)
|
|
AddTooltip(sl, tip)
|
|
if pfUI and pfUI.api and pfUI.api.SkinSlider then pfUI.api.SkinSlider(sl) end
|
|
table.insert(refreshers, function()
|
|
local v = getter()
|
|
sl:SetValue(v)
|
|
if txt then txt:SetText(string.format(fmt, v)) end
|
|
end)
|
|
return sl
|
|
end
|
|
|
|
-- A colour swatch. `key` names the config field holding {r,g,b}.
|
|
local function CreateSwatch(parent, label, key, tip)
|
|
swIndex = swIndex + 1
|
|
local holder = CreateFrame("Button", "BulwarkFrameOptSwatch" .. swIndex, parent)
|
|
holder:SetWidth(18)
|
|
holder:SetHeight(18)
|
|
|
|
local tex = holder:CreateTexture("BulwarkFrameOptSwatchTex" .. swIndex, "ARTWORK")
|
|
tex:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
tex:SetPoint("TOPLEFT", holder, "TOPLEFT", 1, -1)
|
|
tex:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -1, 1)
|
|
|
|
local border = holder:CreateTexture("BulwarkFrameOptSwatchBorder" .. swIndex, "BACKGROUND")
|
|
border:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
border:SetVertexColor(0, 0, 0, 1)
|
|
border:SetAllPoints(holder)
|
|
|
|
local lbl = holder:CreateFontString("BulwarkFrameOptSwatchLabel" .. swIndex, "OVERLAY", "GameFontNormalSmall")
|
|
lbl:SetPoint("LEFT", holder, "RIGHT", 6, 0)
|
|
lbl:SetText(label)
|
|
ApplyFont(lbl)
|
|
|
|
holder:SetScript("OnClick", function()
|
|
local c = db()[key] or { 1, 1, 1 }
|
|
local pr, pg, pb = c[1], c[2], c[3]
|
|
-- Captured for cancel: ColorPickerFrame keeps calling func while the user drags, so the
|
|
-- config is written live and cancel has to put the old value back explicitly.
|
|
ColorPickerFrame.func = function()
|
|
local r, g, b = ColorPickerFrame:GetColorRGB()
|
|
local cc = db()[key]
|
|
cc[1], cc[2], cc[3] = r, g, b
|
|
tex:SetVertexColor(r, g, b)
|
|
Applied()
|
|
end
|
|
ColorPickerFrame.cancelFunc = function()
|
|
local cc = db()[key]
|
|
cc[1], cc[2], cc[3] = pr, pg, pb
|
|
tex:SetVertexColor(pr, pg, pb)
|
|
Applied()
|
|
end
|
|
ColorPickerFrame.opacityFunc = nil
|
|
ColorPickerFrame.hasOpacity = nil
|
|
ColorPickerFrame:SetColorRGB(pr, pg, pb)
|
|
ColorPickerFrame:SetFrameStrata("DIALOG")
|
|
ShowUIPanel(ColorPickerFrame)
|
|
end)
|
|
AddTooltip(holder, tip)
|
|
AddTooltip(AddRowHover(holder, tip, function() holder:Click() end), tip)
|
|
|
|
table.insert(refreshers, function()
|
|
local c = db()[key] or { 1, 1, 1 }
|
|
tex:SetVertexColor(c[1], c[2], c[3])
|
|
end)
|
|
return holder
|
|
end
|
|
|
|
-- A numeric edit box: a label above a small InputBoxTemplate field. Used for the two spell ids,
|
|
-- which are not on any slider scale -- typing the exact id is the only sane input for one.
|
|
-- Applies on Enter or on losing focus (tabbing/clicking away), the same "commit, don't live-type"
|
|
-- behaviour InputBoxTemplate users get everywhere else in the default UI. An unparsable entry is
|
|
-- reverted to the current stored value rather than silently written as nil.
|
|
local function CreateEditBox(parent, label, tip, getter, setter)
|
|
ebIndex = ebIndex + 1
|
|
local name = "BulwarkFrameOptEdit" .. ebIndex
|
|
local holder = CreateFrame("Frame", name .. "Holder", parent)
|
|
holder:SetWidth(COL_W)
|
|
holder:SetHeight(34)
|
|
|
|
local lbl = holder:CreateFontString(name .. "Label", "OVERLAY", "GameFontNormalSmall")
|
|
lbl:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
|
|
lbl:SetJustifyH("LEFT")
|
|
lbl:SetText(label)
|
|
ApplyFont(lbl, 11)
|
|
|
|
local eb = CreateFrame("EditBox", name, holder, "InputBoxTemplate")
|
|
eb:SetWidth(90)
|
|
eb:SetHeight(18)
|
|
eb:SetAutoFocus(false)
|
|
eb:SetMaxLetters(6)
|
|
if eb.SetNumeric then eb:SetNumeric(true) end
|
|
eb:SetPoint("TOPLEFT", holder, "TOPLEFT", 4, -16)
|
|
|
|
local function commit()
|
|
local v = tonumber(eb:GetText())
|
|
if v then
|
|
eb.bfSet(math.floor(v))
|
|
else
|
|
eb:SetText(tostring(getter()))
|
|
end
|
|
eb:ClearFocus()
|
|
Applied()
|
|
end
|
|
eb.bfSet = setter
|
|
eb:SetScript("OnEnterPressed", commit)
|
|
eb:SetScript("OnEscapePressed", function() eb:SetText(tostring(getter())); eb:ClearFocus() end)
|
|
eb:SetScript("OnEditFocusLost", commit)
|
|
|
|
-- No AddRowHover here: an overlay button covering the row would sit above the edit box and
|
|
-- eat the click meant to focus it. The tooltip is on the field and the label only.
|
|
AddTooltip(eb, tip)
|
|
holder:EnableMouse(true)
|
|
AddTooltip(holder, tip)
|
|
table.insert(refreshers, function() eb:SetText(tostring(getter())) end)
|
|
return holder
|
|
end
|
|
|
|
local function CreateButton(parent, label, tip, onClick)
|
|
local b = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
|
|
b:SetWidth(110)
|
|
b:SetHeight(20)
|
|
b:SetText(label)
|
|
b:SetScript("OnClick", onClick)
|
|
AddTooltip(b, tip)
|
|
if pfUI and pfUI.api and pfUI.api.SkinButton then pfUI.api.SkinButton(b) end
|
|
return b
|
|
end
|
|
|
|
local function CreateHeading(parent, text, x, y)
|
|
local fs = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
|
fs:SetText(text)
|
|
ApplyFont(fs, 13)
|
|
fs:SetTextColor(1, 0.82, 0)
|
|
return fs
|
|
end
|
|
|
|
-- ---- panel ------------------------------------------------------------------------------
|
|
|
|
local function BuildPanel()
|
|
if panel then return end
|
|
|
|
-- Chrome copied from TotemBar's options panel so the two addons read as one family:
|
|
-- gold title top-left, close button top-right, clamped to screen, ESC closes, version footer.
|
|
-- The one thing NOT copied is its hardcoded height -- that file carries a comment warning that
|
|
-- every new row has to be added to the constant by hand or the last widget lands on the footer,
|
|
-- which is exactly the bug reported here. The height below is computed from the layout cursor.
|
|
panel = CreateFrame("Frame", "BulwarkFrameOptions", UIParent)
|
|
panel:SetWidth(PANEL_W)
|
|
panel:SetHeight(200) -- provisional; recomputed at the end of this function
|
|
panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
|
panel:SetFrameStrata("DIALOG")
|
|
panel:SetMovable(true)
|
|
panel:EnableMouse(true)
|
|
panel:RegisterForDrag("LeftButton")
|
|
panel:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
panel:SetClampedToScreen(true)
|
|
-- Own frame art, generated by tools/gen_panel_textures.js with the same parameters TotemBar
|
|
-- uses, so the two panels read as one product. Deliberately not pfUI's generic backdrop and
|
|
-- not the Blizzard dialog frame: this is the addon's own chrome, and it ships with the addon.
|
|
panel:SetBackdrop({
|
|
bgFile = "Interface\\AddOns\\BulwarkFrame\\textures\\panel_bg",
|
|
edgeFile = "Interface\\AddOns\\BulwarkFrame\\textures\\panel_border",
|
|
tile = true, tileSize = 128, edgeSize = 16,
|
|
insets = { left = 5, right = 5, top = 5, bottom = 5 },
|
|
})
|
|
panel:SetBackdropColor(1, 1, 1, 0.97)
|
|
|
|
local title = panel:CreateFontString("BulwarkFrameOptionsTitle", "OVERLAY", "GameFontNormal")
|
|
title:SetPoint("TOPLEFT", panel, "TOPLEFT", 16, -16)
|
|
title:SetText("BulwarkFrame Options")
|
|
ApplyFont(title, 14)
|
|
title:SetTextColor(0.85, 0.66, 0.31)
|
|
|
|
local close = CreateFrame("Button", "BulwarkFrameOptionsClose", panel, "UIPanelCloseButton")
|
|
close:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -8, -8)
|
|
if pfUI and pfUI.api and pfUI.api.SkinCloseButton then pfUI.api.SkinCloseButton(close, panel) end
|
|
|
|
-- Layout cursors, one per column. Step sizes match TotemBar's: a checkbox row is 28, a slider
|
|
-- row 44 (it carries its value label above the track), a button row 28, a group heading 22.
|
|
local L, R = 24, PANEL_W / 2 + 6
|
|
local yL, yR = -44, -44
|
|
local lowest = -44
|
|
|
|
local function placeL(w, dy)
|
|
w:SetPoint("TOPLEFT", panel, "TOPLEFT", L, yL)
|
|
yL = yL - dy
|
|
if yL < lowest then lowest = yL end
|
|
end
|
|
local function placeR(w, dy)
|
|
w:SetPoint("TOPLEFT", panel, "TOPLEFT", R, yR)
|
|
yR = yR - dy
|
|
if yR < lowest then lowest = yR end
|
|
end
|
|
-- A slider carries its value label ABOVE the track (OptionsSliderTemplate's $parentText,
|
|
-- re-anchored in CreateSlider), so the cursor has to leave room for it before placing the
|
|
-- track -- otherwise the label overlaps whatever sits above, which is exactly how the
|
|
-- "Absorb model" heading ended up written through "Elemental Weapons rank". TotemBar's 44px
|
|
-- slider row is this same allowance, just folded into one number.
|
|
-- Measured, not guessed: with a 14px allowance the value label's top edge sat at rel-y 408.7
|
|
-- while the heading above ended at 414.7 -- a 6px overlap. 24 puts the label 4px clear.
|
|
local SLIDER_LABEL = 24
|
|
local function sliderL(w) yL = yL - SLIDER_LABEL; placeL(w, 26) end
|
|
local function sliderR(w) yR = yR - SLIDER_LABEL; placeR(w, 26) end
|
|
local function headL(text) CreateHeading(panel, text, L, yL); yL = yL - 22; if yL < lowest then lowest = yL end end
|
|
local function headR(text) CreateHeading(panel, text, R, yR); yR = yR - 22; if yR < lowest then lowest = yR end end
|
|
|
|
-- left column: frame ------------------------------------------------------------------
|
|
headL("Frame")
|
|
|
|
local lock = CreateCheckbox(panel, "Lock position", "Stops the frame from being dragged.",
|
|
function() return db().locked end,
|
|
function(v) db().locked = v end)
|
|
placeL(lock, 28)
|
|
|
|
local mm = CreateCheckbox(panel, "Minimap button", "Show the minimap button.",
|
|
function() return db().showMinimapButton end,
|
|
function(v) db().showMinimapButton = v; if BF.UpdateMinimapButton then BF.UpdateMinimapButton() end end)
|
|
placeL(mm, 28)
|
|
|
|
local hideInactive = CreateCheckbox(panel, "Hide when idle",
|
|
"Hide the frame when the buffer is down and you are out of combat.",
|
|
function() return db().hideWhenInactive end,
|
|
function(v) db().hideWhenInactive = v end)
|
|
placeL(hideInactive, 28)
|
|
|
|
local hideOOC = CreateCheckbox(panel, "Combat only", "Show the frame only while in combat.",
|
|
function() return db().hideOutOfCombat end,
|
|
function(v) db().hideOutOfCombat = v end)
|
|
placeL(hideOOC, 40)
|
|
|
|
local scale = CreateSlider(panel, "Scale", 0.5, 2.0, 0.05, "Scale: %.2f",
|
|
"Overall size of the frame.",
|
|
function() return db().scale end,
|
|
function(v) db().scale = v end)
|
|
sliderL(scale)
|
|
|
|
local width = CreateSlider(panel, "Width", 80, 400, 5, "Width: %d px",
|
|
"Frame width in pixels.",
|
|
function() return db().width end,
|
|
function(v) db().width = math.floor(v) end)
|
|
sliderL(width)
|
|
|
|
local barH = CreateSlider(panel, "Threshold bar height", 6, 30, 1, "Threshold bar: %d px",
|
|
"Height of the hit-threshold bar.",
|
|
function() return db().barHeight end,
|
|
function(v) db().barHeight = math.floor(v) end)
|
|
sliderL(barH)
|
|
|
|
local timeH = CreateSlider(panel, "Expiry bar height", 3, 24, 1, "Expiry bar: %d px",
|
|
"Height of the expiry bar. Deliberately flatter than the threshold bar.",
|
|
function() return db().timeBarHeight end,
|
|
function(v) db().timeBarHeight = math.floor(v) end)
|
|
sliderL(timeH)
|
|
|
|
local swingH = CreateSlider(panel, "Swing line height", 1, 8, 1, "Swing line: %d px",
|
|
"Thickness of the swing line.",
|
|
function() return db().swingHeight end,
|
|
function(v) db().swingHeight = math.floor(v) end)
|
|
sliderL(swingH)
|
|
|
|
local spacing = CreateSlider(panel, "Bar spacing", 0, 10, 1, "Bar spacing: %d px",
|
|
"Vertical gap between the threshold bar, the expiry bar, and the swing line.",
|
|
function() return db().spacing end,
|
|
function(v) db().spacing = math.floor(v) end)
|
|
sliderL(spacing)
|
|
|
|
-- right column: elements, colours, model ----------------------------------------------
|
|
local ry = -46
|
|
headR("Elements")
|
|
|
|
local e1 = CreateCheckbox(panel, "Hit threshold", "Show the threshold bar.",
|
|
function() return db().showThreshold end,
|
|
function(v) db().showThreshold = v end)
|
|
placeR(e1, 28)
|
|
|
|
local e2 = CreateCheckbox(panel, "Expiry", "Show the expiry bar.",
|
|
function() return db().showExpiry end,
|
|
function(v) db().showExpiry = v end)
|
|
placeR(e2, 28)
|
|
|
|
local e3 = CreateCheckbox(panel, "Swing timer", "Show the swing line.",
|
|
function() return db().showSwing end,
|
|
function(v) db().showSwing = v end)
|
|
placeR(e3, 28)
|
|
|
|
local e3b = CreateCheckbox(panel, "Swing on expiry bar",
|
|
"Let the swing marker ride the expiry bar like pfUI's mana tick, instead of taking its own line.",
|
|
function() return db().swingOnExpiryBar end,
|
|
function(v) db().swingOnExpiryBar = v end)
|
|
placeR(e3b, 28)
|
|
|
|
-- Which question the big number answers. A cycle button rather than two checkboxes: the two
|
|
-- readings are exclusive, and showing both at once would invite reading one as the other.
|
|
local modeBtn = CreateButton(panel, "Show: max hit",
|
|
"What the number means. 'max hit' = how large a single hit may be before the buffer is "
|
|
.. "drained. 'max shield' = how much damage it still absorbs. They differ by the absorb "
|
|
.. "rate, so this is not a formatting choice.",
|
|
function()
|
|
local dd = db()
|
|
dd.displayMode = (dd.displayMode == "hit") and "shield" or "hit"
|
|
this:SetText(dd.displayMode == "shield" and "Show: max shield" or "Show: max hit")
|
|
Applied()
|
|
end)
|
|
placeR(modeBtn, 28)
|
|
table.insert(refreshers, function()
|
|
modeBtn:SetText(db().displayMode == "shield" and "Show: max shield" or "Show: max hit")
|
|
end)
|
|
|
|
local e4 = CreateCheckbox(panel, "Threshold text", "Show the current / maximum numbers.",
|
|
function() return db().showThresholdText end,
|
|
function(v) db().showThresholdText = v end)
|
|
placeR(e4, 28)
|
|
|
|
local e5 = CreateCheckbox(panel, "Expiry text", "Show the remaining seconds as a number.",
|
|
function() return db().showExpiryText end,
|
|
function(v) db().showExpiryText = v end)
|
|
placeR(e5, 28)
|
|
|
|
local e6 = CreateCheckbox(panel, "Attack speed text",
|
|
"Show the current weapon attack speed on the left of the expiry bar.",
|
|
function() return db().showSpeedText end,
|
|
function(v) db().showSpeedText = v end)
|
|
placeR(e6, 28)
|
|
|
|
local expiryAlpha = CreateSlider(panel, "Expiry bar opacity", 0, 1, 0.05, "Expiry bar opacity: %.2f",
|
|
"Alpha of the expiry bar. Kept lower than the threshold bar by default so it reads as the "
|
|
.. "secondary readout.",
|
|
function() return db().expiryAlpha end,
|
|
function(v) db().expiryAlpha = v end)
|
|
sliderR(expiryAlpha)
|
|
|
|
headR("Colours")
|
|
|
|
local sw1 = CreateSwatch(panel, "Low", "colorRed", "Colour below the low threshold.")
|
|
placeR(sw1, 24)
|
|
local sw2 = CreateSwatch(panel, "Medium", "colorYellow", "Colour between the two thresholds.")
|
|
placeR(sw2, 24)
|
|
local sw3 = CreateSwatch(panel, "High", "colorGreen", "Colour above the high threshold.")
|
|
placeR(sw3, 24)
|
|
local sw4 = CreateSwatch(panel, "Swing marker", "colorSwing", "Colour of the swing marker.")
|
|
placeR(sw4, 24)
|
|
|
|
-- Background colour is a 4-element {r,g,b,a} table; CreateSwatch only ever touches indices
|
|
-- 1..3, so the alpha channel gets its own slider straight onto the same table rather than a
|
|
-- second colour-helper -- there is no opacity-capable picker in this file to reuse.
|
|
local sw5 = CreateSwatch(panel, "Background", "colorBg", "Background colour of the frame.")
|
|
placeR(sw5, 24)
|
|
|
|
local bgAlpha = CreateSlider(panel, "Background opacity", 0, 1, 0.05, "Background opacity: %.2f",
|
|
"Opacity of the frame's background panel.",
|
|
function() return (db().colorBg or {})[4] or 0.8 end,
|
|
function(v) local c = db().colorBg; if c then c[4] = v end end)
|
|
sliderR(bgAlpha)
|
|
|
|
headR("Thresholds")
|
|
|
|
local poolRed = CreateSlider(panel, "Pool red threshold", 0, 100, 5, "Pool turns red below: %.0f%%",
|
|
"The threshold bar turns red at or below this fraction of the pool.",
|
|
function() return (db().poolRed or 0.30) * 100 end,
|
|
function(v) db().poolRed = v / 100 end)
|
|
sliderR(poolRed)
|
|
|
|
local poolYellow = CreateSlider(panel, "Pool yellow threshold", 0, 100, 5, "Pool turns green above: %.0f%%",
|
|
"The threshold bar turns green above this fraction of the pool (yellow in between).",
|
|
function() return (db().poolYellow or 0.70) * 100 end,
|
|
function(v) db().poolYellow = v / 100 end)
|
|
sliderR(poolYellow)
|
|
|
|
local timeRed = CreateSlider(panel, "Expiry red threshold", 0, BF.BUFFER_DURATION, 0.5,
|
|
"Expiry turns red below: %.1fs",
|
|
"The expiry bar turns red once fewer than this many seconds remain.",
|
|
function() return db().timeRed or 2.0 end,
|
|
function(v) db().timeRed = v end)
|
|
sliderR(timeRed)
|
|
|
|
local timeYellow = CreateSlider(panel, "Expiry yellow threshold", 0, BF.BUFFER_DURATION, 0.5,
|
|
"Expiry turns green above: %.1fs",
|
|
"The expiry bar turns green once more than this many seconds remain (yellow in between).",
|
|
function() return db().timeYellow or 5.0 end,
|
|
function(v) db().timeYellow = v end)
|
|
sliderR(timeYellow)
|
|
|
|
headR("Absorb model")
|
|
|
|
local rank = CreateSlider(panel, "Talent rank", 0, 3, 1, "Elemental Weapons rank: %d",
|
|
"Rank of Elemental Weapons: 5 / 10 / 15 % absorbed. Rank 0 means the talent is not learned.",
|
|
function() return db().talentRank end,
|
|
function(v) db().talentRank = math.floor(v) end)
|
|
sliderR(rank)
|
|
|
|
-- Set bonus: the T2.5 "+3 % absorption" wording is ambiguous (15 -> 18, or 15 x 1.03) and
|
|
-- unmeasured. Rather than pick one silently, both readings are selectable and "none" is the
|
|
-- default -- see README.
|
|
local setBtn = CreateButton(panel, "Set bonus: none",
|
|
"T2.5 set bonus reading. 'add' means 15 -> 18 %, 'mult' means 15 x 1.03. Not yet measured.",
|
|
function()
|
|
local d = db()
|
|
if d.setBonusMode == "none" then d.setBonusMode = "add"
|
|
elseif d.setBonusMode == "add" then d.setBonusMode = "mult"
|
|
else d.setBonusMode = "none" end
|
|
this:SetText("Set bonus: " .. d.setBonusMode)
|
|
Applied()
|
|
end)
|
|
placeR(setBtn, 28)
|
|
table.insert(refreshers, function() setBtn:SetText("Set bonus: " .. (db().setBonusMode or "none")) end)
|
|
|
|
local setVal = CreateSlider(panel, "Set bonus value", 0, 20, 1, "Set bonus value: %d%%",
|
|
"The set bonus percentage the reading above applies. Only used while the set bonus reading "
|
|
.. "is 'add' or 'mult'.",
|
|
function() return db().setBonusValue end,
|
|
function(v) db().setBonusValue = math.floor(v) end)
|
|
sliderR(setVal)
|
|
|
|
local latency = CreateCheckbox(panel, "Latency-adjust swing",
|
|
"Shift the swing marker by your round-trip time, so it shows when to press rather than "
|
|
.. "when the server swung.",
|
|
function() return db().swingUseLatency end,
|
|
function(v) db().swingUseLatency = v; BF.RefreshSwingInputs() end)
|
|
placeR(latency, 28)
|
|
|
|
local latencyShare = CreateSlider(panel, "Latency share", 0, 1, 0.05, "Latency share: %.2f",
|
|
"How much of the round-trip time to shift the swing marker by: 1.00 = the full round trip, "
|
|
.. "0.50 = downstream only. Only used while latency-adjust is on.",
|
|
function() return db().swingLatencyShare end,
|
|
function(v) db().swingLatencyShare = v; BF.RefreshSwingInputs() end)
|
|
sliderR(latencyShare)
|
|
|
|
headR("Aura IDs")
|
|
|
|
local poolAuraBox = CreateEditBox(panel, "Pool aura spell id",
|
|
"Spell id of the Earthen Bulwark Durability debuff, whose stack count is the pool level. "
|
|
.. "TurtleWoW can renumber a custom spell between patches -- if the readout goes blank after "
|
|
.. "an update, /bulwark probe to find the new id and enter it here.",
|
|
function() return db().poolAura end,
|
|
function(v) db().poolAura = v end)
|
|
placeR(poolAuraBox, 36)
|
|
|
|
local timeAuraBox = CreateEditBox(panel, "Time aura spell id",
|
|
"Spell id of the Earthen Bulwark buff, whose remaining duration drives the expiry bar. "
|
|
.. "TurtleWoW can renumber a custom spell between patches -- if the readout goes blank after "
|
|
.. "an update, /bulwark probe to find the new id and enter it here.",
|
|
function() return db().timeAura end,
|
|
function(v) db().timeAura = v end)
|
|
placeR(timeAuraBox, 40)
|
|
|
|
-- footer -------------------------------------------------------------------------------
|
|
local demoBtn = CreateButton(panel, "Demo", "Run the display off a synthetic cycle so colours "
|
|
.. "and sizes can be judged without a fight.", function()
|
|
local on = BF.ToggleDemo()
|
|
this:SetText(on and "Demo: on" or "Demo")
|
|
end)
|
|
demoBtn:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 24, 20)
|
|
-- The one stateful widget that had no refresher, unlike modeBtn and setBtn above: demo mode can
|
|
-- also be toggled from '/bulwark demo', and without this the button kept whatever label it last
|
|
-- set itself -- reading "Demo" over a running demo, or "Demo: on" over a live readout.
|
|
table.insert(refreshers, function()
|
|
demoBtn:SetText(BF.IsDemo() and "Demo: on" or "Demo")
|
|
end)
|
|
|
|
local probeBtn = CreateButton(panel, "Probe", "Dump the live aura/weapon data we still need "
|
|
.. "(also /bulwark probe).", function() BF.RunProbe() end)
|
|
probeBtn:SetPoint("LEFT", demoBtn, "RIGHT", 8, 0)
|
|
|
|
local resetBtn = CreateButton(panel, "Reset", "Restore every setting to its default.", function()
|
|
BF.resetConfig()
|
|
Applied()
|
|
BF.RefreshOptions()
|
|
if BF.UpdateMinimapButton then BF.UpdateMinimapButton() end
|
|
end)
|
|
resetBtn:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -24, 20)
|
|
|
|
-- Height from the layout cursor, not from a hand-maintained constant. `lowest` tracks the
|
|
-- deepest point either column reached, and the footer row is added below it -- so a new option
|
|
-- can never again end up outside the frame (which is what happened: the last checkbox sat 14px
|
|
-- past the bottom edge after two options were added to a fixed 470).
|
|
local FOOTER = 56
|
|
panel:SetHeight(-lowest + FOOTER)
|
|
|
|
-- Footer buttons, full content width split three ways, mirroring TotemBar's block layout.
|
|
local btnW = (PANEL_W - 48 - 16) / 3
|
|
demoBtn:SetWidth(btnW)
|
|
probeBtn:SetWidth(btnW)
|
|
resetBtn:SetWidth(btnW)
|
|
|
|
local ver = (GetAddOnMetadata and GetAddOnMetadata("BulwarkFrame", "Version")) or "0.2.0"
|
|
local verFS = panel:CreateFontString("BulwarkFrameOptionsVersion", "OVERLAY", "GameFontNormalSmall")
|
|
ApplyFont(verFS, 10)
|
|
verFS:SetPoint("BOTTOM", panel, "BOTTOM", 0, 6)
|
|
verFS:SetText("v" .. ver)
|
|
verFS:SetTextColor(0.6, 0.6, 0.6)
|
|
|
|
-- ESC closes it, like every other panel in this UI.
|
|
tinsert(UISpecialFrames, "BulwarkFrameOptions")
|
|
|
|
panel:Hide()
|
|
end
|
|
|
|
function BF.RefreshOptions()
|
|
local i
|
|
for i = 1, table.getn(refreshers) do refreshers[i]() end
|
|
end
|
|
|
|
function BF.ToggleOptions()
|
|
BuildPanel()
|
|
if panel:IsShown() then
|
|
panel:Hide()
|
|
else
|
|
BF.RefreshOptions()
|
|
panel:Show()
|
|
end
|
|
end
|