Nenue@0: --- All the control GUI stuff, including chat command functions Nenue@0: -- @file-author@ Nenue@0: -- @project-revision@ @project-hash@ Nenue@0: -- @file-revision@ @file-hash@ Nenue@0: -- Created: 3/12/2016 12:49 AM Nenue@49: local vn, _G = select(2,...).frame, _G Nenue@49: local M = vn:RegisterModule("Options") Nenue@49: local tostring, tonumber, floor, max, assert = tostring, tonumber, floor, math.max, assert Nenue@49: local unpack, setmetatable, pairs, ipairs, type, wipe = unpack, setmetatable, pairs, ipairs, type, table.wipe Nenue@0: local CreateFrame, IsControlKeyDown = _G.CreateFrame, _G.IsControlKeyDown Nenue@0: local OpacitySliderFrame, ColorPickerFrame = _G.OpacitySliderFrame, _G.ColorPickerFrame Nenue@49: local print = vn.print('Cfgl') Nenue@0: local function round(number, decimals) Nenue@0: if floor(number) == number then Nenue@0: return ('%d'):format(number) Nenue@0: end Nenue@0: Nenue@0: return (("%%.%df"):format(decimals)):format(number) Nenue@0: end Nenue@0: Nenue@49: --- Set up this way to ensure that all the necessary data exists before things domino into something inscrutable Nenue@49: M.prototypes = { Nenue@49: value = setmetatable({}, {__call = function(self, frame) Nenue@49: assert(frame, 'Expected table (received '..type(frame.OptInfo)..')') Nenue@49: assert(frame.OptRoot, 'Invalid config table for frame '.. frame:GetName().. '') Nenue@49: return self[frame.ValueType](frame.OptTab, frame.OptKey, frame.OptRoot) Nenue@49: end}), Nenue@49: reset = setmetatable({}, {__call = function(self, frame) Nenue@49: assert(frame.GetName, 'Invalid frame reference (received '..type(frame.OptInfo)..')') Nenue@49: assert(frame.OptInfo, 'Expecting a table (received '..type(frame.OptInfo)..')') Nenue@49: return self[frame.OptType](frame, frame.OptInfo) Nenue@49: end}) Nenue@49: } Nenue@49: M.config = {} Nenue@24: M.defaults = { Nenue@24: enable = true Nenue@24: } Nenue@49: local GetValue = M.prototypes.value Nenue@49: local ResetField = M.prototypes.reset Nenue@0: --- STATE VARIABLES Nenue@0: local configInit Nenue@0: --- Dummies for addon table upvalues Nenue@0: local configFrames = {} -- actual frame objects Nenue@49: local displays = vn.displays -- anchor objects dummy Nenue@0: Nenue@0: Nenue@0: --- Returns a value retreival function and the current value stored in config Nenue@0: -- @paramsig value, previousValue = configInteger(key) Nenue@0: -- @param key Name of the config field being represented. Nenue@0: local defaultGroup = 'BuffButton' Nenue@49: Nenue@49: GetValue.Integer = function(group, key, parent) Nenue@49: return function(self) Nenue@0: return floor(tonumber(self:GetValue()) + 0.5) Nenue@49: end, (parent[group ..key] or parent[defaultGroup..key]) Nenue@0: end Nenue@49: GetValue.Percent = function(group, key, parent) Nenue@0: return function(self, display) Nenue@0: local value = self:GetValue() Nenue@0: if display then Nenue@0: return tostring(floor(value*100+0.5))..' %' Nenue@0: else Nenue@0: return floor((value*100+0.5))/100 Nenue@0: end Nenue@49: end, (parent[group ..key] or parent[defaultGroup..key]) Nenue@0: end Nenue@49: GetValue.Color = function(group, key, parent) Nenue@0: -- table for config, color value list for text Nenue@0: return function(self, display) Nenue@0: if display then Nenue@0: return "|cFFFF4444" .. round(self.rgba[1], 1) .. "|r, |cFF44FF44" .. round(self.rgba[2], 1) .. "|r, |cFF4488FF" .. Nenue@0: round(self.rgba[3], 1) .. "|r, " .. round(self.rgba[4], 1) Nenue@0: else Nenue@0: return self.rgba Nenue@0: end Nenue@49: end, (parent[group ..key] or parent[defaultGroup..key]) Nenue@0: end Nenue@49: GetValue.Check = function(group, key, parent) Nenue@49: return function(self) return self:GetChecked() end, parent[group ..key] or vn.Conf[defaultGroup..key] Nenue@0: end Nenue@0: -- initializes the corresponding type of config field Nenue@0: local frameTypeConv = { Nenue@0: Color = 'Button', Nenue@0: Font = 'Frame', Nenue@0: } Nenue@49: Nenue@49: Nenue@49: ResetField.Slider = function(frame, optionInfo) Nenue@0: frame:SetMinMaxValues(optionInfo[5], optionInfo[6]) Nenue@0: frame:SetValueStep(optionInfo[7]) Nenue@0: frame:SetStepsPerPage(optionInfo[8]) Nenue@0: print(frame.OptName, '\n {', optionInfo[5], optionInfo[6], optionInfo[7], optionInfo[8], '}') Nenue@49: end Nenue@49: ResetField.CheckButton = function(frame, optionInfo) Nenue@0: frame.SetValue = function(self, ...) Nenue@0: self:SetChecked(...) Nenue@49: self.OptRoot[self.OptName] = self:GetChecked() Nenue@0: print(self.OptTab) Nenue@49: vn.UpdateAll() Nenue@0: end Nenue@0: frame:SetScript("OnClick",function(self) Nenue@49: self.OptRoot[self.OptName] = self:GetChecked() Nenue@49: print(self.OptRoot[self.OptName], self:GetChecked()) Nenue@49: vn.UpdateAll() Nenue@0: end) Nenue@49: end Nenue@49: ResetField.Color = function(frame, optionInfo) Nenue@49: frame.rgba = { frame.current:GetVertexColor() } Nenue@49: local colorPickerCallback = function(restore) Nenue@49: local newR, newG, newB, newA Nenue@49: if restore then Nenue@49: newR, newG, newB, newA = unpack(restore) Nenue@49: else Nenue@49: newA, newR, newG, newB = OpacitySliderFrame:GetValue(), ColorPickerFrame:GetColorRGB() Nenue@49: print('not cancel', newA, newR, newB, newG) Nenue@0: end Nenue@49: frame:SetValue({newR, newG, newB, newA}) Nenue@49: vn.UpdateBuffs(frame.OptTab) Nenue@0: end Nenue@49: frame:SetScript("OnClick", function(self) Nenue@49: print('got a click') Nenue@49: local r, g, b, a = frame.current:GetVertexColor() Nenue@49: ColorPickerFrame:SetColorRGB(r, g, b) Nenue@49: ColorPickerFrame.hasOpacity = (a ~= nil) Nenue@49: ColorPickerFrame.opacity = a Nenue@49: ColorPickerFrame.previousValues = {r,g,b,a} Nenue@49: ColorPickerFrame.func, ColorPickerFrame.opacityFunc, ColorPickerFrame.cancelFunc = Nenue@49: colorPickerCallback, colorPickerCallback,colorPickerCallback Nenue@49: ColorPickerFrame:Hide() Nenue@49: ColorPickerFrame:Show() Nenue@49: end) Nenue@49: frame.SetValue = function(self, rgba) Nenue@49: print(rgba) Nenue@49: frame.rgba = rgba Nenue@49: self.OptRoot[self.OptName] = rgba Nenue@49: frame.current:SetVertexColor(unpack(rgba)) Nenue@49: frame.fieldvalue:SetText(frame.OptValue(frame, true)) Nenue@49: end Nenue@49: end Nenue@49: Nenue@0: --- configDialog Nenue@0: -- @usage tinsert(configDialog, {prefix, row, [...] }) Nenue@0: -- Each top level member defines a group of config value handlers, structured as an iterative table where the Nenue@0: -- first member is a key prefix, the second member is an integer row value, and all following members are treated Nenue@0: -- as a widget resource, defined initially as a complete sub-table, which can be re-used further down by passing Nenue@0: -- the string literal widget suffix. Nenue@0: -- widget table: ... {'suffix', 'description', valueCallback, 'template', [widget parameters]} Nenue@0: -- widget copy: ... 'suffix', ... Nenue@49: M.config.BuffFrame = { Nenue@0: {'BuffButton', 1, Nenue@0: Nenue@49: {'Max', 'Max', 'Integer', 'Slider', Nenue@0: 1, _G.BUFF_MAX_DISPLAY, 1, 1}, -- valueMin, valueMax, valueStep, stepsPerPage Nenue@49: {'PerRow', 'Per Row', 'Integer', 'Slider', Nenue@0: 1, _G.BUFF_MAX_DISPLAY, 1, 1}, -- valueMin, valueMax, valueStep, stepsPerPage, Nenue@49: {'Size', 'Icon Size', 'Integer', 'Slider', Nenue@0: 1, 256, 1, 1}, Nenue@49: {'Spacing', 'Icon Spacing', 'Integer', 'Slider', Nenue@0: 1, 50, 1, 1}, Nenue@49: {'DurationSize', 'Duration Text Height', 'Integer', 'Slider', Nenue@0: 1, 72, 1, 1}, Nenue@49: {'Zoom', 'Icon Zoom', 'Integer', 'Slider', Nenue@0: 0, 100, 1, 1}, Nenue@49: {'Border', 'Border', 'Integer', 'Slider', Nenue@0: 1, 16, 1, 1}, Nenue@49: {'Color', 'Default Border', 'Color', 'Color'}, Nenue@49: {'RaidColor', 'RaidBuff Border', 'Color', 'Color'}, Nenue@49: {'PlayerColor', 'Player Buffs', 'Color', 'Color'}, Nenue@49: {'BossColor', 'Encounter Buffs', 'Color', 'Color'}, Nenue@49: {'ShowSelfCast', 'Show name for self-casts', 'Check', 'CheckButton'} Nenue@0: }, Nenue@0: { 'DebuffButton', 1, Nenue@49: {'Max', 'Max', 'Integer', 'Slider', Nenue@0: 1, _G.DEBUFF_MAX_DISPLAY, 1, 1 } Nenue@0: , Nenue@49: {'PerRow', 'Per Row', 'Integer', 'Slider', Nenue@0: 1, _G.DEBUFF_MAX_DISPLAY, 1, 1 }, Nenue@0: 'Size', 'Spacing', 'DurationSize', 'Zoom', 'Border', Nenue@0: 'Color', 'RaidColor', 'PlayerColor', 'BossColor', Nenue@0: }, Nenue@0: { 'TempEnchant', 1, Nenue@49: {'Max', 'Max', 'Integer', 'Slider', Nenue@0: 1, _G.NUM_TEMP_ENCHANT_FRAMES, 1, 1 }, Nenue@49: {'PerRow', 'Per Row', 'Integer', 'Slider', Nenue@0: 1, _G.NUM_TEMP_ENCHANT_FRAMES, 1, 1}, Nenue@0: 'Size', 'Spacing', 'DurationSize', 'Zoom', 'Border', Nenue@0: 'Color', 'RaidColor', 'PlayerColor', 'BossColor', Nenue@0: }, Nenue@0: { 'ConsolidatedBuff', 2, Nenue@49: {'Position', 'Slot Position', 'Integer', 'Slider', Nenue@0: 1, _G.BUFF_MAX_DISPLAY, 1, 1 } Nenue@0: Nenue@0: }, Nenue@0: { 'ConsolidatedBuff', 2, Nenue@0: 'Size' Nenue@0: }, Nenue@0: { 'Raid', 3, Nenue@49: {'ShowMissing', 'Verbose missing raid buffs', 'Check', 'CheckButton'} Nenue@0: } Nenue@0: } Nenue@0: Nenue@0: Nenue@0: Nenue@0: Nenue@0: local configFrame Nenue@0: local optionTemplates = {} Nenue@0: local configPadding, configSpacing = 3, 3 Nenue@0: Nenue@0: --- Walks the structure table to generate a pretty config panel Nenue@0: local InitConfig = function() Nenue@0: configInit = true Nenue@49: local configWidth = vn:GetWidth() Nenue@0: local optionWidth = (configWidth - configPadding) / 3 - configSpacing Nenue@0: local configHeight = 0 Nenue@0: local bottom_extent = 0 Nenue@0: local clusterHeight = 0 Nenue@0: local clusterOffset = 0 Nenue@0: local lastCluster Nenue@0: local cluster = 1 Nenue@0: local col = 0 Nenue@49: for moduleName, moduleOpts in pairs(M.config) do Nenue@49: for t, taboptions in ipairs(moduleOpts) do Nenue@0: local group = taboptions[1] Nenue@0: cluster = taboptions[2] Nenue@0: col = col + 1 Nenue@0: Nenue@0: Nenue@0: if not configFrames[t] then Nenue@0: configFrames[t] = {} Nenue@0: end Nenue@0: Nenue@0: Nenue@0: if cluster ~= lastCluster then Nenue@0: configHeight = configHeight + clusterHeight Nenue@0: print('|cFFFF8800## new cluster|r, advancing offset from', clusterOffset, 'to', clusterOffset + clusterHeight) Nenue@0: clusterOffset = clusterOffset + clusterHeight Nenue@0: col = 1 Nenue@0: clusterHeight = 0 Nenue@0: lastCluster = cluster Nenue@0: end Nenue@0: Nenue@0: print('processing tab', group) Nenue@0: local row = 0 Nenue@0: for i = 3, #taboptions do Nenue@0: row = row + 1 Nenue@0: local optionInfo = taboptions[i] Nenue@0: if type(optionInfo) == 'string' then Nenue@0: optionInfo = optionTemplates[optionInfo] Nenue@0: end Nenue@49: local key, fieldname, valueType, configType = unpack(optionInfo) Nenue@49: assert(GetValue[valueType], 'Invalid valueType \''..tostring(valueType)..'\' ('..type(valueType)..')') Nenue@49: assert(ResetField[configType], 'Invalid fieldType \''..tostring(configType)..'\' ('..type(configType)..')') Nenue@0: Nenue@0: if not optionTemplates[key] then Nenue@0: optionTemplates[key] = optionInfo Nenue@0: end Nenue@0: Nenue@0: local fullkey = group .. key Nenue@0: print(fullkey, fieldname) Nenue@0: Nenue@0: if not configFrames[t][row] then Nenue@0: print('building frame', t, group, row) Nenue@0: local frameTemplate = 'VeneerConfig'..configType Nenue@0: local frameType = frameTypeConv[configType] or configType Nenue@49: configFrames[t][row] = CreateFrame(frameType, 'Vn_'.. moduleName ..'_'.. fullkey, vn, frameTemplate) Nenue@0: local f = configFrames[t][row] Nenue@49: f.ValueType = valueType Nenue@49: f.OptType = configType Nenue@49: f.FrameType = frameType Nenue@0: f.OptKey = key Nenue@49: f.OptRoot = vn.Conf[moduleName] Nenue@0: f.OptTab = group Nenue@0: f.OptName = fullkey Nenue@49: f.OptInfo = optionInfo Nenue@49: local valueFunc, initialValue = GetValue(f) Nenue@49: print(' value getter', '|cFFFFFF00'..moduleName..'|r.|cFF00FFFF'.. tostring(fullkey),'|r->', valueFunc,initialValue) Nenue@49: ResetField(f) Nenue@0: f.OptValue = valueFunc Nenue@0: Nenue@0: --- Enclosing these to Nenue@0: -- a) make the panel easy to bring up externally Nenue@0: -- b) limit gameplay risk from config frame errors Nenue@0: -- c) milk the iterator scope for all its worth Nenue@0: f.OnChange = function(self) Nenue@0: Nenue@0: -- holding control; mirror this setting in other categories Nenue@0: if IsControlKeyDown() and not (configInit) then Nenue@0: configInit = true Nenue@0: for optTab, opts in pairs(configFrames) do Nenue@0: for _, opt in ipairs(opts) do Nenue@0: if opt.OptKey == key then Nenue@0: if optTab ~= group then Nenue@0: print('mapping to', optTab, opt.OptKey) Nenue@0: opt:SetValue(self:GetValue()) Nenue@0: end Nenue@0: Nenue@0: end Nenue@0: end Nenue@0: end Nenue@0: configInit = nil Nenue@0: end Nenue@0: local newValue = valueFunc(self) Nenue@49: if newValue ~= self.OptRoot[fullkey] then Nenue@0: print(newValue, fullkey) Nenue@0: f.fieldvalue:SetText(valueFunc(self, true)) Nenue@49: self.OptRoot[fullkey] = valueFunc(self) Nenue@0: -- prepare to update Nenue@49: vn[moduleName]:OnUpdate() Nenue@49: vn.UpdateConfigLayers() Nenue@0: end Nenue@0: Nenue@0: end Nenue@0: Nenue@0: f:SetValue(initialValue) Nenue@0: local yBuffer = configPadding Nenue@0: if f.fieldname then Nenue@0: f.fieldname:SetText(fieldname) Nenue@0: yBuffer = yBuffer + f.fieldname:GetHeight() Nenue@0: end Nenue@0: if f.fieldvalue then Nenue@0: f.fieldvalue:SetText(f:OptValue(true)) Nenue@0: end Nenue@0: Nenue@0: local point, relative, x, y = 'TOPLEFT', 'BOTTOMLEFT', 0, -3 Nenue@0: Nenue@0: local base Nenue@0: if (row == 1) then Nenue@0: bottom_extent = 0 Nenue@49: base = vn.header Nenue@0: x = (col-1) * (optionWidth+configSpacing) Nenue@0: y = -configPadding Nenue@0: else Nenue@0: base = configFrames[t][row-1] Nenue@0: end Nenue@0: Nenue@0: print('|cFFFF0088'..cluster..'|r |cFF00FF00'.. row..'|r', col, base:GetName(), x, y - clusterOffset) Nenue@0: Nenue@0: if frameType ~= 'CheckButton' then Nenue@0: f:SetWidth(optionWidth) Nenue@0: end Nenue@0: Nenue@0: f:SetPoint(point, base, relative, x, y-yBuffer-clusterOffset) Nenue@0: --print('creating', frameType, fieldname) Nenue@0: f:Show() Nenue@0: Nenue@0: bottom_extent = bottom_extent + f:GetHeight() + yBuffer + configSpacing Nenue@0: Nenue@0: Nenue@0: Nenue@0: clusterHeight = max(clusterHeight, bottom_extent) Nenue@0: --print('y', floor(yBuffer+0.5), 'f:H', floor(f:GetHeight()+0.5), 'hTally', floor(bottom_extent+0.5), 'hMax', floor(configHeight+0.5)) Nenue@0: end Nenue@0: end Nenue@49: end Nenue@0: end Nenue@0: Nenue@49: Nenue@0: -- grab the last cluster Nenue@0: if lastCluster == cluster then Nenue@0: print('|cFF00FF00##scooping up last cluster info') Nenue@0: configHeight = configHeight + clusterHeight Nenue@0: end Nenue@0: Nenue@49: if not vn.configFramesCreated then Nenue@49: vn.configFramesCreated = true Nenue@49: vn:SetHeight(vn.header:GetStringHeight() + configSpacing*3 + configHeight) Nenue@0: end Nenue@0: if configInit then configInit = nil end Nenue@0: end Nenue@0: Nenue@0: M.Command = function(enable, editbox) Nenue@49: displays = vn.displays Nenue@0: if type(enable) == 'boolean' then Nenue@49: vn.Conf.ConfigMode = enable Nenue@0: else Nenue@49: vn.Conf.ConfigMode = (vn.Conf.ConfigMode == false) and true or false Nenue@0: end Nenue@0: Nenue@49: print('/BUFF', vn.Conf.ConfigMode, type(vn.Conf.ConfigMode)) Nenue@49: if vn.Conf.ConfigMode then Nenue@49: if not vn.configFramesCreated then Nenue@0: InitConfig() Nenue@0: end Nenue@0: print('Veneer config') Nenue@49: vn:Show() Nenue@0: else Nenue@49: vn:Hide() Nenue@0: end Nenue@49: vn.UpdateAll() Nenue@49: vn.UpdateConfigLayers() Nenue@0: end Nenue@0: Nenue@49: vn.Close = function () Nenue@0: M.Command() Nenue@0: end Nenue@0: Nenue@49: vn.ToggleGuides = function(_, self) Nenue@49: vn.Conf.GuidesMode = (not vn.Conf.GuidesMode) Nenue@49: if vn.Conf.GuidesMode then Nenue@0: self:GetNormalTexture():SetTexture(0.94, 0.21, 0.21, 1) Nenue@0: else Nenue@0: self:GetNormalTexture():SetTexture(0, 0, 0, 1) Nenue@0: end Nenue@0: Nenue@49: vn.UpdateConfigLayers() Nenue@0: end Nenue@0: Nenue@0: M.OnEnable = function() Nenue@49: print('|cFFFF0088config module', vn.Conf.ConfigMode) Nenue@49: M.Command(vn.Conf.ConfigMode) Nenue@0: end Nenue@0: Nenue@0: M.OnInitialize = function() Nenue@49: DEFAULT_CHAT_FRAME:AddMessage("|cFF22D822Veneer|r confogulator loaded. Type |cFF00FFFF/vn|r to begin.") Nenue@49: SLASH_VENEER1, SLASH_VENEER2 = "/veneer", "/vn" Nenue@49: SlashCmdList.VENEER = M.Command Nenue@0: Nenue@0: end