local L = WeakAuras.L ---@type string local AddonName = ... ---@class OptionsPrivate local OptionsPrivate = select(2, ...) local texture_types = WeakAuras.StopMotion.texture_types; local texture_data = WeakAuras.StopMotion.texture_data; local animation_types = WeakAuras.StopMotion.animation_types; local setTile = WeakAuras.setTile -- Returns value only for Blizzard flipbooks function OptionsPrivate.GetFlipbookTileSize(name) if texture_data[name] then if texture_data[name].isBlizzardFlipbook then if texture_data[name].tileWidth and texture_data[name].tileHeight then return {["tileWidth"] = texture_data[name].tileWidth, ["tileHeight"] = texture_data[name].tileHeight} end end end end local function setTextureFunc(textureWidget, texturePath, textureName) local data = texture_data[texturePath]; if not(data) then local pattern = "%.x(%d+)y(%d+)f(%d+)%.[tb][gl][ap]" local pattern2 = "%.x(%d+)y(%d+)f(%d+)w(%d+)h(%d+)W(%d+)H(%d+)%.[tb][gl][ap]" local rows, columns, frames = texturePath:lower():match(pattern) if rows then data = { count = tonumber(frames), rows = tonumber(rows), columns = tonumber(columns) } else local rows, columns, frames, frameWidth, frameHeight, fileWidth, fileHeight = texturePath:match(pattern2) if rows then rows, columns, frames, frameWidth, frameHeight, fileWidth, fileHeight = tonumber(rows), tonumber(columns), tonumber(frames), tonumber(frameWidth), tonumber(frameHeight), tonumber(fileWidth), tonumber(fileHeight) local frameScaleW = 1 local frameScaleH = 1 if fileWidth > 0 and frameWidth > 0 then frameScaleW = (frameWidth * columns) / fileWidth end if fileHeight > 0 and frameHeight > 0 then frameScaleH = (frameHeight * rows) / fileHeight end data = { count = frames, rows = rows, columns = columns, frameScaleW = frameScaleW, frameScaleH = frameScaleH } end end end textureWidget.frameNr = 0; if (data) then if (data.rows and data.columns) then -- Texture Atlas textureWidget:SetTexture(texturePath, textureName, true); setTile(textureWidget, data.count, data.rows, data.columns, data.frameScaleW or 1, data.frameScaleH or 1); textureWidget:SetOnUpdate(function(self, elapsed) self.elapsed = (self.elapsed or 0) + elapsed if(self.elapsed > 0.1) then self.elapsed = self.elapsed - 0.1; textureWidget.frameNr = textureWidget.frameNr + 1; if (textureWidget.frameNr == data.count) then textureWidget.frameNr = 1; end setTile(textureWidget, textureWidget.frameNr, data.rows, data.columns, data.frameScaleW or 1, data.frameScaleH or 1); end end) else -- Numbered Textures local texture = texturePath .. format("%03d", texture_data[texturePath].count) textureWidget:SetTexture(texture, textureName, true) textureWidget:SetTexCoord(0, 1, 0, 1); textureWidget:SetOnUpdate(function(self, elapsed) self.elapsed = (self.elapsed or 0) + elapsed if(self.elapsed > 0.1) then self.elapsed = self.elapsed - 0.1; textureWidget.frameNr = textureWidget.frameNr + 1; if (textureWidget.frameNr == data.count) then textureWidget.frameNr = 1; end local texture = texturePath .. format("%03d", textureWidget.frameNr) textureWidget:SetTexture(texture, textureName); end end); end else local texture = texturePath .. format("%03d", 1) textureWidget:SetTexture(texture, textureName, true); end end local function textureNameHasData(textureName) local pattern = "%.x(%d+)y(%d+)f(%d+)%.[tb][gl][ap]$" local pattern2 = "%.x(%d+)y(%d+)f(%d+)w(%d+)h(%d+)W(%d+)H(%d+)%.[tb][gl][ap]$" local ok = textureName:lower():match(pattern) if ok then return true end local ok2 = textureName:match(pattern2) if ok2 then return true else return false end end local function createOptions(id, data) local options = { __title = L["Stop Motion Settings"], __order = 1, foregroundTexture = { type = "input", width = WeakAuras.doubleWidth - 0.15, name = L["Texture"], order = 1, }, chooseForegroundTexture = { type = "execute", width = 0.15, name = L["Choose"], order = 2, func = function() local path = {} local paths = {} for child in OptionsPrivate.Private.TraverseLeafsOrAura(data) do paths[child.id] = path end OptionsPrivate.OpenTexturePicker(data, paths, { texture = "foregroundTexture", color = "foregroundColor", mirror = "mirror", blendMode = "blendMode" }, texture_types, setTextureFunc, true); end, imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon", image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\browse", }, foregroundColor = { type = "color", width = WeakAuras.normalWidth, name = L["Color"], hasAlpha = true, order = 3 }, desaturateForeground = { type = "toggle", width = WeakAuras.normalWidth, name = L["Desaturate"], order = 3.5, }, customForegroundRows = { type = "input", width = WeakAuras.doubleWidth / 3, name = L["Rows"], validate = WeakAuras.ValidateNumeric, get = function() return data.customForegroundRows and tostring(data.customForegroundRows) or ""; end, set = function(info, v) data.customForegroundRows = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 4, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, customForegroundColumns = { type = "input", width = WeakAuras.doubleWidth / 3, name = L["Columns"], validate = WeakAuras.ValidateNumeric, get = function() return data.customForegroundColumns and tostring(data.customForegroundColumns) or ""; end, set = function(info, v) data.customForegroundColumns = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 5, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, customForegroundFrames = { type = "input", width = WeakAuras.doubleWidth / 3, name = L["Frame Count"], validate = WeakAuras.ValidateNumeric, get = function() return data.customForegroundFrames and tostring(data.customForegroundFrames) or ""; end, set = function(info, v) data.customForegroundFrames = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 6, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, customForegroundFileWidth = { type = "input", width = WeakAuras.normalWidth / 2, name = L["File Width"], desc = L["Must be a power of 2"], validate = function(info, val) if val ~= nil and val ~= "" and (not tonumber(val) or tonumber(val) >= 2^31 or math.frexp(val) ~= 0.5) then return false; end return true end, get = function() return data.customForegroundFileWidth and tostring(data.customForegroundFileWidth) or ""; end, set = function(info, v) data.customForegroundFileWidth = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 7, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, customForegroundFileHeight = { type = "input", width = WeakAuras.normalWidth / 2, name = L["File Height"], desc = L["Must be a power of 2"], validate = function(info, val) if val ~= nil and val ~= "" and (not tonumber(val) or tonumber(val) >= 2^31 or math.frexp(val) ~= 0.5) then return false; end return true end, get = function() return data.customForegroundFileHeight and tostring(data.customForegroundFileHeight) or ""; end, set = function(info, v) data.customForegroundFileHeight = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 8, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, customForegroundFrameWidth = { type = "input", width = WeakAuras.normalWidth / 2, name = L["Frame Width"], validate = WeakAuras.ValidateNumeric, desc = L["Can set to 0 if Columns * Width equal File Width"], get = function() return data.customForegroundFrameWidth and tostring(data.customForegroundFrameWidth) or ""; end, set = function(info, v) data.customForegroundFrameWidth = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 9, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, customForegroundFrameHeight = { type = "input", width = WeakAuras.normalWidth / 2, name = L["Frame Height"], validate = WeakAuras.ValidateNumeric, desc = L["Can set to 0 if Rows * Height equal File Height"], get = function() return data.customForegroundFrameHeight and tostring(data.customForegroundFrameHeight) or ""; end, set = function(info, v) data.customForegroundFrameHeight = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 10, hidden = function() return texture_data[data.foregroundTexture] or textureNameHasData(data.foregroundTexture) end }, blendMode = { type = "select", width = WeakAuras.normalWidth, name = L["Blend Mode"], order = 11, values = OptionsPrivate.Private.blend_types }, animationType = { type = "select", width = WeakAuras.normalWidth, name = L["Animation Mode"], order = 12, values = animation_types }, startPercent = { type = "range", control = "WeakAurasSpinBox", width = WeakAuras.normalWidth, name = L["Animation Start"], min = 0, max = 1, --bigStep = 0.01, order = 13, isPercent = true }, endPercent = { type = "range", control = "WeakAurasSpinBox", width = WeakAuras.normalWidth, name = L["Animation End"], min = 0, max = 1, --bigStep = 0.01, order = 14, isPercent = true }, frameRate = { type = "range", control = "WeakAurasSpinBox", width = WeakAuras.normalWidth, name = L["Frame Rate"], min = 3, max = 120, step = 1, bigStep = 3, order = 15, disabled = function() return data.animationType == "progress" end; }, inverse = { type = "toggle", width = WeakAuras.normalWidth, name = L["Inverse"], order = 15.5 }, customBackgroundHeader = { type = "header", name = L["Background Texture"], order = 16, }, hideBackground = { type = "toggle", name = L["Hide Background"], order = 17, width = WeakAuras.normalWidth, }, sameTexture = { type = "toggle", width = WeakAuras.normalWidth, name = L["Same texture as Foreground"], order = 18, disabled = function() return data.hideBackground; end, hidden = function() return data.hideBackground; end }, backgroundTexture = { type = "input", width = WeakAuras.doubleWidth - 0.15, name = L["Background Texture"], order = 19, disabled = function() return data.sameTexture or data.hideBackground end, hidden = function() return data.hideBackground end, get = function() return data.sameTexture and data.foregroundTexture or data.backgroundTexture; end, }, chooseBackgroundTexture = { type = "execute", width = 0.15, name = L["Choose"], order = 20, func = function() local path = {} local paths = {} for child in OptionsPrivate.Private.TraverseLeafsOrAura(data) do paths[child.id] = path end OptionsPrivate.OpenTexturePicker(data, paths, { texture = "backgroundTexture", color = "backgroundColor", mirror = "mirror", blendMode = "blendMode" }, texture_types, setTextureFunc, true); end, disabled = function() return data.sameTexture or data.hideBackground; end, hidden = function() return data.hideBackground end, imageWidth = 24, imageHeight = 24, control = "WeakAurasIcon", image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\browse", }, backgroundColor = { type = "color", width = WeakAuras.normalWidth, name = L["Color"], hasAlpha = true, order = 21, disabled = function() return data.hideBackground; end, hidden = function() return data.hideBackground; end }, desaturateBackground = { type = "toggle", name = L["Desaturate"], order = 22, width = WeakAuras.normalWidth, disabled = function() return data.hideBackground; end, hidden = function() return data.hideBackground; end }, backgroundColorHiddenSpacer = { type = "execute", width = WeakAuras.normalWidth, name = "", order = 23, image = function() return "", 0, 0 end, hidden = function() return not data.hideBackground end }, customBackgroundRows = { type = "input", width = WeakAuras.doubleWidth / 3, name = L["Rows"], validate = WeakAuras.ValidateNumeric, get = function() return data.customBackgroundRows and tostring(data.customBackgroundRows) or ""; end, set = function(info, v) data.customBackgroundRows = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 24, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, customBackgroundColumns = { type = "input", width = WeakAuras.doubleWidth / 3, name = L["Columns"], validate = WeakAuras.ValidateNumeric, get = function() return data.customBackgroundColumns and tostring(data.customBackgroundColumns) or ""; end, set = function(info, v) data.customBackgroundColumns = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 25, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, customBackgroundFrames = { type = "input", width = WeakAuras.doubleWidth / 3, name = L["Frame Count"], validate = WeakAuras.ValidateNumeric, get = function() return data.customBackgroundFrames and tostring(data.customBackgroundFrames) or ""; end, set = function(info, v) data.customBackgroundFrames = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 26, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, customBackgroundFileWidth = { type = "input", width = WeakAuras.normalWidth / 2, name = L["File Width"], desc = L["Must be a power of 2"], validate = function(info, val) if val ~= nil and val ~= "" and (not tonumber(val) or tonumber(val) >= 2^31 or math.frexp(val) ~= 0.5) then return false; end return true end, get = function() return data.customBackgroundFileWidth and tostring(data.customBackgroundFileWidth) or ""; end, set = function(info, v) data.customBackgroundFileWidth = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 27, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, customBackgroundFileHeight = { type = "input", width = WeakAuras.normalWidth / 2, name = L["File Height"], desc = L["Must be a power of 2"], validate = function(info, val) if val ~= nil and val ~= "" and (not tonumber(val) or tonumber(val) >= 2^31 or math.frexp(val) ~= 0.5) then return false; end return true end, get = function() return data.customBackgroundFileHeight and tostring(data.customBackgroundFileHeight) or ""; end, set = function(info, v) data.customBackgroundFileHeight = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 28, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, customBackgroundFrameWidth = { type = "input", width = WeakAuras.normalWidth / 2, name = L["Frame Width"], validate = WeakAuras.ValidateNumeric, desc = L["Can set to 0 if Columns * Width equal File Width"], get = function() return data.customBackgroundFrameWidth and tostring(data.customBackgroundFrameWidth) or ""; end, set = function(info, v) data.customBackgroundFrameWidth = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 29, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, customBackgroundFrameHeight = { type = "input", width = WeakAuras.normalWidth / 2, name = L["Frame Height"], validate = WeakAuras.ValidateNumeric, desc = L["Can set to 0 if Rows * Height equal File Height"], get = function() return data.customBackgroundFrameHeight and tostring(data.customBackgroundFrameHeight) or ""; end, set = function(info, v) data.customBackgroundFrameHeight = v and tonumber(v) or 0 WeakAuras.Add(data); WeakAuras.UpdateThumbnail(data); end, order = 30, hidden = function() return data.sameTexture or texture_data[data.backgroundTexture] or textureNameHasData(data.backgroundTexture) end }, backgroundPercent = { type = "range", control = "WeakAurasSpinBox", width = WeakAuras.normalWidth, name = L["Selected Frame"], min = 0, max = 1, order = 31, isPercent = true, hidden = function() return data.hideBackground; end } }; return { stopmotion = options, progressOptions = OptionsPrivate.commonOptions.ProgressOptions(data), position = OptionsPrivate.commonOptions.PositionOptions(id, data, 2), } end local function createThumbnail() local borderframe = CreateFrame("Frame", nil, UIParent); borderframe:SetWidth(32); borderframe:SetHeight(32); local border = borderframe:CreateTexture(nil, "OVERLAY"); border:SetAllPoints(borderframe); border:SetTexture("Interface\\BUTTONS\\UI-Quickslot2.blp"); border:SetTexCoord(0.2, 0.8, 0.2, 0.8); local texture = borderframe:CreateTexture(); borderframe.texture = texture; texture:SetPoint("CENTER", borderframe, "CENTER"); return borderframe; end local function modifyThumbnail(parent, region, data, fullModify, size) region:SetParent(parent) size = size or 30; local scale; if(data.height > data.width) then scale = size/data.height; region.texture:SetWidth(scale * data.width); region.texture:SetHeight(size); else scale = size/data.width; region.texture:SetWidth(size); region.texture:SetHeight(scale * data.height); end local frame = 1; region.foreground = region.foreground or {} local tdata = texture_data[data.foregroundTexture]; if (tdata) then local lastFrame = tdata.count - 1; region.startFrame = floor( (data.startPercent or 0) * lastFrame) + 1; region.endFrame = floor( (data.endPercent or 1) * lastFrame) + 1; region.foreground.rows = tdata.rows; region.foreground.columns = tdata.columns; region.foreground.fileWidth = 0 region.foreground.fileHeight = 0 region.foreground.frameWidth = 0 region.foreground.frameHeight = 0 else local pattern = "%.x(%d+)y(%d+)f(%d+)%.[tb][gl][ap]" local pattern2 = "%.x(%d+)y(%d+)f(%d+)w(%d+)h(%d+)W(%d+)H(%d+)%.[tb][gl][ap]" local rows, columns, frames = data.foregroundTexture:lower():match(pattern) if rows then local lastFrame = frames - 1; region.startFrame = floor( (data.startPercent or 0) * lastFrame) + 1; region.endFrame = floor( (data.endPercent or 1) * lastFrame) + 1; region.foreground.rows = tonumber(rows); region.foreground.columns = tonumber(columns); region.foreground.fileWidth = 0 region.foreground.fileHeight = 0 region.foreground.frameWidth = 0 region.foreground.frameHeight = 0 else local rows, columns, frames, frameWidth, frameHeight, fileWidth, fileHeight = data.foregroundTexture:match(pattern2) if rows then local lastFrame = frames - 1; region.startFrame = floor( (data.startPercent or 0) * lastFrame) + 1; region.endFrame = floor( (data.endPercent or 1) * lastFrame) + 1; region.foreground.rows = tonumber(rows) region.foreground.columns = tonumber(columns) region.foreground.fileWidth = tonumber(fileWidth) region.foreground.fileHeight = tonumber(fileHeight) region.foreground.frameWidth = tonumber(frameWidth) region.foreground.frameHeight = tonumber(frameHeight) else local lastFrame = data.customForegroundFrames - 1; region.startFrame = floor( (data.startPercent or 0) * lastFrame) + 1; region.endFrame = floor( (data.endPercent or 1) * lastFrame) + 1; region.foreground.rows = data.customForegroundRows; region.foreground.columns = data.customForegroundColumns; region.foreground.fileWidth = data.customForegroundFileWidth region.foreground.fileHeight = data.customForegroundFileHeight region.foreground.frameWidth = data.customForegroundFrameWidth region.foreground.frameHeight = data.customForegroundFrameHeight end end end if (region.startFrame and region.endFrame) then frame = floor(region.startFrame + (region.endFrame - region.startFrame) * 0.75); end local texture = data.foregroundTexture or "Interface\\AddOns\\WeakAuras\\Media\\Textures\\stopmotion"; if (region.foreground.rows and region.foreground.columns) then OptionsPrivate.Private.SetTextureOrAtlas(region.texture, texture) local frameScaleW, frameScaleH = 1, 1 if region.foreground.fileWidth and region.foreground.frameWidth and region.foreground.fileWidth > 0 and region.foreground.frameWidth > 0 then frameScaleW = (region.foreground.frameWidth * region.foreground.columns) / region.foreground.fileWidth end if region.foreground.fileHeight and region.foreground.frameHeight and region.foreground.fileHeight > 0 and region.foreground.frameHeight > 0 then frameScaleH = (region.foreground.frameHeight * region.foreground.rows) / region.foreground.fileHeight end setTile(region.texture, frame, region.foreground.rows, region.foreground.columns, frameScaleW, frameScaleH); region.SetValue = function(self, percent) local frame = floor(percent * (region.endFrame - region.startFrame) + region.startFrame); setTile(self.texture, frame, region.foreground.rows, region.foreground.columns, frameScaleW, frameScaleH); end else region.texture:SetTexture(texture .. format("%03d", frame)); region.texture:SetTexCoord(0, 1, 0, 1); region.SetValue = function(self, percent) local frame = floor(percent * (region.endFrame - region.startFrame) + region.startFrame); self.texture:SetTexture((data.foregroundTexture) .. format("%03d", frame)); end end region.texture:SetVertexColor(data.foregroundColor[1], data.foregroundColor[2], data.foregroundColor[3], data.foregroundColor[4]) region.texture:SetBlendMode(data.blendMode); region.elapsed = 0; region:SetScript("OnUpdate", function(self, elapsed) region.elapsed = region.elapsed + elapsed; if(region.elapsed > 4) then region.elapsed = region.elapsed - 4; end region:SetValue(region.elapsed / 4); end); end local function createIcon() local data = { height = 30, width = 30, foregroundTexture = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\stopmotion", foregroundColor = {1, 1, 1, 1}, blendMode = "ADD", rotate = false, rotation = 0, startPercent = 0, endPercent = 1, backgroundPercent = 1, animationType = "progress" }; local thumbnail = createThumbnail(); modifyThumbnail(UIParent, thumbnail, data, nil, 75); thumbnail.elapsed = 0; thumbnail:SetScript("OnUpdate", function(self, elapsed) thumbnail.elapsed = thumbnail.elapsed + elapsed; if(thumbnail.elapsed > 2) then thumbnail.elapsed = thumbnail.elapsed - 2; end thumbnail:SetValue(thumbnail.elapsed / 2); end); return thumbnail; end OptionsPrivate.registerRegions = OptionsPrivate.registerRegions or {} table.insert(OptionsPrivate.registerRegions, function() OptionsPrivate.Private.RegisterRegionOptions("stopmotion", createOptions, createIcon, L["Stop Motion"], createThumbnail, modifyThumbnail, L["Shows a stop motion texture"]); end)