local _, F = ... F.Options = F.Options or {} local panel local content local controls = {} local questRelatedKeys = { trackedOnly = true, showQuestRestedText = true, showIncompleteQuestXPText = true, showCompletedQuestXPText = true, showIncompleteQuestBar = true, colorQuest = true, colorQuestComplete = true, barTextureQuest = true, barTextureQuestComplete = true, } local TICK_OPACITY_KEY = "tickOpacity" local DEFAULT_STATUSBAR = "Interface\\Buttons\\WHITE8x8" local DEFAULT_FONT = "Fonts\\FRIZQT__.TTF" local function GetLibSharedMedia() if LibStub then return LibStub("LibSharedMedia-3.0", true) end return nil end local function SetOption(key, value) if F.InitDB then F.InitDB() end if not F.DB then return end F.DB[key] = value and true or false if key == "enabled" then if value then if F.UpdateUI then F.UpdateUI() end else if F.Frame and F.Frame.Hide then F.Frame:Hide() end end return end if key == "hideBlizzardXPBar" and F.UI and F.UI.HideBlizzardBars then F.UI.HideBlizzardBars() end if F.UpdateUI then F.UpdateUI() end if key == "questTrackingEnabled" or key == "trackedOnly" then if F.State and F.State.UpdateQuestXP then F.State:UpdateQuestXP() end if key == "questTrackingEnabled" and panel and panel:IsShown() then F.Options.Refresh() end end end local function SetNumericOption(key, value) if F.InitDB then F.InitDB() end if not F.DB then return end if key == "barWidth" then local width = tonumber(value) or 0 local scale = UIParent and UIParent:GetEffectiveScale() or 1 local stepPixels = math.floor(((width / 20) * scale) + 0.5) local snapped = (stepPixels / scale) * 20 if snapped < 200 then snapped = 200 end F.DB[key] = snapped elseif key == TICK_OPACITY_KEY then F.DB[key] = (tonumber(value) or 0) / 100 else F.DB[key] = value end if F.UpdateUI then F.UpdateUI() end end local function SetStringOption(key, value) if F.InitDB then F.InitDB() end if not F.DB then return end F.DB[key] = value if F.UpdateUI then F.UpdateUI() end end local function SetColorOption(key, r, g, b, a) if F.InitDB then F.InitDB() end if not F.DB then return end F.DB[key] = { r = r, g = g, b = b, a = a } if F.UpdateUI then F.UpdateUI() end end local function GetColorOption(key, fallback) if F.InitDB then F.InitDB() end if not F.DB then return fallback[1], fallback[2], fallback[3], fallback[4] end local color = F.DB[key] if type(color) == "table" then if color.r then return color.r or 1, color.g or 1, color.b or 1, color.a or 1 end if color[1] then return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 end end return fallback[1], fallback[2], fallback[3], fallback[4] end local function CreateHeader(parent, text, y) local fs = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal") fs:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) fs:SetText(text) return y - 20 end local function CreateCheckbox(parent, key, label, y) local cb = CreateFrame("CheckButton", nil, parent, "InterfaceOptionsCheckButtonTemplate") cb.Text:SetText(label) cb:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) cb:SetScript("OnClick", function(self) SetOption(key, self:GetChecked()) end) controls[key] = { type = "checkbox", widget = cb } return y - 28 end local function CreateSlider(parent, key, label, y, minValue, maxValue, step) local name = "NXP_Options_" .. key .. "Slider" local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate") slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) slider:SetMinMaxValues(minValue, maxValue) slider:SetValueStep(step) slider:SetObeyStepOnDrag(true) local labelText = _G[name .. "Text"] local lowText = _G[name .. "Low"] local highText = _G[name .. "High"] if labelText then labelText:SetText(label) end if lowText then lowText:SetText(tostring(minValue)) end if highText then highText:SetText(tostring(maxValue)) end slider:SetScript("OnValueChanged", function(self, value) if self._updating then return end local rounded = math.floor(value + 0.5) if step and step < 1 then rounded = value end self._updating = true self:SetValue(rounded) self._updating = false SetNumericOption(key, rounded) end) controls[key] = { type = "slider", widget = slider } return y - 52 end local function CreateSliderWithInput(parent, key, label, y, minValue, maxValue, step, inputWidth) local name = "NXP_Options_" .. key .. "Slider" local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate") slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) slider:SetMinMaxValues(minValue, maxValue) slider:SetValueStep(step) slider:SetObeyStepOnDrag(true) local labelText = _G[name .. "Text"] local lowText = _G[name .. "Low"] local highText = _G[name .. "High"] if labelText then labelText:SetText(label) end if lowText then lowText:SetText(tostring(minValue)) end if highText then highText:SetText(tostring(maxValue)) end local edit = CreateFrame("EditBox", nil, parent, "InputBoxTemplate") edit:SetAutoFocus(false) edit:SetSize(inputWidth or 60, 20) edit:SetPoint("LEFT", slider, "RIGHT", 12, 0) edit:SetNumeric(true) slider:SetScript("OnValueChanged", function(self, value) if self._updating then return end local rounded = math.floor(value + 0.5) if step and step < 1 then rounded = value end self._updating = true self:SetValue(rounded) self._updating = false edit:SetNumber(rounded) SetNumericOption(key, rounded) end) local function commitEdit() local value = tonumber(edit:GetText()) if not value then return end if value < minValue then value = minValue end if value > maxValue then value = maxValue end slider._updating = true slider:SetValue(value) slider._updating = false edit:SetNumber(value) SetNumericOption(key, value) end edit:SetScript("OnEnterPressed", function(self) commitEdit() self:ClearFocus() end) edit:SetScript("OnEditFocusLost", function() commitEdit() end) controls[key] = { type = "offset", slider = slider, edit = edit } return y - 52 end local function BuildMediaList(kind, defaultLabel, defaultValue) local list = {} local order = {} list[defaultValue] = defaultLabel table.insert(order, defaultValue) local LSM = GetLibSharedMedia() if LSM and LSM.HashTable then local entries = {} for name, path in pairs(LSM:HashTable(kind) or {}) do if type(path) == "string" and path ~= "" then table.insert(entries, { name = tostring(name), path = path }) end end table.sort(entries, function(a, b) return a.name < b.name end) for _, entry in ipairs(entries) do list[entry.path] = entry.name table.insert(order, entry.path) end end return list, order end local function CreateDropdown(parent, key, label, y, list, order, width) local name = "NXP_Options_" .. key .. "Dropdown" local dropdown = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate") dropdown:SetPoint("TOPLEFT", parent, "TOPLEFT", -6, y) local labelText = dropdown:CreateFontString(nil, "ARTWORK", "GameFontHighlight") labelText:SetPoint("TOPLEFT", dropdown, "TOPLEFT", 16, 14) labelText:SetText(label) UIDropDownMenu_SetWidth(dropdown, width or 220) UIDropDownMenu_Initialize(dropdown, function(self) for _, value in ipairs(order or {}) do local info = UIDropDownMenu_CreateInfo() info.text = list[value] or value info.value = value info.func = function() UIDropDownMenu_SetSelectedValue(dropdown, value) SetStringOption(key, value) end UIDropDownMenu_AddButton(info) end end) controls[key] = { type = "dropdown", widget = dropdown, list = list, label = labelText } return y - 50 end local function CreateColorPicker(parent, key, label, y, fallback) local labelText = parent:CreateFontString(nil, "ARTWORK", "GameFontHighlight") labelText:SetPoint("TOPLEFT", parent, "TOPLEFT", 16, y) labelText:SetText(label) local swatch = CreateFrame("Button", nil, parent) swatch:SetSize(16, 16) swatch:SetPoint("LEFT", labelText, "RIGHT", 8, 0) swatch:SetNormalTexture("Interface\\Buttons\\WHITE8x8") swatch:SetHighlightTexture("Interface\\Buttons\\WHITE8x8", "ADD") local swatchTex = swatch:GetNormalTexture() local border = swatch:CreateTexture(nil, "BORDER") border:SetPoint("TOPLEFT", swatch, "TOPLEFT", -1, 1) border:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", 1, -1) border:SetColorTexture(0, 0, 0, 1) if swatchTex then swatchTex:SetPoint("TOPLEFT", swatch, "TOPLEFT", 1, -1) swatchTex:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", -1, 1) end swatch:SetScript("OnClick", function() local r, g, b, a = GetColorOption(key, fallback) local previous = { r = r, g = g, b = b, a = a } local function getPickerAlpha() if ColorPickerFrame.GetColorAlpha then return ColorPickerFrame:GetColorAlpha() end return 1 - (ColorPickerFrame.opacity or 0) end local function applyColor() local nr, ng, nb = ColorPickerFrame:GetColorRGB() local na = getPickerAlpha() SetColorOption(key, nr, ng, nb, na) F.Options.Refresh() end local function cancelColor() local pv = previous if pv then SetColorOption(key, pv.r, pv.g, pv.b, pv.a) F.Options.Refresh() end end if ColorPickerFrame.SetupColorPickerAndShow then ColorPickerFrame:SetupColorPickerAndShow({ r = r, g = g, b = b, opacity = 1 - (a or 1), hasOpacity = true, swatchFunc = applyColor, opacityFunc = applyColor, cancelFunc = cancelColor, }) else ColorPickerFrame.hasOpacity = true ColorPickerFrame.opacity = 1 - (a or 1) ColorPickerFrame.previousValues = previous ColorPickerFrame.func = applyColor ColorPickerFrame.opacityFunc = applyColor ColorPickerFrame.cancelFunc = cancelColor ColorPickerFrame:SetColorRGB(r, g, b) ColorPickerFrame:Hide() ColorPickerFrame:Show() end end) controls[key] = { type = "color", widget = swatch, label = labelText, fallback = fallback } return y - 24 end function F.Options.Refresh() if F.InitDB then F.InitDB() end if not F.DB then return end local defaults = F.Config or {} for key, control in pairs(controls) do if control.type == "checkbox" then control.widget:SetChecked(F.DB[key] and true or false) elseif control.type == "slider" then local value = F.DB[key] if value == nil then value = defaults[key] end if value == nil then value = 0 end if key == TICK_OPACITY_KEY then value = (tonumber(value) or 0) * 100 end control.widget._updating = true control.widget:SetValue(tonumber(value) or 0) control.widget._updating = false elseif control.type == "color" then local r, g, b, a = GetColorOption(key, control.fallback) local tex = control.widget:GetNormalTexture() if tex then tex:SetColorTexture(r, g, b, a) end elseif control.type == "offset" then local value = F.DB[key] if value == nil then value = defaults[key] end if value == nil then value = 0 end control.slider._updating = true control.slider:SetValue(tonumber(value) or 0) control.slider._updating = false control.edit:SetNumber(tonumber(value) or 0) elseif control.type == "dropdown" then local defaults = F.Config or {} local value = F.DB[key] if value == nil then value = defaults[key] end UIDropDownMenu_SetSelectedValue(control.widget, value) UIDropDownMenu_SetText(control.widget, control.list and control.list[value] or value or "") end end local questEnabled = F.DB.questTrackingEnabled ~= false for key, _ in pairs(questRelatedKeys) do local control = controls[key] if control then if control.type == "checkbox" or control.type == "slider" then control.widget:SetEnabled(questEnabled) if control.widget.Text then local color = questEnabled and NORMAL_FONT_COLOR or GRAY_FONT_COLOR control.widget.Text:SetTextColor(color.r, color.g, color.b) end elseif control.type == "color" then if questEnabled then control.widget:Enable() control.label:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) else control.widget:Disable() control.label:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b) end end end end end function F.Options.Open() if not panel then return end if Settings and Settings.OpenToCategory then Settings.OpenToCategory(panel.name) elseif InterfaceOptionsFrame_OpenToCategory then InterfaceOptionsFrame_OpenToCategory(panel) InterfaceOptionsFrame_OpenToCategory(panel) end end function F.Options.Create() if panel then return end panel = CreateFrame("Frame", "NXP_OptionsPanel", UIParent) panel.name = "NixxnuxXPBar" local scroll = CreateFrame("ScrollFrame", "NXP_OptionsScrollFrame", panel, "UIPanelScrollFrameTemplate") scroll:SetPoint("TOPLEFT", panel, "TOPLEFT", 0, -8) scroll:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -30, 8) content = CreateFrame("Frame", nil, scroll) content:SetSize(1, 1) scroll:SetScrollChild(content) local title = content:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") title:SetPoint("TOPLEFT", content, "TOPLEFT", 16, -8) title:SetText("NixxnuxXPBar") local y = -40 y = CreateHeader(content, "General", y) y = CreateCheckbox(content, "enabled", "Enable addon", y) y = CreateCheckbox(content, "questTrackingEnabled", "Enable quest tracking", y) y = CreateCheckbox(content, "trackedOnly", "Count tracked (pinned) quests only", y) y = CreateCheckbox(content, "showIncompleteQuestBar", "Show uncompleted quest XP bar", y) y = CreateCheckbox(content, "showTicks", "Show XP ticks", y) y = y - 6 y = CreateSlider(content, "tickOpacity", "Tick opacity", y, 10, 100, 1) y = y - 10 y = CreateHeader(content, "Position", y) y = y - 6 local anchorList = { TOP = "Top", BOTTOM = "Bottom", LEFT = "Left", RIGHT = "Right", CENTER = "Center", } local anchorOrder = { "TOP", "BOTTOM", "LEFT", "RIGHT", "CENTER" } y = CreateDropdown(content, "anchorPoint", "Anchor position", y, anchorList, anchorOrder) y = CreateSliderWithInput(content, "offsetX", "X offset", y, -1000, 1000, 1, 60) y = CreateSliderWithInput(content, "offsetY", "Y offset", y, -1000, 1000, 1, 60) y = y - 10 y = CreateHeader(content, "Bar Size", y) y = y - 6 y = CreateSlider(content, "barWidth", "Bar width", y, 200, 900, 1) y = CreateSlider(content, "barHeight", "Bar height", y, 8, 60, 1) y = y - 6 y = CreateHeader(content, "Bar Colors", y) y = CreateColorPicker(content, "colorMain", "Normal XP", y, { 0.76, 0.38, 1, 1 }) y = CreateColorPicker(content, "colorRested", "Rested XP", y, { 0.34, 0.61, 0.99, 0.8 }) y = CreateColorPicker(content, "colorQuest", "Uncompleted Quest XP", y, { 1, 0.64, 0.0078, 0.25 }) y = CreateColorPicker(content, "colorQuestComplete", "Completed Quest XP", y, { 1, 0.64, 0.0078, 0.8 }) y = y - 6 y = CreateHeader(content, "Textures", y) y = y - 6 local statusbarList, statusbarOrder = BuildMediaList("statusbar", "Default (Solid)", DEFAULT_STATUSBAR) y = CreateDropdown(content, "barTextureMain", "Normal XP texture", y, statusbarList, statusbarOrder) y = CreateDropdown(content, "barTextureRested", "Rested XP texture", y, statusbarList, statusbarOrder) y = CreateDropdown(content, "barTextureQuest", "Uncompleted quest XP texture", y, statusbarList, statusbarOrder) y = CreateDropdown(content, "barTextureQuestComplete", "Completed quest XP texture", y, statusbarList, statusbarOrder) y = y - 6 y = CreateHeader(content, "Font", y) y = y - 6 local fontList, fontOrder = BuildMediaList("font", "Default", DEFAULT_FONT) y = CreateDropdown(content, "fontPath", "Font", y, fontList, fontOrder) y = y - 10 y = CreateHeader(content, "Text Labels", y) y = CreateCheckbox(content, "showLevelText", "Level text", y) y = CreateCheckbox(content, "showXPText", "XP text", y) y = CreateCheckbox(content, "showPercentText", "Percent text", y) y = CreateCheckbox(content, "showQuestRestedText", "Quest/rested percent text", y) y = CreateCheckbox(content, "showLevelingText", "Leveling info text", y) y = CreateCheckbox(content, "showIncompleteQuestXPText", "Uncompleted quest XP text", y) y = CreateCheckbox(content, "showCompletedQuestXPText", "Completed quest XP text", y) y = CreateCheckbox(content, "showRestedXPText", "Rested XP text", y) content:SetHeight(-y + 20) panel:SetScript("OnShow", function() F.Options.Refresh() end) if Settings and Settings.RegisterCanvasLayoutCategory then local category = Settings.RegisterCanvasLayoutCategory(panel, panel.name) Settings.RegisterAddOnCategory(category) elseif InterfaceOptions_AddCategory then InterfaceOptions_AddCategory(panel) end end F.Options.Create()