A World of Warcraft Experience Bar addon
worldofwarcraft wow addon midnight

Merge pull request #2 from bdbch/feat/settings-ui

Add settings UI and customization options

authored by

bdbch and committed by
GitHub
1c717475 8e8e59a4

+672 -78
+35
Config.lua
··· 4 4 -- Display Options 5 5 showAtMaxLevel = false, 6 6 showIncompleteQuestBar = true, 7 + questTrackingEnabled = true, 8 + showTicks = true, 9 + tickOpacity = 0.35, 10 + anchorPoint = "TOP", 11 + offsetX = 0, 12 + offsetY = 100, 7 13 -- Master visibility toggle (can be flipped with /nxp toggle) 8 14 enabled = true, 9 15 16 + -- Bar Size 17 + barWidth = 600, 18 + barHeight = 24, 19 + 20 + -- Textures 21 + barTextureMain = "Interface\\Buttons\\WHITE8x8", 22 + barTextureRested = "Interface\\Buttons\\WHITE8x8", 23 + barTextureQuest = "Interface\\Buttons\\WHITE8x8", 24 + barTextureQuestComplete = "Interface\\Buttons\\WHITE8x8", 25 + 26 + -- Font 27 + fontPath = "Fonts\\FRIZQT__.TTF", 28 + 29 + -- Bar Colors (RGBA) 30 + colorMain = { r = 0.76, g = 0.38, b = 1, a = 1 }, 31 + colorRested = { r = 0.34, g = 0.61, b = 0.99, a = 0.8 }, 32 + colorQuest = { r = 1, g = 0.64, b = 0.0078, a = 0.25 }, 33 + colorQuestComplete = { r = 1, g = 0.64, b = 0.0078, a = 0.8 }, 34 + 10 35 -- Text Toggles 11 36 showLevelText = true, 12 37 showXPText = true, ··· 53 78 local function showHelp() 54 79 print("NixxnuxXPBar: Commands:") 55 80 print(" /nxp help") 81 + print(" /nxp options") 56 82 print(" /nxp toggle | /nxp show | /nxp hide") 57 83 print(" /nxp toggle level | xp | percent | quest | leveling") 58 84 print(" /nxp toggle incomplete | completed | rested") ··· 107 133 F.DB.enabled = true 108 134 print("NixxnuxXPBar: Visible") 109 135 if F.UpdateUI then F.UpdateUI() end 136 + return 137 + end 138 + 139 + if cmd == "options" then 140 + if F.Options and F.Options.Open then 141 + F.Options.Open() 142 + else 143 + print("NixxnuxXPBar: Options not available yet") 144 + end 110 145 return 111 146 end 112 147
+2
NixxnuxXPBar.toc
··· 15 15 ## Author: Nixxnux 16 16 ## Category: Interface Enhancements 17 17 ## IconTexture: Interface\Icons\Inv_cape_special_treasure_c_01 18 + ## SavedVariables: NXP_DB 18 19 19 20 Init.lua 20 21 Config.lua 22 + Options.lua 21 23 Utils.lua 22 24 UI.lua 23 25 State.lua
+515
Options.lua
··· 1 + local _, F = ... 2 + 3 + F.Options = F.Options or {} 4 + 5 + local panel 6 + local content 7 + local controls = {} 8 + local questRelatedKeys = { 9 + trackedOnly = true, 10 + showQuestRestedText = true, 11 + showIncompleteQuestXPText = true, 12 + showCompletedQuestXPText = true, 13 + showIncompleteQuestBar = true, 14 + colorQuest = true, 15 + colorQuestComplete = true, 16 + barTextureQuest = true, 17 + barTextureQuestComplete = true, 18 + } 19 + 20 + local TICK_OPACITY_KEY = "tickOpacity" 21 + local DEFAULT_STATUSBAR = "Interface\\Buttons\\WHITE8x8" 22 + local DEFAULT_FONT = "Fonts\\FRIZQT__.TTF" 23 + 24 + local function GetLibSharedMedia() 25 + if LibStub then 26 + return LibStub("LibSharedMedia-3.0", true) 27 + end 28 + return nil 29 + end 30 + 31 + local function SetOption(key, value) 32 + if F.InitDB then F.InitDB() end 33 + if not F.DB then return end 34 + 35 + F.DB[key] = value and true or false 36 + 37 + if key == "enabled" then 38 + if value then 39 + if F.UpdateUI then F.UpdateUI() end 40 + else 41 + if F.Frame and F.Frame.Hide then F.Frame:Hide() end 42 + end 43 + return 44 + end 45 + 46 + if key == "hideBlizzardXPBar" and F.UI and F.UI.HideBlizzardBars then 47 + F.UI.HideBlizzardBars() 48 + end 49 + 50 + if F.UpdateUI then F.UpdateUI() end 51 + 52 + if key == "questTrackingEnabled" or key == "trackedOnly" then 53 + if F.State and F.State.UpdateQuestXP then 54 + F.State:UpdateQuestXP() 55 + end 56 + if key == "questTrackingEnabled" and panel and panel:IsShown() then 57 + F.Options.Refresh() 58 + end 59 + end 60 + end 61 + 62 + local function SetNumericOption(key, value) 63 + if F.InitDB then F.InitDB() end 64 + if not F.DB then return end 65 + if key == "barWidth" then 66 + local width = tonumber(value) or 0 67 + local scale = UIParent and UIParent:GetEffectiveScale() or 1 68 + local stepPixels = math.floor(((width / 20) * scale) + 0.5) 69 + local snapped = (stepPixels / scale) * 20 70 + if snapped < 200 then snapped = 200 end 71 + F.DB[key] = snapped 72 + elseif key == TICK_OPACITY_KEY then 73 + F.DB[key] = (tonumber(value) or 0) / 100 74 + else 75 + F.DB[key] = value 76 + end 77 + if F.UpdateUI then F.UpdateUI() end 78 + end 79 + 80 + local function SetStringOption(key, value) 81 + if F.InitDB then F.InitDB() end 82 + if not F.DB then return end 83 + F.DB[key] = value 84 + if F.UpdateUI then F.UpdateUI() end 85 + end 86 + 87 + local function SetColorOption(key, r, g, b, a) 88 + if F.InitDB then F.InitDB() end 89 + if not F.DB then return end 90 + F.DB[key] = { r = r, g = g, b = b, a = a } 91 + if F.UpdateUI then F.UpdateUI() end 92 + end 93 + 94 + local function GetColorOption(key, fallback) 95 + if F.InitDB then F.InitDB() end 96 + if not F.DB then return fallback[1], fallback[2], fallback[3], fallback[4] end 97 + local color = F.DB[key] 98 + if type(color) == "table" then 99 + if color.r then 100 + return color.r or 1, color.g or 1, color.b or 1, color.a or 1 101 + end 102 + if color[1] then 103 + return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 104 + end 105 + end 106 + return fallback[1], fallback[2], fallback[3], fallback[4] 107 + end 108 + 109 + local function CreateHeader(parent, text, y) 110 + local fs = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal") 111 + fs:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) 112 + fs:SetText(text) 113 + return y - 20 114 + end 115 + 116 + local function CreateCheckbox(parent, key, label, y) 117 + local cb = CreateFrame("CheckButton", nil, parent, "InterfaceOptionsCheckButtonTemplate") 118 + cb.Text:SetText(label) 119 + cb:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) 120 + cb:SetScript("OnClick", function(self) 121 + SetOption(key, self:GetChecked()) 122 + end) 123 + 124 + controls[key] = { type = "checkbox", widget = cb } 125 + return y - 28 126 + end 127 + 128 + local function CreateSlider(parent, key, label, y, minValue, maxValue, step) 129 + local name = "NXP_Options_" .. key .. "Slider" 130 + local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate") 131 + slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) 132 + slider:SetMinMaxValues(minValue, maxValue) 133 + slider:SetValueStep(step) 134 + slider:SetObeyStepOnDrag(true) 135 + 136 + local labelText = _G[name .. "Text"] 137 + local lowText = _G[name .. "Low"] 138 + local highText = _G[name .. "High"] 139 + if labelText then labelText:SetText(label) end 140 + if lowText then lowText:SetText(tostring(minValue)) end 141 + if highText then highText:SetText(tostring(maxValue)) end 142 + 143 + slider:SetScript("OnValueChanged", function(self, value) 144 + if self._updating then return end 145 + local rounded = math.floor(value + 0.5) 146 + if step and step < 1 then 147 + rounded = value 148 + end 149 + self._updating = true 150 + self:SetValue(rounded) 151 + self._updating = false 152 + SetNumericOption(key, rounded) 153 + end) 154 + 155 + controls[key] = { type = "slider", widget = slider } 156 + return y - 52 157 + end 158 + 159 + local function CreateSliderWithInput(parent, key, label, y, minValue, maxValue, step, inputWidth) 160 + local name = "NXP_Options_" .. key .. "Slider" 161 + local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate") 162 + slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) 163 + slider:SetMinMaxValues(minValue, maxValue) 164 + slider:SetValueStep(step) 165 + slider:SetObeyStepOnDrag(true) 166 + 167 + local labelText = _G[name .. "Text"] 168 + local lowText = _G[name .. "Low"] 169 + local highText = _G[name .. "High"] 170 + if labelText then labelText:SetText(label) end 171 + if lowText then lowText:SetText(tostring(minValue)) end 172 + if highText then highText:SetText(tostring(maxValue)) end 173 + 174 + local edit = CreateFrame("EditBox", nil, parent, "InputBoxTemplate") 175 + edit:SetAutoFocus(false) 176 + edit:SetSize(inputWidth or 60, 20) 177 + edit:SetPoint("LEFT", slider, "RIGHT", 12, 0) 178 + edit:SetNumeric(true) 179 + 180 + slider:SetScript("OnValueChanged", function(self, value) 181 + if self._updating then return end 182 + local rounded = math.floor(value + 0.5) 183 + if step and step < 1 then 184 + rounded = value 185 + end 186 + self._updating = true 187 + self:SetValue(rounded) 188 + self._updating = false 189 + edit:SetNumber(rounded) 190 + SetNumericOption(key, rounded) 191 + end) 192 + 193 + local function commitEdit() 194 + local value = tonumber(edit:GetText()) 195 + if not value then return end 196 + if value < minValue then value = minValue end 197 + if value > maxValue then value = maxValue end 198 + slider._updating = true 199 + slider:SetValue(value) 200 + slider._updating = false 201 + edit:SetNumber(value) 202 + SetNumericOption(key, value) 203 + end 204 + 205 + edit:SetScript("OnEnterPressed", function(self) 206 + commitEdit() 207 + self:ClearFocus() 208 + end) 209 + 210 + edit:SetScript("OnEditFocusLost", function() 211 + commitEdit() 212 + end) 213 + 214 + controls[key] = { type = "offset", slider = slider, edit = edit } 215 + return y - 52 216 + end 217 + 218 + local function BuildMediaList(kind, defaultLabel, defaultValue) 219 + local list = {} 220 + local order = {} 221 + 222 + list[defaultValue] = defaultLabel 223 + table.insert(order, defaultValue) 224 + 225 + local LSM = GetLibSharedMedia() 226 + if LSM and LSM.HashTable then 227 + local entries = {} 228 + for name, path in pairs(LSM:HashTable(kind) or {}) do 229 + if type(path) == "string" and path ~= "" then 230 + table.insert(entries, { name = tostring(name), path = path }) 231 + end 232 + end 233 + table.sort(entries, function(a, b) return a.name < b.name end) 234 + for _, entry in ipairs(entries) do 235 + list[entry.path] = entry.name 236 + table.insert(order, entry.path) 237 + end 238 + end 239 + 240 + return list, order 241 + end 242 + 243 + local function CreateDropdown(parent, key, label, y, list, order, width) 244 + local name = "NXP_Options_" .. key .. "Dropdown" 245 + local dropdown = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate") 246 + dropdown:SetPoint("TOPLEFT", parent, "TOPLEFT", -6, y) 247 + 248 + local labelText = dropdown:CreateFontString(nil, "ARTWORK", "GameFontHighlight") 249 + labelText:SetPoint("TOPLEFT", dropdown, "TOPLEFT", 16, 14) 250 + labelText:SetText(label) 251 + 252 + UIDropDownMenu_SetWidth(dropdown, width or 220) 253 + UIDropDownMenu_Initialize(dropdown, function(self) 254 + for _, value in ipairs(order or {}) do 255 + local info = UIDropDownMenu_CreateInfo() 256 + info.text = list[value] or value 257 + info.value = value 258 + info.func = function() 259 + UIDropDownMenu_SetSelectedValue(dropdown, value) 260 + SetStringOption(key, value) 261 + end 262 + UIDropDownMenu_AddButton(info) 263 + end 264 + end) 265 + 266 + controls[key] = { type = "dropdown", widget = dropdown, list = list, label = labelText } 267 + return y - 50 268 + end 269 + 270 + local function CreateColorPicker(parent, key, label, y, fallback) 271 + local labelText = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlight") 272 + labelText:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) 273 + labelText:SetText(label) 274 + 275 + local swatch = CreateFrame("Button", nil, parent) 276 + swatch:SetSize(16, 16) 277 + swatch:SetPoint("LEFT", labelText, "RIGHT", 8, 0) 278 + swatch:SetNormalTexture("Interface\\Buttons\\WHITE8x8") 279 + swatch:SetHighlightTexture("Interface\\Buttons\\WHITE8x8", "ADD") 280 + local swatchTex = swatch:GetNormalTexture() 281 + 282 + local border = swatch:CreateTexture(nil, "BORDER") 283 + border:SetPoint("TOPLEFT", swatch, "TOPLEFT", -1, 1) 284 + border:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", 1, -1) 285 + border:SetColorTexture(0, 0, 0, 1) 286 + if swatchTex then 287 + swatchTex:SetPoint("TOPLEFT", swatch, "TOPLEFT", 1, -1) 288 + swatchTex:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", -1, 1) 289 + end 290 + 291 + swatch:SetScript("OnClick", function() 292 + local r, g, b, a = GetColorOption(key, fallback) 293 + local previous = { r = r, g = g, b = b, a = a } 294 + 295 + local function getPickerAlpha() 296 + if ColorPickerFrame.GetColorAlpha then 297 + return ColorPickerFrame:GetColorAlpha() 298 + end 299 + return 1 - (ColorPickerFrame.opacity or 0) 300 + end 301 + 302 + local function applyColor() 303 + local nr, ng, nb = ColorPickerFrame:GetColorRGB() 304 + local na = getPickerAlpha() 305 + SetColorOption(key, nr, ng, nb, na) 306 + F.Options.Refresh() 307 + end 308 + 309 + local function cancelColor() 310 + local pv = previous 311 + if pv then 312 + SetColorOption(key, pv.r, pv.g, pv.b, pv.a) 313 + F.Options.Refresh() 314 + end 315 + end 316 + 317 + if ColorPickerFrame.SetupColorPickerAndShow then 318 + ColorPickerFrame:SetupColorPickerAndShow({ 319 + r = r, 320 + g = g, 321 + b = b, 322 + opacity = 1 - (a or 1), 323 + hasOpacity = true, 324 + swatchFunc = applyColor, 325 + opacityFunc = applyColor, 326 + cancelFunc = cancelColor, 327 + }) 328 + else 329 + ColorPickerFrame.hasOpacity = true 330 + ColorPickerFrame.opacity = 1 - (a or 1) 331 + ColorPickerFrame.previousValues = previous 332 + ColorPickerFrame.func = applyColor 333 + ColorPickerFrame.opacityFunc = applyColor 334 + ColorPickerFrame.cancelFunc = cancelColor 335 + ColorPickerFrame:SetColorRGB(r, g, b) 336 + ColorPickerFrame:Hide() 337 + ColorPickerFrame:Show() 338 + end 339 + end) 340 + 341 + controls[key] = { type = "color", widget = swatch, label = labelText, fallback = fallback } 342 + return y - 24 343 + end 344 + 345 + function F.Options.Refresh() 346 + if F.InitDB then F.InitDB() end 347 + if not F.DB then return end 348 + 349 + local defaults = F.Config or {} 350 + 351 + for key, control in pairs(controls) do 352 + if control.type == "checkbox" then 353 + control.widget:SetChecked(F.DB[key] and true or false) 354 + elseif control.type == "slider" then 355 + local value = F.DB[key] 356 + if value == nil then value = defaults[key] end 357 + if value == nil then value = 0 end 358 + if key == TICK_OPACITY_KEY then 359 + value = (tonumber(value) or 0) * 100 360 + end 361 + control.widget._updating = true 362 + control.widget:SetValue(tonumber(value) or 0) 363 + control.widget._updating = false 364 + elseif control.type == "color" then 365 + local r, g, b, a = GetColorOption(key, control.fallback) 366 + local tex = control.widget:GetNormalTexture() 367 + if tex then tex:SetColorTexture(r, g, b, a) end 368 + elseif control.type == "offset" then 369 + local value = F.DB[key] 370 + if value == nil then value = defaults[key] end 371 + if value == nil then value = 0 end 372 + control.slider._updating = true 373 + control.slider:SetValue(tonumber(value) or 0) 374 + control.slider._updating = false 375 + control.edit:SetNumber(tonumber(value) or 0) 376 + elseif control.type == "dropdown" then 377 + local defaults = F.Config or {} 378 + local value = F.DB[key] 379 + if value == nil then value = defaults[key] end 380 + UIDropDownMenu_SetSelectedValue(control.widget, value) 381 + UIDropDownMenu_SetText(control.widget, control.list and control.list[value] or value or "") 382 + end 383 + end 384 + 385 + local questEnabled = F.DB.questTrackingEnabled ~= false 386 + for key, _ in pairs(questRelatedKeys) do 387 + local control = controls[key] 388 + if control then 389 + if control.type == "checkbox" or control.type == "slider" then 390 + control.widget:SetEnabled(questEnabled) 391 + if control.widget.Text then 392 + local color = questEnabled and NORMAL_FONT_COLOR or GRAY_FONT_COLOR 393 + control.widget.Text:SetTextColor(color.r, color.g, color.b) 394 + end 395 + elseif control.type == "color" then 396 + if questEnabled then 397 + control.widget:Enable() 398 + control.label:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) 399 + else 400 + control.widget:Disable() 401 + control.label:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) 402 + end 403 + end 404 + end 405 + end 406 + end 407 + 408 + function F.Options.Open() 409 + if not panel then return end 410 + 411 + if Settings and Settings.OpenToCategory then 412 + Settings.OpenToCategory(panel.name) 413 + elseif InterfaceOptionsFrame_OpenToCategory then 414 + InterfaceOptionsFrame_OpenToCategory(panel) 415 + InterfaceOptionsFrame_OpenToCategory(panel) 416 + end 417 + end 418 + 419 + function F.Options.Create() 420 + if panel then return end 421 + 422 + panel = CreateFrame("Frame", "NXP_OptionsPanel", UIParent) 423 + panel.name = "NixxnuxXPBar" 424 + 425 + local scroll = CreateFrame("ScrollFrame", "NXP_OptionsScrollFrame", panel, "UIPanelScrollFrameTemplate") 426 + scroll:SetPoint("TOPLEFT", panel, "TOPLEFT", 0, -8) 427 + scroll:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -30, 8) 428 + 429 + content = CreateFrame("Frame", nil, scroll) 430 + content:SetSize(1, 1) 431 + scroll:SetScrollChild(content) 432 + 433 + local title = content:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") 434 + title:SetPoint("TOPLEFT", content, "TOPLEFT", 16, -8) 435 + title:SetText("NixxnuxXPBar") 436 + 437 + local y = -40 438 + y = CreateHeader(content, "General", y) 439 + y = CreateCheckbox(content, "enabled", "Enable addon", y) 440 + y = CreateCheckbox(content, "questTrackingEnabled", "Enable quest tracking", y) 441 + y = CreateCheckbox(content, "trackedOnly", "Count tracked (pinned) quests only", y) 442 + y = CreateCheckbox(content, "showIncompleteQuestBar", "Show uncompleted quest XP bar", y) 443 + y = CreateCheckbox(content, "showTicks", "Show XP ticks", y) 444 + y = y - 6 445 + y = CreateSlider(content, "tickOpacity", "Tick opacity", y, 10, 100, 1) 446 + 447 + y = y - 10 448 + y = CreateHeader(content, "Position", y) 449 + y = y - 6 450 + local anchorList = { 451 + TOP = "Top", 452 + BOTTOM = "Bottom", 453 + LEFT = "Left", 454 + RIGHT = "Right", 455 + CENTER = "Center", 456 + } 457 + local anchorOrder = { "TOP", "BOTTOM", "LEFT", "RIGHT", "CENTER" } 458 + y = CreateDropdown(content, "anchorPoint", "Anchor position", y, anchorList, anchorOrder) 459 + y = CreateSliderWithInput(content, "offsetX", "X offset", y, -1000, 1000, 1, 60) 460 + y = CreateSliderWithInput(content, "offsetY", "Y offset", y, -1000, 1000, 1, 60) 461 + 462 + y = y - 10 463 + y = CreateHeader(content, "Bar Size", y) 464 + y = y - 6 465 + y = CreateSlider(content, "barWidth", "Bar width", y, 200, 900, 1) 466 + y = CreateSlider(content, "barHeight", "Bar height", y, 8, 60, 1) 467 + 468 + y = y - 6 469 + y = CreateHeader(content, "Bar Colors", y) 470 + y = CreateColorPicker(content, "colorMain", "Normal XP", y, { 0.76, 0.38, 1, 1 }) 471 + y = CreateColorPicker(content, "colorRested", "Rested XP", y, { 0.34, 0.61, 0.99, 0.8 }) 472 + y = CreateColorPicker(content, "colorQuest", "Uncompleted Quest XP", y, { 1, 0.64, 0.0078, 0.25 }) 473 + y = CreateColorPicker(content, "colorQuestComplete", "Completed Quest XP", y, { 1, 0.64, 0.0078, 0.8 }) 474 + 475 + y = y - 6 476 + y = CreateHeader(content, "Textures", y) 477 + y = y - 6 478 + local statusbarList, statusbarOrder = BuildMediaList("statusbar", "Default (Solid)", DEFAULT_STATUSBAR) 479 + y = CreateDropdown(content, "barTextureMain", "Normal XP texture", y, statusbarList, statusbarOrder) 480 + y = CreateDropdown(content, "barTextureRested", "Rested XP texture", y, statusbarList, statusbarOrder) 481 + y = CreateDropdown(content, "barTextureQuest", "Uncompleted quest XP texture", y, statusbarList, statusbarOrder) 482 + y = CreateDropdown(content, "barTextureQuestComplete", "Completed quest XP texture", y, statusbarList, statusbarOrder) 483 + 484 + y = y - 6 485 + y = CreateHeader(content, "Font", y) 486 + y = y - 6 487 + local fontList, fontOrder = BuildMediaList("font", "Default", DEFAULT_FONT) 488 + y = CreateDropdown(content, "fontPath", "Font", y, fontList, fontOrder) 489 + 490 + y = y - 10 491 + y = CreateHeader(content, "Text Labels", y) 492 + y = CreateCheckbox(content, "showLevelText", "Level text", y) 493 + y = CreateCheckbox(content, "showXPText", "XP text", y) 494 + y = CreateCheckbox(content, "showPercentText", "Percent text", y) 495 + y = CreateCheckbox(content, "showQuestRestedText", "Quest/rested percent text", y) 496 + y = CreateCheckbox(content, "showLevelingText", "Leveling info text", y) 497 + y = CreateCheckbox(content, "showIncompleteQuestXPText", "Uncompleted quest XP text", y) 498 + y = CreateCheckbox(content, "showCompletedQuestXPText", "Completed quest XP text", y) 499 + y = CreateCheckbox(content, "showRestedXPText", "Rested XP text", y) 500 + 501 + content:SetHeight(-y + 20) 502 + 503 + panel:SetScript("OnShow", function() 504 + F.Options.Refresh() 505 + end) 506 + 507 + if Settings and Settings.RegisterCanvasLayoutCategory then 508 + local category = Settings.RegisterCanvasLayoutCategory(panel, panel.name) 509 + Settings.RegisterAddOnCategory(category) 510 + elseif InterfaceOptions_AddCategory then 511 + InterfaceOptions_AddCategory(panel) 512 + end 513 + end 514 + 515 + F.Options.Create()
+7
State.lua
··· 65 65 end 66 66 67 67 function F.State:UpdateQuestXP() 68 + if F.DB and F.DB.questTrackingEnabled == false then 69 + self.questXP = 0 70 + self.completeXP = 0 71 + self.incompleteXP = 0 72 + return 73 + end 74 + 68 75 local numEntries = C_QuestLog.GetNumQuestLogEntries() 69 76 local qXP, cXP, iXP = 0, 0, 0 70 77
+113 -78
UI.lua
··· 32 32 function F.UI.Create() 33 33 local f = CreateFrame("Frame", "NXP_Frame", UIParent) 34 34 f:SetSize(CFG.width, CFG.height) 35 - f:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 260) -- Default Position 35 + f:SetPoint("TOP", UIParent, "TOP", 0, 100) -- Default Position 36 36 f:SetFrameStrata("LOW") 37 37 38 - -- Enable dragging (only while Shift is held) 39 - f:SetMovable(true) 38 + f:SetMovable(false) 40 39 f:EnableMouse(false) 41 - f:RegisterForDrag("LeftButton") 42 - 43 - local function stopAndMaybeSnap(self) 44 - if self.__nxb_moving then 45 - self:StopMovingOrSizing() 46 - self.__nxb_moving = nil 47 - 48 - local fx, fy = self:GetCenter() 49 - if fx and fy then 50 - local cx, cy = UIParent:GetCenter() 51 - if cx and cy then 52 - local dx, dy = fx - cx, fy - cy 53 - local distSq = dx * dx + dy * dy 54 - local snapRadius = 40 55 - if distSq <= (snapRadius * snapRadius) then 56 - self:ClearAllPoints() 57 - self:SetPoint("CENTER", UIParent, "CENTER", 0, 0) 58 - end 59 - end 60 - end 61 - 62 - self:SetScript("OnUpdate", nil) 63 - end 64 - end 65 - 66 - f:SetScript("OnDragStart", function(self) 67 - if IsShiftKeyDown() then 68 - self:StartMoving() 69 - self.__nxb_moving = true 70 - 71 - self:SetScript("OnUpdate", function(self, elapsed) 72 - if not IsShiftKeyDown() or not IsMouseButtonDown("LeftButton") then 73 - stopAndMaybeSnap(self) 74 - end 75 - end) 76 - end 77 - end) 78 - 79 - f:SetScript("OnDragStop", stopAndMaybeSnap) 80 - 81 - f:SetScript("OnMouseDown", function(self, button) 82 - if button == "LeftButton" and IsShiftKeyDown() then 83 - self:StartMoving() 84 - self.__nxb_moving = true 85 - 86 - self:SetScript("OnUpdate", function(self, elapsed) 87 - if not IsShiftKeyDown() or not IsMouseButtonDown("LeftButton") then 88 - stopAndMaybeSnap(self) 89 - end 90 - end) 91 - end 92 - end) 93 - 94 - f:SetScript("OnMouseUp", function(self, button) 95 - if button == "LeftButton" then 96 - stopAndMaybeSnap(self) 97 - end 98 - end) 99 - 100 - f:RegisterEvent("MODIFIER_STATE_CHANGED") 101 - f:SetScript("OnEvent", function(self, event, key, state) 102 - if event == "MODIFIER_STATE_CHANGED" then 103 - -- Enable/disable clicks based on Shift state 104 - self:EnableMouse(IsShiftKeyDown()) 105 - if not IsShiftKeyDown() and self.__nxb_moving then 106 - stopAndMaybeSnap(self) 107 - end 108 - end 109 - end) 110 40 111 41 F.Frame = f 112 42 ··· 127 57 128 58 -- Main XP Bar 129 59 F.BarMain = F.UI.CreateStatusBar(f, "ARTWORK", 1, CFG.colorMain) 60 + 61 + -- XP Ticks (20 segments) 62 + local tickContainer = CreateFrame("Frame", nil, f) 63 + tickContainer:SetAllPoints() 64 + tickContainer.ticks = {} 65 + for i = 1, 19 do 66 + local tick = tickContainer:CreateTexture(nil, "OVERLAY") 67 + tick:SetColorTexture(0, 0, 0, 0.35) 68 + tick:SetWidth(1) 69 + tickContainer.ticks[i] = tick 70 + end 71 + F.TickContainer = tickContainer 130 72 131 73 local overlay = CreateFrame("Frame", nil, f) 132 74 overlay:SetAllPoints() ··· 216 158 local s = F.State 217 159 local db = F.DB 218 160 161 + if db and db.barWidth and db.barHeight then 162 + local width = db.barWidth 163 + if width then 164 + local scale = F.Frame:GetEffectiveScale() or 1 165 + local stepPixels = math.floor(((width / 20) * scale) + 0.5) 166 + width = (stepPixels / scale) * 20 167 + end 168 + F.Frame:SetSize(width or db.barWidth, db.barHeight) 169 + end 170 + 171 + if db then 172 + local anchor = db.anchorPoint or "TOP" 173 + local x = tonumber(db.offsetX) or 0 174 + local y = tonumber(db.offsetY) or 0 175 + if anchor == "TOP" then 176 + y = -y 177 + end 178 + F.Frame:ClearAllPoints() 179 + F.Frame:SetPoint(anchor, UIParent, anchor, x, y) 180 + end 181 + 182 + if F.TickContainer then 183 + if db and db.showTicks then 184 + local width = F.Frame:GetWidth() 185 + local height = F.Frame:GetHeight() 186 + if width and width > 0 then 187 + local step = width / 20 188 + local scale = F.Frame:GetEffectiveScale() or 1 189 + local opacity = 0.35 190 + if db and type(db.tickOpacity) == "number" then 191 + opacity = db.tickOpacity 192 + end 193 + for i = 1, 19 do 194 + local tick = F.TickContainer.ticks[i] 195 + if tick then 196 + local x = step * i 197 + x = math.floor((x * scale) + 0.5) / scale 198 + tick:ClearAllPoints() 199 + tick:SetColorTexture(0, 0, 0, opacity) 200 + tick:SetPoint("TOPLEFT", F.Frame, "TOPLEFT", x, 0) 201 + tick:SetPoint("BOTTOMLEFT", F.Frame, "BOTTOMLEFT", x, 0) 202 + tick:Show() 203 + end 204 + end 205 + F.TickContainer:Show() 206 + else 207 + F.TickContainer:Hide() 208 + end 209 + else 210 + F.TickContainer:Hide() 211 + end 212 + end 213 + 214 + local function resolveColor(color, fallback) 215 + if type(color) == "table" then 216 + if color.r then 217 + return color.r or 1, color.g or 1, color.b or 1, color.a or 1 218 + end 219 + if color[1] then 220 + return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 221 + end 222 + end 223 + return fallback[1], fallback[2], fallback[3], fallback[4] 224 + end 225 + 226 + if db then 227 + local fontPath = db.fontPath or CFG.font 228 + F.LevelText:SetFont(fontPath, CFG.barFontSize, "OUTLINE") 229 + F.TextCenter:SetFont(fontPath, CFG.barFontSize, "OUTLINE") 230 + F.TextRight:SetFont(fontPath, CFG.barFontSize, "OUTLINE") 231 + F.InfoText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE") 232 + F.LevelingText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE") 233 + if F.QuestXPText then 234 + F.QuestXPText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE") 235 + end 236 + 237 + local mainTexture = db.barTextureMain or CFG.texture 238 + local restedTexture = db.barTextureRested or CFG.texture 239 + local questTexture = db.barTextureQuest or CFG.texture 240 + local questCompleteTexture = db.barTextureQuestComplete or CFG.texture 241 + F.BarMain:SetStatusBarTexture(mainTexture) 242 + F.BarRested:SetStatusBarTexture(restedTexture) 243 + F.BarQuest:SetStatusBarTexture(questTexture) 244 + F.BarQuestComplete:SetStatusBarTexture(questCompleteTexture) 245 + 246 + F.BarMain:SetStatusBarColor(resolveColor(db.colorMain, CFG.colorMain)) 247 + F.BarRested:SetStatusBarColor(resolveColor(db.colorRested, CFG.colorRested)) 248 + F.BarQuest:SetStatusBarColor(resolveColor(db.colorQuest, CFG.colorQuest)) 249 + F.BarQuestComplete:SetStatusBarColor(resolveColor(db.colorQuestComplete, CFG.colorQuestComplete)) 250 + end 251 + 219 252 if s.isMaxLevel and not db.showAtMaxLevel then 220 253 F.Frame:Hide() 221 254 return ··· 316 349 local infoParts = {} 317 350 318 351 if db.showQuestRestedText and s.maxXP > 0 then 319 - if (s.completeXP or 0) > 0 then 352 + if db.questTrackingEnabled ~= false and (s.completeXP or 0) > 0 then 320 353 table.insert(infoParts, string.format("Completed Quests: %.1f%%", ((s.completeXP or 0) / s.maxXP * 100))) 321 354 end 322 355 if (s.restedXP or 0) > 0 then ··· 350 383 351 384 do 352 385 local xpParts = {} 353 - if db.showIncompleteQuestXPText and (s.incompleteXP or 0) > 0 then 354 - table.insert(xpParts, string.format("Uncompleted Quest XP: %s", FormatLargeNumber(s.incompleteXP or 0))) 355 - end 356 - if db.showCompletedQuestXPText and (s.completeXP or 0) > 0 then 357 - table.insert(xpParts, string.format("Completed Quest XP: %s", FormatLargeNumber(s.completeXP or 0))) 386 + if db.questTrackingEnabled ~= false then 387 + if db.showIncompleteQuestXPText and (s.incompleteXP or 0) > 0 then 388 + table.insert(xpParts, string.format("Uncompleted Quest XP: %s", FormatLargeNumber(s.incompleteXP or 0))) 389 + end 390 + if db.showCompletedQuestXPText and (s.completeXP or 0) > 0 then 391 + table.insert(xpParts, string.format("Completed Quest XP: %s", FormatLargeNumber(s.completeXP or 0))) 392 + end 358 393 end 359 394 if db.showRestedXPText and (s.restedXP or 0) > 0 then 360 395 table.insert(xpParts, string.format("Rested XP: %s", FormatLargeNumber(s.restedXP or 0)))