if not WeakAuras.IsCorrectVersion() then return end local L = WeakAuras.L; local defaultFont = WeakAuras.defaultFont local defaultFontSize = WeakAuras.defaultFontSize -- Credit to CommanderSirow for taking the time to properly craft the TransformPoint function -- to the enhance the abilities of Progress Textures. -- Also Credit to Semlar for explaining how circular progress can be shown -- NOTES: -- Most SetValue() changes are quite equal (among compress/non-compress) -- (There is no GUI button for mirror_v, but mirror_h) -- New/Used variables -- region.user_x (0) - User defined center x-shift [-1, 1] -- region.user_y (0) - User defined center y-shift [-1, 1] -- region.mirror_v (false) - Mirroring along x-axis [bool] -- region.mirror_h (false) - Mirroring along y-axis [bool] -- region.scale (1.0) - user defined scaling [1, INF] -- region.full_rotation (false) - Allow full rotation [bool] local default = { foregroundTexture = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\Aura3", backgroundTexture = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\Aura3", desaturateBackground = false, desaturateForeground = false, sameTexture = true, compress = false, blendMode = "BLEND", textureWrapMode = "CLAMP", backgroundOffset = 2, width = 200, height = 200, orientation = "VERTICAL", inverse = false, foregroundColor = {1, 1, 1, 1}, backgroundColor = {0.5, 0.5, 0.5, 0.5}, startAngle = 0, endAngle = 360, user_x = 0, user_y = 0, crop_x = 0.41, crop_y = 0.41, rotation = 0, selfPoint = "CENTER", anchorPoint = "CENTER", anchorFrameType = "SCREEN", xOffset = 0, yOffset = 0, font = defaultFont, fontSize = defaultFontSize, mirror = false, frameStrata = 1, slantMode = "INSIDE" }; WeakAuras.regionPrototype.AddAlphaToDefault(default); WeakAuras.regionPrototype.AddAdjustedDurationToDefault(default); local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; local properties = { desaturateForeground = { display = L["Desaturate Foreground"], setter = "SetForegroundDesaturated", type = "bool", }, desaturateBackground = { display = L["Desaturate Background"], setter = "SetBackgroundDesaturated", type = "bool", }, foregroundColor = { display = L["Foreground Color"], setter = "Color", type = "color" }, backgroundColor = { display = L["Background Color"], setter = "SetBackgroundColor", type = "color" }, width = { display = L["Width"], setter = "SetRegionWidth", type = "number", min = 1, softMax = screenWidth, bigStep = 1, default = 32 }, height = { display = L["Height"], setter = "SetRegionHeight", type = "number", min = 1, softMax = screenHeight, bigStep = 1, default = 32 }, orientation = { display = L["Orientation"], setter = "SetOrientation", type = "list", values = WeakAuras.orientation_with_circle_types }, inverse = { display = L["Inverse"], setter = "SetInverse", type = "bool" }, mirror = { display = L["Mirror"], setter = "SetMirror", type = "bool" } } WeakAuras.regionPrototype.AddProperties(properties, default); local function GetProperties(data) local overlayInfo = WeakAuras.GetOverlayInfo(data); if (overlayInfo and next(overlayInfo)) then local auraProperties = {}; WeakAuras.DeepCopy(properties, auraProperties); for id, display in ipairs(overlayInfo) do auraProperties["overlays." .. id] = { display = string.format(L["%s Overlay Color"], display), setter = "SetOverlayColor", arg1 = id, type = "color", } end return auraProperties; else return CopyTable(properties); end end local spinnerFunctions = {}; function spinnerFunctions.SetTexture(self, texture) for i = 1, 3 do WeakAuras.SetTexture(self.textures[i], texture) end end function spinnerFunctions.SetDesaturated(self, desaturate) for i = 1, 3 do self.textures[i]:SetDesaturated(desaturate); end end function spinnerFunctions.SetBlendMode(self, blendMode) for i = 1, 3 do self.textures[i]:SetBlendMode(blendMode); end end function spinnerFunctions.Show(self) for i = 1, 3 do self.textures[i]:Show(); end end function spinnerFunctions.Hide(self) for i = 1, 3 do self.textures[i]:Hide(); end end function spinnerFunctions.Color(self, r, g, b, a) for i = 1, 3 do self.textures[i]:SetVertexColor(r, g, b, a); end end function spinnerFunctions.UpdateSize(self) if (self.region) then self:SetProgress(self.region, self.angle1, self.angle2); end end function spinnerFunctions.SetProgress(self, region, angle1, angle2) self.region = region; self.angle1 = angle1; self.angle2 = angle2; local crop_x = region.crop_x or 1; local crop_y = region.crop_y or 1; local rotation = region.rotation or 0; local mirror_h = region.mirror_h or false; if region.mirror then mirror_h = not mirror_h end local mirror_v = region.mirror_v or false; local width = region.width * (region.scalex or 1) + 2 * self.offset; local height = region.height * (region.scaley or 1) + 2 * self.offset; if (angle2 - angle1 >= 360) then -- SHOW everything self.coords[1]:SetFull(); self.coords[1]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[1]:Show(); self.coords[2]:Hide(); self.coords[3]:Hide(); return; end if (angle1 == angle2) then self.coords[1]:Hide(); self.coords[2]:Hide(); self.coords[3]:Hide(); return; end local index1 = floor((angle1 + 45) / 90); local index2 = floor((angle2 + 45) / 90); if (index1 + 1 >= index2) then self.coords[1]:SetAngle(width, height, angle1, angle2); self.coords[1]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[1]:Show(); self.coords[2]:Hide(); self.coords[3]:Hide(); elseif(index1 + 3 >= index2) then local firstEndAngle = (index1 + 1) * 90 + 45; self.coords[1]:SetAngle(width, height, angle1, firstEndAngle); self.coords[1]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[1]:Show(); self.coords[2]:SetAngle(width, height, firstEndAngle, angle2); self.coords[2]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[2]:Show(); self.coords[3]:Hide(); else local firstEndAngle = (index1 + 1) * 90 + 45; local secondEndAngle = firstEndAngle + 180; self.coords[1]:SetAngle(width, height, angle1, firstEndAngle); self.coords[1]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[1]:Show(); self.coords[2]:SetAngle(width, height, firstEndAngle, secondEndAngle); self.coords[2]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[2]:Show(); self.coords[3]:SetAngle(width, height, secondEndAngle, angle2); self.coords[3]:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v); self.coords[3]:Show(); end end function spinnerFunctions.SetBackgroundOffset(self, region, offset) self.offset = offset; for i = 1, 3 do self.textures[i]:SetPoint('TOPRIGHT', region, offset, offset) self.textures[i]:SetPoint('BOTTOMRIGHT', region, offset, -offset) self.textures[i]:SetPoint('BOTTOMLEFT', region, -offset, -offset) self.textures[i]:SetPoint('TOPLEFT', region, -offset, offset) end self:UpdateSize(); end function spinnerFunctions:SetHeight(height) for i = 1, 3 do self.textures[i]:SetHeight(height); end end function spinnerFunctions:SetWidth(width) for i = 1, 3 do self.textures[i]:SetWidth(width); end end local defaultTexCoord = { ULx = 0, ULy = 0, LLx = 0, LLy = 1, URx = 1, URy = 0, LRx = 1, LRy = 1, }; local function createTexCoord(texture) local coord = { ULx = 0, ULy = 0, LLx = 0, LLy = 1, URx = 1, URy = 0, LRx = 1, LRy = 1, ULvx = 0, ULvy = 0, LLvx = 0, LLvy = 0, URvx = 0, URvy = 0, LRvx = 0, LRvy = 0, texture = texture; }; function coord:MoveCorner(width, height, corner, x, y) local rx = defaultTexCoord[corner .. "x"] - x; local ry = defaultTexCoord[corner .. "y"] - y; coord[corner .. "vx"] = -rx * width; coord[corner .. "vy"] = ry * height; coord[corner .. "x"] = x; coord[corner .. "y"] = y; end function coord:Hide() coord.texture:Hide(); end function coord:Show() coord:Apply(); coord.texture:Show(); end function coord:SetFull() coord.ULx = 0; coord.ULy = 0; coord.LLx = 0; coord.LLy = 1; coord.URx = 1; coord.URy = 0; coord.LRx = 1; coord.LRy = 1; coord.ULvx = 0; coord.ULvy = 0; coord.LLvx = 0; coord.LLvy = 0; coord.URvx = 0; coord.URvy = 0; coord.LRvx = 0; coord.LRvy = 0; end function coord:Apply() --coord.texture:SetVertexOffset(UPPER_RIGHT_VERTEX, coord.URvx, coord.URvy); --coord.texture:SetVertexOffset(UPPER_LEFT_VERTEX, coord.ULvx, coord.ULvy); --coord.texture:SetVertexOffset(LOWER_RIGHT_VERTEX, coord.LRvx, coord.LRvy); --coord.texture:SetVertexOffset(LOWER_LEFT_VERTEX, coord.LLvx, coord.LLvy); coord.texture:SetTexCoord(coord.ULx, coord.ULy, coord.LLx, coord.LLy, coord.URx, coord.URy, coord.LRx, coord.LRy); end local exactAngles = { {0.5, 0}, -- 0° {1, 0}, -- 45° {1, 0.5}, -- 90° {1, 1}, -- 135° {0.5, 1}, -- 180° {0, 1}, -- 225° {0, 0.5}, -- 270° {0, 0} -- 315° } local function angleToCoord(angle) angle = angle % 360; if (angle % 45 == 0) then local index = floor (angle / 45) + 1; return exactAngles[index][1], exactAngles[index][2]; end if (angle < 45) then return 0.5 + tan(angle) / 2, 0; elseif (angle < 135) then return 1, 0.5 + tan(angle - 90) / 2 ; elseif (angle < 225) then return 0.5 - tan(angle) / 2, 1; elseif (angle < 315) then return 0, 0.5 - tan(angle - 90) / 2; elseif (angle < 360) then return 0.5 + tan(angle) / 2, 0; end end local pointOrder = { "LL", "UL", "UR", "LR", "LL", "UL", "UR", "LR", "LL", "UL", "UR", "LR" } function coord:SetAngle(width, height, angle1, angle2) local index = floor((angle1 + 45) / 90); local middleCorner = pointOrder[index + 1]; local startCorner = pointOrder[index + 2]; local endCorner1 = pointOrder[index + 3]; local endCorner2 = pointOrder[index + 4]; -- LL => 32, 32 -- UL => 32, -32 self:MoveCorner(width, height, middleCorner, 0.5, 0.5) self:MoveCorner(width, height, startCorner, angleToCoord(angle1)); local edge1 = floor((angle1 - 45) / 90); local edge2 = floor((angle2 -45) / 90); if (edge1 == edge2) then self:MoveCorner(width, height, endCorner1, angleToCoord(angle2)); else self:MoveCorner(width, height, endCorner1, defaultTexCoord[endCorner1 .. "x"], defaultTexCoord[endCorner1 .. "y"]); end self:MoveCorner(width, height, endCorner2, angleToCoord(angle2)); end local function TransformPoint(x, y, scalex, scaley, rotation, mirror_h, mirror_v, user_x, user_y) -- 1) Translate texture-coords to user-defined center x = x - 0.5 y = y - 0.5 -- 2) Shrink texture by 1/sqrt(2) x = x * 1.4142 y = y * 1.4142 -- Not yet supported for circular progress -- 3) Scale texture by user-defined amount x = x / scalex y = y / scaley -- 4) Apply mirroring if defined if mirror_h then x = -x end if mirror_v then y = -y end local cos_rotation = cos(rotation); local sin_rotation = sin(rotation); -- 5) Rotate texture by user-defined value x, y = cos_rotation * x - sin_rotation * y, sin_rotation * x + cos_rotation * y -- 6) Translate texture-coords back to (0,0) x = x + 0.5 y = y + 0.5 x = x + (user_x or 0); y = y + (user_y or 0); return x, y end function coord:Transform(scalex, scaley, rotation, mirror_h, mirror_v, user_x, user_y) coord.ULx, coord.ULy = TransformPoint(coord.ULx, coord.ULy, scalex, scaley, rotation, mirror_h, mirror_v, user_x, user_y); coord.LLx, coord.LLy = TransformPoint(coord.LLx, coord.LLy, scalex, scaley, rotation, mirror_h, mirror_v, user_x, user_y); coord.URx, coord.URy = TransformPoint(coord.URx, coord.URy, scalex, scaley, rotation, mirror_h, mirror_v, user_x, user_y); coord.LRx, coord.LRy = TransformPoint(coord.LRx, coord.LRy, scalex, scaley, rotation, mirror_h, mirror_v, user_x, user_y); end return coord; end local function createSpinner(parent, layer, drawlayer) local spinner = {}; spinner.textures = {}; spinner.coords = {}; spinner.offset = 0; for i = 1, 3 do local texture = parent:CreateTexture(nil, layer); texture:SetDrawLayer(layer, drawlayer); texture:SetAllPoints(parent); spinner.textures[i] = texture; spinner.coords[i] = createTexCoord(texture); end for k, v in pairs(spinnerFunctions) do spinner[k] = v; end return spinner; end -- Make available for the thumbnail display WeakAuras.createSpinner = createSpinner; local orientationToAnchorPoint = { ["HORIZONTAL"] = "LEFT", ["HORIZONTAL_INVERSE"] = "RIGHT", ["VERTICAL"] = "BOTTOM", ["VERTICAL_INVERSE"] = "TOP" } local textureFunctions = { SetValueFunctions = { ["HORIZONTAL"] = function(self, startProgress, endProgress) self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", startProgress, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", startProgress, 1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", endProgress, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", endProgress, 1 ); end, ["HORIZONTAL_INVERSE"] = function(self, startProgress, endProgress) self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", 1 - endProgress, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", 1 - endProgress, 1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", 1 - startProgress, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", 1 - startProgress, 1 ); end, ["VERTICAL"] = function(self, startProgress, endProgress) self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", 0, 1 - endProgress ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", 1, 1 - endProgress ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", 0, 1 - startProgress ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", 1, 1 - startProgress ); end, ["VERTICAL_INVERSE"] = function(self, startProgress, endProgress) self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", 0, startProgress ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", 1, startProgress ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", 0, endProgress ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", 1, endProgress ); end, }, SetValueFunctionsSlanted = { ["HORIZONTAL"] = function(self, startProgress, endProgress) local slant = self.slant or 0; if (self.slantMode == "EXTEND") then startProgress = startProgress * (1 + slant) - slant; endProgress = endProgress * (1 + slant) - slant; else startProgress = startProgress * (1 - slant); endProgress = endProgress * (1 - slant); end local slant1 = self.slantFirst and 0 or slant; local slant2 = self.slantFirst and slant or 0; self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", startProgress + slant1, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", startProgress + slant2, 1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", endProgress + slant1, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", endProgress + slant2, 1 ); end, ["HORIZONTAL_INVERSE"] = function(self, startProgress, endProgress) local slant = self.slant or 0; if (self.slantMode == "EXTEND") then startProgress = startProgress * (1 + slant) - slant; endProgress = endProgress * (1 + slant) - slant; else startProgress = startProgress * (1 - slant); endProgress = endProgress * (1 - slant); end local slant1 = self.slantFirst and slant or 0; local slant2 = self.slantFirst and 0 or slant; self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", 1 - endProgress - slant1, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", 1 - endProgress - slant2, 1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", 1 - startProgress - slant1, 0 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", 1 - startProgress - slant2, 1 ); end, ["VERTICAL"] = function(self, startProgress, endProgress) local slant = self.slant or 0; if (self.slantMode == "EXTEND") then startProgress = startProgress * (1 + slant) - slant; endProgress = endProgress * (1 + slant) - slant; else startProgress = startProgress * (1 - slant); endProgress = endProgress * (1 - slant); end local slant1 = self.slantFirst and slant or 0; local slant2 = self.slantFirst and 0 or slant; self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", 0, 1 - endProgress - slant1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", 1, 1 - endProgress - slant2 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", 0, 1 - startProgress - slant1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", 1, 1 - startProgress - slant2 ); end, ["VERTICAL_INVERSE"] = function(self, startProgress, endProgress) local slant = self.slant or 0; if (self.slantMode == "EXTEND") then startProgress = startProgress * (1 + slant) - slant; endProgress = endProgress * (1 + slant) - slant; else startProgress = startProgress * (1 - slant); endProgress = endProgress * (1 - slant); end local slant1 = self.slantFirst and 0 or slant; local slant2 = self.slantFirst and slant or 0; self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UL", 0, startProgress + slant1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "UR", 1, startProgress + slant2 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LL", 0, endProgress + slant1 ); self.coord:MoveCorner(self:GetWidth(), self:GetHeight(), "LR", 1, endProgress + slant2 ); end, }, SetBackgroundOffset = function(self, backgroundOffset) self.backgroundOffset = backgroundOffset; end, SetOrientation = function(self, orientation, compress, slanted, slant, slantFirst, slantMode) self.SetValueFunction = slanted and self.SetValueFunctionsSlanted[orientation] or self.SetValueFunctions[orientation]; self.compress = compress; self.slanted = slanted; self.slant = slant; self.slantFirst = slantFirst; self.slantMode = slantMode; if (self.compress) then self:ClearAllPoints(); local anchor = orientationToAnchorPoint[orientation]; self:SetPoint(anchor, self.region, anchor); self.horizontal = orientation == "HORIZONTAL" or orientation == "HORIZONTAL_INVERSE"; else local offset = self.backgroundOffset or 0; self:ClearAllPoints(); self:SetPoint("BOTTOMLEFT", self.region, "BOTTOMLEFT", -1 * offset, -1 * offset); self:SetPoint("TOPRIGHT", self.region, "TOPRIGHT", offset, offset); end self:Update(); end, SetValue = function(self, startProgress, endProgress) self.startProgress = startProgress; self.endProgress = endProgress; if (self.compress) then local progress = self.region.progress or 1; local horScale = self.horizontal and progress or 1; local verScale = self.horizontal and 1 or progress; self:SetWidth(self.region:GetWidth() * horScale); self:SetHeight(self.region:GetHeight() * verScale); if (progress > 0.1) then startProgress = startProgress / progress; endProgress = endProgress / progress; else startProgress, endProgress = 0, 0; end end self.coord:SetFull(); self:SetValueFunction(startProgress, endProgress); local region = self.region; local crop_x = region.crop_x or 1; local crop_y = region.crop_y or 1; local rotation = region.rotation or 0; local mirror_h = region.mirror_h or false; if region.mirror then mirror_h = not mirror_h end local mirror_v = region.mirror_v or false; local user_x = region.user_x; local user_y = region.user_y; self.coord:Transform(crop_x, crop_y, rotation, mirror_h, mirror_v, user_x, user_y); self.coord:Apply(); end, Update = function(self) self:SetValue(self.startProgress, self.endProgress); end, } local function createTexture(region, layer, drawlayer) local texture = region:CreateTexture(nil, layer); texture:SetDrawLayer(layer, drawlayer); for k, v in pairs(textureFunctions) do texture[k] = v; end local OrgSetTexture = texture.SetTexture; -- WORKAROUND, setting the same texture with a different wrap mode does not change the wrap mode texture.SetTexture = function(self, texture, horWrapMode, verWrapMode) local needToClear = (self.horWrapMode and self.horWrapMode ~= horWrapMode) or (self.verWrapMode and self.verWrapMode ~= verWrapMode); self.horWrapMode = horWrapMode; self.verWrapMode = verWrapMode; if (needToClear) then OrgSetTexture(self, nil); end OrgSetTexture(self, texture, horWrapMode, verWrapMode); end texture.coord = createTexCoord(texture); texture.region = region; texture.startProgress = 0; texture.endProgress = 1; texture:SetAllPoints(region); return texture; end local TextureSetValueFunction = function(self, progress) self.progress = progress; progress = max(0, progress); progress = min(1, progress); self.foreground:SetValue(0, progress); end local CircularSetValueFunctions = { ["CLOCKWISE"] = function(self, progress) local startAngle = self.startAngle; local endAngle = self.endAngle; progress = progress or 0; self.progress = progress; if (progress < 0) then progress = 0; end if (progress > 1) then progress = 1; end local pAngle = (endAngle - startAngle) * progress + startAngle; self.foregroundSpinner:SetProgress(self, startAngle, pAngle); end, ["ANTICLOCKWISE"] = function(self, progress) local startAngle = self.startAngle; local endAngle = self.endAngle; progress = progress or 0; self.progress = progress; if (progress < 0) then progress = 0; end if (progress > 1) then progress = 1; end progress = 1 - progress; local pAngle = (endAngle - startAngle) * progress + startAngle; self.foregroundSpinner:SetProgress(self, pAngle, endAngle); end } local function hideExtraTextures(extraTextures, from) for i = from, #extraTextures do extraTextures[i]:Hide(); end end local function ensureExtraTextures(region, count) for i = #region.extraTextures + 1, count do local extraTexture = createTexture(region, "ARTWORK", min(i, 7)); extraTexture:SetTexture(region.currentTexture, region.textureWrapMode, region.textureWrapMode) extraTexture:SetBlendMode(region.foreground:GetBlendMode()); extraTexture:SetOrientation(region.orientation, region.compress, region.slanted, region.slant, region.slantFirst, region.slantMode); region.extraTextures[i] = extraTexture; end end local function ensureExtraSpinners(region, count) local parent = region:GetParent(); for i = #region.extraSpinners + 1, count do local extraSpinner = createSpinner(region, "OVERLAY", min(i, 7)); extraSpinner:SetTexture(region.currentTexture); extraSpinner:SetBlendMode(region.foreground:GetBlendMode()); region.extraSpinners[i] = extraSpinner; end end local function convertToProgress(rprogress, additionalProgress, adjustMin, totalWidth, inverse, clamp) local startProgress = 0; local endProgress = 0; if (additionalProgress.min and additionalProgress.max) then if (totalWidth ~= 0) then startProgress = max( (additionalProgress.min - adjustMin) / totalWidth, 0); endProgress = (additionalProgress.max - adjustMin) / totalWidth; if (inverse) then startProgress = 1 - startProgress; endProgress = 1 - endProgress; end end elseif (additionalProgress.direction) then local forwardDirection = (additionalProgress.direction or "forward") == "forward"; if (inverse) then forwardDirection = not forwardDirection; end local width = additionalProgress.width or 0; local offset = additionalProgress.offset or 0; if (width ~= 0) then if (forwardDirection) then startProgress = rprogress + offset / totalWidth ; endProgress = rprogress + (offset + width) / totalWidth; else startProgress = rprogress - (width + offset) / totalWidth; endProgress = rprogress - offset / totalWidth; end end end if (clamp) then startProgress = max(0, min(1, startProgress)); endProgress = max(0, min(1, endProgress)); end return startProgress, endProgress; end local function UpdateAdditionalProgress(self) self:SetAdditionalProgress(self.additionalProgress, self.additionalProgressMin, self.additionalProgressMax, self.additionalProgressInverse) end local function SetAdditionalProgress(self, additionalProgress, min, max, inverse) self.additionalProgress = additionalProgress; self.additionalProgressMin = min; self.additionalProgressMax = max; self.additionalProgressInverse = inverse; local effectiveInverse = (inverse and not self.inverseDirection) or (not inverse and self.inverseDirection); if (additionalProgress) then ensureExtraTextures(self, #additionalProgress); for index, additionalProgress in ipairs(additionalProgress) do local extraTexture = self.extraTextures[index]; local totalWidth = max - min; local startProgress, endProgress = convertToProgress(self.progress, additionalProgress, min, totalWidth, effectiveInverse, self.overlayclip); if ((endProgress - startProgress) == 0) then extraTexture:Hide(); else extraTexture:Show(); local color = self.overlays[index]; if (color) then extraTexture:SetVertexColor(unpack(color)); else extraTexture:SetVertexColor(1, 1, 1, 1); end extraTexture:SetValue(startProgress, endProgress) end end hideExtraTextures(self.extraTextures, #additionalProgress + 1); else hideExtraTextures(self.extraTextures, 1); end end local function SetAdditionalProgressCircular(self, additionalProgress, min, max, inverse) self.additionalProgress = additionalProgress; self.additionalProgressMin = min; self.additionalProgressMax = max; self.additionalProgressInverse = inverse; local effectiveInverse = (inverse and not self.inverseDirection) or (not inverse and self.inverseDirection); if (additionalProgress) then ensureExtraSpinners(self, #additionalProgress); for index, additionalProgress in ipairs(additionalProgress) do local extraSpinner = self.extraSpinners[index]; local totalWidth = max - min; local startProgress, endProgress = convertToProgress(self.progress, additionalProgress, min, totalWidth, effectiveInverse, self.overlayclip); if (endProgress < startProgress) then startProgress, endProgress = endProgress, startProgress; end if (self.orientation == "ANTICLOCKWISE") then startProgress, endProgress = 1 - endProgress, 1 - startProgress; end if ((endProgress - startProgress) == 0) then extraSpinner:SetProgress(self, 0, 0); else local color = self.overlays[index]; if (color) then extraSpinner:Color(unpack(color)); else extraSpinner:Color(1, 1, 1, 1); end local startAngle = self.startAngle; local diffAngle = self.endAngle - startAngle; local pAngleStart = diffAngle * startProgress + startAngle; local pAngleEnd = diffAngle * endProgress + startAngle; if (pAngleStart < 0) then pAngleStart = pAngleStart + 360; pAngleEnd = pAngleEnd + 360; end extraSpinner:SetProgress(self, pAngleStart, pAngleEnd); end end else hideExtraTextures(self.extraSpinners, 1); end end local function showCircularProgress(region) region.foreground:Hide(); region.background:Hide(); region.foregroundSpinner:Show(); region.backgroundSpinner:Show(); for i = 1, #region.extraTextures do region.extraTextures[i]:Hide(); end end local function hideCircularProgress(region) region.foreground:Show(); region.background:Show(); region.foregroundSpinner:Hide(); region.backgroundSpinner:Hide(); for i = 1, #region.extraSpinners do region.extraSpinners[i]:Hide(); end end local function SetOrientation(region, orientation) region.orientation = orientation; if(region.orientation == "CLOCKWISE" or region.orientation == "ANTICLOCKWISE") then showCircularProgress(region); region.foregroundSpinner:UpdateSize(); region.backgroundSpinner:UpdateSize(); region.SetValueOnTexture = CircularSetValueFunctions[region.orientation]; region.SetAdditionalProgress = SetAdditionalProgressCircular; else hideCircularProgress(region); region.background:SetOrientation(orientation, nil, region.slanted, region.slant, region.slantFirst, region.slantMode); region.foreground:SetOrientation(orientation, region.compress, region.slanted, region.slant, region.slantFirst, region.slantMode); region.SetValueOnTexture = TextureSetValueFunction; region.SetAdditionalProgress = SetAdditionalProgress; for _, extraTexture in ipairs(region.extraTextures) do extraTexture:SetOrientation(orientation, region.compress, region.slanted, region.slant, region.slantFirst, region.slantMode); end end region:SetValueOnTexture(region.progress); region:UpdateAdditionalProgress(); end local function create(parent) local font = "GameFontHighlight"; local region = CreateFrame("FRAME", nil, parent); region:SetMovable(true); region:SetResizable(true); region:SetMinResize(1, 1); local background = createTexture(region, "BACKGROUND", 0); region.background = background; -- For horizontal/vertical progress local foreground = createTexture(region, "ARTWORK", 0); region.foreground = foreground; region.foregroundSpinner = createSpinner(region, "ARTWORK", 1); region.backgroundSpinner = createSpinner(region, "BACKGROUND", 1); region.extraTextures = {}; region.extraSpinners = {}; region.values = {}; -- Use a dummy object for the SmoothStatusBarMixin, because our SetValue -- is used for a different purpose region.smoothProgress = {}; WeakAuras:Mixin(region.smoothProgress, SmoothStatusBarMixin); region.smoothProgress.SetValue = function(self, progress) region:SetValueOnTexture(progress); region:UpdateAdditionalProgress(); end region.smoothProgress.GetValue = function(self) return region.progress; end region.smoothProgress.GetMinMaxValues = function(self) return 0, 1; end region.SetOrientation = SetOrientation; WeakAuras.regionPrototype.create(region); region.AnchorSubRegion = WeakAuras.regionPrototype.AnchorSubRegion return region; end local function TimerTick(self) local adjustMin = self.adjustedMin or self.adjustedMinRel or 0; local duration = self.state.duration self:SetTime( (duration ~= 0 and (self.adjustedMax or self.adjustedMaxRel) or duration) - adjustMin, self.state.expirationTime - adjustMin, self.state.inverse); end local function modify(parent, region, data) WeakAuras.regionPrototype.modify(parent, region, data); local background, foreground = region.background, region.foreground; local foregroundSpinner, backgroundSpinner = region.foregroundSpinner, region.backgroundSpinner; region:SetWidth(data.width); region:SetHeight(data.height); region.width = data.width; region.height = data.height; region.scalex = 1; region.scaley = 1; region.aspect = data.width / data.height; region.overlayclip = data.overlayclip; region.textureWrapMode = data.textureWrapMode; background:SetBackgroundOffset(data.backgroundOffset); background:SetTexture(data.sameTexture and data.foregroundTexture or data.backgroundTexture, region.textureWrapMode, region.textureWrapMode); background:SetDesaturated(data.desaturateBackground) background:SetVertexColor(data.backgroundColor[1], data.backgroundColor[2], data.backgroundColor[3], data.backgroundColor[4]); background:SetBlendMode(data.blendMode); backgroundSpinner:SetTexture(data.sameTexture and data.foregroundTexture or data.backgroundTexture); backgroundSpinner:SetDesaturated(data.desaturateBackground) backgroundSpinner:Color(data.backgroundColor[1], data.backgroundColor[2], data.backgroundColor[3], data.backgroundColor[4]); backgroundSpinner:SetBlendMode(data.blendMode); region.currentTexture = data.foregroundTexture; foreground:SetTexture(data.foregroundTexture, region.textureWrapMode, region.textureWrapMode); foreground:SetDesaturated(data.desaturateForeground) foreground:SetBlendMode(data.blendMode); foregroundSpinner:SetTexture(data.foregroundTexture); foregroundSpinner:SetDesaturated(data.desaturateForeground); foregroundSpinner:SetBlendMode(data.blendMode); for _, extraTexture in ipairs(region.extraTextures) do extraTexture:SetTexture(data.foregroundTexture, region.textureWrapMode, region.textureWrapMode) extraTexture:SetBlendMode(data.blendMode); end for _, extraSpinner in ipairs(region.extraSpinners) do extraSpinner:SetTexture(data.foregroundTexture); extraSpinner:SetBlendMode(data.blendMode); end region.mirror = data.mirror region.crop_x = 1 + (data.crop_x or 0.41); region.crop_y = 1 + (data.crop_y or 0.41); region.rotation = data.rotation or 0; region.user_x = -1 * (data.user_x or 0); region.user_y = data.user_y or 0; region.startAngle = (data.startAngle or 0) % 360; region.endAngle = (data.endAngle or 360) % 360; if (region.endAngle <= region.startAngle) then region.endAngle = region.endAngle + 360; end region.compress = data.compress; region.inverseDirection = data.inverse; region.progress = 0.667; backgroundSpinner:SetProgress(region, region.startAngle, region.endAngle); backgroundSpinner:SetBackgroundOffset(region, data.backgroundOffset); region.overlays = {}; if (data.overlays) then WeakAuras.DeepCopy(data.overlays, region.overlays); end region.UpdateAdditionalProgress = UpdateAdditionalProgress; region.slanted = data.slanted; region.slant = data.slant; region.slantFirst = data.slantFirst; region.slantMode = data.slantMode; region:SetOrientation(data.orientation); local function DoPosition(region) local mirror = region.mirror_h if region.mirror then mirror = not mirror end if(mirror) then if(data.orientation == "HORIZONTAL_INVERSE") then foreground:SetPoint("RIGHT", region, "RIGHT"); elseif(data.orientation == "HORIZONTAL") then foreground:SetPoint("LEFT", region, "LEFT"); end else if(data.orientation == "HORIZONTAL") then foreground:SetPoint("LEFT", region, "LEFT"); elseif(data.orientation == "HORIZONTAL_INVERSE") then foreground:SetPoint("RIGHT", region, "RIGHT"); end end if(region.mirror_v) then if(data.orientation == "VERTICAL_INVERSE") then foreground:SetPoint("TOP", region, "TOP"); elseif(data.orientation == "VERTICAL") then foreground:SetPoint("BOTTOM", region, "BOTTOM"); end else if(data.orientation == "VERTICAL") then foreground:SetPoint("BOTTOM", region, "BOTTOM"); elseif(data.orientation == "VERTICAL_INVERSE") then foreground:SetPoint("TOP", region, "TOP"); end end region:SetWidth(region.width * region.scalex); region:SetHeight(region.height * region.scaley); if (data.orientation == "CLOCKWISE" or data.orientation == "ANTICLOCKWISE") then region.foregroundSpinner:UpdateSize(); region.backgroundSpinner:UpdateSize(); for i = 1, #region.extraSpinners do region.extraSpinners[i]:UpdateSize(); end else region.background:Update(); region.foreground:Update(); for _, extraTexture in ipairs(region.extraTextures) do extraTexture:Update(); end end end function region:Scale(scalex, scaley) if(scalex < 0) then region.mirror_h = true; scalex = scalex * -1; end if(scaley < 0) then region.mirror_v = true; scaley = scaley * -1; end region.scalex = scalex; region.scaley = scaley; DoPosition(region) end function region:SetMirror(mirror) region.mirror = mirror DoPosition(region) end function region:Rotate(angle) region.rotation = angle or 0; if (data.orientation == "CLOCKWISE" or data.orientation == "ANTICLOCKWISE") then region.foregroundSpinner:UpdateSize(); region.backgroundSpinner:UpdateSize(); for i = 1, #region.extraSpinners do region.extraSpinners[i]:UpdateSize(); end else region.background:Update(); region.foreground:Update(); for _, extraTexture in ipairs(region.extraTextures) do extraTexture:Update(); end end end function region:GetRotation() return region.rotation; end function region:Color(r, g, b, a) region.color_r = r; region.color_g = g; region.color_b = b; if (r or g or b) then a = a or 1; end region.color_a = a; foreground:SetVertexColor(region.color_anim_r or r, region.color_anim_g or g, region.color_anim_b or b, region.color_anim_a or a); foregroundSpinner:Color(region.color_anim_r or r, region.color_anim_g or g, region.color_anim_b or b, region.color_anim_a or a); end function region:ColorAnim(r, g, b, a) region.color_anim_r = r; region.color_anim_g = g; region.color_anim_b = b; region.color_anim_a = a; if (r or g or b) then a = a or 1; end foreground:SetVertexColor(r or region.color_r, g or region.color_g, b or region.color_b, a or region.color_a); foregroundSpinner:Color(r or region.color_r, g or region.color_g, b or region.color_b, a or region.color_a); end function region:GetColor() return region.color_r or data.foregroundColor[1], region.color_g or data.foregroundColor[2], region.color_b or data.foregroundColor[3], region.color_a or data.foregroundColor[4]; end region:Color(data.foregroundColor[1], data.foregroundColor[2], data.foregroundColor[3], data.foregroundColor[4]); function region:SetTime(duration, expirationTime, inverse) local progress = 1; if (duration ~= 0) then local remaining = expirationTime - GetTime(); progress = remaining / duration; local inversed = (not inverse and region.inverseDirection) or (inverse and not region.inverseDirection); if(inversed) then progress = 1 - progress; end end progress = progress > 0.0001 and progress or 0.0001; if (data.smoothProgress) then region.smoothProgress:SetSmoothedValue(progress); else region:SetValueOnTexture(progress); region:UpdateAdditionalProgress(); end end function region:SetValue(value, total) local progress = 1 if(total > 0) then progress = value / total; if(region.inverseDirection) then progress = 1 - progress; end end progress = progress > 0.0001 and progress or 0.0001; if (data.smoothProgress) then region.smoothProgress:SetSmoothedValue(progress); else region:SetValueOnTexture(progress); region:UpdateAdditionalProgress(); end end function region:Update() local state = region.state local max if state.progressType == "timed" then local expirationTime = state.expirationTime and state.expirationTime > 0 and state.expirationTime or math.huge; local duration = state.duration or 0 if region.adjustedMinRelPercent then region.adjustedMinRel = region.adjustedMinRelPercent * duration end local adjustMin = region.adjustedMin or region.adjustedMinRel or 0; if duration == 0 then max = 0 elseif region.adjustedMax then max = region.adjustedMax elseif region.adjustedMaxRelPercent then region.adjustedMaxRel = region.adjustedMaxRelPercent * duration max = region.adjustedMaxRel else max = duration end region:SetTime(max - adjustMin, expirationTime - adjustMin, state.inverse); if not region.TimerTick then region.TimerTick = TimerTick region:UpdateRegionHasTimerTick() end elseif state.progressType == "static" then local value = state.value or 0; local total = state.total or 0; if region.adjustedMinRelPercent then region.adjustedMinRel = region.adjustedMinRelPercent * total end local adjustMin = region.adjustedMin or region.adjustedMinRel or 0; if region.adjustedMax then max = region.adjustedMax elseif region.adjustedMaxRelPercent then region.adjustedMaxRel = region.adjustedMaxRelPercent * total max = region.adjustedMaxRel else max = total end region:SetValue(value - adjustMin, max - adjustMin); if region.TimerTick then region.TimerTick = nil region:UpdateRegionHasTimerTick() end else region:SetTime(0, math.huge) if region.TimerTick then region.TimerTick = nil region:UpdateRegionHasTimerTick() end end max = max or 0 region:SetAdditionalProgress(state.additionalProgress, region.adjustMin or 0, region.state.duration ~= 0 and max or state.total or state.duration or 0, state.inverse) if state.texture then region:SetTexture(state.texture) end end function region:SetTexture(texture) region.currentTexture = texture; region.foreground:SetTexture(texture, region.textureWrapMode, region.textureWrapMode); foregroundSpinner:SetTexture(texture); if (data.sameTexture) then background:SetTexture(texture, region.textureWrapMode, region.textureWrapMode); backgroundSpinner:SetTexture(texture); end for _, extraTexture in ipairs(region.extraTextures) do extraTexture:SetTexture(texture, region.textureWrapMode, region.textureWrapMode) end for _, extraSpinner in ipairs(region.extraSpinners) do extraSpinner:SetTexture(texture); end end function region:SetForegroundDesaturated(b) region.foreground:SetDesaturated(b); region.foregroundSpinner:SetDesaturated(b); end function region:SetBackgroundDesaturated(b) region.background:SetDesaturated(b); region.backgroundSpinner:SetDesaturated(b); end function region:SetBackgroundColor(r, g, b, a) region.background:SetVertexColor(r, g, b, a); region.backgroundSpinner:Color(r, g, b, a); end function region:SetRegionWidth(width) region.width = width; region:Scale(region.scalex, region.scaley); end function region:SetRegionHeight(height) region.height = height; region:Scale(region.scalex, region.scaley); end function region:SetInverse(inverse) if (region.inverseDirection == inverse) then return; end region.inverseDirection = inverse; local progress = 1 - region.progress; progress = progress > 0.0001 and progress or 0.0001; region:SetValueOnTexture(progress); region:UpdateAdditionalProgress(); end function region:SetOverlayColor(id, r, g, b, a) self.overlays[id] = { r, g, b, a}; if (self.extraTextures[id]) then self.extraTextures[id]:SetVertexColor(r, g, b, a); end if (self.extraSpinners[id]) then self.extraSpinners[id]:Color(r, g, b, a); end end WeakAuras.regionPrototype.modifyFinish(parent, region, data); end WeakAuras.RegisterRegionType("progresstexture", create, modify, default, GetProperties);