A World of Warcraft Experience Bar addon
worldofwarcraft wow addon midnight
at main 515 lines 19 kB view raw
1local _, F = ... 2 3F.Options = F.Options or {} 4 5local panel 6local content 7local controls = {} 8local 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 20local TICK_OPACITY_KEY = "tickOpacity" 21local DEFAULT_STATUSBAR = "Interface\\Buttons\\WHITE8x8" 22local DEFAULT_FONT = "Fonts\\FRIZQT__.TTF" 23 24local function GetLibSharedMedia() 25 if LibStub then 26 return LibStub("LibSharedMedia-3.0", true) 27 end 28 return nil 29end 30 31local 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 60end 61 62local 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 78end 79 80local 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 85end 86 87local 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 92end 93 94local 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] 107end 108 109local 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 114end 115 116local 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 126end 127 128local 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 157end 158 159local 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 216end 217 218local 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 241end 242 243local 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 268end 269 270local 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 343end 344 345function 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 406end 407 408function 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 417end 418 419function 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 513end 514 515F.Options.Create()