···11+local _, F = ...
22+33+F.Options = F.Options or {}
44+55+local panel
66+local content
77+local controls = {}
88+local questRelatedKeys = {
99+ trackedOnly = true,
1010+ showQuestRestedText = true,
1111+ showIncompleteQuestXPText = true,
1212+ showCompletedQuestXPText = true,
1313+ showIncompleteQuestBar = true,
1414+ colorQuest = true,
1515+ colorQuestComplete = true,
1616+ barTextureQuest = true,
1717+ barTextureQuestComplete = true,
1818+}
1919+2020+local TICK_OPACITY_KEY = "tickOpacity"
2121+local DEFAULT_STATUSBAR = "Interface\\Buttons\\WHITE8x8"
2222+local DEFAULT_FONT = "Fonts\\FRIZQT__.TTF"
2323+2424+local function GetLibSharedMedia()
2525+ if LibStub then
2626+ return LibStub("LibSharedMedia-3.0", true)
2727+ end
2828+ return nil
2929+end
3030+3131+local function SetOption(key, value)
3232+ if F.InitDB then F.InitDB() end
3333+ if not F.DB then return end
3434+3535+ F.DB[key] = value and true or false
3636+3737+ if key == "enabled" then
3838+ if value then
3939+ if F.UpdateUI then F.UpdateUI() end
4040+ else
4141+ if F.Frame and F.Frame.Hide then F.Frame:Hide() end
4242+ end
4343+ return
4444+ end
4545+4646+ if key == "hideBlizzardXPBar" and F.UI and F.UI.HideBlizzardBars then
4747+ F.UI.HideBlizzardBars()
4848+ end
4949+5050+ if F.UpdateUI then F.UpdateUI() end
5151+5252+ if key == "questTrackingEnabled" or key == "trackedOnly" then
5353+ if F.State and F.State.UpdateQuestXP then
5454+ F.State:UpdateQuestXP()
5555+ end
5656+ if key == "questTrackingEnabled" and panel and panel:IsShown() then
5757+ F.Options.Refresh()
5858+ end
5959+ end
6060+end
6161+6262+local function SetNumericOption(key, value)
6363+ if F.InitDB then F.InitDB() end
6464+ if not F.DB then return end
6565+ if key == "barWidth" then
6666+ local width = tonumber(value) or 0
6767+ local scale = UIParent and UIParent:GetEffectiveScale() or 1
6868+ local stepPixels = math.floor(((width / 20) * scale) + 0.5)
6969+ local snapped = (stepPixels / scale) * 20
7070+ if snapped < 200 then snapped = 200 end
7171+ F.DB[key] = snapped
7272+ elseif key == TICK_OPACITY_KEY then
7373+ F.DB[key] = (tonumber(value) or 0) / 100
7474+ else
7575+ F.DB[key] = value
7676+ end
7777+ if F.UpdateUI then F.UpdateUI() end
7878+end
7979+8080+local function SetStringOption(key, value)
8181+ if F.InitDB then F.InitDB() end
8282+ if not F.DB then return end
8383+ F.DB[key] = value
8484+ if F.UpdateUI then F.UpdateUI() end
8585+end
8686+8787+local function SetColorOption(key, r, g, b, a)
8888+ if F.InitDB then F.InitDB() end
8989+ if not F.DB then return end
9090+ F.DB[key] = { r = r, g = g, b = b, a = a }
9191+ if F.UpdateUI then F.UpdateUI() end
9292+end
9393+9494+local function GetColorOption(key, fallback)
9595+ if F.InitDB then F.InitDB() end
9696+ if not F.DB then return fallback[1], fallback[2], fallback[3], fallback[4] end
9797+ local color = F.DB[key]
9898+ if type(color) == "table" then
9999+ if color.r then
100100+ return color.r or 1, color.g or 1, color.b or 1, color.a or 1
101101+ end
102102+ if color[1] then
103103+ return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1
104104+ end
105105+ end
106106+ return fallback[1], fallback[2], fallback[3], fallback[4]
107107+end
108108+109109+local function CreateHeader(parent, text, y)
110110+ local fs = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
111111+ fs:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y)
112112+ fs:SetText(text)
113113+ return y - 20
114114+end
115115+116116+local function CreateCheckbox(parent, key, label, y)
117117+ local cb = CreateFrame("CheckButton", nil, parent, "InterfaceOptionsCheckButtonTemplate")
118118+ cb.Text:SetText(label)
119119+ cb:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y)
120120+ cb:SetScript("OnClick", function(self)
121121+ SetOption(key, self:GetChecked())
122122+ end)
123123+124124+ controls[key] = { type = "checkbox", widget = cb }
125125+ return y - 28
126126+end
127127+128128+local function CreateSlider(parent, key, label, y, minValue, maxValue, step)
129129+ local name = "NXP_Options_" .. key .. "Slider"
130130+ local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
131131+ slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y)
132132+ slider:SetMinMaxValues(minValue, maxValue)
133133+ slider:SetValueStep(step)
134134+ slider:SetObeyStepOnDrag(true)
135135+136136+ local labelText = _G[name .. "Text"]
137137+ local lowText = _G[name .. "Low"]
138138+ local highText = _G[name .. "High"]
139139+ if labelText then labelText:SetText(label) end
140140+ if lowText then lowText:SetText(tostring(minValue)) end
141141+ if highText then highText:SetText(tostring(maxValue)) end
142142+143143+ slider:SetScript("OnValueChanged", function(self, value)
144144+ if self._updating then return end
145145+ local rounded = math.floor(value + 0.5)
146146+ if step and step < 1 then
147147+ rounded = value
148148+ end
149149+ self._updating = true
150150+ self:SetValue(rounded)
151151+ self._updating = false
152152+ SetNumericOption(key, rounded)
153153+ end)
154154+155155+ controls[key] = { type = "slider", widget = slider }
156156+ return y - 52
157157+end
158158+159159+local function CreateSliderWithInput(parent, key, label, y, minValue, maxValue, step, inputWidth)
160160+ local name = "NXP_Options_" .. key .. "Slider"
161161+ local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
162162+ slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y)
163163+ slider:SetMinMaxValues(minValue, maxValue)
164164+ slider:SetValueStep(step)
165165+ slider:SetObeyStepOnDrag(true)
166166+167167+ local labelText = _G[name .. "Text"]
168168+ local lowText = _G[name .. "Low"]
169169+ local highText = _G[name .. "High"]
170170+ if labelText then labelText:SetText(label) end
171171+ if lowText then lowText:SetText(tostring(minValue)) end
172172+ if highText then highText:SetText(tostring(maxValue)) end
173173+174174+ local edit = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
175175+ edit:SetAutoFocus(false)
176176+ edit:SetSize(inputWidth or 60, 20)
177177+ edit:SetPoint("LEFT", slider, "RIGHT", 12, 0)
178178+ edit:SetNumeric(true)
179179+180180+ slider:SetScript("OnValueChanged", function(self, value)
181181+ if self._updating then return end
182182+ local rounded = math.floor(value + 0.5)
183183+ if step and step < 1 then
184184+ rounded = value
185185+ end
186186+ self._updating = true
187187+ self:SetValue(rounded)
188188+ self._updating = false
189189+ edit:SetNumber(rounded)
190190+ SetNumericOption(key, rounded)
191191+ end)
192192+193193+ local function commitEdit()
194194+ local value = tonumber(edit:GetText())
195195+ if not value then return end
196196+ if value < minValue then value = minValue end
197197+ if value > maxValue then value = maxValue end
198198+ slider._updating = true
199199+ slider:SetValue(value)
200200+ slider._updating = false
201201+ edit:SetNumber(value)
202202+ SetNumericOption(key, value)
203203+ end
204204+205205+ edit:SetScript("OnEnterPressed", function(self)
206206+ commitEdit()
207207+ self:ClearFocus()
208208+ end)
209209+210210+ edit:SetScript("OnEditFocusLost", function()
211211+ commitEdit()
212212+ end)
213213+214214+ controls[key] = { type = "offset", slider = slider, edit = edit }
215215+ return y - 52
216216+end
217217+218218+local function BuildMediaList(kind, defaultLabel, defaultValue)
219219+ local list = {}
220220+ local order = {}
221221+222222+ list[defaultValue] = defaultLabel
223223+ table.insert(order, defaultValue)
224224+225225+ local LSM = GetLibSharedMedia()
226226+ if LSM and LSM.HashTable then
227227+ local entries = {}
228228+ for name, path in pairs(LSM:HashTable(kind) or {}) do
229229+ if type(path) == "string" and path ~= "" then
230230+ table.insert(entries, { name = tostring(name), path = path })
231231+ end
232232+ end
233233+ table.sort(entries, function(a, b) return a.name < b.name end)
234234+ for _, entry in ipairs(entries) do
235235+ list[entry.path] = entry.name
236236+ table.insert(order, entry.path)
237237+ end
238238+ end
239239+240240+ return list, order
241241+end
242242+243243+local function CreateDropdown(parent, key, label, y, list, order, width)
244244+ local name = "NXP_Options_" .. key .. "Dropdown"
245245+ local dropdown = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate")
246246+ dropdown:SetPoint("TOPLEFT", parent, "TOPLEFT", -6, y)
247247+248248+ local labelText = dropdown:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
249249+ labelText:SetPoint("TOPLEFT", dropdown, "TOPLEFT", 16, 14)
250250+ labelText:SetText(label)
251251+252252+ UIDropDownMenu_SetWidth(dropdown, width or 220)
253253+ UIDropDownMenu_Initialize(dropdown, function(self)
254254+ for _, value in ipairs(order or {}) do
255255+ local info = UIDropDownMenu_CreateInfo()
256256+ info.text = list[value] or value
257257+ info.value = value
258258+ info.func = function()
259259+ UIDropDownMenu_SetSelectedValue(dropdown, value)
260260+ SetStringOption(key, value)
261261+ end
262262+ UIDropDownMenu_AddButton(info)
263263+ end
264264+ end)
265265+266266+ controls[key] = { type = "dropdown", widget = dropdown, list = list, label = labelText }
267267+ return y - 50
268268+end
269269+270270+local function CreateColorPicker(parent, key, label, y, fallback)
271271+ local labelText = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
272272+ labelText:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y)
273273+ labelText:SetText(label)
274274+275275+ local swatch = CreateFrame("Button", nil, parent)
276276+ swatch:SetSize(16, 16)
277277+ swatch:SetPoint("LEFT", labelText, "RIGHT", 8, 0)
278278+ swatch:SetNormalTexture("Interface\\Buttons\\WHITE8x8")
279279+ swatch:SetHighlightTexture("Interface\\Buttons\\WHITE8x8", "ADD")
280280+ local swatchTex = swatch:GetNormalTexture()
281281+282282+ local border = swatch:CreateTexture(nil, "BORDER")
283283+ border:SetPoint("TOPLEFT", swatch, "TOPLEFT", -1, 1)
284284+ border:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", 1, -1)
285285+ border:SetColorTexture(0, 0, 0, 1)
286286+ if swatchTex then
287287+ swatchTex:SetPoint("TOPLEFT", swatch, "TOPLEFT", 1, -1)
288288+ swatchTex:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", -1, 1)
289289+ end
290290+291291+ swatch:SetScript("OnClick", function()
292292+ local r, g, b, a = GetColorOption(key, fallback)
293293+ local previous = { r = r, g = g, b = b, a = a }
294294+295295+ local function getPickerAlpha()
296296+ if ColorPickerFrame.GetColorAlpha then
297297+ return ColorPickerFrame:GetColorAlpha()
298298+ end
299299+ return 1 - (ColorPickerFrame.opacity or 0)
300300+ end
301301+302302+ local function applyColor()
303303+ local nr, ng, nb = ColorPickerFrame:GetColorRGB()
304304+ local na = getPickerAlpha()
305305+ SetColorOption(key, nr, ng, nb, na)
306306+ F.Options.Refresh()
307307+ end
308308+309309+ local function cancelColor()
310310+ local pv = previous
311311+ if pv then
312312+ SetColorOption(key, pv.r, pv.g, pv.b, pv.a)
313313+ F.Options.Refresh()
314314+ end
315315+ end
316316+317317+ if ColorPickerFrame.SetupColorPickerAndShow then
318318+ ColorPickerFrame:SetupColorPickerAndShow({
319319+ r = r,
320320+ g = g,
321321+ b = b,
322322+ opacity = 1 - (a or 1),
323323+ hasOpacity = true,
324324+ swatchFunc = applyColor,
325325+ opacityFunc = applyColor,
326326+ cancelFunc = cancelColor,
327327+ })
328328+ else
329329+ ColorPickerFrame.hasOpacity = true
330330+ ColorPickerFrame.opacity = 1 - (a or 1)
331331+ ColorPickerFrame.previousValues = previous
332332+ ColorPickerFrame.func = applyColor
333333+ ColorPickerFrame.opacityFunc = applyColor
334334+ ColorPickerFrame.cancelFunc = cancelColor
335335+ ColorPickerFrame:SetColorRGB(r, g, b)
336336+ ColorPickerFrame:Hide()
337337+ ColorPickerFrame:Show()
338338+ end
339339+ end)
340340+341341+ controls[key] = { type = "color", widget = swatch, label = labelText, fallback = fallback }
342342+ return y - 24
343343+end
344344+345345+function F.Options.Refresh()
346346+ if F.InitDB then F.InitDB() end
347347+ if not F.DB then return end
348348+349349+ local defaults = F.Config or {}
350350+351351+ for key, control in pairs(controls) do
352352+ if control.type == "checkbox" then
353353+ control.widget:SetChecked(F.DB[key] and true or false)
354354+ elseif control.type == "slider" then
355355+ local value = F.DB[key]
356356+ if value == nil then value = defaults[key] end
357357+ if value == nil then value = 0 end
358358+ if key == TICK_OPACITY_KEY then
359359+ value = (tonumber(value) or 0) * 100
360360+ end
361361+ control.widget._updating = true
362362+ control.widget:SetValue(tonumber(value) or 0)
363363+ control.widget._updating = false
364364+ elseif control.type == "color" then
365365+ local r, g, b, a = GetColorOption(key, control.fallback)
366366+ local tex = control.widget:GetNormalTexture()
367367+ if tex then tex:SetColorTexture(r, g, b, a) end
368368+ elseif control.type == "offset" then
369369+ local value = F.DB[key]
370370+ if value == nil then value = defaults[key] end
371371+ if value == nil then value = 0 end
372372+ control.slider._updating = true
373373+ control.slider:SetValue(tonumber(value) or 0)
374374+ control.slider._updating = false
375375+ control.edit:SetNumber(tonumber(value) or 0)
376376+ elseif control.type == "dropdown" then
377377+ local defaults = F.Config or {}
378378+ local value = F.DB[key]
379379+ if value == nil then value = defaults[key] end
380380+ UIDropDownMenu_SetSelectedValue(control.widget, value)
381381+ UIDropDownMenu_SetText(control.widget, control.list and control.list[value] or value or "")
382382+ end
383383+ end
384384+385385+ local questEnabled = F.DB.questTrackingEnabled ~= false
386386+ for key, _ in pairs(questRelatedKeys) do
387387+ local control = controls[key]
388388+ if control then
389389+ if control.type == "checkbox" or control.type == "slider" then
390390+ control.widget:SetEnabled(questEnabled)
391391+ if control.widget.Text then
392392+ local color = questEnabled and NORMAL_FONT_COLOR or GRAY_FONT_COLOR
393393+ control.widget.Text:SetTextColor(color.r, color.g, color.b)
394394+ end
395395+ elseif control.type == "color" then
396396+ if questEnabled then
397397+ control.widget:Enable()
398398+ control.label:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
399399+ else
400400+ control.widget:Disable()
401401+ control.label:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
402402+ end
403403+ end
404404+ end
405405+ end
406406+end
407407+408408+function F.Options.Open()
409409+ if not panel then return end
410410+411411+ if Settings and Settings.OpenToCategory then
412412+ Settings.OpenToCategory(panel.name)
413413+ elseif InterfaceOptionsFrame_OpenToCategory then
414414+ InterfaceOptionsFrame_OpenToCategory(panel)
415415+ InterfaceOptionsFrame_OpenToCategory(panel)
416416+ end
417417+end
418418+419419+function F.Options.Create()
420420+ if panel then return end
421421+422422+ panel = CreateFrame("Frame", "NXP_OptionsPanel", UIParent)
423423+ panel.name = "NixxnuxXPBar"
424424+425425+ local scroll = CreateFrame("ScrollFrame", "NXP_OptionsScrollFrame", panel, "UIPanelScrollFrameTemplate")
426426+ scroll:SetPoint("TOPLEFT", panel, "TOPLEFT", 0, -8)
427427+ scroll:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -30, 8)
428428+429429+ content = CreateFrame("Frame", nil, scroll)
430430+ content:SetSize(1, 1)
431431+ scroll:SetScrollChild(content)
432432+433433+ local title = content:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
434434+ title:SetPoint("TOPLEFT", content, "TOPLEFT", 16, -8)
435435+ title:SetText("NixxnuxXPBar")
436436+437437+ local y = -40
438438+ y = CreateHeader(content, "General", y)
439439+ y = CreateCheckbox(content, "enabled", "Enable addon", y)
440440+ y = CreateCheckbox(content, "questTrackingEnabled", "Enable quest tracking", y)
441441+ y = CreateCheckbox(content, "trackedOnly", "Count tracked (pinned) quests only", y)
442442+ y = CreateCheckbox(content, "showIncompleteQuestBar", "Show uncompleted quest XP bar", y)
443443+ y = CreateCheckbox(content, "showTicks", "Show XP ticks", y)
444444+ y = y - 6
445445+ y = CreateSlider(content, "tickOpacity", "Tick opacity", y, 10, 100, 1)
446446+447447+ y = y - 10
448448+ y = CreateHeader(content, "Position", y)
449449+ y = y - 6
450450+ local anchorList = {
451451+ TOP = "Top",
452452+ BOTTOM = "Bottom",
453453+ LEFT = "Left",
454454+ RIGHT = "Right",
455455+ CENTER = "Center",
456456+ }
457457+ local anchorOrder = { "TOP", "BOTTOM", "LEFT", "RIGHT", "CENTER" }
458458+ y = CreateDropdown(content, "anchorPoint", "Anchor position", y, anchorList, anchorOrder)
459459+ y = CreateSliderWithInput(content, "offsetX", "X offset", y, -1000, 1000, 1, 60)
460460+ y = CreateSliderWithInput(content, "offsetY", "Y offset", y, -1000, 1000, 1, 60)
461461+462462+ y = y - 10
463463+ y = CreateHeader(content, "Bar Size", y)
464464+ y = y - 6
465465+ y = CreateSlider(content, "barWidth", "Bar width", y, 200, 900, 1)
466466+ y = CreateSlider(content, "barHeight", "Bar height", y, 8, 60, 1)
467467+468468+ y = y - 6
469469+ y = CreateHeader(content, "Bar Colors", y)
470470+ y = CreateColorPicker(content, "colorMain", "Normal XP", y, { 0.76, 0.38, 1, 1 })
471471+ y = CreateColorPicker(content, "colorRested", "Rested XP", y, { 0.34, 0.61, 0.99, 0.8 })
472472+ y = CreateColorPicker(content, "colorQuest", "Uncompleted Quest XP", y, { 1, 0.64, 0.0078, 0.25 })
473473+ y = CreateColorPicker(content, "colorQuestComplete", "Completed Quest XP", y, { 1, 0.64, 0.0078, 0.8 })
474474+475475+ y = y - 6
476476+ y = CreateHeader(content, "Textures", y)
477477+ y = y - 6
478478+ local statusbarList, statusbarOrder = BuildMediaList("statusbar", "Default (Solid)", DEFAULT_STATUSBAR)
479479+ y = CreateDropdown(content, "barTextureMain", "Normal XP texture", y, statusbarList, statusbarOrder)
480480+ y = CreateDropdown(content, "barTextureRested", "Rested XP texture", y, statusbarList, statusbarOrder)
481481+ y = CreateDropdown(content, "barTextureQuest", "Uncompleted quest XP texture", y, statusbarList, statusbarOrder)
482482+ y = CreateDropdown(content, "barTextureQuestComplete", "Completed quest XP texture", y, statusbarList, statusbarOrder)
483483+484484+ y = y - 6
485485+ y = CreateHeader(content, "Font", y)
486486+ y = y - 6
487487+ local fontList, fontOrder = BuildMediaList("font", "Default", DEFAULT_FONT)
488488+ y = CreateDropdown(content, "fontPath", "Font", y, fontList, fontOrder)
489489+490490+ y = y - 10
491491+ y = CreateHeader(content, "Text Labels", y)
492492+ y = CreateCheckbox(content, "showLevelText", "Level text", y)
493493+ y = CreateCheckbox(content, "showXPText", "XP text", y)
494494+ y = CreateCheckbox(content, "showPercentText", "Percent text", y)
495495+ y = CreateCheckbox(content, "showQuestRestedText", "Quest/rested percent text", y)
496496+ y = CreateCheckbox(content, "showLevelingText", "Leveling info text", y)
497497+ y = CreateCheckbox(content, "showIncompleteQuestXPText", "Uncompleted quest XP text", y)
498498+ y = CreateCheckbox(content, "showCompletedQuestXPText", "Completed quest XP text", y)
499499+ y = CreateCheckbox(content, "showRestedXPText", "Rested XP text", y)
500500+501501+ content:SetHeight(-y + 20)
502502+503503+ panel:SetScript("OnShow", function()
504504+ F.Options.Refresh()
505505+ end)
506506+507507+ if Settings and Settings.RegisterCanvasLayoutCategory then
508508+ local category = Settings.RegisterCanvasLayoutCategory(panel, panel.name)
509509+ Settings.RegisterAddOnCategory(category)
510510+ elseif InterfaceOptions_AddCategory then
511511+ InterfaceOptions_AddCategory(panel)
512512+ end
513513+end
514514+515515+F.Options.Create()
+7
State.lua
···6565end
66666767function F.State:UpdateQuestXP()
6868+ if F.DB and F.DB.questTrackingEnabled == false then
6969+ self.questXP = 0
7070+ self.completeXP = 0
7171+ self.incompleteXP = 0
7272+ return
7373+ end
7474+6875 local numEntries = C_QuestLog.GetNumQuestLogEntries()
6976 local qXP, cXP, iXP = 0, 0, 0
7077
+113-78
UI.lua
···3232function F.UI.Create()
3333 local f = CreateFrame("Frame", "NXP_Frame", UIParent)
3434 f:SetSize(CFG.width, CFG.height)
3535- f:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 260) -- Default Position
3535+ f:SetPoint("TOP", UIParent, "TOP", 0, 100) -- Default Position
3636 f:SetFrameStrata("LOW")
37373838- -- Enable dragging (only while Shift is held)
3939- f:SetMovable(true)
3838+ f:SetMovable(false)
4039 f:EnableMouse(false)
4141- f:RegisterForDrag("LeftButton")
4242-4343- local function stopAndMaybeSnap(self)
4444- if self.__nxb_moving then
4545- self:StopMovingOrSizing()
4646- self.__nxb_moving = nil
4747-4848- local fx, fy = self:GetCenter()
4949- if fx and fy then
5050- local cx, cy = UIParent:GetCenter()
5151- if cx and cy then
5252- local dx, dy = fx - cx, fy - cy
5353- local distSq = dx * dx + dy * dy
5454- local snapRadius = 40
5555- if distSq <= (snapRadius * snapRadius) then
5656- self:ClearAllPoints()
5757- self:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
5858- end
5959- end
6060- end
6161-6262- self:SetScript("OnUpdate", nil)
6363- end
6464- end
6565-6666- f:SetScript("OnDragStart", function(self)
6767- if IsShiftKeyDown() then
6868- self:StartMoving()
6969- self.__nxb_moving = true
7070-7171- self:SetScript("OnUpdate", function(self, elapsed)
7272- if not IsShiftKeyDown() or not IsMouseButtonDown("LeftButton") then
7373- stopAndMaybeSnap(self)
7474- end
7575- end)
7676- end
7777- end)
7878-7979- f:SetScript("OnDragStop", stopAndMaybeSnap)
8080-8181- f:SetScript("OnMouseDown", function(self, button)
8282- if button == "LeftButton" and IsShiftKeyDown() then
8383- self:StartMoving()
8484- self.__nxb_moving = true
8585-8686- self:SetScript("OnUpdate", function(self, elapsed)
8787- if not IsShiftKeyDown() or not IsMouseButtonDown("LeftButton") then
8888- stopAndMaybeSnap(self)
8989- end
9090- end)
9191- end
9292- end)
9393-9494- f:SetScript("OnMouseUp", function(self, button)
9595- if button == "LeftButton" then
9696- stopAndMaybeSnap(self)
9797- end
9898- end)
9999-100100- f:RegisterEvent("MODIFIER_STATE_CHANGED")
101101- f:SetScript("OnEvent", function(self, event, key, state)
102102- if event == "MODIFIER_STATE_CHANGED" then
103103- -- Enable/disable clicks based on Shift state
104104- self:EnableMouse(IsShiftKeyDown())
105105- if not IsShiftKeyDown() and self.__nxb_moving then
106106- stopAndMaybeSnap(self)
107107- end
108108- end
109109- end)
1104011141 F.Frame = f
11242···1275712858 -- Main XP Bar
12959 F.BarMain = F.UI.CreateStatusBar(f, "ARTWORK", 1, CFG.colorMain)
6060+6161+ -- XP Ticks (20 segments)
6262+ local tickContainer = CreateFrame("Frame", nil, f)
6363+ tickContainer:SetAllPoints()
6464+ tickContainer.ticks = {}
6565+ for i = 1, 19 do
6666+ local tick = tickContainer:CreateTexture(nil, "OVERLAY")
6767+ tick:SetColorTexture(0, 0, 0, 0.35)
6868+ tick:SetWidth(1)
6969+ tickContainer.ticks[i] = tick
7070+ end
7171+ F.TickContainer = tickContainer
1307213173 local overlay = CreateFrame("Frame", nil, f)
13274 overlay:SetAllPoints()
···216158 local s = F.State
217159 local db = F.DB
218160161161+ if db and db.barWidth and db.barHeight then
162162+ local width = db.barWidth
163163+ if width then
164164+ local scale = F.Frame:GetEffectiveScale() or 1
165165+ local stepPixels = math.floor(((width / 20) * scale) + 0.5)
166166+ width = (stepPixels / scale) * 20
167167+ end
168168+ F.Frame:SetSize(width or db.barWidth, db.barHeight)
169169+ end
170170+171171+ if db then
172172+ local anchor = db.anchorPoint or "TOP"
173173+ local x = tonumber(db.offsetX) or 0
174174+ local y = tonumber(db.offsetY) or 0
175175+ if anchor == "TOP" then
176176+ y = -y
177177+ end
178178+ F.Frame:ClearAllPoints()
179179+ F.Frame:SetPoint(anchor, UIParent, anchor, x, y)
180180+ end
181181+182182+ if F.TickContainer then
183183+ if db and db.showTicks then
184184+ local width = F.Frame:GetWidth()
185185+ local height = F.Frame:GetHeight()
186186+ if width and width > 0 then
187187+ local step = width / 20
188188+ local scale = F.Frame:GetEffectiveScale() or 1
189189+ local opacity = 0.35
190190+ if db and type(db.tickOpacity) == "number" then
191191+ opacity = db.tickOpacity
192192+ end
193193+ for i = 1, 19 do
194194+ local tick = F.TickContainer.ticks[i]
195195+ if tick then
196196+ local x = step * i
197197+ x = math.floor((x * scale) + 0.5) / scale
198198+ tick:ClearAllPoints()
199199+ tick:SetColorTexture(0, 0, 0, opacity)
200200+ tick:SetPoint("TOPLEFT", F.Frame, "TOPLEFT", x, 0)
201201+ tick:SetPoint("BOTTOMLEFT", F.Frame, "BOTTOMLEFT", x, 0)
202202+ tick:Show()
203203+ end
204204+ end
205205+ F.TickContainer:Show()
206206+ else
207207+ F.TickContainer:Hide()
208208+ end
209209+ else
210210+ F.TickContainer:Hide()
211211+ end
212212+ end
213213+214214+ local function resolveColor(color, fallback)
215215+ if type(color) == "table" then
216216+ if color.r then
217217+ return color.r or 1, color.g or 1, color.b or 1, color.a or 1
218218+ end
219219+ if color[1] then
220220+ return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1
221221+ end
222222+ end
223223+ return fallback[1], fallback[2], fallback[3], fallback[4]
224224+ end
225225+226226+ if db then
227227+ local fontPath = db.fontPath or CFG.font
228228+ F.LevelText:SetFont(fontPath, CFG.barFontSize, "OUTLINE")
229229+ F.TextCenter:SetFont(fontPath, CFG.barFontSize, "OUTLINE")
230230+ F.TextRight:SetFont(fontPath, CFG.barFontSize, "OUTLINE")
231231+ F.InfoText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE")
232232+ F.LevelingText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE")
233233+ if F.QuestXPText then
234234+ F.QuestXPText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE")
235235+ end
236236+237237+ local mainTexture = db.barTextureMain or CFG.texture
238238+ local restedTexture = db.barTextureRested or CFG.texture
239239+ local questTexture = db.barTextureQuest or CFG.texture
240240+ local questCompleteTexture = db.barTextureQuestComplete or CFG.texture
241241+ F.BarMain:SetStatusBarTexture(mainTexture)
242242+ F.BarRested:SetStatusBarTexture(restedTexture)
243243+ F.BarQuest:SetStatusBarTexture(questTexture)
244244+ F.BarQuestComplete:SetStatusBarTexture(questCompleteTexture)
245245+246246+ F.BarMain:SetStatusBarColor(resolveColor(db.colorMain, CFG.colorMain))
247247+ F.BarRested:SetStatusBarColor(resolveColor(db.colorRested, CFG.colorRested))
248248+ F.BarQuest:SetStatusBarColor(resolveColor(db.colorQuest, CFG.colorQuest))
249249+ F.BarQuestComplete:SetStatusBarColor(resolveColor(db.colorQuestComplete, CFG.colorQuestComplete))
250250+ end
251251+219252 if s.isMaxLevel and not db.showAtMaxLevel then
220253 F.Frame:Hide()
221254 return
···316349 local infoParts = {}
317350318351 if db.showQuestRestedText and s.maxXP > 0 then
319319- if (s.completeXP or 0) > 0 then
352352+ if db.questTrackingEnabled ~= false and (s.completeXP or 0) > 0 then
320353 table.insert(infoParts, string.format("Completed Quests: %.1f%%", ((s.completeXP or 0) / s.maxXP * 100)))
321354 end
322355 if (s.restedXP or 0) > 0 then
···350383351384 do
352385 local xpParts = {}
353353- if db.showIncompleteQuestXPText and (s.incompleteXP or 0) > 0 then
354354- table.insert(xpParts, string.format("Uncompleted Quest XP: %s", FormatLargeNumber(s.incompleteXP or 0)))
355355- end
356356- if db.showCompletedQuestXPText and (s.completeXP or 0) > 0 then
357357- table.insert(xpParts, string.format("Completed Quest XP: %s", FormatLargeNumber(s.completeXP or 0)))
386386+ if db.questTrackingEnabled ~= false then
387387+ if db.showIncompleteQuestXPText and (s.incompleteXP or 0) > 0 then
388388+ table.insert(xpParts, string.format("Uncompleted Quest XP: %s", FormatLargeNumber(s.incompleteXP or 0)))
389389+ end
390390+ if db.showCompletedQuestXPText and (s.completeXP or 0) > 0 then
391391+ table.insert(xpParts, string.format("Completed Quest XP: %s", FormatLargeNumber(s.completeXP or 0)))
392392+ end
358393 end
359394 if db.showRestedXPText and (s.restedXP or 0) > 0 then
360395 table.insert(xpParts, string.format("Rested XP: %s", FormatLargeNumber(s.restedXP or 0)))