--[[ Name: LibSimpleOptions-1.0 Revision: $Rev: 46 $ Author(s): ckknight (ckknight@gmail.com) Website: http://ckknight.wowinterface.com/ Description: A library to provide a way to easily create controls for Blizzard's options system License: MIT ]] local MAJOR_VERSION = "LibSimpleOptions-1.0" local MINOR_VERSION = 90000 + tonumber(("$Revision: 47 $"):match("(%d+)")) if not LibStub then error(MAJOR_VERSION .. " requires LibStub") end -- #AUTODOC_NAMESPACE LibSimpleOptions local LibSimpleOptions, oldLib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) if not LibSimpleOptions then return end if oldLib then oldLib = {} for k, v in pairs(LibSimpleOptions) do LibSimpleOptions[k] = nil oldLib[k] = v end end local getArgs, doneArgs do local tmp = {} function getArgs(...) assert(next(tmp) == nil) for i = 1, select('#', ...), 2 do local k, v = select(i, ...) if type(k) ~= "string" then error(("Received a bad key, must be a %q, got %q (%s)"):format("string", type(k), tostring(k)), 3) elseif tmp[k] ~= nil then error(("Received key %q twice"):format(k), 3) end tmp[k] = v end return tmp end function doneArgs(args) assert(args == tmp) for k in pairs(args) do args[k] = nil end return nil end end local WotLK = not not ToggleAchievementFrame local panels if oldLib then panels = oldLib.panels or {} else panels = {} end LibSimpleOptions.panels = panels local panelMeta if oldLib then panelMeta = oldLib.panelMeta or {} else panelMeta = {} end LibSimpleOptions.panelMeta = panelMeta for funcName in pairs(panelMeta) do for panel in pairs(panels) do panel[funcName] = nil end panelMeta[funcName] = nil end do local function update(control, ...) if (...) ~= control.value then control:SetValue(...) end end --- Refresh a panel's controls -- This updates any controls that provide a getFunc -- When a panel is shown, this is automatically called -- @name panel:Refresh -- @usage panel:Refresh() function panelMeta:Refresh() for control in pairs(self.controls) do if control.getFunc then update(control, control.getFunc()) end end if self.refreshFunc then self:refreshFunc() end end local function panel_refresh(self) self:Refresh() end local function panel_OnShow(self) self:SetScript("OnShow", self.Refresh) self:controlCreationFunc() self.controlCreationFunc = nil self:Refresh() end local function panel_okay(self) for control in pairs(self.controls) do control.oldValue = control.value if control.okayFunc then control.okayFunc() end end end local function panel_cancel(self) for control in pairs(self.controls) do control:SetValue(control.oldValue) if control.cancelFunc then control.cancelFunc() end end end local function panel_default(self) for control in pairs(self.controls) do control:SetValue(control.default) if control.defaultFunc then control.defaultFunc() end end end local function makePanel(name, parentName, controlCreationFunc) local panel if not parentName then panel = CreateFrame("Frame", name .. "_Panel") else panel = CreateFrame("Frame", parentName .. "_Panel_" .. name) end panels[panel] = true panel.name = name panel.controls = {} panel.parent = parentName panel.okay = panel_okay panel.cancel = panel_cancel panel.default = panel_default panel.refresh = panel_refresh InterfaceOptions_AddCategory(panel) panel.controlCreationFunc = controlCreationFunc panel:SetScript("OnShow", panel_OnShow) for k, v in pairs(panelMeta) do panel[k] = v end return panel end --- Make a new options panel and add it to the Blizzard Interface Options -- @param name name of your panel -- @param controlCreationFunc function to call when the panel is first shown -- @usage LibStub("LibSimpleOptions-1.0").AddOptionsPanel("My Options", function(panel) ... end) -- @return the created panel function LibSimpleOptions.AddOptionsPanel(name, controlCreationFunc) return makePanel(name, nil, controlCreationFunc) end --- Make a new options panel that is a child of another options panel and add it to the Blizzard Interface Options -- @param parentName name of the parent panel -- @param name name of your panel -- @param controlCreationFunc function to call when the panel is first shown -- @usage LibStub("LibSimpleOptions-1.0").AddOptionsPanel("My Options", "My Suboptions", function(panel) ... end) -- @return the created panel function LibSimpleOptions.AddSuboptionsPanel(parentName, name, controlCreationFunc) return makePanel(name, parentName, controlCreationFunc) end end --- Return a new title text and sub-text for a panel. -- Note that this automatically places the title and sub-text appropriately -- @name panel:MakeTitleTextAndSubText -- @param titleText the text to show as the title -- @param subTextText the text to show as the sub-text -- @usage local title, subText = panel:MakeTitleTextAndSubText("My Options", "These allow you to change assorted options") -- @return the title FontString -- @return the sub-text FontString function panelMeta:MakeTitleTextAndSubText(titleText, subTextText) local title = self:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") title:SetText(titleText) title:SetJustifyH("LEFT") title:SetJustifyV("TOP") title:SetPoint("TOPLEFT", 16, -16) local subText = self:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") subText:SetText(subTextText) subText:SetNonSpaceWrap(true) subText:SetJustifyH("LEFT") subText:SetJustifyV("TOP") subText:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8) subText:SetPoint("RIGHT", -32, 0) return title, subText end do local backdrop = { bgFile = [=[Interface\Buttons\WHITE8X8]=], edgeFile = [=[Interface\Tooltips\UI-Tooltip-Border]=], tile = true, tileSize = 16, edgeSize = 16, insets = { left = 3, right = 3, top = 3, bottom = 3 }, } --- Return a scrollable frame to organize controls within -- This is useful to create if you have too many controls to properly fit within one panel -- @name panel:MakeScrollFrame -- @usage local scrollFrame = panel:MakeScrollFrame() -- @return the ScrollFrame function panelMeta:MakeScrollFrame() local name local i = 0 repeat i = i + 1 name = self:GetName() .. "_ScrollFrame" .. i until not _G[name] local scrollFrame = CreateFrame("ScrollFrame", name, self, "UIPanelScrollFrameTemplate") scrollFrame:SetFrameLevel(scrollFrame:GetFrameLevel() + 1) local bg = CreateFrame("Frame", nil, scrollFrame) bg:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", -3, 3) bg:SetPoint("BOTTOMRIGHT", scrollFrame, "BOTTOMRIGHT", 3, -3) bg:SetBackdrop(backdrop) bg:SetBackdropColor(0, 0, 0, 0.25) local scrollChild = CreateFrame("Frame", name .. "_Child", scrollFrame) scrollFrame:SetScrollChild(scrollChild) scrollChild:SetWidth(1) scrollChild:SetHeight(1) return scrollFrame, scrollChild end end do local function slider_OnValueChanged(self) self.value = self:GetValue() self:SetValue(self:GetValue()) end local function slider_SetValue(self, value) getmetatable(self).__index.SetValue(self, value) self.value = value self.changeFunc(value) if self.currentText then self.currentText:SetText(self.currentTextFunc(value)) end end --- Return a horizontal slider -- This is primarily for manipulating numbers within a range -- @name panel:MakeSlider -- @param ... tuple of key-value pairs<br/> -- name: What the slider displays above it<br/> -- description: What the tooltip displays when hovering over<br/> -- minText: What the slider shows on the left side<br/> -- maxText: What the slider shows on the right side<br/> -- minValue: The minimum value of the slider<br/> -- maxValue: The maximum value of the slider<br/> -- [optional] step: The amount that the slider steps between movements<br/> -- default: The default value<br/> -- current: The current value - can provide either this or getFunc<br/> -- getFunc: Function to get the current value<br/> -- setFunc: What is called when the value changes<br/> -- [optional] currentTextFunc: What is called to get text value at the bottom<br/> -- [optional] okayFunc: Called when the okay button is pressed<br/> -- [optional] cancelFunc: Called when the cancel button is pressed<br/> -- [optional] defaultFunc: Called when the default button is pressed -- @usage panel:MakeSlider( -- 'name', 'Range', -- 'description', 'Specify your tooltip description', -- 'minText', '0%', -- 'maxText', '100%', -- 'minValue', 0, -- 'maxValue', 1, -- 'step', 0.05, -- 'default', 0.5, -- 'current', db.currentRange, -- 'setFunc', function(value) db.currentRange = value end, -- 'currentTextFunc', function(value) return ("%.0f%%"):format(value * 100) end -- ) -- @return the Slider function panelMeta:MakeSlider(...) local args = getArgs(...) if type(args.name) ~= "string" then error(("name must be %q, got %q (%s)"):format("string", type(args.name), tostring(args.name)), 2) elseif type(args.description) ~= "string" then error(("description must be %q, got %q (%s)"):format("string", type(args.description), tostring(args.description)), 2) elseif type(args.minText) ~= "string" then error(("minText must be %q, got %q (%s)"):format("string", type(args.minText), tostring(args.minText)), 2) elseif type(args.maxText) ~= "string" then error(("maxText must be %q, got %q (%s)"):format("string", type(args.maxText), tostring(args.maxText)), 2) elseif type(args.minValue) ~= "number" then error(("minValue must be %q, got %q (%s)"):format("number", type(args.minValue), tostring(args.minValue)), 2) elseif type(args.maxValue) ~= "number" then error(("maxValue must be %q, got %q (%s)"):format("number", type(args.maxValue), tostring(args.maxValue)), 2) elseif args.step and type(args.step) ~= "number" then error(("step must be %q or %q, got %q (%s)"):format("nil", "number", type(args.step), tostring(args.step)), 2) elseif type(args.default) ~= "number" then error(("default must be %q, got %q (%s)"):format("number", type(args.default), tostring(args.default)), 2) elseif args.default < args.minValue or args.default > args.maxValue then error(("default must be [%s, %s], got %s"):format(args.minValue, args.maxValue, tostring(args.default)), 2) elseif not args.current == not args.getFunc then error(("either current or getFunc must be supplied, but not both"), 2) elseif args.current and type(args.current) ~= "number" then error(("current must be %q, got %q (%s)"):format("number", type(args.current), tostring(args.current)), 2) elseif args.getFunc and type(args.getFunc) ~= "function" then error(("getFunc must be %q, got %q (%s)"):format("function", type(args.getFunc), tostring(args.getFunc)), 2) elseif type(args.setFunc) ~= "function" then error(("setFunc must be %q, got %q (%s)"):format("function", type(args.setFunc), tostring(args.setFunc)), 2) elseif args.currentTextFunc and type(args.currentTextFunc) ~= "function" then error(("currentTextFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.currentTextFunc), tostring(args.currentTextFunc)), 2) elseif args.okayFunc and type(args.okayFunc) ~= "function" then error(("okayFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.okayFunc), tostring(args.okayFunc)), 2) elseif args.cancelFunc and type(args.cancelFunc) ~= "function" then error(("cancelFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.cancelFunc), tostring(args.cancelFunc)), 2) elseif args.defaultFunc and type(args.defaultFunc) ~= "function" then error(("defaultFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.defaultFunc), tostring(args.defaultFunc)), 2) end local name local i = 0 repeat i = i + 1 name = self:GetName() .. "_Slider" .. i until not _G[name] local slider = CreateFrame("Slider", name, self, "OptionsSliderTemplate") self.controls[slider] = true _G[slider:GetName() .. "Text"]:SetText(args.name) slider.tooltipText = args.description _G[slider:GetName() .. "Text"]:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) _G[slider:GetName() .. "Low"]:SetText(args.minText) _G[slider:GetName() .. "Low"]:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) _G[slider:GetName() .. "High"]:SetText(args.maxText) _G[slider:GetName() .. "High"]:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) local current if args.getFunc then slider.getFunc = args.getFunc current = args.getFunc() else current = args.current end if args.currentTextFunc then slider.currentTextFunc = args.currentTextFunc local currentText = slider:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") slider.currentText = currentText currentText:SetPoint("TOP", slider, "CENTER", 0, -8) currentText:SetText(args.currentTextFunc(current)) end slider.default = args.default slider:SetMinMaxValues(args.minValue, args.maxValue) if args.step then slider:SetValueStep(args.step) end slider.oldValue = current slider.value = current slider:SetValue(current) slider.changeFunc = args.setFunc slider.SetValue = slider_SetValue slider:SetScript("OnValueChanged", slider_OnValueChanged) slider.okayFunc = args.okayFunc slider.cancelFunc = args.cancelFunc slider.defaultFunc = args.defaultFunc args = doneArgs(args) return slider end end local function generic_OnEnter(self) GameTooltip:SetOwner(self, "ANCHOR_TOPRIGHT") GameTooltip:SetText(self.tooltipText, nil, nil, nil, nil, 1) end local function generic_OnLeave(self) GameTooltip:Hide() end do local function dropDown_SetValue(self, value) self.value = value UIDropDownMenu_SetSelectedValue(self, value) self.changeFunc(value) end local helper__num, helper__values local function helper() local value, text = helper__values[helper__num], helper__values[helper__num+1] if value == nil then helper__num, helper__values = nil, nil return nil end helper__num = helper__num + 2 return value, text end local function get_iter(values) if type(values) == "function" then return values end helper__num = 1 helper__values = values return helper end local SetValue_wrapper if WotLK then function SetValue_wrapper(self, ...) return dropDown_SetValue(...) end else SetValue_wrapper = dropDown_SetValue end local function dropDown_menu(self) for value, text in get_iter(self.values) do local info = UIDropDownMenu_CreateInfo() info.text = text info.value = value info.checked = self.value == value info.func = SetValue_wrapper info.arg1 = self info.arg2 = value UIDropDownMenu_AddButton(info) end end local tmp = {} --- Return a single-choice dropdown menu -- This is for choosing a single choice among many -- @name panel:MakeDropDown -- @param ... tuple of key-value pairs<br/> -- name: What shows above the dropdown<br/> -- description: What shows when hovering over the dropdown<br/> -- values: A list of options, in order, where the odd keys are the key and even are its corresponding value<br/> -- default: The default key<br/> -- current: The current key - you can either provide this or getFunc<br/> -- getFunc: Function to return the current key<br/> -- setFunc: What is called when the key changes<br/> -- [optional] okayFunc: Called when the okay button is pressed<br/> -- [optional] cancelFunc: Called when the cancel button is pressed<br/> -- [optional] defaultFunc: Called when the default button is pressed -- @usage panel:MakeDropDown( -- 'name', 'Choose', -- 'description', 'Specify your tooltip description', -- 'values', { -- 'ONE', "One", -- 'TWO', "Two", -- 'THREE', "Three", -- }, -- 'default', 'ONE', -- 'current', db.choice, -- 'setFunc', function(value) db.choice = value end, -- ) -- @return the DropDown frame function panelMeta:MakeDropDown(...) local args = getArgs(...) if type(args.name) ~= "string" then error(("name must be %q, got %q (%s)"):format("string", type(args.name), tostring(args.name)), 2) elseif type(args.description) ~= "string" then error(("description must be %q, got %q (%s)"):format("string", type(args.description), tostring(args.description)), 2) elseif type(args.values) ~= "function" then if type(args.values) ~= "table" then error(("values must be %q, got %q (%s)"):format("table", type(args.values), tostring(args.values)), 2) elseif #args.values%2 ~= 0 then error(("values must have an even number of items, got %d"):format(#args.values), 2) end for i = 1, #args.values, 2 do local k, v = args.values[i], args.values[2] if type(k) ~= "string" and type(k) ~= "number" then error(("values' keys must be %q or %q, got %q (%s)"):format("string", "number", type(k), tostring(k))) elseif type(v) ~= "string" then error(("values' values must be %q, got %q (%s)"):format("string", type(v), tostring(v))) end tmp[k] = v end end if type(args.default) ~= "number" and type(args.default) ~= "string" then error(("default must be %q or %q, got %q (%s)"):format("number", "string", type(args.default), tostring(args.default)), 2) elseif type(args.values) ~= "function" and not tmp[args.default] then error(("default must be in values, %s is not"):format(tostring(args.default)), 2) elseif not args.current == not args.getFunc then error(("either current or getFunc must be supplied, but not both"), 2) elseif args.current and type(args.current) ~= "string" and type(args.current) ~= "number" then error(("current must be %q or %q, got %q (%s)"):format("string", "number", type(args.current), tostring(args.current)), 2) elseif type(args.values) ~= "function" and args.current and not tmp[args.current] then error(("current must be in values, %s is not"):format(tostring(args.current)), 2) elseif args.getFunc and type(args.getFunc) ~= "function" then error(("getFunc must be %q, got %q (%s)"):format("function", type(args.getFunc), tostring(args.getFunc)), 2) elseif type(args.setFunc) ~= "function" then error(("setFunc must be %q, got %q (%s)"):format("function", type(args.setFunc), tostring(args.setFunc)), 2) elseif args.okayFunc and type(args.okayFunc) ~= "function" then error(("okayFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.okayFunc), tostring(args.okayFunc)), 2) elseif args.cancelFunc and type(args.cancelFunc) ~= "function" then error(("cancelFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.cancelFunc), tostring(args.cancelFunc)), 2) elseif args.defaultFunc and type(args.defaultFunc) ~= "function" then error(("defaultFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.defaultFunc), tostring(args.defaultFunc)), 2) end for k in pairs(tmp) do tmp[k] = nil end local name local i = 0 repeat i = i + 1 name = self:GetName() .. "_DropDown" .. i until not _G[name] local dropDown = CreateFrame("Frame", name, self, "UIDropDownMenuTemplate") self.controls[dropDown] = true if args.name ~= "" then local label = dropDown:CreateFontString(nil, "BACKGROUND", "GameFontNormal") label:SetText(args.name) label:SetPoint("BOTTOMLEFT", dropDown, "TOPLEFT", 16, 3) end dropDown.tooltipText = args.description dropDown.values = args.values UIDropDownMenu_Initialize(dropDown, function() dropDown_menu(dropDown) end) if WotLK then UIDropDownMenu_SetWidth(dropDown, 90) else UIDropDownMenu_SetWidth(90, dropDown) end local current if args.getFunc then dropDown.getFunc = args.getFunc current = args.getFunc() else current = args.current end UIDropDownMenu_SetSelectedValue(dropDown, current) dropDown.default = args.default dropDown.value = args.current dropDown.oldValue = args.current dropDown.changeFunc = args.setFunc dropDown.SetValue = dropDown_SetValue dropDown:EnableMouse(true) dropDown:SetScript("OnEnter", generic_OnEnter) dropDown:SetScript("OnLeave", generic_OnLeave) dropDown.okayFunc = args.okayFunc dropDown.cancelFunc = args.cancelFunc dropDown.defaultFunc = args.defaultFunc args = doneArgs(args) return dropDown end end do local function donothing() end local function button_OnClick(self) self.clickFunc() end --- Return a button -- @name panel:MakeButton -- @param ... tuple of key-value pairs<br/> -- name: What shows above the dropdown<br/> -- description: What shows when hovering over the dropdown<br/> -- func: What is called when the button is pressed -- @usage panel:MakeButton( -- 'name', 'Click', -- 'description', 'Specify your tooltip description', -- 'func', function() DEFAULT_CHAT_FRAME:AddMessage("Clicked!") end -- ) -- @return the Button function panelMeta:MakeButton(...) local args = getArgs(...) if type(args.name) ~= "string" then error(("name must be %q, got %q (%s)"):format("string", type(args.name), tostring(args.name)), 2) elseif type(args.description) ~= "string" then error(("description must be %q, got %q (%s)"):format("string", type(args.description), tostring(args.description)), 2) elseif type(args.func) ~= "function" then error(("description must be %q, got %q (%s)"):format("function", type(args.func), tostring(args.func)), 2) end local name local i = 0 repeat i = i + 1 name = self:GetName() .. "_Button" .. i until not _G[name] local button = CreateFrame("Button", name, self, "UIPanelButtonTemplate") self.controls[button] = true button:SetText(args.name) button.tooltipText = args.description button:SetWidth(120) button:SetHeight(22) button.SetValue = donothing button.clickFunc = args.func button:SetScript("OnClick", button_OnClick) button:SetScript("OnEnter", generic_OnEnter) button:SetScript("OnLeave", generic_OnLeave) args = doneArgs(args) return button end end do local function toggle_SetValue(self, value) value = not not value self.changeFunc(value) self.value = value self:SetChecked(value) end local function toggle_OnClick(self) self:SetValue(not not self:GetChecked()) end --- Return a checkbox -- @name panel:MakeToggle -- @param ... tuple of key-value pairs<br/> -- name: What appears to the right of the checkbox<br/> -- description: What the tooltip shows when hovering over<br/> -- default: The default value<br/> -- current: The current value - you can provide this or getFunc<br/> -- getFunc: Function to return the current value<br/> -- setFunc: What is called when the value changes<br/> -- [optional] okayFunc: Called when the okay button is pressed<br/> -- [optional] cancelFunc: Called when the cancel button is pressed<br/> -- [optional] defaultFunc: Called when the default button is pressed -- @usage panel:MakeToggle( -- 'name', 'Toggle', -- 'description', 'Specify your tooltip description', -- 'default', false, -- 'getFunc', function() return db.myToggle end -- 'setFunc', function(value) db.myToggle = value end -- ) -- @return the CheckButton function panelMeta:MakeToggle(...) local args = getArgs(...) if type(args.name) ~= "string" then error(("name must be %q, got %q (%s)"):format("string", type(args.name), tostring(args.name)), 2) elseif type(args.description) ~= "string" then error(("description must be %q, got %q (%s)"):format("string", type(args.description), tostring(args.description)), 2) elseif type(args.default) ~= "boolean" then error(("default must be %q, got %q (%s)"):format("boolean", type(args.default), tostring(args.default)), 2) elseif (args.current == nil) == not args.getFunc then error(("either current or getFunc must be supplied, but not both"), 2) elseif args.current and type(args.current) ~= "boolean" then error(("current must be %q, got %q (%s)"):format("boolean", type(args.current), tostring(args.current)), 2) elseif args.getFunc and type(args.getFunc) ~= "function" then error(("getFunc must be %q, got %q (%s)"):format("function", type(args.getFunc), tostring(args.getFunc)), 2) elseif type(args.setFunc) ~= "function" then error(("setFunc must be %q, got %q (%s)"):format("function", type(args.setFunc), tostring(args.setFunc)), 2) elseif args.okayFunc and type(args.okayFunc) ~= "function" then error(("okayFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.okayFunc), tostring(args.okayFunc)), 2) elseif args.cancelFunc and type(args.cancelFunc) ~= "function" then error(("cancelFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.cancelFunc), tostring(args.cancelFunc)), 2) elseif args.defaultFunc and type(args.defaultFunc) ~= "function" then error(("defaultFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.defaultFunc), tostring(args.defaultFunc)), 2) end local name local i = 0 repeat i = i + 1 name = self:GetName() .. "_Toggle" .. i until not _G[name] local toggle = CreateFrame("CheckButton", name, self, "InterfaceOptionsCheckButtonTemplate") self.controls[toggle] = true _G[toggle:GetName() .. "Text"]:SetText(args.name) toggle:SetHitRectInsets(0, -_G[toggle:GetName() .. "Text"]:GetWidth() - 1, 0, 0) toggle.tooltipText = args.description toggle.default = args.default local current if args.getFunc then toggle.getFunc = args.getFunc current = args.getFunc() else current = args.current end toggle.value = current toggle.oldValue = current toggle.changeFunc = args.setFunc toggle.SetValue = toggle_SetValue toggle:SetScript("OnClick", toggle_OnClick) toggle:SetChecked(current) toggle:SetScript("OnEnter", generic_OnEnter) toggle:SetScript("OnLeave", generic_OnLeave) toggle.okayFunc = args.okayFunc toggle.cancelFunc = args.cancelFunc toggle.defaultFunc = args.defaultFunc args = doneArgs(args) return toggle end end do local function update(self, r, g, b, a) if not self.hasAlpha then a = 1 end self.info.r = r self.info.g = g self.info.b = b if self.hasAlpha then self.info.opacity = a end self.color:SetColorTexture(r, g, b, a) if self.value == self.oldValue then self.value = {} end self.value[1] = r self.value[2] = g self.value[3] = b if self.hasAlpha then self.value[4] = a end self.info.r = r self.info.g = g self.info.b = b if self.hasAlpha then self.info.opacity = 1 - a end if self.hasAlpha then self.changeFunc(r, g, b, a) else self.changeFunc(r, g, b) end end local function button_SetValue(self, ...) if select('#', ...) == 1 and type((...)) == "table" then return button_SetValue(self, unpack(value)) end update(self, ...) end local function button_OnClick(self) OpenColorPicker(self.info) end local function swatchFunc(self) local r, g, b = ColorPickerFrame:GetColorRGB() local opacity = 1 - OpacitySliderFrame:GetValue() update(self, r, g, b, opacity) end local function cancelFunc(self) local previousValues = ColorPickerFrame.previousValues local r, g, b, opacity = previousValues.r, previousValues.g, previousValues.b, hasAlpha and 1 - previousValues.opacity or 1 update(self, r, g, b, opacity) end --- Return a color swatch that opens a color picker -- @name panel:MakeColorPicker -- @param ... tuple of key-value pairs<br/> -- name: What shows up next to the swatch<br /> -- description: What shows up in the tooltip on hover<br /> -- hasAlpha: Whether the color picker should have an alpha setting<br /> -- defaultR: Default red value [0, 1]<br /> -- defaultG: Default green value [0, 1]<br /> -- defaultB: Default blue value [0, 1]<br /> -- defaultA: Default alpha value [0, 1], only needed if hasAlpha is true<br /> -- currentR: The current red value - you can provide this or getFunc<br /> -- currentG: The current green value - you can provide this or getFunc<br /> -- currentB: The current blue value - you can provide this or getFunc<br /> -- currentA: The current alpha value - you can provide this or getFunc<br /> -- getFunc: Function to return the current color as a tuple<br /> -- setFunc: What is called when the color changes<br /> -- [optional] okayFunc: Called when the okay button is pressed<br /> -- [optional] cancelFunc: Called when the cancel button is pressed<br /> -- [optional] defaultFunc: Called when the default button is pressed -- @usage panel:MakeColorPicker( -- 'name', 'Pick a color', -- 'description', 'Specify your tooltip description', -- 'hasAlpha', false, -- 'defaultR', 1, -- 'defaultG', 0.82, -- 'defaultB', 0, -- 'getFunc', function() return unpack(db.color) end -- 'setFunc', function(r, g, b) db.color[1], db.color[2], db.color[3] = r, g, b end -- ) -- @usage panel:MakeColorPicker( -- 'name', 'Pick a color', -- 'description', 'Specify your tooltip description', -- 'hasAlpha', true, -- 'defaultR', 0, -- 'defaultG', 1, -- 'defaultB', 0, -- 'defaultA', 0.5, -- 'currentR', db.color2.r, -- 'currentG', db.color2.g, -- 'currentB', db.color2.b, -- 'currentA', db.color2.a, -- 'setFunc', function(r, g, b, a) db.color2.r, db.color2.g db.color2.b, db.color2.a = r, g, b, a end -- ) -- @return the color swatch function panelMeta:MakeColorPicker(...) local args = getArgs(...) if type(args.name) ~= "string" then error(("name must be %q, got %q (%s)"):format("string", type(args.name), tostring(args.name)), 2) elseif type(args.description) ~= "string" then error(("description must be %q, got %q (%s)"):format("string", type(args.description), tostring(args.description)), 2) elseif type(args.hasAlpha) ~= "boolean" then error(("hasAlpha must be %q, got %q (%s)"):format("boolean", type(args.hasAlpha), tostring(args.hasAlpha)), 2) elseif type(args.defaultR) ~= "number" then error(("defaultR must be %q, got %q (%s)"):format("number", type(args.defaultR), tostring(args.defaultR)), 2) elseif args.defaultR < 0 or args.defaultR > 1 then error(("defaultR must be [0, 1], got %s"):format(tostring(args.defaultR)), 2) elseif type(args.defaultG) ~= "number" then error(("defaultG must be %q, got %q (%s)"):format("number", type(args.defaultG), tostring(args.defaultG)), 2) elseif args.defaultG < 0 or args.defaultG > 1 then error(("defaultG must be [0, 1], got %s"):format(tostring(args.defaultG)), 2) elseif type(args.defaultB) ~= "number" then error(("defaultB must be %q, got %q (%s)"):format("number", type(args.defaultB), tostring(args.defaultB)), 2) elseif args.defaultB < 0 or args.defaultB > 1 then error(("defaultB must be [0, 1], got %s"):format(tostring(args.defaultB)), 2) elseif args.hasAlpha and type(args.defaultA) ~= "number" then error(("defaultA must be %q, got %q (%s)"):format("number", type(args.defaultA), tostring(args.defaultA)), 2) elseif args.hasAlpha and (args.defaultA < 0 or args.defaultA > 1) then error(("defaultA must be [0, 1], got %s"):format(tostring(args.defaultA)), 2) elseif not args.currentR == not args.getFunc then error(("either currentR or getFunc must be supplied, but not both"), 2) elseif args.currentR and (not args.currentG or not args.currentB or (args.hasAlpha and not args.currentA)) then error(("if you supply currentR, you must supply currentG and currentB (and currentA if hasAlpha)"), 2) elseif args.currentR and type(args.currentR) ~= "number" then error(("current must be %q, got %q (%s)"):format("number", type(args.currentR), tostring(args.currentR)), 2) elseif args.currentG and type(args.currentG) ~= "number" then error(("current must be %q, got %q (%s)"):format("number", type(args.currentG), tostring(args.currentG)), 2) elseif args.currentB and type(args.currentB) ~= "number" then error(("current must be %q, got %q (%s)"):format("number", type(args.currentB), tostring(args.currentB)), 2) elseif args.currentA and type(args.currentA) ~= "number" then error(("current must be %q, got %q (%s)"):format("number", type(args.currentA), tostring(args.currentA)), 2) elseif args.getFunc and type(args.getFunc) ~= "function" then error(("getFunc must be %q, got %q (%s)"):format("function", type(args.getFunc), tostring(args.getFunc)), 2) elseif type(args.setFunc) ~= "function" then error(("setFunc must be %q, got %q (%s)"):format("function", type(args.setFunc), tostring(args.setFunc)), 2) elseif args.okayFunc and type(args.okayFunc) ~= "function" then error(("okayFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.okayFunc), tostring(args.okayFunc)), 2) elseif args.cancelFunc and type(args.cancelFunc) ~= "function" then error(("cancelFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.cancelFunc), tostring(args.cancelFunc)), 2) elseif args.defaultFunc and type(args.defaultFunc) ~= "function" then error(("defaultFunc must be %q or %q, got %q (%s)"):format("nil", "function", type(args.defaultFunc), tostring(args.defaultFunc)), 2) end local name local i = 0 repeat i = i + 1 name = self:GetName() .. "_ColorPicker" .. i until not _G[name] if not args.hasAlpha then args.defaultA = 1 end local button = CreateFrame("Button", name, self) self.controls[button] = true local currentR, currentG, currentB, currentA if args.getFunc then button.getFunc = args.getFunc currentR, currentG, currentB, currentA = button.getFunc() if not args.hasAlpha then currentA = 1 end else currentR = args.currentR currentG = args.currentG currentB = args.currentB if not args.hasAlpha then currentA = 1 else currentR = args.currentA end end button.tooltipText = args.description local text = button:CreateFontString(nil, "ARTWORK", "GameFontHighlight") text:SetText(args.name) text:SetPoint("LEFT", button, "RIGHT", 0, 1) button:SetHitRectInsets(0, -text:GetWidth() - 1, 0, 0) local color = button:CreateTexture(nil, "ARTWORK") button.color = color color:SetColorTexture(currentR, currentG, currentB, currentA) local background = button:CreateTexture(nil, "BORDER") background:SetTexture([=[Tileset\Generic\Checkers]=]) background:SetTexCoord(0, 0.5, 0, 0.5) local border = button:CreateTexture(nil, "BACKGROUND") border:SetTexture([=[Interface\ChatFrame\ChatFrameColorSwatch]=]) button:SetWidth(26) button:SetHeight(26) background:SetPoint("CENTER") background:SetWidth(14) background:SetHeight(14) color:SetPoint("CENTER") color:SetWidth(14) color:SetHeight(14) border:SetAllPoints(button) button.default = { args.defaultR, args.defaultG, args.defaultB } button.oldValue = { currentR, currentG, currentB } if hasAlpha then button.default[4] = args.defaultA button.oldValue[4] = currentA end button.value = button.oldValue button.hasAlpha = args.hasAlpha button.changeFunc = args.setFunc button.SetValue = button_SetValue local function swatchFunc_wrapper() swatchFunc(button) end local function cancelFunc_wrapper() cancelFunc(button) end button.info = { swatchFunc = swatchFunc_wrapper, hasOpacity = args.hasAlpha, opacityFunc = args.hasAlpha and swatchFunc_wrapper or nil, r = currentR, g = currentB, b = currentG, opacity = args.hasAlpha and 1 - currentA or nil, cancelFunc = cancelFunc_wrapper, } button:SetScript("OnClick", button_OnClick) button:SetScript("OnEnter", generic_OnEnter) button:SetScript("OnLeave", generic_OnLeave) button:RegisterForClicks("LeftButtonUp") button.okayFunc = args.okayFunc button.cancelFunc = args.cancelFunc button.defaultFunc = args.defaultFunc args = doneArgs(args) return button end end --- Add a slash command to open a specific options panel -- @param name name of the panel to open -- @param ... tuple of slash commands -- @usage LibStub("LibSimpleOptions-1.0").AddSlashCommand("My Options", "/MyOpt", "/MO") function LibSimpleOptions.AddSlashCommand(name, ...) local num = 0 local name_upper = name:upper() for i = 1, select('#', ...) do local cmd = select(i, ...) num = num + 1 _G["SLASH_" .. name_upper .. num] = cmd local cmd_lower = cmd:lower() if cmd_lower ~= cmd then num = num + 1 _G["SLASH_" .. name_upper .. num] = cmd_lower end end _G.hash_SlashCmdList[name_upper] = nil _G.SlashCmdList[name_upper] = function() InterfaceOptionsFrame_OpenToCategory(name) end end for funcName, func in pairs(panelMeta) do LibSimpleOptions[funcName] = func for panel in pairs(panels) do panel[funcName] = func end end