From 12b4c29817899402bdc9b1c20cc57e9f56ff4ad0 Mon Sep 17 00:00:00 2001 From: Infus Date: Sun, 11 Jun 2023 15:56:58 +0200 Subject: [PATCH] Refactor progress handling * Make progress data (that is expirationTime/duration/value/total/etc) far less special state properties * Instead state metadata can now describe multiple properties as progress values and which other properties are relevant, e.g. charge = { display = "Charges", type = "number", total = "totalCharge" } will allow the user to select "charge" as progress source, which will automatically use state.totalCharge for the total value. Or someTime = { display = "X", type = "timer", total = "propertyName", modRate = "propertyName", inverse = "propertyName", paused = "propertyName", remaining = "propertyName" } All of the additional properties are optional. And lastly, someOtherTime = { display = "Y", type = "elapsedTimer" } For progress sources that are past time stamps. * Stacks, Unit count, etc are all potential progress sources now. * On the Display tab, the progress source can be selected from any trigger. * Conditions can switch the progress source * Ticks progress source is configurable and can be different from the main aura. This can be used via custom trigger to augment a main trigger * state.autoHide can be a time point at which the state should automatically hide. * Some behaviour changes, e.g. animations now observe the paused/remaining time and min/max progress settings * Probably some regressions Fixes: #4527 Fixes: #1449 --- WeakAuras/Animations.lua | 275 +++++----- WeakAuras/BossMods.lua | 20 +- WeakAuras/BuffTrigger2.lua | 108 +++- WeakAuras/Conditions.lua | 163 ++++-- WeakAuras/GenericTrigger.lua | 246 +++++---- WeakAuras/Init.lua | 25 +- WeakAuras/Modernize.lua | 24 + WeakAuras/Prototypes.lua | 149 +++-- WeakAuras/RegionTypes/AuraBar.lua | 174 ++---- WeakAuras/RegionTypes/Icon.lua | 132 ++--- WeakAuras/RegionTypes/ProgressTexture.lua | 196 +++---- WeakAuras/RegionTypes/RegionPrototype.lua | 508 +++++++++++------- WeakAuras/RegionTypes/StopMotion.lua | 236 +++++--- WeakAuras/RegionTypes/Text.lua | 22 +- WeakAuras/SubRegionTypes/SubText.lua | 15 +- WeakAuras/SubRegionTypes/Tick.lua | 258 +++++---- WeakAuras/SubscribableObject.lua | 17 +- WeakAuras/Types.lua | 54 +- WeakAuras/WeakAuras.lua | 260 ++++++--- .../AceGUIWidget-WeakAurasDisplayButton.lua | 18 +- ...ceGUIWidget-WeakAurasTwoColumnDropDown.lua | 7 +- WeakAurasOptions/AnimationOptions.lua | 28 +- WeakAurasOptions/CommonOptions.lua | 148 +++++ WeakAurasOptions/ConditionOptions.lua | 138 ++++- WeakAurasOptions/GenericTrigger.lua | 6 + WeakAurasOptions/RegionOptions/AuraBar.lua | 3 +- WeakAurasOptions/RegionOptions/Icon.lua | 11 +- .../RegionOptions/ProgressTexture.lua | 7 +- WeakAurasOptions/RegionOptions/StopMotion.lua | 16 +- WeakAurasOptions/SubRegionOptions/Tick.lua | 131 ++++- 30 files changed, 2120 insertions(+), 1275 deletions(-) diff --git a/WeakAuras/Animations.lua b/WeakAuras/Animations.lua index cfaeed66f9..d3d1f6deee 100644 --- a/WeakAuras/Animations.lua +++ b/WeakAuras/Animations.lua @@ -12,159 +12,166 @@ local function noopErrorHandler() end local frame = Private.frames["WeakAuras Main Frame"] -local updatingAnimations; -local last_update = GetTime(); -local function UpdateAnimations() - Private.StartProfileSystem("animations"); - - for groupUid, groupRegion in pairs(pending_controls) do - pending_controls[groupUid] = nil; - groupRegion:DoPositionChildren(); +local function RunAnimation(key, anim, elapsed, time) + Private.StartProfileUID(anim.auraUID) + local finished = false + if(anim.duration_type == "seconds") then + if anim.duration > 0 then + anim.progress = anim.progress + (elapsed / anim.duration) + else + anim.progress = anim.progress + (elapsed / 1) + end + if(anim.progress >= 1) then + anim.progress = 1 + finished = true + end + elseif(anim.duration_type == "relative") then + local region = anim.region + if ((region.progressType == "timed" and region.duration < 0.01) + or (region.progressType == "static" and region.value < 0.01)) + then + anim.progress = 0 + if(anim.type == "start" or anim.type == "finish") then + finished = true + end + else + local relativeProgress = 0 + if(region.progressType == "static") then + relativeProgress = region.value / region.total + elseif (region.progressType == "timed") then + relativeProgress = 1 - ((region.expirationTime - time) / region.duration) + end + relativeProgress = region.inverse and (1 - relativeProgress) or relativeProgress + anim.progress = anim.duration > 0 and relativeProgress / anim.duration or 0 + local iteration = math.floor(anim.progress) + --anim.progress = anim.progress - iteration + if not(anim.iteration) then + anim.iteration = iteration + elseif(anim.iteration ~= iteration) then + anim.iteration = nil + finished = true + end + end + else + anim.progress = 1 end - - local time = GetTime(); - local elapsed = time - last_update; - last_update = time; - local num = 0; - for key, anim in pairs(animations) do - Private.StartProfileUID(anim.auraUID); - num = num + 1; - local finished = false; - if(anim.duration_type == "seconds") then - if anim.duration > 0 then - anim.progress = anim.progress + (elapsed / anim.duration); - else - anim.progress = anim.progress + (elapsed / 1); + local progress = anim.inverse and (1 - anim.progress) or anim.progress + progress = anim.easeFunc(progress, anim.easeStrength or 3) + Private.ActivateAuraEnvironmentForRegion(anim.region) + if(anim.translateFunc) then + local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Slide Animation"]) + if (anim.region.SetOffsetAnim) then + local ok, x, y = xpcall(anim.translateFunc, errorHandler, progress, 0, 0, anim.dX, anim.dY) + anim.region:SetOffsetAnim(x, y) + else + anim.region:ClearAllPoints() + local ok, x, y = xpcall(anim.translateFunc, errorHandler, progress, anim.startX, anim.startY, anim.dX, anim.dY) + if (ok) then + anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, x, y) end - if(anim.progress >= 1) then - anim.progress = 1; - finished = true; + end + end + if(anim.alphaFunc) then + local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Fade Animation"]) + local ok, alpha = xpcall(anim.alphaFunc, errorHandler, progress, anim.startAlpha, anim.dAlpha) + if (ok) then + if (anim.region.SetAnimAlpha) then + anim.region:SetAnimAlpha(alpha) + else + anim.region:SetAlpha(alpha) end - elseif(anim.duration_type == "relative") then - local state = anim.region.state; - if (not state - or (state.progressType == "timed" and state.duration < 0.01) - or (state.progressType == "static" and state.value < 0.01)) then - anim.progress = 0; - if(anim.type == "start" or anim.type == "finish") then - finished = true; - end + end + end + if(anim.scaleFunc) then + local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler + or Private.GetErrorHandlerUid(anim.auraUID, L["Zoom Animation"]) + local ok, scaleX, scaleY = xpcall(anim.scaleFunc, errorHandler, progress, 1, 1, anim.scaleX, anim.scaleY) + if (ok) then + if(anim.region.Scale) then + anim.region:Scale(scaleX, scaleY) else - local relativeProgress = 0; - if(state.progressType == "static") then - relativeProgress = state.value / state.total; - elseif (state.progressType == "timed") then - relativeProgress = 1 - ((state.expirationTime - time) / state.duration); - end - relativeProgress = state.inverse and (1 - relativeProgress) or relativeProgress; - anim.progress = anim.duration > 0 and relativeProgress / anim.duration or 0 - local iteration = math.floor(anim.progress); - --anim.progress = anim.progress - iteration; - if not(anim.iteration) then - anim.iteration = iteration; - elseif(anim.iteration ~= iteration) then - anim.iteration = nil; - finished = true; - end + anim.region:SetWidth(anim.startWidth * scaleX) + anim.region:SetHeight(anim.startHeight * scaleY) end - else - anim.progress = 1; end - local progress = anim.inverse and (1 - anim.progress) or anim.progress; - progress = anim.easeFunc(progress, anim.easeStrength or 3) - Private.ActivateAuraEnvironmentForRegion(anim.region) - if(anim.translateFunc) then - local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Slide Animation"]) + end + if(anim.rotateFunc and anim.region.SetAnimRotation) then + local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler + or Private.GetErrorHandlerUid(anim.auraUID, L["Rotate Animation"]) + local ok, rotate = xpcall(anim.rotateFunc, errorHandler, progress, anim.region:GetBaseRotation(), anim.rotate) + if (ok) then + anim.region:SetAnimRotation(rotate) + end + end + if(anim.colorFunc and anim.region.ColorAnim) then + local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler + or Private.GetErrorHandlerUid(anim.auraUID, L["Color Animation"]) + local startR, startG, startB, startA = anim.region:GetColor() + startR, startG, startB, startA = startR or 1, startG or 1, startB or 1, startA or 1 + local ok, r, g, b, a = xpcall(anim.colorFunc, errorHandler, progress, startR, startG, startB, startA, + anim.colorR, anim.colorG, anim.colorB, anim.colorA) + if (ok) then + local errorHandler = Private.GetErrorHandlerId(anim.region.id, "Custom Color") + xpcall(anim.region.ColorAnim, errorHandler, anim.region, r, g, b, a) + end + end + Private.ActivateAuraEnvironment(nil) + if(finished) then + if not(anim.loop) then if (anim.region.SetOffsetAnim) then - local ok, x, y = xpcall(anim.translateFunc, errorHandler, progress, 0, 0, anim.dX, anim.dY); - anim.region:SetOffsetAnim(x, y); + anim.region:SetOffsetAnim(0, 0) else - anim.region:ClearAllPoints(); - local ok, x, y = xpcall(anim.translateFunc, errorHandler, progress, anim.startX, anim.startY, anim.dX, anim.dY); - if (ok) then - anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, x, y); + if(anim.startX) then + anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, anim.startX, anim.startY) end end - end - if(anim.alphaFunc) then - local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Fade Animation"]) - local ok, alpha = xpcall(anim.alphaFunc, errorHandler, progress, anim.startAlpha, anim.dAlpha); - if (ok) then - if (anim.region.SetAnimAlpha) then - anim.region:SetAnimAlpha(alpha); - else - anim.region:SetAlpha(alpha); - end + if (anim.region.SetAnimAlpha) then + anim.region:SetAnimAlpha(nil) + elseif(anim.startAlpha) then + anim.region:SetAlpha(anim.startAlpha) end - end - if(anim.scaleFunc) then - local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Zoom Animation"]) - local ok, scaleX, scaleY = xpcall(anim.scaleFunc, errorHandler, progress, 1, 1, anim.scaleX, anim.scaleY); - if (ok) then + if(anim.startWidth) then if(anim.region.Scale) then - anim.region:Scale(scaleX, scaleY); + anim.region:Scale(1, 1) else - anim.region:SetWidth(anim.startWidth * scaleX); - anim.region:SetHeight(anim.startHeight * scaleY); + anim.region:SetWidth(anim.startWidth) + anim.region:SetHeight(anim.startHeight) end end - end - if(anim.rotateFunc and anim.region.SetAnimRotation) then - local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Rotate Animation"]) - local ok, rotate = xpcall(anim.rotateFunc, errorHandler, progress, anim.region:GetBaseRotation(), anim.rotate); - if (ok) then - anim.region:SetAnimRotation(rotate); + if(anim.region.SetAnimRotation) then + anim.region:SetAnimRotation(nil) end - end - if(anim.colorFunc and anim.region.ColorAnim) then - local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or Private.GetErrorHandlerUid(anim.auraUID, L["Color Animation"]) - local startR, startG, startB, startA = anim.region:GetColor(); - startR, startG, startB, startA = startR or 1, startG or 1, startB or 1, startA or 1; - local ok, r, g, b, a = xpcall(anim.colorFunc, errorHandler, progress, startR, startG, startB, startA, anim.colorR, anim.colorG, anim.colorB, anim.colorA); - if (ok) then - local errorHandler = Private.GetErrorHandlerId(anim.region.id, "Custom Color") - xpcall(anim.region.ColorAnim, errorHandler, anim.region, r, g, b, a) + if(anim.region.ColorAnim) then + anim.region:ColorAnim(nil) end + animations[key] = nil end - Private.ActivateAuraEnvironment(nil); - if(finished) then - if not(anim.loop) then - if (anim.region.SetOffsetAnim) then - anim.region:SetOffsetAnim(0, 0); - else - if(anim.startX) then - anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, anim.startX, anim.startY); - end - end - if (anim.region.SetAnimAlpha) then - anim.region:SetAnimAlpha(nil); - elseif(anim.startAlpha) then - anim.region:SetAlpha(anim.startAlpha); - end - if(anim.startWidth) then - if(anim.region.Scale) then - anim.region:Scale(1, 1); - else - anim.region:SetWidth(anim.startWidth); - anim.region:SetHeight(anim.startHeight); - end - end - if(anim.region.SetAnimRotation) then - anim.region:SetAnimRotation(nil) - end - if(anim.region.ColorAnim) then - anim.region:ColorAnim(nil); - end - animations[key] = nil; - end - if(anim.loop) then - Private.Animate(anim.namespace, anim.auraUID, anim.type, anim.anim, anim.region, anim.inverse, anim.onFinished, anim.loop, anim.region.cloneId); - elseif(anim.onFinished) then - anim.onFinished(); - end + if(anim.loop) then + Private.Animate(anim.namespace, anim.auraUID, anim.type, anim.anim, anim.region, anim.inverse, anim.onFinished, + anim.loop, anim.region.cloneId) + elseif(anim.onFinished) then + anim.onFinished() end - Private.StopProfileUID(anim.auraUID); + end + Private.StopProfileUID(anim.auraUID) +end + +local updatingAnimations; +local last_update = GetTime(); +local function UpdateAnimations() + Private.StartProfileSystem("animations"); + + for groupUid, groupRegion in pairs(pending_controls) do + pending_controls[groupUid] = nil; + groupRegion:DoPositionChildren(); + end + + local time = GetTime(); + local elapsed = time - last_update; + last_update = time; + for key, anim in pairs(animations) do + RunAnimation(key, anim, elapsed, time) end Private.StopProfileSystem("animations"); @@ -177,7 +184,6 @@ function Private.RegisterGroupForPositioning(uid, region) end function Private.Animate(namespace, uid, type, anim, region, inverse, onFinished, loop, cloneId) - local auraDisplayName = Private.UIDtoID(uid) local key = tostring(region); local valid; if(anim and anim.type == "custom" and (anim.use_translate or anim.use_alpha or (anim.use_scale and region.Scale) or (anim.use_rotate and region.SetAnimRotation) or (anim.use_color and region.Color))) then @@ -371,6 +377,7 @@ function Private.Animate(namespace, uid, type, anim, region, inverse, onFinished frame:SetScript("OnUpdate", UpdateAnimations); updatingAnimations = true; end + RunAnimation(key, animation, 0, GetTime()) return true; else if(animations[key]) then diff --git a/WeakAuras/BossMods.lua b/WeakAuras/BossMods.lua index c4da07cb62..985c3c8da1 100644 --- a/WeakAuras/BossMods.lua +++ b/WeakAuras/BossMods.lua @@ -377,6 +377,7 @@ Private.event_prototypes["DBM Stage"] = { }, automaticrequired = true, statesParameter = "one", + progressType = "none" } Private.category_event_prototype.addons["DBM Stage"] = L["DBM Stage"] @@ -433,7 +434,8 @@ Private.event_prototypes["DBM Announce"] = { init = "use_cloneId and WeakAuras.GetUniqueCloneId() or ''" }, }, - timedrequired = true + timedrequired = true, + progressType = "timed" } Private.category_event_prototype.addons["DBM Announce"] = L["DBM Announce"] @@ -445,7 +447,7 @@ Private.event_prototypes["DBM Timer"] = { }, force_events = "DBM_TimerForce", name = L["DBM Timer"], - canHaveDuration = "timed", + progressType = "timed", triggerFunction = function(trigger) Private.ExecEnv.BossMods.DBM:RegisterTimer() local ret = [=[ @@ -1007,6 +1009,7 @@ Private.event_prototypes["BigWigs Stage"] = { }, automaticrequired = true, statesParameter = "one", + progressType = "none" } Private.category_event_prototype.addons["BigWigs Stage"] = L["BigWigs Stage"] @@ -1072,7 +1075,8 @@ Private.event_prototypes["BigWigs Message"] = { init = "use_cloneId and WeakAuras.GetUniqueCloneId() or ''" }, }, - timedrequired = true + timedrequired = true, + progressType = "timed" } Private.category_event_prototype.addons["BigWigs Message"] = L["BigWigs Message"] @@ -1084,7 +1088,7 @@ Private.event_prototypes["BigWigs Timer"] = { }, force_events = "BigWigs_Timer_Force", name = L["BigWigs Timer"], - canHaveDuration = "timed", + progressType = "timed", triggerFunction = function(trigger) Private.ExecEnv.BossMods.BigWigs:RegisterTimer() local ret = [=[ @@ -1350,6 +1354,7 @@ Private.event_prototypes["Boss Mod Stage"] = { }, automaticrequired = true, statesParameter = "one", + progressType = "none" } Private.category_event_prototype.addons["Boss Mod Stage"] = L["Boss Mod Stage"] @@ -1382,7 +1387,7 @@ Private.event_prototypes["Boss Mod Stage (Event)"] = { }, }, statesParameter = "one", - canHaveDuration = "timed", + progressType = "timed", delayEvents = true, timedrequired = true } @@ -1463,7 +1468,8 @@ Private.event_prototypes["Boss Mod Announce"] = { text = ActiveBossModText }, }, - timedrequired = true + timedrequired = true, + progressType = "timed" } Private.category_event_prototype.addons["Boss Mod Announce"] = L["Boss Mod Announce"] @@ -1475,7 +1481,7 @@ Private.event_prototypes["Boss Mod Timer"] = { }, force_events = "BossMod_TimerForce", name = L["Boss Mod Timer"], - canHaveDuration = "timed", + progressType = "timed", triggerFunction = function(trigger) Private.ExecEnv.BossMods.Generic:RegisterTimer() local ret = [=[ diff --git a/WeakAuras/BuffTrigger2.lua b/WeakAuras/BuffTrigger2.lua index 25384af9f1..87bfdab4b9 100644 --- a/WeakAuras/BuffTrigger2.lua +++ b/WeakAuras/BuffTrigger2.lua @@ -28,15 +28,9 @@ Updates all buff triggers in data. # Helper functions mainly for the WeakAuras Options # ##################################################### -CanHaveDuration(data, triggernum) -Returns whether the trigger can have a duration. - GetOverlayInfo(data, triggernum) Returns a table containing all overlays. Currently there aren't any -CanHaveClones(data, triggernum) -Returns whether the trigger can have clones. - CanHaveTooltip(data, triggernum) Returns the type of tooltip to show for the trigger. @@ -3233,13 +3227,6 @@ function BuffTrigger.Add(data) PublishProblems(problems, data.uid) end ---- Returns whether the trigger can have a duration. ---- @param data table ---- @param triggernum number -function BuffTrigger.CanHaveDuration(data, triggernum) - return "timed" -end - --- Returns a table containing the names of all overlays --- @param data table --- @param triggernum number @@ -3251,7 +3238,7 @@ end --- @param data table --- @param triggernum number --- @return boolean -function BuffTrigger.CanHaveClones(data, triggernum) +local function CanHaveClones(data, triggernum) local trigger = data.triggers[triggernum].trigger if not IsSingleMissing(trigger) and trigger.showClones then return true @@ -3401,6 +3388,97 @@ function BuffTrigger.GetAdditionalProperties(data, triggernum) return ret end +function BuffTrigger.GetProgressSources(data, triggernum, values) + local trigger = data.triggers[triggernum].trigger + tinsert(values, { + trigger = triggernum, + property = "matchCount", + type = "number", + display = L["Match Count"] + }) + tinsert(values, { + trigger = triggernum, + property = "matchCountPerUnit", + type = "number", + display = L["Match Count per Unit"] + }) + tinsert(values, { + trigger = triggernum, + property = "unitCount", + type = "number", + display = L["Units Affected"], + total = trigger.unit ~= "multi" and "maxUnitCount" or nil + }) + tinsert(values, { + trigger = triggernum, + property = "stacks", + type = "number", + display = L["Stacks"] + }) + tinsert(values, { + trigger = triggernum, + property = "totalStacks", + type = "number", + display = L["Total stacks over all matches"] + }) + + if not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip then + tinsert(values, { + trigger = triggernum, + property = "tooltip1", + type = "number", + display = L["Tooltip 1"] + }) + tinsert(values, { + trigger = triggernum, + property = "tooltip2", + type = "number", + display = L["Tooltip 2"] + }) + tinsert(values, { + trigger = triggernum, + property = "tooltip3", + type = "number", + display = L["Tooltip 3"] + }) + end + + tinsert(values, { + trigger = triggernum, + property = "expirationTime", + type = "timer", + display = L["Timed Progress"], + total = "duration", + modRate = "modRate", + paused = "paused", + remaining = "remaining" + }) + tinsert(values, { + trigger = triggernum, + property = "stackGainTime", + type = "elapsedTimer", + display = L["Time since stack gain"], + }) + tinsert(values, { + trigger = triggernum, + property = "stackLostTime", + type = "elapsedTimer", + display = L["Time since stack lost"], + }) + tinsert(values, { + trigger = triggernum, + property = "initialTime", + type = "elapsedTimer", + display = L["Time since initial application"], + }) + tinsert(values, { + trigger = triggernum, + property = "refreshTime", + type = "elapsedTimer", + display = L["Time since last refresh"], + }) +end + function BuffTrigger.GetTriggerConditions(data, triggernum) local trigger = data.triggers[triggernum].trigger local result = {} @@ -4288,7 +4366,7 @@ function BuffTrigger.CreateFakeStates(id, triggernum) state.progressType = "timed" state.stacks = 1 allStates[""] = state - if BuffTrigger.CanHaveClones(data, triggernum) then + if CanHaveClones(data, triggernum) then for i = 1, 2 do local state = {} BuffTrigger.CreateFallbackState(data, triggernum, state) diff --git a/WeakAuras/Conditions.lua b/WeakAuras/Conditions.lua index 8fb4be86af..8a4fe245e8 100644 --- a/WeakAuras/Conditions.lua +++ b/WeakAuras/Conditions.lua @@ -35,7 +35,7 @@ end Private.callbacks:RegisterCallback("Delete", OnDelete) -local function formatValueForAssignment(vType, value, pathToCustomFunction, pathToFormatters) +local function formatValueForAssignment(vType, value, pathToCustomFunction, pathToFormatters, data) if (value == nil) then value = false; end @@ -50,6 +50,43 @@ local function formatValueForAssignment(vType, value, pathToCustomFunction, path return tostring(value) end return "nil" + elseif vType == "progressSource" then + if type(value) == "table" then + local progressSource = Private.AddProgressSourceMetaData(data, value) + local trigger = progressSource[1] or -1 + local progressType = progressSource[2] or "auto" + local property = progressSource[3] + local totalProperty = progressSource[4] + local modRateProperty = progressSource[5] + local inverseProperty = progressSource[6] + local pausedProperty = progressSource[7] + local remainingProperty = progressSource[8] + + if trigger == 0 then + -- Manual progress + local serialized = string.format("{%s, %s, %s, %s}", + trigger, + Private.QuotedString(progressType), + property or "0", -- Actually: value + totalProperty or "100" -- Actually: total + ) + return serialized + else + local serialized = string.format("{%s, %s, %s, %s, %s, %s, %s, %s}", + trigger, + Private.QuotedString(progressType), + Private.QuotedString(property or "nil"), + totalProperty and Private.QuotedString(totalProperty) or "nil", + modRateProperty and Private.QuotedString(modRateProperty) or "nil", + inverseProperty and Private.QuotedString(inverseProperty) or "nil", + pausedProperty and Private.QuotedString(pausedProperty) or "nil", + remainingProperty and Private.QuotedString(remainingProperty) or "nil" + ) + return serialized + end + else + return "nil" + end elseif (vType == "icon") then if type(value) == "string" then return string.format("%s", Private.QuotedString(value)) @@ -127,7 +164,9 @@ local function formatValueForAssignment(vType, value, pathToCustomFunction, path end local function formatValueForCall(type, property) - if (type == "bool" or type == "number" or type == "list" or type == "icon" or type == "string") then + if type == "bool" or type == "number" or type == "list" or type == "icon" or type == "string" + or type == "progressSource" + then return "propertyChanges['" .. property .. "']"; elseif (type == "color") then local pcp = "propertyChanges['" .. property .. "']"; @@ -175,7 +214,8 @@ function Private.ExecEnv.CallCustomConditionTest(uid, testFunctionNumber, ...) end end -local function CreateTestForCondition(uid, input, allConditionsTemplate, usedStates) +local function CreateTestForCondition(data, input, allConditionsTemplate, usedStates) + local uid = data.uid local trigger = input and input.trigger; local variable = input and input.variable; local op = input and input.op; @@ -188,7 +228,7 @@ local function CreateTestForCondition(uid, input, allConditionsTemplate, usedSta local test = {}; if (input.checks) then for i, subcheck in ipairs(input.checks) do - local subtest, subrecheckCode = CreateTestForCondition(uid, subcheck, allConditionsTemplate, usedStates); + local subtest, subrecheckCode = CreateTestForCondition(data, subcheck, allConditionsTemplate, usedStates); if (subtest) then tinsert(test, "(" .. subtest .. ")"); end @@ -212,9 +252,18 @@ local function CreateTestForCondition(uid, input, allConditionsTemplate, usedSta local conditionTemplate = allConditionsTemplate[trigger] and allConditionsTemplate[trigger][variable]; local cType = conditionTemplate and conditionTemplate.type; - local useModRate = conditionTemplate and conditionTemplate.useModRate local test = conditionTemplate and conditionTemplate.test; local preamble = conditionTemplate and conditionTemplate.preamble; + local progressSource + local modRateProperty + local pausedProperty + local remainingProperty + if cType == "timer" then + progressSource = Private.GetProgressSourceFor(data, trigger, variable) + modRateProperty = progressSource and progressSource[5] + pausedProperty = progressSource and progressSource[7] + remainingProperty = progressSource[8] + end local stateCheck = "state[" .. trigger .. "] and state[" .. trigger .. "].show and "; local stateVariableCheck = string.format("state[" .. trigger .. "][%q]", variable) .. "~= nil and "; @@ -260,34 +309,29 @@ local function CreateTestForCondition(uid, input, allConditionsTemplate, usedSta check = "true" elseif (cType == "number" and value and op) then local v = tonumber(value) - if (v) then - if useModRate then - check = stateCheck .. stateVariableCheck .. "(state[" .. trigger .. "]" .. string.format("[%q]", variable) - .. "/ (state[" .. trigger .. "].modRate or 1.0))" .. op .. v; - else check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]" .. string.format("[%q]", variable) .. op .. v; - end end elseif (cType == "timer" and value and op) then local triggerState = "state[" .. trigger .. "]" local varString = triggerState .. string.format("[%q]", variable) - local remainingTime = "(" .. triggerState .. ".paused and (" .. triggerState .. ".remaining or 0) or (" .. varString .. " - now)" .. ")" - if useModRate then - local modRateString = "(state[" .. trigger .. "].modRate or 1.0)" - - if (op == "==") then - check = stateCheck .. stateVariableCheck .. "abs((" .. remainingTime .. "-" .. value .. ")/" .. modRateString .. ") < 0.05" - else - check = stateCheck .. stateVariableCheck .. remainingTime .. "/" .. modRateString .. op .. value - end + local remainingTime = "(" .. varString .. " - now)" + if pausedProperty and remainingProperty then + local pausedString = "state[" .. trigger .. "]" .. string.format("[%q]", pausedProperty) + local remainingString = "(state[" .. trigger .. "]" .. string.format("[%q]", remainingProperty) .. " or 0)" + + remainingTime = "((" .. pausedString .. " and " .. remainingString .. ") or " .. remainingTime .. ")" + end + + local divideModRate = modRateProperty + and " / (state[" .. trigger .. "]" .. string.format("[%q]", modRateProperty) .. " or 1.0)" + or "" + + if (op == "==") then + check = stateCheck .. stateVariableCheck .. "abs((" .. remainingTime .. "-" .. value .. ")" .. divideModRate .. ") < 0.05" else - if (op == "==") then - check = stateCheck .. stateVariableCheck .. "abs(" .. remainingTime .. "-" .. value .. ") < 0.05" - else - check = stateCheck .. stateVariableCheck .. remainingTime .. op .. value - end + check = stateCheck .. stateVariableCheck .. remainingTime .. divideModRate .. op .. value end elseif (cType == "elapsedTimer" and value and op) then if (op == "==") then @@ -371,15 +415,18 @@ local function CreateTestForCondition(uid, input, allConditionsTemplate, usedSta -- If adding a new condition type, don't forget to adjust the validator in the options code if (cType == "timer" and value) then - if useModRate then - recheckCode = " nextTime = state[" .. trigger .. "] and not state[" .. trigger .. "].paused" - .. " and state[" .. trigger .. "]" .. string.format("[%q]", variable) - .. " and (state[" .. trigger .. "]" .. string.format("[%q]", variable) .. " - " .. value .. " * (state[" .. trigger .. "].modRate or 1.0))\n" - else - recheckCode = " nextTime = state[" .. trigger .. "] and not state[" .. trigger .. "].paused" - .. " and state[" .. trigger .. "]" .. string.format("[%q]", variable) - .. " and (state[" .. trigger .. "]" .. string.format("[%q]", variable) .. " - " .. value .. ")\n" - end + local variableString = "state[" .. trigger .. "]" .. string.format("[%q]", variable) + local multiplyModRate = modRateProperty + and " * (state[" .. trigger .. "]" .. string.format("[%q]", modRateProperty) .. " or 1.0)" + or "" + local andNotPaused = pausedProperty + and "and not " .. "state[" .. trigger .. "]" .. string.format("[%q]", pausedProperty) + or "" + + recheckCode = " nextTime = state[" .. trigger .. "] " .. andNotPaused + .. " and " .. variableString + .. " and " .. "(" .. variableString .. " - " .. value .. multiplyModRate .. ")\n" + recheckCode = recheckCode .. " if (nextTime and (not recheckTime or nextTime < recheckTime) and nextTime >= now) then\n" recheckCode = recheckCode .. " recheckTime = nextTime\n"; recheckCode = recheckCode .. " end\n" @@ -394,9 +441,9 @@ local function CreateTestForCondition(uid, input, allConditionsTemplate, usedSta return check, recheckCode; end -local function CreateCheckCondition(uid, ret, condition, conditionNumber, allConditionsTemplate, nextIsLinked, debug) +local function CreateCheckCondition(data, ret, condition, conditionNumber, allConditionsTemplate, nextIsLinked, debug) local usedStates = {}; - local check, recheckCode = CreateTestForCondition(uid, condition.check, allConditionsTemplate, usedStates); + local check, recheckCode = CreateTestForCondition(data, condition.check, allConditionsTemplate, usedStates); if not check then check = "false" end @@ -454,8 +501,16 @@ local function CreateDeactivateCondition(ret, condition, conditionNumber, data, local propertyData = properties and properties[change.property] if (propertyData and propertyData.type and propertyData.setter) then usedProperties[change.property] = true; - ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property)) .. "\n"; - if (debug) then ret = ret .. " print('- " .. change.property .. " " ..formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property)) .. "')\n"; end + ret = ret .. " propertyChanges['" .. change.property .. "'] = " + .. formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property), + nil, nil, data) + .. "\n"; + if (debug) then + ret = ret .. " print('- " .. change.property .. " " + .. formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property), + nil, nil, data) + .. "')\n"; + end end end end @@ -465,7 +520,7 @@ local function CreateDeactivateCondition(ret, condition, conditionNumber, data, return ret; end -local function CreateActivateCondition(ret, id, condition, conditionNumber, properties, debug) +local function CreateActivateCondition(ret, id, condition, conditionNumber, data, properties, debug) if (condition.changes) then ret = ret .. " if (newActiveConditions[" .. conditionNumber .. "]) then\n" ret = ret .. " if (not activatedConditions[".. conditionNumber .. "]) then\n" @@ -477,9 +532,11 @@ local function CreateActivateCondition(ret, id, condition, conditionNumber, prop if (propertyData and propertyData.type) then if (propertyData.setter) then ret = ret .. " propertyChanges['" .. change.property .. "'] = " - .. formatValueForAssignment(propertyData.type, change.value) .. "\n" - if (debug) then ret = ret .. " print('- " .. change.property .. " " - .. formatValueForAssignment(propertyData.type, change.value) .. "')\n" end + .. formatValueForAssignment(propertyData.type, change.value, nil, nil, data) .. "\n" + if (debug) then + ret = ret .. " print('- " .. change.property .. " " + .. formatValueForAssignment(propertyData.type, change.value, nil, nil, data) .. "')\n" + end elseif (propertyData.action) then local pathToCustomFunction = "nil"; local pathToFormatter = "nil" @@ -498,9 +555,15 @@ local function CreateActivateCondition(ret, id, condition, conditionNumber, prop id, conditionNumber, changeNum); end ret = ret .. " region:" .. propertyData.action .. "(" - .. formatValueForAssignment(propertyData.type, change.value, pathToCustomFunction, pathToFormatter) .. ")" .. "\n"; - if (debug) then ret = ret .. " print('# " .. propertyData.action .. "(" - .. formatValueForAssignment(propertyData.type, change.value, pathToCustomFunction, pathToFormatter) .. "')\n"; end + .. formatValueForAssignment(propertyData.type, change.value, + pathToCustomFunction, pathToFormatter, data) + .. ")" .. "\n"; + if (debug) then + ret = ret .. " print('# " .. propertyData.action .. "(" + .. formatValueForAssignment(propertyData.type, change.value, + pathToCustomFunction, pathToFormatter, data) + .. "')\n"; + end end end end @@ -513,9 +576,9 @@ local function CreateActivateCondition(ret, id, condition, conditionNumber, prop if (propertyData and propertyData.type and propertyData.setter) then ret = ret .. " if(propertyChanges['" .. change.property .. "'] ~= nil) then\n" ret = ret .. " propertyChanges['" .. change.property .. "'] = " - .. formatValueForAssignment(propertyData.type, change.value) .. "\n" + .. formatValueForAssignment(propertyData.type, change.value, nil, nil, data) .. "\n" if (debug) then ret = ret .. " print('- " .. change.property .. " " - .. formatValueForAssignment(propertyData.type, change.value) .. "')\n" end + .. formatValueForAssignment(propertyData.type, change.value, nil, nil, data) .. "')\n" end ret = ret .. " end\n" end end @@ -600,7 +663,7 @@ function Private.LoadConditionPropertyFunctions(data) end return change.value[fullKey] end - local formatters = change.value and Private.CreateFormatters(change.value.message, getter, true) + local formatters = change.value and Private.CreateFormatters(change.value.message, getter, true, data) Private.ExecEnv.conditionTextFormatters[id] = Private.ExecEnv.conditionTextFormatters[id] or {} Private.ExecEnv.conditionTextFormatters[id][conditionNumber] = Private.ExecEnv.conditionTextFormatters[id][conditionNumber] or {}; Private.ExecEnv.conditionTextFormatters[id][conditionNumber].changes = Private.ExecEnv.conditionTextFormatters[id][conditionNumber].changes or {}; @@ -692,7 +755,7 @@ local function ConstructConditionFunction(data) for conditionNumber, condition in ipairs(data.conditions) do local nextIsLinked = data.conditions[conditionNumber + 1] and data.conditions[conditionNumber + 1].linked local additionalRecheckCode - ret, additionalRecheckCode = CreateCheckCondition(data.uid, ret, condition, conditionNumber, allConditionsTemplate, nextIsLinked, debug) + ret, additionalRecheckCode = CreateCheckCondition(data, ret, condition, conditionNumber, allConditionsTemplate, nextIsLinked, debug) if additionalRecheckCode then recheckCode = recheckCode .. "\n" .. additionalRecheckCode end @@ -722,7 +785,7 @@ local function ConstructConditionFunction(data) -- Third Loop deals with conditions that are newly active if (data.conditions) then for conditionNumber, condition in ipairs(data.conditions) do - ret = CreateActivateCondition(ret, data.id, condition, conditionNumber, properties, debug) + ret = CreateActivateCondition(ret, data.id, condition, conditionNumber, data, properties, debug) end end diff --git a/WeakAuras/GenericTrigger.lua b/WeakAuras/GenericTrigger.lua index ed7db1f100..db9045f537 100644 --- a/WeakAuras/GenericTrigger.lua +++ b/WeakAuras/GenericTrigger.lua @@ -27,16 +27,9 @@ Modernizes all generic triggers in data. ##################################################### # Helper functions mainly for the WeakAuras Options # ##################################################### - -CanHaveDuration(data, triggernum) -Returns whether the trigger can have a duration. - GetOverlayInfo(data, triggernum) Returns a table containing the names of all overlays -CanHaveClones(data) -Returns whether the trigger can have clones. - CanHaveTooltip(data, triggernum) Returns the type of tooltip to show for the trigger. @@ -46,6 +39,9 @@ Returns the name and icon to show in the options. GetAdditionalProperties(data, triggernum) Returns the a tooltip for the additional properties. +GetProgressSources(data, triggernum, outValues) + Fills outValues with the potential progress sources + GetTriggerConditions(data, triggernum) Returns potential conditions that this trigger provides. ]]-- @@ -482,12 +478,17 @@ local function RunOverlayFuncs(event, state, id, errorHandler) state.changed = changed or state.changed; end -local function callFunctionForActivateEvent(func, trigger, fallback, errorHandler) +local function callFunctionForActivateEvent(func, trigger, state, property, errorHandler) if not func then - return fallback + return end local ok, value = xpcall(func, errorHandler, trigger) - return ok and value or fallback + if ok then + if state[property] ~= value then + state[property] = value + state.changed = true + end + end end function Private.ActivateEvent(id, triggernum, data, state, errorHandler) @@ -587,32 +588,13 @@ function Private.ActivateEvent(id, triggernum, data, state, errorHandler) end end - local name = callFunctionForActivateEvent(data.nameFunc, data.trigger, state.name, errorHandler or Private.GetErrorHandlerId(id, L["Name Function"])) - local icon = callFunctionForActivateEvent(data.iconFunc, data.trigger, state.icon, errorHandler or Private.GetErrorHandlerId(id, L["Icon Function"])) - local texture = callFunctionForActivateEvent(data.textureFunc, data.trigger, state.texture, errorHandler or Private.GetErrorHandlerId(id, L["Texture Function"])) - local stacks = callFunctionForActivateEvent(data.stacksFunc, data.trigger, state.stacks, errorHandler or Private.GetErrorHandlerId(id, L["Stacks Function"])) - - if (state.name ~= name) then - state.name = name; - changed = true; - end - if (state.icon ~= icon) then - state.icon = icon; - changed = true; - end - if (state.texture ~= texture) then - state.texture = texture; - changed = true; - end - if (state.stacks ~= stacks) then - state.stacks = stacks; - changed = true; - end + callFunctionForActivateEvent(data.nameFunc, data.trigger, state, "name", errorHandler or Private.GetErrorHandlerId(id, L["Name Function"])) + callFunctionForActivateEvent(data.iconFunc, data.trigger, state, "icon", errorHandler or Private.GetErrorHandlerId(id, L["Icon Function"])) + callFunctionForActivateEvent(data.textureFunc, data.trigger, state, "texture", errorHandler or Private.GetErrorHandlerId(id, L["Texture Function"])) + callFunctionForActivateEvent(data.stacksFunc, data.trigger, state, "stacks", errorHandler or Private.GetErrorHandlerId(id, L["Stacks Function"])) if (data.overlayFuncs) then RunOverlayFuncs(data, state, id, errorHandler); - else - state.additionalProgress = nil; end state.changed = state.changed or changed; @@ -949,10 +931,37 @@ function Private.ScanEventsWatchedTrigger(id, watchedTriggernums) Private.ActivateAuraEnvironment(nil) end -local function AddFakeInformation(state, eventData) +--- @type fun(data: auraData, triggernum: number) : "timed"|boolean, boolean? +local function ProgressType(data, triggernum) + local trigger = data.triggers[triggernum].trigger + + local prototype = GenericTrigger.GetPrototype(trigger) + if prototype then + if prototype.progressType then + local progressType = prototype.progressType + if type(progressType) == "function" then + progressType = progressType(trigger) + end + return progressType, prototype.useModRate + elseif prototype.timedrequired then + return "timed" + end + elseif (trigger.type == "custom") then + if trigger.custom_type == "event" and trigger.custom_hide == "timed" and trigger.duration then + return "timed"; + elseif (trigger.customDuration and trigger.customDuration ~= "") then + return "timed"; + elseif (trigger.custom_type == "stateupdate") then + return "timed"; + end + end + return false +end + +--- @type fun(data: auraData, triggernum: integer, state: state, eventData: table) +local function AddFakeInformation(data, triggernum, state, eventData) state.autoHide = false - local canHaveDuration = eventData.prototype and eventData.prototype.canHaveDuration == "timed" - if canHaveDuration and state.expirationTime == nil then + if ProgressType(data, triggernum) == "timed" and state.expirationTime == nil then state.progressType = "timed" end if state.progressType == "timed" then @@ -974,6 +983,7 @@ local function AddFakeInformation(state, eventData) end end +--- @type fun(id: auraId, triggernum: integer) function GenericTrigger.CreateFakeStates(id, triggernum) local data = WeakAuras.GetData(id) local eventData = events[id][triggernum] @@ -981,7 +991,6 @@ function GenericTrigger.CreateFakeStates(id, triggernum) Private.ActivateAuraEnvironment(id); local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); - local arg1 if eventData.statesParameter == "unit" then local unit = eventData.trigger.unit @@ -1000,7 +1009,7 @@ function GenericTrigger.CreateFakeStates(id, triggernum) shown = shown + 1 end - AddFakeInformation(state, eventData) + AddFakeInformation(data, triggernum, state, eventData) end if shown == 0 then @@ -1008,7 +1017,7 @@ function GenericTrigger.CreateFakeStates(id, triggernum) GenericTrigger.CreateFallbackState(data, triggernum, state) allStates[""] = state - AddFakeInformation(state, eventData) + AddFakeInformation(data, triggernum, state, eventData) end Private.ActivateAuraEnvironment(nil); @@ -3734,7 +3743,7 @@ function WeakAuras.GetUniqueCloneId() return uniqueId; end ---- @type fun(trigger: triggerData) : prototypeData +--- @type fun(trigger: triggerData) : prototypeData? function GenericTrigger.GetPrototype(trigger) if trigger.type and trigger.event then if Private.category_event_prototype[trigger.type] then @@ -3743,41 +3752,6 @@ function GenericTrigger.GetPrototype(trigger) end end ---- @type fun(data: auraData, triggernum: number) : "timed"|boolean, boolean? -function GenericTrigger.CanHaveDuration(data, triggernum) - local trigger = data.triggers[triggernum].trigger - - local prototype = GenericTrigger.GetPrototype(trigger) - if prototype then - if prototype.durationFunc then - if(type(prototype.init) == "function") then - prototype.init(trigger); - end - local current, maximum, custom = prototype.durationFunc(trigger); - current = type(current) ~= "number" and current or 0 - maximum = type(maximum) ~= "number" and maximum or 0 - if(custom) then - return {current = current, maximum = maximum}; - else - return "timed"; - end - elseif prototype.canHaveDuration then - return prototype.canHaveDuration, prototype.useModRate - elseif prototype.timedrequired then - return "timed" - end - elseif (trigger.type == "custom") then - if trigger.custom_type == "event" and trigger.custom_hide == "timed" and trigger.duration then - return "timed"; - elseif (trigger.customDuration and trigger.customDuration ~= "") then - return "timed"; - elseif (trigger.custom_type == "stateupdate") then - return "timed"; - end - end - return false -end - --- @type fun(data: auraData): number? function GenericTrigger.GetDelay(data) if data.event then @@ -3867,10 +3841,6 @@ function GenericTrigger.GetOverlayInfo(data, triggernum) return result; end -function GenericTrigger.CanHaveClones(data) - return false; -end - --- @type fun(data: auraData, triggernum: number): string?, string? function GenericTrigger.GetNameAndIcon(data, triggernum) local trigger = data.triggers[triggernum].trigger @@ -4005,14 +3975,51 @@ function GenericTrigger.GetAdditionalProperties(data, triggernum) return ret; end +function GenericTrigger.GetProgressSources(data, triggernum, values) + local variables = GenericTrigger.GetTriggerConditions(data, triggernum) + if (type(variables) == "table") then + for var, varData in pairs(variables) do + if (type(varData) == "table") then + if varData.type == "number" or varData.type == "timer" or varData.type == "elapsedTimer" then + local modRateProperty = varData.modRate + if not modRateProperty and varData.useModRate then + modRateProperty = "modRate" + end + + tinsert(values, { + trigger = triggernum, + property = var, + type = varData.type, + display = varData.display, + total = varData.total, + modRate = varData.modRate, + inverse = varData.inverse, + paused = varData.paused, + remaining = varData.remaining + }) + end + end + end + end +end + local commonConditions = { expirationTime = { display = L["Remaining Duration"], type = "timer", + total = "duration", + inverse = "inverse", + paused = "paused", + remaining = "remaining", }, expirationTimeModRate = { display = L["Remaining Duration"], type = "timer", + total = "duration", + modRate = "modRate", + inverse = "inverse", + paused = "paused", + remaining = "remaining", useModRate = true }, duration = { @@ -4034,6 +4041,7 @@ local commonConditions = { value = { display = L["Progress Value"], type = "number", + total = "total" }, total = { display = L["Progress Total"], @@ -4070,7 +4078,32 @@ function Private.ExpandCustomVariables(variables) end end ---- @type fun(data: auraData, triggernum: number): table +function Private.GetTsuConditionVariablesExpanded(id, triggernum) + if events[id][triggernum] and events[id][triggernum].tsuConditionVariables then + Private.ActivateAuraEnvironment(id, nil, nil, nil, true) + local result = GenericTrigger.GetTsuConditionVariables(id, triggernum) + Private.ActivateAuraEnvironment(nil) + if type(result) ~= "table" then + return nil + end + Private.ExpandCustomVariables(result) + -- Clean up, remove non table entries and check for a valid display name + for k, v in pairs(result) do + if type(v) ~= "table" then + result[k] = nil + elseif (v.display == nil or type(v.display) ~= "string") then + if type(k) == "string" then + v.display = k + else + result[k] = nil + end + end + end + + return result + end +end + function GenericTrigger.GetTriggerConditions(data, triggernum) local trigger = data.triggers[triggernum].trigger @@ -4078,16 +4111,8 @@ function GenericTrigger.GetTriggerConditions(data, triggernum) if prototype then local result = {}; - local canHaveDuration, modRated = GenericTrigger.CanHaveDuration(data, triggernum); - local timedDuration = canHaveDuration; - local valueDuration = canHaveDuration; - if (canHaveDuration == "timed") then - valueDuration = false; - elseif (type(canHaveDuration) == "table") then - timedDuration = false; - end - - if (timedDuration) then + local progressType, modRated = ProgressType(data, triggernum); + if progressType == "timed" then if modRated then result.expirationTime = commonConditions.expirationTimeModRate; result.duration = commonConditions.durationModRate; @@ -4098,7 +4123,7 @@ function GenericTrigger.GetTriggerConditions(data, triggernum) result.paused = commonConditions.paused end - if (valueDuration) then + if progressType == "static" then result.value = commonConditions.value; result.total = commonConditions.total; end @@ -4150,6 +4175,22 @@ function GenericTrigger.GetTriggerConditions(data, triggernum) if (v.operator_types) then result[v.name].operator_types = v.operator_types; end + -- for ProgressSource + if v.progressTotal then + result[v.name].total = v.progressTotal + end + if v.progressModRate then + result[v.name].modRate = v.progressModRate + end + if v.progressInverse then + result[v.name].inverse = v.progressInverse + end + if v.progressPaused then + result[v.name].paused = v.progressPaused + end + if v.progressRemaining then + result[v.name].remaining = v.progressRemaining + end end end end @@ -4191,28 +4232,7 @@ function GenericTrigger.GetTriggerConditions(data, triggernum) return result; elseif (trigger.custom_type == "stateupdate") then - if (events[data.id][triggernum] and events[data.id][triggernum].tsuConditionVariables) then - Private.ActivateAuraEnvironment(data.id, nil, nil, nil, true) - local result = GenericTrigger.GetTsuConditionVariables(data.id, triggernum) - Private.ActivateAuraEnvironment(nil) - if (type(result)) ~= "table" then - return nil; - end - Private.ExpandCustomVariables(result) - for k, v in pairs(result) do - if (type(v) ~= "table") then - result[k] = nil; - elseif (v.display == nil or type(v.display) ~= "string") then - if (type(k) == "string") then - v.display = k; - else - result[k] = nil; - end - end - end - - return result; - end + return Private.GetTsuConditionVariablesExpanded(data.id, triggernum) end end diff --git a/WeakAuras/Init.lua b/WeakAuras/Init.lua index 69d2257266..062da0e8f3 100644 --- a/WeakAuras/Init.lua +++ b/WeakAuras/Init.lua @@ -14,6 +14,18 @@ local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetad --- @field cloneId string? --- @field show boolean? --- @field changed boolean? +--- @field paused boolean? +--- @field remaining number? +--- @field autoHide boolean|string|nil +--- @field progressType "timed"|"static"|nil +--- @field expirationTime number? +--- @field duration number? +--- @field name any? +--- @field icon any? +--- @field value number? +--- @field total number? +--- @field inverse boolean? + --- @alias non_transmissable_field table @@ -27,9 +39,15 @@ local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetad --- @alias traverseFunction fun(): auraData +---@class WARegion : Frame +---@field state state +---@field states state[] +---@field regionType string + --- @class Private --- @field ActivateAuraEnvironment fun(id: auraId?, cloneId: string?, state: state?, states: state[]?, config: boolean?) --- @field ActivateAuraEnvironmentForRegion fun(region: table, onlyConfig: boolean?) +--- @field ActivateEvent fun(id: auraId, triggernum: integer, data: table, state: state, errorHandler: fun(...)) : boolean? --- @field AddToWatchedTriggerDelay fun(id: auraId, triggerNum: number) --- @field anchor_frame_types table --- @field anchor_frame_types_group table @@ -50,7 +68,7 @@ local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetad --- @field DebugLog debugLog --- @field dynamic_texts table --- @field EndEvent fun(state: state): boolean? ---- @field EnsureRegion fun(id: auraId, cloneId: string?): Frame +--- @field EnsureRegion fun(id: auraId, cloneId: string?): WARegion --- @field ExecEnv table --- @field event_prototypes table --- @field event_categories table @@ -79,7 +97,7 @@ local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetad --- @field non_transmissable_fields_v2000 table --- @field orientation_types table --- @field orientation_with_circle_types table ---- @field ParseNumber fun (numString: string|number): number? +--- @field ParseNumber fun (numString: string|number): number?, string? --- @field point_types table --- @field PreShowModels fun() --- @field PrintHelp fun() @@ -141,7 +159,7 @@ local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetad --- @field instance_size table|nil --- @field itemName string? --- @field itemSetName string? ---- @field itemTypeName table|ni +--- @field itemTypeName table|nil --- @field range number?l --- @field realSpellName string? --- @field rune number? @@ -177,6 +195,7 @@ local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetad ---@field internal_events (fun(tigger: triggerData): table)|nil ---@field name string ---@field statesParamater "unit"|"one"|"all"|nil +---@field progressType "timed"|"static"|"none" --- @class triggerUntriggerData --- @field trigger triggerData diff --git a/WeakAuras/Modernize.lua b/WeakAuras/Modernize.lua index d5698bcf8e..e225f3f902 100644 --- a/WeakAuras/Modernize.lua +++ b/WeakAuras/Modernize.lua @@ -1972,6 +1972,30 @@ function Private.Modernize(data) migrateToTable(data.load, "itemequiped") end + if data.internalVersion < 70 then + if data.regionType == 'icon' or data.regionType == 'aurabar' + or data.regionType == 'progresstexture' + or data.regionType == 'stopmotion' + then + data.progressSource = {-1, ""} + else + data.progressSource = nil + end + if data.subRegions then + for index, subRegionData in ipairs(data.subRegions) do + if subRegionData.type == "subtick" then + local tick_placement = subRegionData.tick_placement + subRegionData.tick_placements = {} + subRegionData.tick_placements[1] = tick_placement + subRegionData.progressSources = {{-2, ""}} + subRegionData.tick_placement = nil + end + end + end + + end + + data.internalVersion = max(data.internalVersion or 0, WeakAuras.InternalVersion()) end diff --git a/WeakAuras/Prototypes.lua b/WeakAuras/Prototypes.lua index 17a3593739..29edb3e3c7 100644 --- a/WeakAuras/Prototypes.lua +++ b/WeakAuras/Prototypes.lua @@ -2346,11 +2346,12 @@ Private.event_prototypes = { test = "WeakAuras.UnitExistsFixed(unit, smart) and specificUnitCheck" } }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Faction Reputation"] = { type = "unit", - canHaveDuration = false, + progressType = "static", events = { ["events"] = { "UPDATE_FACTION", @@ -2477,6 +2478,7 @@ Private.event_prototypes = { hidden = true, test = "true", conditionType = "number", + progressTotal = "total", }, { name = "standingId", @@ -2511,7 +2513,8 @@ Private.event_prototypes = { test = "true", conditionType = "number", enable = WeakAuras.IsRetail(), - hidden = not WeakAuras.IsRetail() + hidden = not WeakAuras.IsRetail(), + progressTotal = "friendshipMaxRank" }, { name = "friendshipMaxRank", @@ -2536,7 +2539,7 @@ Private.event_prototypes = { }, ["Experience"] = { type = "unit", - canHaveDuration = false, + progressType = "static", events = { ["events"] = { "PLAYER_XP_UPDATE", @@ -2574,6 +2577,7 @@ Private.event_prototypes = { operator = "and", limit = 2 }, + progressTotal = "totalXP" }, { name = "totalXP", @@ -2670,7 +2674,7 @@ Private.event_prototypes = { ["Health"] = { type = "unit", includePets = "true", - canHaveDuration = true, + progressType = "static", events = function(trigger) local unit = trigger.unit local result = {} @@ -2749,6 +2753,7 @@ Private.event_prototypes = { operator = "and", limit = 2 }, + progressTotal = "maxhealth" }, { name = "value", @@ -2794,6 +2799,7 @@ Private.event_prototypes = { operator = "and", limit = 2 }, + progressTotal = "total" }, { name = "maxhealth", @@ -2809,16 +2815,16 @@ Private.event_prototypes = { }, { name = "showAbsorb", - display = L["Show Absorb"], + display = L["Fetch Absorb"], type = "toggle", test = "true", reloadOptions = true, enable = WeakAuras.IsRetail(), - hidden = not WeakAuras.IsRetail() + hidden = not WeakAuras.IsRetail(), }, { name = "absorbMode", - display = L["Absorb Display"], + display = L["Absorb Overlay"], type = "select", test = "true", values = "absorb_modes", @@ -2828,7 +2834,7 @@ Private.event_prototypes = { }, { name = "showHealAbsorb", - display = L["Show Heal Absorb"], + display = L["Fetch Heal Absorb"], type = "toggle", test = "true", reloadOptions = true, @@ -2837,7 +2843,7 @@ Private.event_prototypes = { }, { name = "absorbHealMode", - display = L["Absorb Heal Display"], + display = L["Absorb Heal Overlay"], type = "select", test = "true", values = "absorb_modes", @@ -2858,6 +2864,7 @@ Private.event_prototypes = { operator = "and", limit = 2 }, + progressTotal = "total" }, { name = "healabsorb", @@ -2879,6 +2886,7 @@ Private.event_prototypes = { type = "toggle", test = "true", reloadOptions = true, + progressTotal = "total" }, { name = "healprediction", @@ -3129,7 +3137,7 @@ Private.event_prototypes = { }, ["Power"] = { type = "unit", - canHaveDuration = true, + progressType = "static", events = function(trigger) local unit = trigger.unit local result = {} @@ -3386,6 +3394,7 @@ Private.event_prototypes = { operator = "and", limit = 2 }, + progressTotal = "total" }, { name = "value", @@ -3439,6 +3448,7 @@ Private.event_prototypes = { operator = "and", limit = 2 }, + progressTotal = "total" }, { name = "maxpower", @@ -3699,7 +3709,7 @@ Private.event_prototypes = { }, ["Alternate Power"] = { type = "unit", - canHaveDuration = true, + progressType = "static", events = function(trigger) local unit = trigger.unit local result = {} @@ -3753,7 +3763,8 @@ Private.event_prototypes = { name = "power", display = L["Alternate Power"], type = "number", - init = "UnitPower(unit, 10)" + init = "UnitPower(unit, 10)", + progressTotal = "total" }, { name = "value", @@ -4558,6 +4569,7 @@ Private.event_prototypes = { countEvents = true, delayEvents = true, timedrequired = true, + progressType = "timed" }, ["Spell Activation Overlay"] = { type = "spell", @@ -4603,7 +4615,8 @@ Private.event_prototypes = { local _, _, icon = GetSpellInfo(trigger.spellName or 0); return icon; end, - automaticrequired = true + automaticrequired = true, + progressType = "static" }, ["Cooldown Progress (Spell)"] = { type = "spell", @@ -4788,7 +4801,7 @@ Private.event_prototypes = { return ret; end, statesParameter = "one", - canHaveDuration = "timed", + progressType = "timed", useModRate = true, args = { { @@ -4922,7 +4935,8 @@ Private.event_prototypes = { display = L["Charges"], type = "number", store = true, - conditionType = "number" + conditionType = "number", + progressTotal = "maxCharges" }, { name = "spellCount", @@ -5128,7 +5142,8 @@ Private.event_prototypes = { return icon; end, hasSpellID = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Charges Changed"] = { type = "spell", @@ -5212,7 +5227,8 @@ Private.event_prototypes = { return icon; end, hasSpellID = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Cooldown Progress (Item)"] = { type = "item", @@ -5377,6 +5393,7 @@ Private.event_prototypes = { end, hasItemID = true, automaticrequired = true, + progressType = "timed" }, ["Cooldown Progress (Equipment Slot)"] = { type = "item", @@ -5535,6 +5552,7 @@ Private.event_prototypes = { return GetInventoryItemTexture("player", trigger.itemSlot or 0) or "Interface\\Icons\\INV_Misc_QuestionMark"; end, automaticrequired = true, + progressType = "timed" }, ["Cooldown Ready (Item)"] = { type = "item", @@ -5572,7 +5590,8 @@ Private.event_prototypes = { return icon; end, hasItemID = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Cooldown Ready (Equipment Slot)"] = { type = "item", @@ -5608,7 +5627,8 @@ Private.event_prototypes = { return GetInventoryItemTexture("player", trigger.itemSlot or 0) or "Interface\\Icons\\INV_Misc_QuestionMark"; end, hasItemID = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["GTFO"] = { type = "addons", @@ -5628,7 +5648,8 @@ Private.event_prototypes = { conditionType = "select" }, }, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Global Cooldown"] = { type = "spell", @@ -5679,6 +5700,7 @@ Private.event_prototypes = { end, hasSpellID = true, automaticrequired = true, + progressType = "timed" }, ["Swing Timer"] = { type = "unit", @@ -5789,7 +5811,7 @@ Private.event_prototypes = { } }, automaticrequired = true, - canHaveDuration = true, + progressType = "timed", statesParameter = "one" }, ["Action Usable"] = { @@ -5886,7 +5908,7 @@ Private.event_prototypes = { type = "number", enable = function(trigger) return not(trigger.use_inverse) end, store = true, - conditionType = "number" + conditionType = "number", }, { name = "spellCount", @@ -5894,7 +5916,7 @@ Private.event_prototypes = { type = "number", enable = function(trigger) return not(trigger.use_inverse) end, store = true, - conditionType = "number" + conditionType = "number", }, { hidden = true, @@ -5972,7 +5994,8 @@ Private.event_prototypes = { end end, hasSpellID = true, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Talent Known"] = { type = "unit", @@ -6255,6 +6278,7 @@ Private.event_prototypes = { }, automaticrequired = true, statesParameter = "one", + progressType = "none" }, ["PvP Talent Selected"] = { type = "unit", @@ -6355,6 +6379,7 @@ Private.event_prototypes = { }, automaticrequired = true, statesParameter = "one", + progressType = "none" }, ["Class/Spec"] = { type = "unit", @@ -6395,6 +6420,7 @@ Private.event_prototypes = { }, automaticrequired = true, statesParameter = "one", + progressType = "none" }, ["Totem"] = { type = "spell", @@ -6410,7 +6436,7 @@ Private.event_prototypes = { force_events = "PLAYER_ENTERING_WORLD", name = L["Totem"], statesParameter = "full", - canHaveDuration = "timed", + progressType = "timed", triggerFunction = function(trigger) local ret = [[return function (states) @@ -6682,7 +6708,6 @@ Private.event_prototypes = { hidden = true, store = true, test = "true", - conditionType = "number" }, { name = "icon", @@ -6718,7 +6743,8 @@ Private.event_prototypes = { }, statesParameter = "one", hasItemID = true, - automaticrequired = true + automaticrequired = true, + progressType = "static" }, ["Stance/Form/Aura"] = { type = "unit", @@ -6817,7 +6843,8 @@ Private.event_prototypes = { end return icon or "136116" end, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Weapon Enchant"] = { type = "item", @@ -6973,7 +7000,7 @@ Private.event_prototypes = { } }, automaticrequired = true, - canHaveDuration = true, + progressType = "timed", statesParameter = "one" }, ["Chat Message"] = { @@ -7071,7 +7098,8 @@ Private.event_prototypes = { }, countEvents = true, delayEvents = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Spell Cast Succeeded"] = { type = "event", @@ -7132,7 +7160,8 @@ Private.event_prototypes = { }, countEvents = true, delayEvents = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Ready Check"] = { type = "event", @@ -7143,7 +7172,8 @@ Private.event_prototypes = { args = {}, statesParameter = "one", delayEvents = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Combat Events"] = { type = "event", @@ -7167,7 +7197,8 @@ Private.event_prototypes = { statesParameter = "one", countEvents = true, delayEvents = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Encounter Events"] = { type = "event", @@ -7238,7 +7269,8 @@ Private.event_prototypes = { statesParameter = "one", countEvents = true, delayEvents = true, - timedrequired = true + timedrequired = true, + progressType = "timed" }, ["Evoker Essence"] = { type = "unit", @@ -7380,6 +7412,7 @@ Private.event_prototypes = { }, }, automaticrequired = true, + progressType = "timed" }, ["Death Knight Rune"] = { type = "unit", @@ -7623,6 +7656,13 @@ Private.event_prototypes = { end end, automaticrequired = true, + progressType = function(trigger) + if trigger.use_rune then + return "timed" + else + return "static" + end + end }, ["Item Equipped"] = { type = "item", @@ -7683,7 +7723,8 @@ Private.event_prototypes = { return icon; end, hasItemID = true, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Item Type Equipped"] = { type = "item", @@ -7705,7 +7746,8 @@ Private.event_prototypes = { test = "IsEquippedItemType(Private.ExecEnv.GetItemSubClassInfo(%s))" }, }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Item Bonus Id Equipped"] = { type = "item", @@ -7808,7 +7850,8 @@ Private.event_prototypes = { test = "not inverse == (itemBonusId and slotValidation or false)", } }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Item Set"] = { type = "item", @@ -7858,7 +7901,8 @@ Private.event_prototypes = { end, nameFunc = function(trigger) return select(3, WeakAuras.GetNumSetItemsEquipped(trigger.itemSetId and tonumber(trigger.itemSetId) or 0)); - end + end, + progressType = "static" }, ["Equipment Set"] = { type = "item", @@ -7927,7 +7971,8 @@ Private.event_prototypes = { return numEquipped, numItems, true; end, hasItemID = true, - automaticrequired = true + automaticrequired = true, + progressType = "static" }, ["Threat Situation"] = { type = "unit", @@ -7968,7 +8013,7 @@ Private.event_prototypes = { ]]; return ret .. unitHelperFunctions.SpecificUnitCheck(trigger); end, - canHaveDuration = true, + progressType = "static", statesParameter = "unit", args = { { @@ -8078,7 +8123,6 @@ Private.event_prototypes = { }, force_events = "LOSS_OF_CONTROL_UPDATE", name = L["Crowd Controlled"], - canHaveDuration = true, statesParameter = "one", init = function(trigger) local ret = [=[ @@ -8213,6 +8257,7 @@ Private.event_prototypes = { }, }, automaticrequired = true, + progressType = "timed" }, ["Cast"] = { type = "unit", @@ -8274,7 +8319,7 @@ Private.event_prototypes = { end end, force_events = unitHelperFunctions.UnitChangedForceEventsWithPets, - canHaveDuration = "timed", + progressType = "timed", name = L["Cast"], init = function(trigger) trigger.unit = trigger.unit or "player"; @@ -9614,7 +9659,8 @@ Private.event_prototypes = { }, }, }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Conditions"] = { type = "unit", @@ -9802,7 +9848,8 @@ Private.event_prototypes = { hidden = not WeakAuras.IsRetail(), }, }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Spell Known"] = { @@ -9884,7 +9931,8 @@ Private.event_prototypes = { local _, _, icon = GetSpellInfo(trigger.spellName or 0); return icon; end, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Pet Behavior"] = { @@ -9981,7 +10029,8 @@ Private.event_prototypes = { test = "true" }, }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Queued Action"] = { @@ -10031,7 +10080,8 @@ Private.event_prototypes = { local _, _, icon = GetSpellInfo(trigger.spellName or 0); return icon; end, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, ["Range Check"] = { @@ -10111,7 +10161,8 @@ Private.event_prototypes = { test = "UnitExists(unit)" } }, - automaticrequired = true + automaticrequired = true, + progressType = "none" }, }; diff --git a/WeakAuras/RegionTypes/AuraBar.lua b/WeakAuras/RegionTypes/AuraBar.lua index 65faab5148..9f49ef4119 100644 --- a/WeakAuras/RegionTypes/AuraBar.lua +++ b/WeakAuras/RegionTypes/AuraBar.lua @@ -10,6 +10,7 @@ local default = { icon = false, desaturate = false, iconSource = -1, + progressSource = {-1, "" }, texture = "Blizzard", width = 200, height = 15, @@ -42,7 +43,7 @@ local default = { zoom = 0 }; -Private.regionPrototype.AddAdjustedDurationToDefault(default); +Private.regionPrototype.AddProgressSourceToDefault(default) Private.regionPrototype.AddAlphaToDefault(default); local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; @@ -169,6 +170,7 @@ local function GetProperties(data) end auraProperties.iconSource.values = Private.IconSources(data) + auraProperties.progressSource.values = Private.GetProgressSourcesForUi(data) return auraProperties; end @@ -749,6 +751,17 @@ local function GetTexCoordZoom(texWidth) return unpack(texCoord) end +local function FrameTick(self) + local expirationTime = self.expirationTime + local remaining = expirationTime - GetTime() + local duration = self.duration + local progress = duration ~= 0 and remaining / duration or 0; + if self.inverse then + progress = 1 - progress; + end + self:SetProgress(progress) +end + local funcs = { AnchorSubRegion = function(self, subRegion, anchorType, selfPoint, anchorPoint, anchorXOffset, anchorYOffset) if anchorType == "area" then @@ -840,12 +853,7 @@ local funcs = { self.height = height; self:Scale(self.scalex, self.scaley); end, - SetValue = function(self, value, total) - local progress = 0; - if (total ~= 0) then - progress = value / total; - end - + SetProgress = function(self, progress) if self.inverseDirection then progress = 1 - progress; end @@ -857,22 +865,34 @@ local funcs = { self.bar:SetValue(progress); end end, - SetTime = function(self, duration, expirationTime, inverse) - local remaining = expirationTime - GetTime(); - local progress = duration ~= 0 and remaining / duration or 0; - -- Need to invert? - if ( - (self.inverseDirection and not inverse) - or (inverse and not self.inverseDirection) - ) - then + UpdateValue = function(self) + local progress = 0; + if (self.total ~= 0) then + progress = self.value / self.total; + end + + self:SetProgress(progress) + + if self.FrameTick then + self.FrameTick = nil + self.subRegionEvents:RemoveSubscriber("FrameTick", self) + end + end, + UpdateTime = function(self) + local remaining = self.expirationTime - GetTime(); + local progress = self.duration ~= 0 and remaining / self.duration or 0; + if self.inverse then progress = 1 - progress; end - if (self.smoothProgress) then - self.bar.targetValue = progress - self.bar:SetSmoothedValue(progress); - else - self.bar:SetValue(progress); + self:SetProgress(progress) + + if self.paused and self.FrameTick then + self.FrameTick = nil + self.subRegionEvents:RemoveSubscriber("FrameTick", self) + end + if not self.paused and not self.FrameTick then + self.FrameTick = FrameTick + self.subRegionEvents:AddSubscriber("FrameTick", self) end end, SetInverse = function(self, inverse) @@ -1141,14 +1161,6 @@ local function create(parent) return region; end -local function TimerTick(self) - local state = self.state - local duration = state.duration or 0 - local adjustMin = self.adjustedMin or self.adjustedMinRel or 0; - local expirationTime = state.expirationTime and state.expirationTime > 0 and state.expirationTime or math.huge; - self:SetTime((duration ~= 0 and (self.adjustedMax or self.adjustedMaxRel) or duration) - adjustMin, expirationTime - adjustMin, state.inverse); -end - -- Modify a given region/display local function modify(parent, region, data) region.timer = nil @@ -1265,107 +1277,15 @@ local function modify(parent, region, data) region.tooltipFrame:EnableMouse(false); end - function region:UpdateMinMax() - local state = region.state - local min - local max - if state.progressType == "timed" then - local duration = state.duration or 0 - if region.adjustedMinRelPercent then - region.adjustedMinRel = region.adjustedMinRelPercent * duration - end - - min = 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 - elseif state.progressType == "static" then - local total = state.total or 0; - if region.adjustedMinRelPercent then - region.adjustedMinRel = region.adjustedMinRelPercent * total - end - min = 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 - end - region.currentMin, region.currentMax = min, max - end - - function region:GetMinMax() - return region.currentMin or 0, region.currentMax or 0 - end - - region.TimerTick = nil + region.FrameTick = nil function region:Update() - local state = region.state - region:UpdateMinMax() - if state.progressType == "timed" then - local expirationTime - if state.paused == true then - if not region.paused then - region:Pause() - end - if region.TimerTick then - region.TimerTick = nil - region.subRegionEvents:RemoveSubscriber("TimerTick", self) - end - expirationTime = GetTime() + (state.remaining or 0) - else - if region.paused then - region:Resume() - end - if not region.TimerTick then - region.TimerTick = TimerTick - region.subRegionEvents:AddSubscriber("TimerTick", self, true) - end - expirationTime = state.expirationTime and state.expirationTime > 0 and state.expirationTime or math.huge; - end - local duration = state.duration or 0 - - region:SetTime(region.currentMax - region.currentMin, expirationTime - region.currentMin, state.inverse); - elseif state.progressType == "static" then - if region.paused then - region:Resume() - end - local value = state.value or 0; - local total = state.total or 0; - - region:SetValue(value - region.currentMin, region.currentMax - region.currentMin); - if region.TimerTick then - region.TimerTick = nil - region.subRegionEvents:RemoveSubscriber("TimerTick", region) - end - else - if region.paused then - region:Resume() - end - region:SetTime(0, math.huge) - if region.TimerTick then - region.TimerTick = nil - region.subRegionEvents:RemoveSubscriber("TimerTick", region) - end - end - + region:UpdateProgress() region:UpdateIcon() + end - local duration = state.duration or 0 - local effectiveInverse = (state.inverse and not region.inverseDirection) or (not state.inverse and region.inverseDirection); - region.bar:SetAdditionalBars(state.additionalProgress, region.overlays, region.overlaysTexture, region.currentMin, region.currentMax, effectiveInverse, region.overlayclip); + function region:SetAdditionalProgress(additionalProgress, currentMin, currentMax, inverse) + local effectiveInverse = (inverse and not region.inverseDirection) or (not inverse and region.inverseDirection); + region.bar:SetAdditionalBars(additionalProgress, region.overlays, region.overlaysTexture, currentMin, currentMax, effectiveInverse, region.overlayclip); end -- Scale update function diff --git a/WeakAuras/RegionTypes/Icon.lua b/WeakAuras/RegionTypes/Icon.lua index ab1425cdd3..8192ea8b28 100644 --- a/WeakAuras/RegionTypes/Icon.lua +++ b/WeakAuras/RegionTypes/Icon.lua @@ -15,6 +15,7 @@ local default = { icon = true, desaturate = false, iconSource = -1, + progressSource = {-1, "" }, inverse = false, width = 64, height = 64, @@ -34,6 +35,7 @@ local default = { useCooldownModRate = true }; +Private.regionPrototype.AddProgressSourceToDefault(default) Private.regionPrototype.AddAlphaToDefault(default); local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; @@ -115,6 +117,7 @@ Private.regionPrototype.AddProperties(properties, default); local function GetProperties(data) local result = CopyTable(properties) result.iconSource.values = Private.IconSources(data) + result.progressSource.values = Private.GetProgressSourcesForUi(data) return result end @@ -399,7 +402,30 @@ local function modify(parent, region, data) region.tooltipFrame:EnableMouse(false); end - cooldown:SetReverse(not data.inverse); + function region:SetInverse(inverse) + if region.inverse == inverse then + return + end + + region.inverse = inverse + region:UpdateEffectiveInverse() + end + + function region:UpdateEffectiveInverse() + -- If cooldown.inverse == false then effectiveReverse = not inverse + -- If cooldown.inverse == true then effectiveReverse = inverse + local effectiveReverse = not region.inverse == not cooldown.inverse + cooldown:SetReverse(effectiveReverse) + if (cooldown.expirationTime and cooldown.duration and cooldown:IsShown()) then + -- WORKAROUND SetReverse not applying until next frame + cooldown:SetCooldown(0, 0) + cooldown:SetCooldown(cooldown.expirationTime - cooldown.duration, + cooldown.duration, + cooldown.useCooldownModRate and cooldown.modRate or nil) + end + end + + region:SetInverse(data.inverse) function region:SetHideCountdownNumbers(cooldownTextDisabled) cooldown:SetHideCountdownNumbers(cooldownTextDisabled); @@ -506,16 +532,7 @@ local function modify(parent, region, data) region:UpdateSize(); end - function region:SetInverse(inverse) - cooldown:SetReverse(not inverse); - if (cooldown.expirationTime and cooldown.duration and cooldown:IsShown()) then - -- WORKAROUND SetReverse not applying until next frame - cooldown:SetCooldown(0, 0); - cooldown:SetCooldown(cooldown.expirationTime - cooldown.duration, - cooldown.duration, - cooldown.useCooldownModRate and cooldown.modRate or nil); - end - end + function region:SetCooldownSwipe(cooldownSwipe) region.cooldownSwipe = cooldownSwipe; @@ -541,29 +558,38 @@ local function modify(parent, region, data) cooldown.useCooldownModRate = data.useCooldownModRate cooldown:Hide() if(data.cooldown) then - function region:SetValue(value, total) - cooldown.value = value - cooldown.total = total + function region:UpdateValue() + cooldown.value = self.value + cooldown.total = self.total cooldown.modRate = nil - if (value >= 0 and value <= total) then + if (self.value >= 0 and self.value <= self.total) then cooldown:Show() - cooldown:SetCooldown(GetTime() - (total - value), total) + cooldown:SetCooldown(GetTime() - (self.total - self.value), self.total) + cooldown:Pause() else cooldown:Hide(); end end - function region:SetTime(duration, expirationTime, modRate) - if (duration > 0 and expirationTime > GetTime()) then + function region:UpdateTime() + if self.paused then + cooldown:Pause() + else + cooldown:Resume() + end + if (self.duration > 0 and self.expirationTime > GetTime() and self.expirationTime ~= math.huge) then cooldown:Show(); - cooldown.expirationTime = expirationTime; - cooldown.duration = duration; - cooldown.modRate = modRate; - cooldown:SetCooldown(expirationTime - duration, duration, cooldown.useCooldownModRate and modRate or nil); + cooldown.expirationTime = self.expirationTime + cooldown.duration = self.duration + cooldown.modRate = self.modRate + cooldown.inverse = self.inverse + region:UpdateEffectiveInverse() + cooldown:SetCooldown(self.expirationTime - self.duration, self.duration, + cooldown.useCooldownModRate and self.modRate or nil) else - cooldown.expirationTime = expirationTime; - cooldown.duration = duration; - cooldown.modRate = modRate; + cooldown.expirationTime = self.expirationTime + cooldown.duration = self.duration + cooldown.modRate = self.modRate cooldown:Hide(); end end @@ -579,64 +605,14 @@ local function modify(parent, region, data) end function region:Update() - local state = region.state - if state.progressType == "timed" then - local expirationTime - if state.paused == true then - if not region.paused then - region:Pause() - end - cooldown:Pause() - expirationTime = GetTime() + (state.remaining or 0) - else - if region.paused then - region:Resume() - end - cooldown:Resume() - expirationTime = state.expirationTime and state.expirationTime > 0 and state.expirationTime or math.huge; - end - - 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; - - local max - 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.modRate); - 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; - local max = region.adjustedMax or region.adjustedMaxRel or total; - region:SetValue(value - adjustMin, max - adjustMin); - cooldown:Pause() - else - region:SetTime(0, math.huge) - end - + region:UpdateProgress() region:UpdateIcon() end else - region.SetValue = nil - region.SetTime = nil + region.UpdateValue = nil + region.UpdateTime = nil function region:Update() - local state = region.state region:UpdateIcon() end end diff --git a/WeakAuras/RegionTypes/ProgressTexture.lua b/WeakAuras/RegionTypes/ProgressTexture.lua index 3dcb3b077b..dd852d4f4f 100644 --- a/WeakAuras/RegionTypes/ProgressTexture.lua +++ b/WeakAuras/RegionTypes/ProgressTexture.lua @@ -23,6 +23,7 @@ local defaultFontSize = WeakAuras.defaultFontSize -- region.full_rotation (false) - Allow full rotation [bool] local default = { + progressSource = {-1, "" }, foregroundTexture = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\Aura3", backgroundTexture = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\Aura3", desaturateBackground = false, @@ -60,7 +61,7 @@ local default = { Private.regionPrototype.AddAlphaToDefault(default); -Private.regionPrototype.AddAdjustedDurationToDefault(default); +Private.regionPrototype.AddProgressSourceToDefault(default) local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; @@ -136,16 +137,16 @@ local properties = { max = 360, bigStep = 1, default = 0 - } + }, } Private.regionPrototype.AddProperties(properties, default); local function GetProperties(data) local overlayInfo = Private.GetOverlayInfo(data); + local auraProperties = CopyTable(properties) + auraProperties.progressSource.values = Private.GetProgressSourcesForUi(data) if (overlayInfo and next(overlayInfo)) then - local auraProperties = CopyTable(properties); - for id, display in ipairs(overlayInfo) do auraProperties["overlays." .. id] = { display = string.format(L["%s Overlay Color"], display), @@ -154,10 +155,9 @@ local function GetProperties(data) type = "color", } end - - return auraProperties; + return auraProperties else - return CopyTable(properties); + return auraProperties end end @@ -813,7 +813,6 @@ local function convertToProgress(rprogress, additionalProgress, adjustMin, total local startProgress = 0; local endProgress = 0; - if (additionalProgress.min and additionalProgress.max) then if (totalWidth ~= 0) then startProgress = (additionalProgress.min - adjustMin) / totalWidth; @@ -850,11 +849,12 @@ local function convertToProgress(rprogress, additionalProgress, adjustMin, total return startProgress, endProgress; end -local function UpdateAdditionalProgress(self) - self:SetAdditionalProgress(self.additionalProgress, self.additionalProgressMin, self.additionalProgressMax, self.additionalProgressInverse) +local function ReapplyAdditionalProgress(self) + self:ApplyAdditionalProgress(self.additionalProgress, self.additionalProgressMin, + self.additionalProgressMax, self.additionalProgressInverse) end -local function SetAdditionalProgress(self, additionalProgress, min, max, inverse) +local function ApplyAdditionalProgress(self, additionalProgress, min, max, inverse) self.additionalProgress = additionalProgress; self.additionalProgressMin = min; self.additionalProgressMax = max; @@ -863,11 +863,13 @@ local function SetAdditionalProgress(self, additionalProgress, min, max, inverse local effectiveInverse = (inverse and not self.inverseDirection) or (not inverse and self.inverseDirection); if (additionalProgress) then - local totalWidth = max - min; ensureExtraTextures(self, #additionalProgress); + local totalWidth = max - min; for index, additionalProgress in ipairs(additionalProgress) do local extraTexture = self.extraTextures[index]; - local startProgress, endProgress = convertToProgress(self.progress, additionalProgress, min, totalWidth, effectiveInverse, self.overlayclip); + + local startProgress, endProgress = convertToProgress(self.progress, additionalProgress, min, + totalWidth, effectiveInverse, self.overlayclip) if ((endProgress - startProgress) == 0) then extraTexture:Hide(); else @@ -889,7 +891,7 @@ local function SetAdditionalProgress(self, additionalProgress, min, max, inverse end end -local function SetAdditionalProgressCircular(self, additionalProgress, min, max, inverse) +local function ApplyAdditionalProgressCircular(self, additionalProgress, min, max, inverse) self.additionalProgress = additionalProgress; self.additionalProgressMin = min; self.additionalProgressMax = max; @@ -899,11 +901,10 @@ local function SetAdditionalProgressCircular(self, additionalProgress, min, max, if (additionalProgress) then ensureExtraSpinners(self, #additionalProgress); - + local totalWidth = max - min; 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; @@ -971,20 +972,20 @@ local function SetOrientation(region, orientation) region.foregroundSpinner:UpdateSize(); region.backgroundSpinner:UpdateSize(); region.SetValueOnTexture = CircularSetValueFunctions[region.orientation]; - region.SetAdditionalProgress = SetAdditionalProgressCircular; + region.ApplyAdditionalProgress = ApplyAdditionalProgressCircular; 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; + region.ApplyAdditionalProgress = ApplyAdditionalProgress; 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(); + region:ReapplyAdditionalProgress() end local function create(parent) @@ -1017,7 +1018,7 @@ local function create(parent) Mixin(region.smoothProgress, Private.SmoothStatusBarMixin); region.smoothProgress.SetValue = function(self, progress) region:SetValueOnTexture(progress); - region:UpdateAdditionalProgress(); + region:ReapplyAdditionalProgress() end region.smoothProgress.GetValue = function(self) @@ -1035,10 +1036,28 @@ local function create(parent) 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); +local function FrameTick(self) + local duration = self.duration + local expirationTime = self.expirationTime + local inverse = self.inverse + + local progress = 1; + if (duration ~= 0) then + local remaining = expirationTime - GetTime(); + progress = remaining / duration; + local inversed = (not inverse and self.inverseDirection) or (inverse and not self.inverseDirection); + if(inversed) then + progress = 1 - progress; + end + end + + progress = progress > 0.0001 and progress or 0.0001; + if (self.useSmoothProgress) then + self.smoothProgress:SetSmoothedValue(progress); + else + self:SetValueOnTexture(progress); + self:ReapplyAdditionalProgress() + end end local function modify(parent, region, data) @@ -1057,6 +1076,7 @@ local function modify(parent, region, data) region.overlayclip = data.overlayclip; region.textureWrapMode = data.textureWrapMode; + region.useSmoothProgress = data.smoothProgress background:SetBackgroundOffset(data.backgroundOffset); Private.SetTextureOrAtlas(background, data.sameTexture and data.foregroundTexture or data.backgroundTexture, region.textureWrapMode, region.textureWrapMode); @@ -1116,7 +1136,7 @@ local function modify(parent, region, data) region.overlays = {} end - region.UpdateAdditionalProgress = UpdateAdditionalProgress; + region.ReapplyAdditionalProgress = ReapplyAdditionalProgress region.slanted = data.slanted; region.slant = data.slant; @@ -1282,44 +1302,53 @@ local function modify(parent, region, data) region:Color(data.foregroundColor[1], data.foregroundColor[2], data.foregroundColor[3], data.foregroundColor[4]); - function region:SetTime(duration, expirationTime, inverse) + function region:UpdateTime() 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 (self.duration ~= 0) then + local remaining = self.expirationTime - GetTime() + progress = remaining / self.duration + local inversed = self.inverse ~= region.inverseDirection if(inversed) then progress = 1 - progress; end end progress = progress > 0.0001 and progress or 0.0001; - if (data.smoothProgress) then + if (region.useSmoothProgress) then region.smoothProgress:SetSmoothedValue(progress); else region:SetValueOnTexture(progress); - region:UpdateAdditionalProgress(); + region:ReapplyAdditionalProgress() + end + + if self.paused and self.FrameTick then + self.FrameTick = nil + self.subRegionEvents:RemoveSubscriber("FrameTick", region) + end + if not self.paused and not self.FrameTick then + self.FrameTick = FrameTick + self.subRegionEvents:AddSubscriber("FrameTick", region) end end - function region:SetValue(value, total) + function region:UpdateValue() local progress = 1 - if(total > 0) then - progress = value / total; + if(self.total > 0) then + progress = self.value / self.total; if(region.inverseDirection) then progress = 1 - progress; end end progress = progress > 0.0001 and progress or 0.0001; - if (data.smoothProgress) then + if (region.useSmoothProgress) then region.smoothProgress:SetSmoothedValue(progress); else region:SetValueOnTexture(progress); - region:UpdateAdditionalProgress(); + region:ReapplyAdditionalProgress() end end - if data.smoothProgress then + if region.useSmoothProgress then region.PreShow = function() region.smoothProgress:ResetSmoothedValue(); end @@ -1327,92 +1356,15 @@ local function modify(parent, region, data) region.PreShow = nil end - region.TimerTick = nil + function region:SetAdditionalProgress(additionalProgress, currentMin, currentMax, inverse) + region:ApplyAdditionalProgress(additionalProgress, currentMin, currentMax, inverse) + end + + region.FrameTick = nil function region:Update() + region:UpdateProgress() local state = region.state - local max - local adjustMin - if state.progressType == "timed" then - local expirationTime - if state.paused == true then - if not region.paused then - region:Pause() - end - if region.TimerTick then - region.TimerTick = nil - region.subRegionEvents:RemoveSubscriber("TimerTick", region) - end - expirationTime = GetTime() + (state.remaining or 0) - else - if region.paused then - region:Resume() - end - if not region.TimerTick then - region.TimerTick = TimerTick - region.subRegionEvents:AddSubscriber("TimerTick", region, true) - end - expirationTime = state.expirationTime and state.expirationTime > 0 and state.expirationTime or math.huge; - end - - local duration = state.duration or 0 - if region.adjustedMinRelPercent then - region.adjustedMinRel = region.adjustedMinRelPercent * duration - end - 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); - elseif state.progressType == "static" then - if region.paused then - region:Resume() - end - - local value = state.value or 0; - local total = state.total or 0; - if region.adjustedMinRelPercent then - region.adjustedMinRel = region.adjustedMinRelPercent * total - end - 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.subRegionEvents:RemoveSubscriber("TimerTick", region) - end - else - if region.paused then - region:Resume() - end - region:SetTime(0, math.huge) - if region.TimerTick then - region.TimerTick = nil - region.subRegionEvents:RemoveSubscriber("TimerTick", region) - end - end - - max = max or 0 - - region:SetAdditionalProgress(state.additionalProgress, 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 @@ -1469,7 +1421,7 @@ local function modify(parent, region, data) local progress = 1 - region.progress; progress = progress > 0.0001 and progress or 0.0001; region:SetValueOnTexture(progress); - region:UpdateAdditionalProgress(); + region:ReapplyAdditionalProgress() end function region:SetOverlayColor(id, r, g, b, a) diff --git a/WeakAuras/RegionTypes/RegionPrototype.lua b/WeakAuras/RegionTypes/RegionPrototype.lua index 458a214d90..8a498c96c8 100644 --- a/WeakAuras/RegionTypes/RegionPrototype.lua +++ b/WeakAuras/RegionTypes/RegionPrototype.lua @@ -13,67 +13,11 @@ function Private.regionPrototype.AddAlphaToDefault(default) default.alpha = 1.0; end --- Adjusted Duration - -function Private.regionPrototype.AddAdjustedDurationToDefault(default) - default.useAdjustededMax = false; - default.useAdjustededMin = false; -end - -function Private.regionPrototype.AddAdjustedDurationOptions(options, data, order) - options.useAdjustededMin = { - type = "toggle", - width = WeakAuras.normalWidth, - name = L["Set Minimum Progress"], - desc = L["Values/Remaining Time below this value are displayed as no progress."], - order = order - }; - - options.adjustedMin = { - type = "input", - validate = WeakAuras.ValidateNumericOrPercent, - width = WeakAuras.normalWidth, - order = order + 0.01, - name = L["Minimum"], - hidden = function() return not data.useAdjustededMin end, - desc = L["Enter static or relative values with %"] - }; - - options.useAdjustedMinSpacer = { - type = "description", - width = WeakAuras.normalWidth, - name = "", - order = order + 0.02, - hidden = function() return not (not data.useAdjustededMin and data.useAdjustededMax) end, - }; - - options.useAdjustededMax = { - type = "toggle", - width = WeakAuras.normalWidth, - name = L["Set Maximum Progress"], - desc = L["Values/Remaining Time above this value are displayed as full progress."], - order = order + 0.03 - }; - - options.adjustedMax = { - type = "input", - width = WeakAuras.normalWidth, - validate = WeakAuras.ValidateNumericOrPercent, - order = order + 0.04, - name = L["Maximum"], - hidden = function() return not data.useAdjustededMax end, - desc = L["Enter static or relative values with %"] - }; - - options.useAdjustedMaxSpacer = { - type = "description", - width = WeakAuras.normalWidth, - name = "", - order = order + 0.05, - hidden = function() return not (data.useAdjustededMin and not data.useAdjustededMax) end, - }; - - return options; +-- Progress Sources +function Private.regionPrototype.AddProgressSourceToDefault(default) + default.progressSource = {-1, ""} + default.useAdjustededMax = false + default.useAdjustededMin = false end local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; @@ -151,6 +95,29 @@ function Private.regionPrototype.AddProperties(properties, defaultsForRegion) isPercent = true } end + + if defaultsForRegion and defaultsForRegion.progressSource then + properties["progressSource"] = { + display = L["Progress Source"], + setter = "SetProgressSource", + type = "progressSource", + values = {}, + } + + properties["adjustedMin"] = { + display = L["Minimum Progress"], + setter = "SetAdjustedMin", + type = "string", + validate = WeakAuras.ValidateNumericOrPercent, + } + + properties["adjustedMax"] = { + display = L["Maximum Progress"], + setter = "SetAdjustedMax", + type = "string", + validate = WeakAuras.ValidateNumericOrPercent, + } + end end local function SoundRepeatStop(self) @@ -371,55 +338,274 @@ local function GetRegionAlpha(self) return self.animAlpha or self.alpha or 1; end -local function SetAnimAlpha(self, alpha) - if (self.animAlpha == alpha) then - return; +local function SetProgressSource(self, progressSource) + self.progressSource = progressSource + self:UpdateProgress() +end + +local function SetAdjustedMin(self, adjustedMin) + local percent = string.match(adjustedMin, "(%d+)%%") + if percent then + self.adjustedMinRelPercent = tonumber(percent) / 100 + self.adjustedMin = nil + else + self.adjustedMin = tonumber(adjustedMin) + self.adjustedMinRelPercent = nil end - self.animAlpha = alpha; - if (WeakAuras.IsOptionsOpen()) then - xpcall(self.SetAlpha, Private.GetErrorHandlerId(self.id, L["Custom Fade Animation"]), self, max(self.animAlpha or self.alpha or 1, 0.5)) + self:UpdateProgress() +end + +local function SetAdjustedMax(self, adjustedMax) + local percent = string.match(adjustedMax, "(%d+)%%") + if percent then + self.adjustedMaxRelPercent = tonumber(percent) / 100 else - xpcall(self.SetAlpha, Private.GetErrorHandlerId(self.id, L["Custom Fade Animation"]), self, self.animAlpha or self.alpha or 1) + self.adjustedMax = tonumber(adjustedMax) end - self.subRegionEvents:Notify("AlphaChanged") + self:UpdateProgress() end -local function SetTriggerProvidesTimer(self, timerTick) - self.triggerProvidesTimer = timerTick - self:UpdateTimerTick() +local function GetProgressSource(self) + return self.progressSource end -local function TimerTickForRegion(region) - Private.StartProfileSystem("timer tick") - Private.StartProfileAura(region.id); - region.subRegionEvents:Notify("TimerTick") - Private.StopProfileAura(region.id); - Private.StopProfileSystem("timer tick") +local function GetMinMaxProgress(self) + return self.minProgress or 0, self.maxProgress or 0 +end + +local function UpdateProgressFromState(self, minMaxConfig, state, progressSource) + local progressType = progressSource[2] + local property = progressSource[3] + local totalProperty = progressSource[4] + local modRateProperty = progressSource[5] + local inverseProperty = progressSource[6] + local pausedProperty = progressSource[7] + local remainingProperty = progressSource[8] + + if progressType == "number" then + local value = state[property] or 0 + local total = totalProperty and state[totalProperty] or 0 + -- We don't care about inverse, modRate or paused + local adjustMin + if minMaxConfig.adjustedMin then + adjustMin = minMaxConfig.adjustedMin + elseif minMaxConfig.adjustedMinRelPercent then + adjustMin = minMaxConfig.adjustedMinRelPercent * total + else + adjustMin = 0 + end + local max + if minMaxConfig.adjustedMax then + max = minMaxConfig.adjustedMax + elseif minMaxConfig.adjustedMaxRelPercent then + max = minMaxConfig.adjustedMaxRelPercent * total + else + max = total + end + -- The output of UpdateProgress is setting various values on self + -- and calling UpdateTime/UpdateValue. Not an ideal interface, but + -- the animation code/sub elements needs those values in some convenient place + self.minProgress, self.maxProgress = adjustMin, max + self.progressType = "static" + self.value = value - adjustMin + self.total = max - adjustMin + if self.UpdateValue then + self:UpdateValue() + end + if self.SetAdditionalProgress then + self:SetAdditionalProgress(state.additionalProgress, adjustMin, max, false) + end + elseif progressType == "timer" then + local expirationTime + local paused = pausedProperty and state[pausedProperty] + local inverse = inverseProperty and state[inverseProperty] + local remaining + if paused then + remaining = remainingProperty and state[remainingProperty] + expirationTime = GetTime() + (remaining or 0) + else + expirationTime = state[property] or math.huge + end + + local duration = totalProperty and state[totalProperty] or 0 + local modRate = modRateProperty and state[modRateProperty] or nil + local adjustMin + if minMaxConfig.adjustedMin then + adjustMin = minMaxConfig.adjustedMin + elseif minMaxConfig.adjustedMinRelPercent then + adjustMin = minMaxConfig.adjustedMinRelPercent * duration + else + adjustMin = 0 + end + + local max + if minMaxConfig.adjustedMax then + max = minMaxConfig.adjustedMax + elseif minMaxConfig.adjustedMaxRelPercent then + max = minMaxConfig.adjustedMaxRelPercent * duration + else + max = duration + end + self.minProgress, self.maxProgress = adjustMin, max + self.progressType = "timed" + self.duration = max - adjustMin + self.expirationTime = expirationTime - adjustMin + self.remaining = remaining + self.modRate = modRate + self.inverse = inverse + self.paused = paused + if self.UpdateTime then + self:UpdateTime() + end + if self.SetAdditionalProgress then + self:SetAdditionalProgress(state.additionalProgress, adjustMin, max, inverse) + end + elseif progressType == "elapsedTimer" then + local startTime = state[property] or math.huge + local duration = totalProperty and state[totalProperty] or 0 + local adjustMin + if minMaxConfig.adjustedMin then + adjustMin = minMaxConfig.adjustedMin + elseif minMaxConfig.adjustedMinRelPercent then + adjustMin = minMaxConfig.adjustedMinRelPercent * duration + else + adjustMin = 0 + end + + local max + if minMaxConfig.adjustedMax then + max = minMaxConfig.adjustedMax + elseif minMaxConfig.adjustedMaxRelPercent then + max = minMaxConfig.adjustedMaxRelPercent * duration + else + max = duration + end + self.minProgress, self.maxProgress = adjustMin, max + self.progressType = "timed" + self.duration = max - adjustMin + self.expirationTime = startTime + adjustMin + self.duration + self.modRate = nil + self.inverse = true + self.paused = false + self.remaining = nil + if self.UpdateTime then + self:UpdateTime() + end + if self.SetAdditionalProgress then + self:SetAdditionalProgress(state.additionalProgress, adjustMin, max, false) + end + end end -local function UpdateTimerTick(self) - if self.triggerProvidesTimer and self.subRegionEvents:HasSubscribers("TimerTick") and self.toShow then - if not self:GetScript("OnUpdate") then - self:SetScript("OnUpdate", function() - TimerTickForRegion(self) - end); +local autoTimedProgressSource = {-1, "timer", "expirationTime", "duration", "modRate", "inverse", "paused", "remaining"} +local autoStaticProgressSource = {-1, "number", "value", "total", nil, nil, nil, nil} +local function UpdateProgressFromAuto(self, minMaxConfig, state) + if state.progressType == "timed" then + UpdateProgressFromState(self, minMaxConfig, state, autoTimedProgressSource) + elseif state.progressType == "static"then + UpdateProgressFromState(self, minMaxConfig, state, autoStaticProgressSource) + else + self.minProgress, self.maxProgress = nil, nil + self.progressType = "timed" + self.duration = 0 + self.expirationTime = math.huge + self.modRate = nil + self.inverse = false + self.paused = true + self.remaining = math.huge + if self.UpdateTime then + self:UpdateTime() end + if self.SetAdditionalProgress then + self:SetAdditionalProgress(nil) + end + end +end + +local function UpdateProgressFromManual(self, minMaxConfig, state, value, total) + value = type(value) == "number" and value or 0 + total = type(total) == "number" and total or 0 + local adjustMin + if minMaxConfig.adjustedMin then + adjustMin = minMaxConfig.adjustedMin + elseif minMaxConfig.adjustedMinRelPercent then + adjustMin = minMaxConfig.adjustedMinRelPercent * total + else + adjustMin = 0 + end + local max + if minMaxConfig.adjustedMax then + max = minMaxConfig.adjustedMax + elseif minMaxConfig.adjustedMaxRelPercent then + max = minMaxConfig.adjustedMaxRelPercent * total + else + max = total + end + self.minProgress, self.maxProgress = adjustMin, max + self.progressType = "static" + self.value = value - adjustMin + self.total = max - adjustMin + if self.UpdateValue then + self:UpdateValue() + end + if self.SetAdditionalProgress then + self:SetAdditionalProgress(state.additionalProgress, adjustMin, max) + end +end + +local function UpdateProgressFrom(self, progressSource, minMaxConfig, state, states, parent) + local trigger = progressSource and progressSource[1] or -1 + + if trigger == -2 then + -- sub element'a auto uses the whatever progress the main region has + UpdateProgressFromAuto(self, minMaxConfig, parent) + elseif trigger == -1 then + -- auto for regions uses the state + UpdateProgressFromAuto(self, minMaxConfig, state) + elseif trigger == 0 then + UpdateProgressFromManual(self, minMaxConfig, state, progressSource[3], progressSource[4]) else - if self:GetScript("OnUpdate") then - self:SetScript("OnUpdate", nil); + UpdateProgressFromState(self, minMaxConfig, states[trigger] or {}, progressSource) + end +end + +-- For regions +local function UpdateProgress(self) + UpdateProgressFrom(self, self.progressSource, self, self.state, self.states) +end + +Private.UpdateProgressFrom = UpdateProgressFrom + +local function SetAnimAlpha(self, alpha) + if alpha then + if alpha > 1 then + alpha = 1 + elseif alpha < 0 then + alpha = 0 end end + if (self.animAlpha == alpha) then + return; + end + self.animAlpha = alpha; + local errorHandler = Private.GetErrorHandlerId(self.id, L["Custom Fade Animation"]) + if (WeakAuras.IsOptionsOpen()) then + xpcall(self.SetAlpha, errorHandler, self, max(self.animAlpha or self.alpha or 1, 0.5)) + else + xpcall(self.SetAlpha, errorHandler, self, self.animAlpha or self.alpha or 1) + end + self.subRegionEvents:Notify("AlphaChanged") end -local function UpdateFrameTick(self) +local function UpdateTick(self) if self.subRegionEvents:HasSubscribers("FrameTick") and self.toShow then - Private.FrameTick:AddSubscriber("FrameTick", self) + Private.FrameTick:AddSubscriber("Tick", self) else - Private.FrameTick:RemoveSubscriber("FrameTick", self) + Private.FrameTick:RemoveSubscriber("Tick", self) end end -local function FrameTick(self) +local function Tick(self) Private.StartProfileAura(self.id) self.values.lastCustomTextUpdate = nil self.subRegionEvents:Notify("FrameTick") @@ -476,12 +662,18 @@ function Private.regionPrototype.create(region) region.SetRegionAlpha = SetRegionAlpha; region.GetRegionAlpha = GetRegionAlpha; end + if defaultsForRegion and defaultsForRegion.progressSource then + region.SetProgressSource = SetProgressSource + region.GetProgressSource = GetProgressSource + region.SetAdjustedMin = SetAdjustedMin + region.SetAdjustedMax = SetAdjustedMax + end + region.UpdateProgress = UpdateProgress + region.GetMinMaxProgress = GetMinMaxProgress region.SetAnimAlpha = SetAnimAlpha; - region.SetTriggerProvidesTimer = SetTriggerProvidesTimer - region.UpdateTimerTick = UpdateTimerTick - region.UpdateFrameTick = UpdateFrameTick - region.FrameTick = FrameTick + region.UpdateTick = UpdateTick + region.Tick = Tick region.subRegionEvents = Private.CreateSubscribableObject() region.AnchorSubRegion = AnchorSubRegion @@ -490,14 +682,12 @@ function Private.regionPrototype.create(region) region:SetPoint("CENTER", UIParent, "CENTER") end --- SetDurationInfo - function Private.regionPrototype.modify(parent, region, data) region.state = nil region.states = nil region.subRegionEvents:ClearSubscribers() region.subRegionEvents:ClearCallbacks() - Private.FrameTick:RemoveSubscriber("FrameTick", region) + Private.FrameTick:RemoveSubscriber("Tick", region) local defaultsForRegion = Private.regionTypes[data.regionType] and Private.regionTypes[data.regionType].default; @@ -505,18 +695,20 @@ function Private.regionPrototype.modify(parent, region, data) region:SetRegionAlpha(data.alpha) end - local hasAdjustedMin = defaultsForRegion and defaultsForRegion.useAdjustededMin ~= nil and data.useAdjustededMin - and data.adjustedMin; - local hasAdjustedMax = defaultsForRegion and defaultsForRegion.useAdjustededMax ~= nil and data.useAdjustededMax - and data.adjustedMax; + local hasProgressSource = defaultsForRegion and defaultsForRegion.progressSource + local hasAdjustedMin = hasProgressSource and data.useAdjustededMin and data.adjustedMin + local hasAdjustedMax = hasProgressSource and data.useAdjustededMax and data.adjustedMax + region.progressSource = nil region.adjustedMin = nil - region.adjustedMinRel = nil region.adjustedMinRelPercent = nil region.adjustedMax = nil - region.adjustedMaxRel = nil region.adjustedMaxRelPercent = nil + if hasProgressSource then + region.progressSource = Private.AddProgressSourceMetaData(data, data.progressSource) + end + if (hasAdjustedMin) then local percent = string.match(data.adjustedMin, "(%d+)%%") if percent then @@ -568,7 +760,7 @@ function Private.regionPrototype.modify(parent, region, data) data.actions.start[fullKey] = default end return data.actions.start[fullKey] - end, true) + end, true, data) region.finishFormatters = Private.CreateFormatters(data.actions.finish.message, function(key, default) local fullKey = "message_format_" .. key @@ -576,7 +768,7 @@ function Private.regionPrototype.modify(parent, region, data) data.actions.finish[fullKey] = default end return data.actions.finish[fullKey] - end, true) + end, true, data) end function Private.regionPrototype.modifyFinish(parent, region, data) @@ -607,91 +799,57 @@ function Private.regionPrototype.modifyFinish(parent, region, data) end end - region.subRegionEvents:SetOnSubscriptionStatusChanged("TimerTick", function() - region:UpdateTimerTick() - end) - region:UpdateTimerTick() - region.subRegionEvents:SetOnSubscriptionStatusChanged("FrameTick", function() - region:UpdateFrameTick() + region:UpdateTick() end) - region:UpdateFrameTick() + region:UpdateTick() Private.ApplyFrameLevel(region) end -local function SetProgressValue(region, value, total) - local adjustMin = region.adjustedMin or 0; - local max = region.adjustedMax or total; - - region:SetValue(value - adjustMin, max - adjustMin); -end - -local regionsForFrameTick = {} - local frameForFrameTick = CreateFrame("Frame"); Private.frames["Frame Tick Frame"] = frameForFrameTick Private.FrameTick = Private.CreateSubscribableObject() Private.FrameTick.OnUpdateHandler = function() - if WeakAuras.IsOptionsOpen() then - return - end Private.StartProfileSystem("frame tick") - Private.FrameTick:Notify("FrameTick") + Private.FrameTick:Notify("Tick") Private.StopProfileSystem("frame tick") end -Private.FrameTick:SetOnSubscriptionStatusChanged("FrameTick", function() - if Private.FrameTick:HasSubscribers("FrameTick") then +Private.FrameTick:SetOnSubscriptionStatusChanged("Tick", function() + if Private.FrameTick:HasSubscribers("Tick") then frameForFrameTick:SetScript("OnUpdate", Private.FrameTick.OnUpdateHandler); else frameForFrameTick:SetScript("OnUpdate", nil) end end) -local function TimerTickForSetDuration(self) - local duration = self.duration - local adjustMin = self.adjustedMin or 0; - - local max - if duration == 0 then - max = 0 - elseif self.adjustedMax then - max = self.adjustedMax - else - max = duration - end - - self:SetTime(max - adjustMin, self.expirationTime - adjustMin, self.inverse); -end - -function Private.regionPrototype.AddSetDurationInfo(region) - if (region.SetValue and region.SetTime) then - region.generatedSetDurationInfo = true; - - -- WeakAuras no longer calls SetDurationInfo, but some people do that, - -- In that case we also need to overwrite TimerTick +function Private.regionPrototype.AddSetDurationInfo(region, uid) + if (region.UpdateValue and region.UpdateTime) then + -- WeakAuras no longer calls SetDurationInfo, but some people do that region.SetDurationInfo = function(self, duration, expirationTime, customValue, inverse) - self.duration = duration or 0 - self.expirationTime = expirationTime; - self.inverse = inverse; - + -- For now don't warn against SetDurationInfo + -- Private.AuraWarnings.UpdateWarning(uid, "SetDurationInfo", "warning", L["Aura is using deprecated SetDurationInfo"]) if customValue then - SetProgressValue(region, duration, expirationTime); - region.TimerTick = nil - region.subRegionEvents:RemoveSubscriber("TimerTick", self) - else local adjustMin = region.adjustedMin or 0; - region:SetTime((duration ~= 0 and region.adjustedMax or duration) - adjustMin, expirationTime - adjustMin, inverse); - - region.TimerTick = TimerTickForSetDuration - region.subRegionEvents:AddSubscriber("TimerTick", self, true) + local max = self.adjustedMax or expirationTime + region.progressType = "static" + region.value = duration - adjustMin + region.total = max - adjustMin + region:UpdateValue() + else + local adjustMin = self.adjustedMin or 0; + self.progressType = "timed" + self.duration = (duration ~= 0 and self.adjustedMax or duration) - adjustMin + self.expirationTime = expirationTime - adjustMin + self.modRate = nil + self.inverse = inverse + self.paused = false + self.remaining = nil + self:UpdateTime() end end - elseif (region.generatedSetDurationInfo) then - region.generatedSetDurationInfo = nil; - region.SetDurationInfo = nil; end end @@ -800,8 +958,7 @@ function Private.regionPrototype.AddExpandFunction(data, region, cloneId, parent region:SoundRepeatStop(); end - region:UpdateFrameTick() - region:UpdateTimerTick() + region:UpdateTick() end function region:Expand() if (region.toShow) then @@ -836,8 +993,7 @@ function Private.regionPrototype.AddExpandFunction(data, region, cloneId, parent end parent:ActivateChild(data.id, cloneId); - region:UpdateFrameTick() - region:UpdateTimerTick() + region:UpdateTick() end elseif not(data.controlledChildren) then function region:Collapse() @@ -859,8 +1015,7 @@ function Private.regionPrototype.AddExpandFunction(data, region, cloneId, parent region:SoundRepeatStop(); end - region:UpdateFrameTick() - region:UpdateTimerTick() + region:UpdateTick() end function region:Expand() if data.anchorFrameType == "SELECTFRAME" @@ -907,8 +1062,7 @@ function Private.regionPrototype.AddExpandFunction(data, region, cloneId, parent parent:UpdateBorder(region); end - region:UpdateFrameTick() - region:UpdateTimerTick() + region:UpdateTick() end end -- Stubs that allow for polymorphism @@ -918,16 +1072,6 @@ function Private.regionPrototype.AddExpandFunction(data, region, cloneId, parent if not region.Expand then function region:Expand() end end - if not region.Pause then - function region:Pause() - self.paused = true - end - end - if not region.Resume then - function region:Resume() - self.paused = nil - end - end end function Private.SetTextureOrAtlas(texture, path, wrapModeH, wrapModeV) diff --git a/WeakAuras/RegionTypes/StopMotion.lua b/WeakAuras/RegionTypes/StopMotion.lua index 2106bddc76..59b8dd3fb7 100644 --- a/WeakAuras/RegionTypes/StopMotion.lua +++ b/WeakAuras/RegionTypes/StopMotion.lua @@ -6,6 +6,7 @@ local texture_data = WeakAuras.StopMotion.texture_data; local L = WeakAuras.L; local default = { + progressSource = {-1, "" }, foregroundTexture = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\stopmotion", backgroundTexture = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\stopmotion", desaturateBackground = false, @@ -42,6 +43,8 @@ local default = { hideBackground = true }; +Private.regionPrototype.AddProgressSourceToDefault(default) + local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20; local properties = { @@ -85,8 +88,15 @@ local properties = { Private.regionPrototype.AddProperties(properties, default); +local function GetProperties(data) + local result = CopyTable(properties) + result.progressSource.values = Private.GetProgressSourcesForUi(data) + return result +end + local function create(parent) local frame = CreateFrame("Frame", nil, UIParent); + --- @cast frame WARegion frame.regionType = "stopmotion" frame:SetMovable(true); frame:SetResizable(true); @@ -157,10 +167,115 @@ local function SetFrameViaFrames(self, texture, frame) self:SetTexture(texture .. format("%03d", frame)); end +local function SetProgress(self, progress) + local frames + local startFrame = self.startFrame + local endFrame = self.endFrame + local inverse = self.inverseDirection + if (endFrame >= startFrame) then + frames = endFrame - startFrame + 1 + else + frames = startFrame - endFrame + 1 + startFrame, endFrame = endFrame, startFrame + inverse = not inverse + end + local frame = floor( (frames - 1) * progress) + startFrame + + if (inverse) then + frame = endFrame - frame + startFrame; + end + + if (frame > endFrame) then + frame = endFrame + end + if (frame < startFrame) then + frame = startFrame + end + self.foreground:SetFrame(self.foregroundTexture, frame); +end + +local FrameTickFunctions = { + progressTimer = function(self) + Private.StartProfileSystem("stopmotion") + Private.StartProfileAura(self.id) + local remaining = self.expirationTime - GetTime() + local progress = 1 - (remaining / self.duration) + + self:SetProgress(progress) + + Private.StopProfileAura(self.id) + Private.StopProfileSystem("stopmotion") + end, + timed = function(self) + if (not self.startTime) then return end + + Private.StartProfileSystem("stopmotion") + Private.StartProfileAura(self.id) + + local timeSinceStart = (GetTime() - self.startTime) + local newCurrentFrame = floor(timeSinceStart * (self.frameRate or 15)) + if (newCurrentFrame == self.currentFrame) then + Private.StopProfileAura(self.id) + Private.StopProfileSystem("stopmotion") + return + end + + self.currentFrame = newCurrentFrame + + local frames + local startFrame = self.startFrame + local endFrame = self.endFrame + local inverse = self.inverseDirection + if (endFrame >= startFrame) then + frames = endFrame - startFrame + 1 + else + frames = startFrame - endFrame + 1 + startFrame, endFrame = endFrame, startFrame + inverse = not inverse + end + + local frame = 0 + if (self.animationType == "loop") then + frame = (newCurrentFrame % frames) + startFrame + elseif (self.animationType == "bounce") then + local direction = floor(newCurrentFrame / frames) % 2 + if (direction == 0) then + frame = (newCurrentFrame % frames) + startFrame + else + frame = endFrame - (newCurrentFrame % frames) + end + elseif (self.animationType == "once") then + frame = newCurrentFrame + startFrame + if (frame > endFrame) then + frame = endFrame + end + end + if (inverse) then + frame = endFrame - frame + startFrame + end + + if (frame > endFrame) then + frame = endFrame + end + if (frame < startFrame) then + frame = startFrame + end + self.foreground:SetFrame(self.foregroundTexture, frame) + + Private.StopProfileAura(self.id) + Private.StopProfileSystem("stopmotion") + end, +} + local function modify(parent, region, data) Private.regionPrototype.modify(parent, region, data); region.foreground = region.foreground or {} region.background = region.background or {} + region.frameRate = data.frameRate + region.inverseDirection = data.inverse + region.animationType = data.animationType + region.foregroundTexture = data.foregroundTexture + region.FrameTick = nil 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]" @@ -372,101 +487,52 @@ local function modify(parent, region, data) function region:PreShow() region.startTime = GetTime(); - end - - local function onUpdate() - if (not region.startTime) then return end - - Private.StartProfileAura(region.id); - Private.StartProfileSystem("stopmotion") - local timeSinceStart = (GetTime() - region.startTime); - local newCurrentFrame = floor(timeSinceStart * (data.frameRate or 15)); - if (newCurrentFrame == region.currentFrame) then - Private.StopProfileAura(region.id); - Private.StopProfileSystem("stopmotion") - return; + if region.FrameTick then + region:FrameTick() end + end - region.currentFrame = newCurrentFrame; - - local frames; - local startFrame = region.startFrame; - local endFrame = region.endFrame; - local inverse = data.inverse; - if (endFrame >= startFrame) then - frames = endFrame - startFrame + 1; - else - frames = startFrame - endFrame + 1; - startFrame, endFrame = endFrame, startFrame; - inverse = not inverse; - end + region.SetProgress = SetProgress - local frame = 0; - if (data.animationType == "loop") then - frame = (newCurrentFrame % frames) + startFrame; - elseif (data.animationType == "bounce") then - local direction = floor(newCurrentFrame / frames) % 2; - if (direction == 0) then - frame = (newCurrentFrame % frames) + startFrame; - else - frame = endFrame - (newCurrentFrame % frames); - end - elseif (data.animationType == "once") then - frame = newCurrentFrame + startFrame - if (frame > endFrame) then - frame = endFrame; - end - elseif (data.animationType == "progress") then - if (not region.state) then - -- Do nothing - elseif (region.state.progressType == "static") then - local value = region.state.value or 0 - local total = region.state.total ~= 0 and region.state.total or 1 - frame = floor((frames - 1) * value / total) + startFrame; - else - local remaining - if region.state.paused then - remaining = region.state.remaining or 0; - else - remaining = region.state.expirationTime and (region.state.expirationTime - GetTime()) or 0; - end - local progress = region.state.duration and region.state.duration > 0 and (1 - (remaining / region.state.duration)) or 0; - frame = floor( (frames - 1) * progress) + startFrame; - end + if data.animationType == "loop" or data.animationType == "bounce" or data.animationType == "once" then + region.FrameTick = FrameTickFunctions.timed + region.subRegionEvents:AddSubscriber("FrameTick", region, true) + function region:Update() end - - if (inverse) then - frame = endFrame - frame + startFrame; + elseif data.animationType == "progress" then + function region:Update() + region:UpdateProgress() end - if (frame > endFrame) then - frame = endFrame - end - if (frame < startFrame) then - frame = startFrame + function region:UpdateValue() + local progress = 0; + if (self.total ~= 0) then + progress = self.value / self.total + end + self:SetProgress(progress) + if self.FrameTick then + self.FrameTick = nil + self.subRegionEvents:RemoveSubscriber("FrameTick", self) + end end - region.foreground:SetFrame(data.foregroundTexture, frame); - - Private.StopProfileAura(region.id); - Private.StopProfileSystem("stopmotion") - end; - region.FrameTick = onUpdate; - if region.FrameTick then - region.subRegionEvents:AddSubscriber("FrameTick", region, true) - end - - function region:Update() - if region.state.paused then - if not region.paused then - region:Pause() + function region:UpdateTime() + if self.paused and self.FrameTick then + self.FrameTick = nil + self.subRegionEvents:RemoveSubscriber("FrameTick", self) end - else - if region.paused then - region:Resume() + self.expirationTime = self.expirationTime + self.duration = self.duration + if not self.paused then + if not self.FrameTick then + self.FrameTick = FrameTickFunctions.progressTimer + self.subRegionEvents:AddSubscriber("FrameTick", self) + end + + self:FrameTick() end + end - onUpdate(); end function region:SetForegroundDesaturated(b) @@ -496,4 +562,4 @@ local function validate(data) Private.EnforceSubregionExists(data, "subbackground") end -Private.RegisterRegionType("stopmotion", create, modify, default, properties, validate); +Private.RegisterRegionType("stopmotion", create, modify, default, GetProperties, validate); diff --git a/WeakAuras/RegionTypes/Text.lua b/WeakAuras/RegionTypes/Text.lua index b7391bf91e..7d5f60bada 100644 --- a/WeakAuras/RegionTypes/Text.lua +++ b/WeakAuras/RegionTypes/Text.lua @@ -55,10 +55,6 @@ local properties = { Private.regionPrototype.AddProperties(properties, default); -local function GetProperties(data) - return properties; -end - local function create(parent) local region = CreateFrame("Frame", nil, parent); region.regionType = "text" @@ -69,9 +65,6 @@ local function create(parent) text:SetWordWrap(true); text:SetNonSpaceWrap(true); - region.duration = 0; - region.expirationTime = math.huge; - Private.regionPrototype.create(region); return region; @@ -217,7 +210,7 @@ local function modify(parent, region, data) end end - formatters = Private.CreateFormatters(texts, getter) + formatters = Private.CreateFormatters(texts, getter, false, data) end local customTextFunc = nil @@ -249,12 +242,11 @@ local function modify(parent, region, data) Update = UpdateText or function() end end - local TimerTick + local FrameTick if Private.ContainsPlaceHolders(self.displayText, "p") then - TimerTick = UpdateText + FrameTick = UpdateText end - local FrameTick if customTextFunc and data.customTextUpdate == "update" then if Private.ContainsCustomPlaceHolder(self.displayText) then FrameTick = function() @@ -266,7 +258,6 @@ local function modify(parent, region, data) self.Update = Update self.FrameTick = FrameTick - self.TimerTick = TimerTick if not UpdateText then local textStr = self.displayText @@ -282,11 +273,6 @@ local function modify(parent, region, data) self.subRegionEvents:RemoveSubscriber("FrameTick", self) end - if self.TimerTick then - self.subRegionEvents:AddSubscriber("TimerTick", self, true) - else - self.subRegionEvents:RemoveSubscriber("TimerTick", self) - end if self.Update and self.state then self:Update() end @@ -343,7 +329,7 @@ local function validate(data) Private.EnforceSubregionExists(data, "subbackground") end -Private.RegisterRegionType("text", create, modify, default, GetProperties, validate); +Private.RegisterRegionType("text", create, modify, default, properties, validate); -- Fallback region type diff --git a/WeakAuras/SubRegionTypes/SubText.lua b/WeakAuras/SubRegionTypes/SubText.lua index 2a39138cf1..b85428ee65 100644 --- a/WeakAuras/SubRegionTypes/SubText.lua +++ b/WeakAuras/SubRegionTypes/SubText.lua @@ -305,7 +305,7 @@ local function modify(parent, region, parentData, data, first) end return data[fullKey] end - region.subTextFormatters = Private.CreateFormatters(texts, getter) + region.subTextFormatters = Private.CreateFormatters(texts, getter, false, parentData) function region:ConfigureTextUpdate() local UpdateText @@ -334,12 +334,11 @@ local function modify(parent, region, parentData, data, first) Update = UpdateText end - local TimerTick + local FrameTick if Private.ContainsPlaceHolders(region.text_text, "p") then - TimerTick = UpdateText + FrameTick = UpdateText end - local FrameTick if parent.customTextFunc and parentData.customTextUpdate == "update" then if Private.ContainsCustomPlaceHolder(region.text_text) then FrameTick = function() @@ -354,7 +353,6 @@ local function modify(parent, region, parentData, data, first) region.Update = Update region.FrameTick = FrameTick - region.TimerTick = TimerTick if not UpdateText then if text:GetFont() then @@ -381,13 +379,6 @@ local function modify(parent, region, parentData, data, first) else parent.subRegionEvents:RemoveSubscriber("FrameTick", region) end - if self.TimerTick then - if visible then - parent.subRegionEvents:AddSubscriber("TimerTick", region) - end - else - parent.subRegionEvents:RemoveSubscriber("TimerTick", region) - end if self.Update and parent.state and visible then self:Update() end diff --git a/WeakAuras/SubRegionTypes/Tick.lua b/WeakAuras/SubRegionTypes/Tick.lua index d8d6e01ed5..2d5a1c8844 100644 --- a/WeakAuras/SubRegionTypes/Tick.lua +++ b/WeakAuras/SubRegionTypes/Tick.lua @@ -9,7 +9,8 @@ local default = function() tick_visible = true, tick_color = {1, 1, 1, 1}, tick_placement_mode = "AtValue", - tick_placement = "50", + tick_placements = {"50"}, + progressSources = {{-2, ""}}, automatic_length = true, tick_thickness = 2, tick_length = 30, @@ -42,12 +43,6 @@ local properties = { type = "list", values = Private.tick_placement_modes, }, - tick_placement = { - display = L["Placement"], - setter = "SetTickPlacement", - type = "number", - validate = WeakAuras.ValidateNumeric, - }, automatic_length = { display = L["Automatic Length"], setter = "SetAutomaticLength", @@ -92,6 +87,22 @@ local properties = { }, } +local function GetProperties(parentData, data) + local result = CopyTable(properties) + for i in ipairs(data.tick_placements) do + + result["tick_placement" .. i] = { + display = #data.tick_placements > 1 and L["Placement %i"]:format(i) or L["Placement"], + setter = "SetTickPlacement", + type = "number", + arg1 = i, + validate = WeakAuras.ValidateNumeric, + } + end + + return result +end + local auraBarAnchor = { ["HORIZONTAL"] = "LEFT", ["HORIZONTAL_INVERSE"] = "RIGHT", @@ -108,11 +119,7 @@ local auraBarAnchorInverse = { local function create() local subRegion = CreateFrame("Frame", nil, UIParent) - subRegion.texture = subRegion:CreateTexture() - subRegion.texture:SetSnapToPixelGrid(false) - subRegion.texture:SetTexelSnappingBias(0) - subRegion.texture:SetDrawLayer("ARTWORK", 3) - subRegion.texture:SetAllPoints(subRegion) + subRegion.ticks = {} return subRegion end @@ -125,19 +132,14 @@ local function onRelease(subRegion) end local funcs = { - Update = function(self, state) - self.trigger_inverse = state.inverse - self.state = state - if state.progressType == "timed" then - self.trigger_total = state.duration - elseif state.progressType == "static" then - self.trigger_total = state.total - else - self.trigger_total = nil + Update = function(self, state, states) + for i, progressSource in ipairs(self.progressSources) do + self.progressData[i] = {} + Private.UpdateProgressFrom(self.progressData[i], progressSource, {}, state, states, self.parent) end self:UpdateVisible() self:UpdateTickPlacement(); - self:UpdateTimerTick() + self:UpdateFrameTick() end, OrientationChanged = function(self) self.orientation = self.parent:GetEffectiveOrientation() @@ -157,7 +159,7 @@ local funcs = { self:UpdateTickSize() end, InverseChanged = function(self) - self.inverse = self.parent:GetInverse() + self.inverse_direction = self.parent:GetInverse() self:UpdateTickPlacement() end, SetVisible = function(self, visible) @@ -166,21 +168,29 @@ local funcs = { self:UpdateVisible() end end, - UpdateVisible = function(self) - local missingProgress = self.tick_placement_mode ~= "AtPercent" and not self.trigger_total - if self.tick_visible and not missingProgress then - self:Show() + UpdateVisibleOne = function(self, i) + if self.tick_visible and self.hasProgress[i] then + self.ticks[i]:Show() else - self:Hide() + self.ticks[i]:Hide() + end + end, + UpdateVisible = function(self) + for i in ipairs(self.ticks) do + self:UpdateVisibleOne(i) end end, SetTickColor = function(self, r, g, b, a) self.tick_color[1], self.tick_color[2], self.tick_color[3], self.tick_color[4] = r, g, b, a or 1 if self.use_texture then - self.texture:SetVertexColor(r, g, b, a or 1) + for _, tick in ipairs(self.ticks) do + tick:SetVertexColor(r, g, b, a or 1) + end self:UpdateTickDesaturated() else - self.texture:SetColorTexture(r, g, b, a or 1) + for _, tick in ipairs(self.ticks) do + tick:SetColorTexture(r, g, b, a or 1) + end end end, SetTickPlacementMode = function(self, placement_mode) @@ -188,78 +198,91 @@ local funcs = { self.tick_placement_mode = placement_mode self:UpdateTickPlacement() self:UpdateVisible() - self:UpdateTimerTick() + self:UpdateFrameTick() end end, - UpdateTimerTick = function(self) - if self.tick_placement_mode == "ValueOffset" - and self.state - and self.state.progressType == "timed" - and not self.paused - then - if not self.TimerTick then - self.TimerTick = self.UpdateTickPlacement - self.parent.subRegionEvents:AddSubscriber("TimerTick", self) + UpdateFrameTick = function(self) + local requiresFrameTick = false + if self.tick_placement_mode == "ValueOffset" then + for i, progress in ipairs(self.progressData) do + if progress.progressType == "timed" and not progress.paused then + requiresFrameTick = true + break + end + end + end + + if requiresFrameTick then + if not self.FrameTick then + self.FrameTick = self.UpdateTickPlacement + self.parent.subRegionEvents:AddSubscriber("FrameTick", self) end else - if self.TimerTick then - self.TimerTick = nil - self.parent.subRegionEvents:RemoveSubscriber("TimerTick", self) + if self.FrameTick then + self.FrameTick = nil + self.parent.subRegionEvents:RemoveSubscriber("FrameTick", self) end end end, - SetTickPlacement = function(self, placement) + SetTickPlacement = function(self, tick, placement) placement = tonumber(placement) - if self.tick_placement ~= placement then - self.tick_placement = placement - self:UpdateTickPlacement() + if self.tick_placements[tick] ~= placement then + self.tick_placements[tick] = placement + self:UpdateTickPlacementOne(tick) end end, UpdateTickPlacement = function(self) - local offset, offsetx, offsety = self.tick_placement, 0, 0 + for i in ipairs(self.tick_placements) do + self:UpdateTickPlacementOne(i) + end + end, + UpdateTickPlacementOne = function(self, i) + local offsetx, offsety = 0, 0 local width = self.parentMajorSize - local minValue, maxValue = self.parent:GetMinMax() + local minValue, maxValue = self.parent:GetMinMaxProgress() local valueRange = maxValue - minValue + local inverse = self.inverse_direction + + if self.parent.inverse then + inverse = not inverse + end local tick_placement if self.tick_placement_mode == "AtValue" then - tick_placement = self.tick_placement + tick_placement = self.tick_placements[i] elseif self.tick_placement_mode == "AtMissingValue" then - tick_placement = self.trigger_total and self.trigger_total - self.tick_placement + tick_placement = maxValue - self.tick_placements[i] elseif self.tick_placement_mode == "AtPercent" then - if self.tick_placement >= 0 and self.tick_placement <= 100 and self.trigger_total then - tick_placement = self.tick_placement * self.trigger_total / 100 + if self.tick_placements[i] >= 0 and self.tick_placements[i] <= 100 and maxValue then + tick_placement = minValue + self.tick_placements[i] * valueRange / 100 end elseif self.tick_placement_mode == "ValueOffset" then - if self.trigger_total and self.trigger_total ~= 0 then - if self.state.progressType == "timed" then - if self.state.paused then - if self.state.remaining then - tick_placement = self.state.remaining + self.tick_placement + if maxValue ~= 0 and self.progressData[i] then + if self.progressData[i].progressType == "timed" then + if self.progressData[i].paused then + if self.progressData[i].remaining then + tick_placement = self.progressData[i].remaining + self.tick_placements[i] end else - tick_placement = self.state.expirationTime - GetTime() + self.tick_placement + tick_placement = self.progressData[i].expirationTime - GetTime() + self.tick_placements[i] end - else - tick_placement = self.state.value + self.tick_placement + elseif self.progressType == "static" then + tick_placement = self.progressData[i].value + self.progressData[i].tick_placements[i] end end end + local offset local percent = valueRange ~= 0 and tick_placement and (tick_placement - minValue) / valueRange if not percent or (percent and percent < 0 or percent > 1) then - self.texture:Hide() offset = 0 + self.hasProgress[i] = false else - self.texture:Show() offset = percent * width + self.hasProgress[i] = true end - - local inverse = self.inverse - if self.trigger_inverse then - inverse = not inverse - end + self:UpdateVisible(i) if (self.orientation == "HORIZONTAL_INVERSE") or (self.orientation == "VERTICAL") then offset = -offset @@ -275,10 +298,10 @@ local funcs = { offsetx = offset end local side = inverse and auraBarAnchorInverse or auraBarAnchor - self:ClearAllPoints() - self:SetPoint("CENTER", self.parent.bar, side[self.orientation], - offsetx + self.tick_xOffset, - offsety + self.tick_yOffset) + self.ticks[i]:ClearAllPoints() + self.ticks[i]:SetPoint("CENTER", self.parent.bar, side[self.orientation], + offsetx + self.tick_xOffset, + offsety + self.tick_yOffset) end, SetAutomaticLength = function(self, automatic_length) if self.automatic_length ~= automatic_length then @@ -300,16 +323,24 @@ local funcs = { end, UpdateTickSize = function(self) if self.vertical then - self:SetHeight(self.tick_thickness) + for i, tick in ipairs(self.ticks) do + tick:SetHeight(self.tick_thickness) + end else - self:SetWidth(self.tick_thickness) + for i, tick in ipairs(self.ticks) do + tick:SetWidth(self.tick_thickness) + end end local length = self.automatic_length and self.parentMinorSize or self.tick_length if self.vertical then - self:SetWidth(length) + for i, tick in ipairs(self.ticks) do + tick:SetWidth(length) + end else - self:SetHeight(length) + for i, tick in ipairs(self.ticks) do + tick:SetHeight(length) + end end end, SetTickDesaturated = function(self, desaturate) @@ -319,7 +350,9 @@ local funcs = { end end, UpdateTickDesaturated = function(self) - self.texture:SetDesaturated(self.tick_desaturate) + for i, tick in ipairs(self.ticks) do + tick:SetDesaturated(self.tick_desaturate) + end end, SetTickRotation = function(self, degrees) if self.tick_rotation ~= degrees then @@ -328,8 +361,10 @@ local funcs = { end end, UpdateTickRotation = function(self) - local rad = math.rad(self.tick_rotation) - self.texture:SetRotation(rad) + local rad = math.rad(self.tick_rotation) + for _, tick in ipairs(self.ticks) do + tick:SetRotation(rad) + end end, SetTickMirror = function(self, mirror) if self.mirror ~= mirror then @@ -339,9 +374,13 @@ local funcs = { end, UpdateTickMirror = function(self) if self.mirror then - self.texture:SetTexCoord(0, 1, 1, 1, 0, 0, 1, 0) + for _, tick in ipairs(self.ticks) do + tick:SetTexCoord(0, 1, 1, 1, 0, 0, 1, 0) + end else - self.texture:SetTexCoord(0, 0, 1, 0, 0, 1, 1, 1) + for _, tick in ipairs(self.ticks) do + tick:SetTexCoord(0, 0, 1, 0, 0, 1, 1, 1) + end end end, SetTickBlendMode = function(self, mode) @@ -352,19 +391,22 @@ local funcs = { end, UpdateTickBlendMode = function(self) if self.use_texture then - self.texture:SetBlendMode(self.tick_blend_mode) + for _, tick in ipairs(self.ticks) do + tick:SetBlendMode(self.tick_blend_mode) + end else - self.texture:SetBlendMode("BLEND") + for _, tick in ipairs(self.ticks) do + tick:SetBlendMode("BLEND") + end end end, } local function modify(parent, region, parentData, data, first) - region:SetParent(parent) region.orientation = parent.effectiveOrientation - region.inverse = parentData.inverse - region.trigger_inverse = false + region.inverse_direction = parentData.inverse + region.inverse = false region.vertical = region.orientation == "VERTICAL" or region.orientation == "VERTICAL_INVERSE" if (region.vertical) then region.parentMinorSize, region.parentMajorSize = parent.bar:GetRealSize() @@ -377,7 +419,37 @@ local function modify(parent, region, parentData, data, first) region.tick_visible = data.tick_visible region.tick_color = CopyTable(data.tick_color) region.tick_placement_mode = data.tick_placement_mode - region.tick_placement = tonumber(data.tick_placement) + region.tick_placements = {} + region.progressSources = {} + region.progressData = {} + for i, tick_placement in ipairs(data.tick_placements) do + local value = tonumber(tick_placement) + if region.tick_placement_mode == "ValueOffset" then + local progressSource = Private.AddProgressSourceMetaData(parentData, data.progressSources[i] or {-2, ""}) + if value and progressSource then + tinsert(region.tick_placements, value) + tinsert(region.progressSources, progressSource or {}) + end + else + if value then + tinsert(region.tick_placements, value) + end + end + + if region.ticks[i] == nil then + local texture = region:CreateTexture() + texture:SetSnapToPixelGrid(false) + texture:SetTexelSnappingBias(0) + texture:SetDrawLayer("ARTWORK", 3) + texture:SetAllPoints(region) + region.ticks[i] = texture + end + end + + for i = #data.tick_placements + 1, #region.ticks do + region.ticks[i]:Hide() + end + region.automatic_length = data.automatic_length region.tick_thickness = data.tick_thickness region.tick_length = data.tick_length @@ -387,12 +459,16 @@ local function modify(parent, region, parentData, data, first) region.tick_xOffset = data.tick_xOffset region.tick_yOffset = data.tick_yOffset + region.hasProgress = {} + for k, v in pairs(funcs) do region[k] = v end if data.use_texture then - Private.SetTextureOrAtlas(region.texture, data.tick_texture, "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") + for _, tick in ipairs(region.ticks) do + Private.SetTextureOrAtlas(tick, data.tick_texture, "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") + end end region:SetVisible(data.tick_visible) @@ -409,7 +485,9 @@ local function modify(parent, region, parentData, data, first) parent.subRegionEvents:AddSubscriber("InverseChanged", region) parent.subRegionEvents:AddSubscriber("OnRegionSizeChanged", region) - region.TimerTick = nil + region.FrameTick = nil + region:ClearAllPoints() + region:SetAllPoints(parent.bar) end local function supports(regionType) @@ -417,4 +495,4 @@ local function supports(regionType) end WeakAuras.RegisterSubRegionType("subtick", L["Tick"], supports, create, modify, onAcquire, onRelease, - default, nil, properties); + default, nil, GetProperties); diff --git a/WeakAuras/SubscribableObject.lua b/WeakAuras/SubscribableObject.lua index 0e32145b17..3d53e38d23 100644 --- a/WeakAuras/SubscribableObject.lua +++ b/WeakAuras/SubscribableObject.lua @@ -8,7 +8,7 @@ local L = WeakAuras.L --- @class SubscribableObject --- @field events table Subscribers ordered by "priority" --- @field subscribers table Subscribers lookup ---- @field callback table +--- @field callbacks table --- @field ClearSubscribers fun(self: SubscribableObject) --- @field ClearCallbacks fun(self: SubscribableObject) --- @field AddSubscriber fun(self: SubscribableObject, event: string, subscriber: frame, highPriority: boolean?) @@ -19,6 +19,10 @@ local L = WeakAuras.L --- @type SubscribableObject local SubscribableObject = { + events = {}, + subscribers = {}, + callbacks = {}, + --- @type fun(self: SubscribableObject) ClearSubscribers = function(self) self.events = {} @@ -55,8 +59,6 @@ local SubscribableObject = end end, - - --- @type fun(self: SubscribableObject, event: string, subscriber: frame) RemoveSubscriber = function(self, event, subscriber) if self.events[event] then @@ -99,12 +101,5 @@ local SubscribableObject = } function Private.CreateSubscribableObject() - local system = {} - for f, func in pairs(SubscribableObject) do - system[f] = func - system.events = {} - system.subscribers = {} - system.callbacks = {} - end - return system + return CopyTable(SubscribableObject) end diff --git a/WeakAuras/Types.lua b/WeakAuras/Types.lua index fd156c0e90..5a9f1ea936 100644 --- a/WeakAuras/Types.lua +++ b/WeakAuras/Types.lua @@ -306,7 +306,7 @@ Private.format_types = { disabled = function() return get(symbol .. "_time_format", 0) ~= 0 end }) end, - CreateFormatter = function(symbol, get) + CreateFormatter = function(symbol, get, wihoutColor, data) local format = get(symbol .. "_time_format", 0) local threshold = get(symbol .. "_time_dynamic_threshold", 60) local precision = get(symbol .. "_time_precision", 1) @@ -321,9 +321,45 @@ Private.format_types = { end local mainFormater = simpleFormatters.time[format] + local modRateProperty = {} + if modRate then + -- For the mod rate support, we need to know which state member is the modRate, as + -- different progressSources can have different modRates + -- Here, we only collect the names, so that the actual formatter can quickly lookup + -- the property + -- This is somewhat complicated by legacy behaviour (for %p, %t) and that %foo, can + -- be the foo of different triggers that might use different modRate properties + local triggerNum, sym = string.match(symbol, "(.+)%.(.+)") + triggerNum = triggerNum and tonumber(triggerNum) + + if triggerNum then + if sym == "p" or sym == "t" then + modRateProperty[triggerNum] = "modRate" + else + local progressSource = Private.AddProgressSourceMetaData(data, { triggerNum, sym }) + if progressSource and progressSource[5] then + modRateProperty[triggerNum] = progressSource[5] + end + end + else + if symbol == "p" or symbol == "t" then + for i = 1, #data.triggers do + modRateProperty[i] = "modRate" + end + else + for i = 1, #data.triggers do + local progressSource = Private.AddProgressSourceMetaData(data, { i, symbol }) + if progressSource and progressSource[5] then + modRateProperty[i] = progressSource[5] + end + end + end + end + end + local formatter if threshold == 0 then - formatter = function(value, state) + formatter = function(value, state, trigger) if type(value) ~= 'number' or value == math.huge then return "" end @@ -331,23 +367,23 @@ Private.format_types = { return "" end - if modRate then - value = value / (state.modRate or 1.0) + if modRate and trigger and modRateProperty[trigger] then + value = value / (state[modRateProperty[trigger]] or 1.0) end return mainFormater(value) end else local formatString = "%." .. precision .. "f" - formatter = function(value, state) + formatter = function(value, state, trigger) if type(value) ~= 'number' or value == math.huge then return "" end if value <= 0 then return "" end - if modRate then - value = value / (state.modRate or 1.0) + if modRate and trigger and modRateProperty[trigger] then + value = value / (state[modRateProperty[trigger]] or 1.0) end if value < threshold then return string.format(formatString, value) @@ -363,11 +399,11 @@ Private.format_types = { -- Special case %p and %t. Since due to how the formatting -- work previously, the time formatter only formats %p and %t -- if the progress type is timed! - return function(value, state) + return function(value, state, trigger) if not state or state.progressType ~= "timed" then return value end - return formatter(value, state) + return formatter(value, state, trigger) end else return formatter diff --git a/WeakAuras/WeakAuras.lua b/WeakAuras/WeakAuras.lua index 993faed099..ca1adc5ab9 100644 --- a/WeakAuras/WeakAuras.lua +++ b/WeakAuras/WeakAuras.lua @@ -1,7 +1,7 @@ --- @type string, Private local AddonName, Private = ... -local internalVersion = 69 +local internalVersion = 70 -- Lua APIs local insert = table.insert @@ -3165,22 +3165,16 @@ function Private.SetRegion(data, cloneId) local anim_cancelled = loginFinished and Private.CancelAnimation(region, true, true, true, true, true, true); regionTypes[regionType].modify(parent, region, data); - Private.regionPrototype.AddSetDurationInfo(region); + Private.regionPrototype.AddSetDurationInfo(region, data.uid) Private.regionPrototype.AddExpandFunction(data, region, cloneId, parent, parent.regionType) data.animation = data.animation or {}; data.animation.start = data.animation.start or {type = "none"}; data.animation.main = data.animation.main or {type = "none"}; data.animation.finish = data.animation.finish or {type = "none"}; - if(Private.CanHaveDuration(data)) then - data.animation.start.duration_type = data.animation.start.duration_type or "seconds"; - data.animation.main.duration_type = data.animation.main.duration_type or "seconds"; - data.animation.finish.duration_type = data.animation.finish.duration_type or "seconds"; - else - data.animation.start.duration_type = "seconds"; - data.animation.main.duration_type = "seconds"; - data.animation.finish.duration_type = "seconds"; - end + data.animation.start.duration_type = data.animation.start.duration_type or "seconds" + data.animation.main.duration_type = data.animation.main.duration_type or "seconds" + data.animation.finish.duration_type = data.animation.finish.duration_type or "seconds" if(cloneId) then clonePool[regionType] = clonePool[regionType] or {}; @@ -3752,8 +3746,6 @@ local function wrapTriggerSystemFunction(functionName, mode) return func; end -Private.CanHaveDuration = wrapTriggerSystemFunction("CanHaveDuration", "firstValue"); -Private.CanHaveClones = wrapTriggerSystemFunction("CanHaveClones", "or"); Private.CanHaveTooltip = wrapTriggerSystemFunction("CanHaveTooltip", "or"); -- This has to be in WeakAuras for now, because GetNameAndIcon can be called from the options -- before the Options has access to Private @@ -3762,7 +3754,7 @@ Private.GetTriggerDescription = wrapTriggerSystemFunction("GetTriggerDescription local wrappedGetOverlayInfo = wrapTriggerSystemFunction("GetOverlayInfo", "table"); -Private.GetAdditionalProperties = function(data, triggernum, ...) +Private.GetAdditionalProperties = function(data) local additionalProperties = "" for i = 1, #data.triggers do local triggerSystem = GetTriggerSystem(data, i); @@ -3786,6 +3778,120 @@ Private.GetAdditionalProperties = function(data, triggernum, ...) return additionalProperties end +Private.GetProgressSources = function(data) + local values = {} + if Private.IsGroupType(data) then + return values + end + for i = 1, #data.triggers do + local triggerSystem = GetTriggerSystem(data, i); + if (triggerSystem) then + triggerSystem.GetProgressSources(data, i, values) + end + end + return values +end + +Private.GetProgressSourceFor = function(data, trigger, property) + local values = {} + local triggerSystem = GetTriggerSystem(data, trigger); + if (triggerSystem) then + triggerSystem.GetProgressSources(data, trigger, values) + for _, v in ipairs(values) do + if v.property == property then + return {trigger, v.type, v.property, v.total, v.modRate, v.inverse, v.paused, v.remaining} + end + end + end + return nil +end + +-- In the aura data we only store trigger + property +-- But for the region we don't want to gather necessary meta data all the time +-- So we collect that in region:modify + on creation of the conditions function +Private.AddProgressSourceMetaData = function(data, progressSource) + if not progressSource then + return {} + end + local trigger = progressSource[1] + local property = progressSource[2] + if trigger == -1 then + return {-1, "auto", ""} + elseif trigger == 0 then + return {0, "manual", progressSource[3], progressSource[4]} + else + return Private.GetProgressSourceFor(data, trigger, property) + end +end + +-- ProgressSource values +-- For AceOptions to work correctly progress sources need to be comparable +-- via ==. We use a constants table so that identical tables use the same table +-- Additional while data.progressSource does contain additional data e.g. for manual progress +-- This is only for the progress source combobox, which only cares about the first or first two values +-- The greatness of the hacks knows no bounds +-- The constants table has weak keys +do + local function CompareProgressValueTables(a, b) + -- For auto/manual progreess, only compare a[] with b[1] + if a[1] == -1 or a[1] == 0 then + return a[1] == b[1] + end + -- Only care about trigger + property + return a[1] == b[1] and a[2] == b[2] + end + + local progressValueConstants = {} + setmetatable(progressValueConstants, {_mode = "v"}) + + function Private.GetProgressValueConstant(v) + if v == nil then + return v + end + + -- This uses pairs because there could be empty slots + for _, constant in pairs(progressValueConstants) do + if CompareProgressValueTables(v, constant) then + return constant + end + end + -- And this inserts into the first empty slot for the array + tinsert(progressValueConstants, v) + return v + end +end + +function Private.GetProgressSourcesForUi(data, subelement) + local values + + if subelement then + -- Sub elements Automatic means to use the main auras' progress + values = { + [{-2, ""}] = L["Automatic"] + } + else + values = { + [{-1, ""}] = L["Automatic"], + [{0, ""}] = L["Manual"], + } + end + + local triggerValues = Private.GetProgressSources(data) + for _, e in ipairs(triggerValues) do + if e.trigger and e.property then + values[{e.trigger, e.property}] = {L["Trigger %s"]:format(e.trigger), e.display} + end + end + + local result = {} + for k, v in pairs(values) do + result[Private.GetProgressValueConstant(k)] = v + end + + return result +end + + function Private.GetOverlayInfo(data, triggernum) local overlayInfo; if (data.controlledChildren) then @@ -4289,60 +4395,66 @@ do end end +--- @type fun(id: auraId, triggernum: integer, cloneId: string) +local function stopAutoHideTimer(id, triggernum, cloneId) + if(timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then + local record = timers[id][triggernum][cloneId]; + if (record.handle) then + timer:CancelTimer(record.handle); + end + record.handle = nil; + record.expirationTime = nil; + record.state = nil + end +end + +--- @type fun(id: auraId, triggernum: integer, cloneId: string, state: state) local function startStopTimers(id, cloneId, triggernum, state) - if (state.show) then - if (state.autoHide and state.duration and state.duration > 0 and not state.paused) then -- autohide, update timer - timers[id] = timers[id] or {}; - timers[id][triggernum] = timers[id][triggernum] or {}; - timers[id][triggernum][cloneId] = timers[id][triggernum][cloneId] or {}; - local record = timers[id][triggernum][cloneId]; - if (state.expirationTime == nil) then - state.expirationTime = GetTime() + state.duration; - end - if (record.expirationTime ~= state.expirationTime or record.state ~= state) then - if (record.handle ~= nil) then - timer:CancelTimer(record.handle); - end + if not state.show or not state.autoHide then + stopAutoHideTimer(id, triggernum, cloneId) + return + end - record.handle = timer:ScheduleTimerFixed( - function() - if (state.show ~= false and state.show ~= nil) then - state.show = false; - state.changed = true; + -- state.autoHide can be a timer, or a boolean + -- if it's a bool, for backwards compability we look at paused + local expirationTime + if type(state.autoHide) == "boolean" then + if state.paused then + stopAutoHideTimer(id, triggernum, cloneId) + return + else + expirationTime = state.expirationTime + end + elseif type(state.autoHide) == "number" then + expirationTime = state.autoHide + end - -- if the trigger has updated then check to see if it is flagged for WatchedTrigger and send to queue if it is - if Private.watched_trigger_events[id] and Private.watched_trigger_events[id][triggernum] then - Private.AddToWatchedTriggerDelay(id, triggernum) - end + timers[id] = timers[id] or {}; + timers[id][triggernum] = timers[id][triggernum] or {}; + timers[id][triggernum][cloneId] = timers[id][triggernum][cloneId] or {}; + local record = timers[id][triggernum][cloneId]; + if (record.expirationTime ~= expirationTime or record.state ~= state) then + if (record.handle ~= nil) then + timer:CancelTimer(record.handle); + end - Private.UpdatedTriggerState(id); - end - end, - state.expirationTime - GetTime()); - record.expirationTime = state.expirationTime; - record.state = state - end - else -- no auto hide, delete timer - if (timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then - local record = timers[id][triggernum][cloneId]; - if (record.handle) then - timer:CancelTimer(record.handle); + record.handle = timer:ScheduleTimerFixed( + function() + if (state.show ~= false and state.show ~= nil) then + state.show = false; + state.changed = true; + + -- if the trigger has updated then check to see if it is flagged for WatchedTrigger and send to queue if it is + if Private.watched_trigger_events[id] and Private.watched_trigger_events[id][triggernum] then + Private.AddToWatchedTriggerDelay(id, triggernum) + end + + Private.UpdatedTriggerState(id); end - record.handle = nil; - record.expirationTime = nil; - record.state = nil - end - end - else -- not shown - if(timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then - local record = timers[id][triggernum][cloneId]; - if (record.handle) then - timer:CancelTimer(record.handle); - end - record.handle = nil; - record.expirationTime = nil; - record.state = nil - end + end, + expirationTime - GetTime()); + record.expirationTime = expirationTime; + record.state = state end end @@ -4420,7 +4532,6 @@ local function ApplyStatesToRegions(id, activeTrigger, states) local applyChanges = not region.toShow or state.changed or region.state ~= state region.state = state region.states = region.states or {} - local needsTimerTick = false for triggernum = -1, triggerState[id].numTriggers do local triggerState if triggernum == activeTrigger then @@ -4436,11 +4547,8 @@ local function ApplyStatesToRegions(id, activeTrigger, states) end region.states[triggernum] = triggerState - needsTimerTick = needsTimerTick or (triggerState and triggerState.show and triggerState.progressType == "timed") end - region:SetTriggerProvidesTimer(needsTimerTick) - if (applyChanges) then ApplyStateToRegion(id, cloneId, region, parent); Private.RunConditions(region, data.uid, not state.show) @@ -4623,7 +4731,7 @@ function Private.RunCustomTextFunc(region, customFunc) return custom end -local function ReplaceValuePlaceHolders(textStr, region, customFunc, state, formatter) +local function ReplaceValuePlaceHolders(textStr, region, customFunc, state, formatter, trigger) local value; if string.sub(textStr, 1, 1) == "c" then local custom @@ -4655,7 +4763,7 @@ local function ReplaceValuePlaceHolders(textStr, region, customFunc, state, form end value = variable.get(state) if formatter then - value = formatter(value, state) + value = formatter(value, state, trigger) elseif variable.func then value = variable.func(value) end @@ -4789,12 +4897,12 @@ local function ValueForSymbol(symbol, region, customFunc, regionState, regionSta if regionStates[triggerNum][sym] then local value = regionStates[triggerNum][sym] if formatters[symbol] then - return tostring(formatters[symbol](value, regionStates[triggerNum]) or "") or "" + return tostring(formatters[symbol](value, regionStates[triggerNum], triggerNum) or "") or "" else return tostring(value) or "" end else - local value = ReplaceValuePlaceHolders(sym, region, customFunc, regionStates[triggerNum], formatters[symbol]); + local value = ReplaceValuePlaceHolders(sym, region, customFunc, regionStates[triggerNum], formatters[symbol], triggerNum); return value or "" end end @@ -4804,14 +4912,16 @@ local function ValueForSymbol(symbol, region, customFunc, regionState, regionSta if(useHiddenStates or regionState.show) then local value = regionState[symbol] if formatters[symbol] then - return tostring(formatters[symbol](value, regionState) or "") or "" + return tostring(formatters[symbol](value, regionState, triggerState[regionState.id].activeTrigger) or "") or "" else return tostring(value) or "" end end return "" else - local value = (useHiddenStates or regionState.show) and ReplaceValuePlaceHolders(symbol, region, customFunc, regionState, formatters[symbol]); + local activeTrigger = triggerState[regionState.id].activeTrigger + local value = (useHiddenStates or regionState.show) + and ReplaceValuePlaceHolders(symbol, region, customFunc, regionState, formatters[symbol], activeTrigger) return value or "" end end @@ -4947,7 +5057,7 @@ function Private.ParseTextStr(textStr, symbolCallback) end end -function Private.CreateFormatters(input, getter, withoutColor) +function Private.CreateFormatters(input, getter, withoutColor, data) local seenSymbols = {} local formatters = {} @@ -4961,7 +5071,7 @@ function Private.CreateFormatters(input, getter, withoutColor) local default = (sym == "p" or sym == "t") and "timed" or "none" local selectedFormat = getter(symbol .. "_format", default) if (Private.format_types[selectedFormat]) then - formatters[symbol] = Private.format_types[selectedFormat].CreateFormatter(symbol, getter, withoutColor) + formatters[symbol] = Private.format_types[selectedFormat].CreateFormatter(symbol, getter, withoutColor, data) end end end diff --git a/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasDisplayButton.lua b/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasDisplayButton.lua index a9a2a9b447..18f474f66d 100644 --- a/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasDisplayButton.lua +++ b/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasDisplayButton.lua @@ -970,6 +970,9 @@ local methods = { for index, childId in pairs(data.controlledChildren) do tinsert(namestable, indent .. childId); local childData = WeakAuras.GetData(childId) + if not childData then + return + end if (childData.controlledChildren) then addChildrenNames(childData, indent .. " ") end @@ -994,9 +997,6 @@ local methods = { else OptionsPrivate.Private.GetTriggerDescription(data, -1, namestable) end - if(OptionsPrivate.Private.CanHaveClones(data)) then - tinsert(namestable, {" ", "|cFF00FF00"..L["Auto-cloning enabled"]}) - end local hasDescription = data.desc and data.desc ~= ""; local hasUrl = data.url and data.url ~= ""; @@ -1056,11 +1056,13 @@ local methods = { end end, ["StopGrouping"] = function(self) - self.grouping = nil; - self:UpdateIconsVisible() - self:SetNormalTooltip(); - self.frame:SetScript("OnClick", self.callbacks.OnClickNormal); - self:Enable(); + if self.grouping then + self.grouping = nil + self:UpdateIconsVisible() + self:SetNormalTooltip() + self.frame:SetScript("OnClick", self.callbacks.OnClickNormal) + self:Enable() + end end, ["Ungroup"] = function(self) if (WeakAuras.IsImporting()) then return end; diff --git a/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasTwoColumnDropDown.lua b/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasTwoColumnDropDown.lua index 59491e95fb..70b4c486cf 100644 --- a/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasTwoColumnDropDown.lua +++ b/WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasTwoColumnDropDown.lua @@ -154,8 +154,11 @@ local methods = { self.firstDropdown = nil self.secondDropDown = nil end, - ["SetLabel"] = function(self, ...) - self.firstDropdown:SetLabel(...) + ["SetLabel"] = function(self, v) + if v == "" then + v = " " + end + self.firstDropdown:SetLabel(v) end, ["SetValue"] = function(self, value) for displayName, treeValue in pairs(self.userdata.tree) do diff --git a/WeakAurasOptions/AnimationOptions.lua b/WeakAurasOptions/AnimationOptions.lua index e5bdbd658a..3bf32c9565 100644 --- a/WeakAurasOptions/AnimationOptions.lua +++ b/WeakAurasOptions/AnimationOptions.lua @@ -144,18 +144,6 @@ function OptionsPrivate.GetAnimationOptions(data) values = function() return filterAnimPresetTypes(anim_start_preset_types, id) end, hidden = function() return data.animation.start.type ~= "preset" end }, - start_duration_type_no_choice = { - type = "select", - width = WeakAuras.halfWidth, - name = L["Time in"], - order = 33, - values = duration_types_no_choice, - disabled = true, - hidden = function() - return data.animation.start.type ~= "custom" or OptionsPrivate.Private.CanHaveDuration(data) - end, - get = function() return "seconds" end - }, start_duration_type = { type = "select", width = WeakAuras.halfWidth, @@ -163,7 +151,7 @@ function OptionsPrivate.GetAnimationOptions(data) order = 33, values = duration_types, hidden = function() - return data.animation.start.type ~= "custom" or not OptionsPrivate.Private.CanHaveDuration(data) + return data.animation.start.type ~= "custom" end }, start_duration = { @@ -419,18 +407,6 @@ function OptionsPrivate.GetAnimationOptions(data) values = function() return filterAnimPresetTypes(anim_main_preset_types, id) end, hidden = function() return data.animation.main.type ~= "preset" end }, - main_duration_type_no_choice = { - type = "select", - width = WeakAuras.halfWidth, - name = L["Time in"], - order = 53, - values = duration_types_no_choice, - disabled = true, - hidden = function() - return data.animation.main.type ~= "custom" or OptionsPrivate.Private.CanHaveDuration(data) - end, - get = function() return "seconds" end - }, main_duration_type = { type = "select", width = WeakAuras.halfWidth, @@ -438,7 +414,7 @@ function OptionsPrivate.GetAnimationOptions(data) order = 53, values = duration_types, hidden = function() - return data.animation.main.type ~= "custom" or not OptionsPrivate.Private.CanHaveDuration(data) + return data.animation.main.type ~= "custom" end }, main_duration = { diff --git a/WeakAurasOptions/CommonOptions.lua b/WeakAurasOptions/CommonOptions.lua index 8859767f1d..f65dccb193 100644 --- a/WeakAurasOptions/CommonOptions.lua +++ b/WeakAurasOptions/CommonOptions.lua @@ -1046,6 +1046,153 @@ local function CreateExecuteAll(subOption) end end +local function ProgressOptions(data) + local order = 1 + local options = { + __title = L["Progress Settings"], + __order = 98, + } + + options.progressSource = { + type = "select", + width = WeakAuras.doubleWidth, + name = L["Progress Source"], + order = order, + control = "WeakAurasTwoColumnDropdown", + values = OptionsPrivate.Private.GetProgressSourcesForUi(data), + get = function(info) + return OptionsPrivate.Private.GetProgressValueConstant(data.progressSource) + end, + set = function(info, value) + if value then + data.progressSource = data.progressSource or {} + -- Copy only trigger + property + data.progressSource[1] = value[1] + data.progressSource[2] = value[2] + else + data.progressSource = nil + end + WeakAuras.Add(data) + end + } + + options.progressSourceWarning = { + type = "description", + width = WeakAuras.doubleWidth, + name = L["Note: This progress source does not provide a total value/duration. A total value/duration must be set via \"Set Maximum Progress\""], + order = order + 0.5, + hidden = function() + local progressSource = OptionsPrivate.Private.AddProgressSourceMetaData(data, data.progressSource) + -- Auto progress, Manual Progress or the progress source has a total property + if not progressSource or progressSource[2] == "auto" or progressSource[1] == 0 or progressSource[4] ~= nil then + return true + end + return false + end + } + + local function hiddenManual() + if data.progressSource and data.progressSource[1] == 0 then + return false + end + return true + end + + options.progressSourceManualValue = { + type = "range", + control = "WeakAurasSpinBox", + width = WeakAuras.normalWidth, + name = L["Value"], + order = order + 0.7, + min = 0, + softMax = 100, + bigStep = 1, + hidden = hiddenManual, + get = function(info) + return data.progressSource and data.progressSource[3] or 0 + end, + set = function(info, value) + data.progressSource = data.progressSource or {} + data.progressSource[3] = value + WeakAuras.Add(data) + end + } + + options.progressSourceManualTotal = { + type = "range", + control = "WeakAurasSpinBox", + width = WeakAuras.normalWidth, + name = L["Total"], + order = order + 0.8, + min = 0, + softMax = 100, + bigStep = 1, + hidden = hiddenManual, + get = function(info) + return data.progressSource and data.progressSource[4] or 100 + end, + set = function(info, value) + data.progressSource = data.progressSource or {} + data.progressSource[4] = value + WeakAuras.Add(data) + end + } + + options.useAdjustededMin = { + type = "toggle", + width = WeakAuras.normalWidth, + name = L["Set Minimum Progress"], + desc = L["Values/Remaining Time below this value are displayed as zero progress."], + order = order + 1 + }; + + options.adjustedMin = { + type = "input", + validate = WeakAuras.ValidateNumericOrPercent, + width = WeakAuras.normalWidth, + order = order + 2, + name = L["Minimum"], + hidden = function() return not data.useAdjustededMin end, + desc = L["Enter static or relative values with %"] + }; + + options.useAdjustedMinSpacer = { + type = "description", + width = WeakAuras.normalWidth, + name = "", + order = order + 3, + hidden = function() return not (not data.useAdjustededMin and data.useAdjustededMax) end, + } + + options.useAdjustededMax = { + type = "toggle", + width = WeakAuras.normalWidth, + name = L["Set Maximum Progress"], + desc = L["Values/Remaining Time above this value are displayed as full progress."], + order = order + 4 + } + + options.adjustedMax = { + type = "input", + width = WeakAuras.normalWidth, + validate = WeakAuras.ValidateNumericOrPercent, + order = order + 5, + name = L["Maximum"], + hidden = function() return not data.useAdjustededMax end, + desc = L["Enter static or relative values with %"] + } + + options.useAdjustedMaxSpacer = { + type = "description", + width = WeakAuras.normalWidth, + name = "", + order = order + 6, + hidden = function() return not (data.useAdjustededMin and not data.useAdjustededMax) end, + } + + return options +end + local function PositionOptions(id, data, _, hideWidthHeight, disableSelfPoint, group) local metaOrder = 99 local function IsParentDynamicGroup() @@ -1626,6 +1773,7 @@ OptionsPrivate.commonOptions.CreateSetAll = CreateSetAll OptionsPrivate.commonOptions.CreateExecuteAll = CreateExecuteAll OptionsPrivate.commonOptions.PositionOptions = PositionOptions +OptionsPrivate.commonOptions.ProgressOptions = ProgressOptions OptionsPrivate.commonOptions.BorderOptions = BorderOptions OptionsPrivate.commonOptions.AddCodeOption = AddCodeOption diff --git a/WeakAurasOptions/ConditionOptions.lua b/WeakAurasOptions/ConditionOptions.lua index 5c094cd5cd..1c007d5323 100644 --- a/WeakAurasOptions/ConditionOptions.lua +++ b/WeakAurasOptions/ConditionOptions.lua @@ -71,6 +71,17 @@ local function compareValues(a, b, propertytype) and a[2] == b[2] and a[3] == b[3] and a[4] == b[4]; + elseif propertytype == "progressSource" then + if type(a) == "table" and type(b) == "table" then + local triggerA, propertyA, triggerB, propertyB = a[1], a[2], b[1], b[2] + if triggerA ~= triggerB or propertyA ~= propertyB then + return false + end + if triggerA == 0 then + return a[3] == b[3] and a[4] == b[4] + end + return true + end end return a == b; end @@ -139,15 +150,36 @@ local function descIfSubset(data, reference, totalAuraCount) return ""; end -local function descIfNoValue(data, object, variable, type, values) +local function descIfNoValue(data, object, variable, propertyType, values) if (data.controlledChildren) then if (object["same" .. variable] == false) then local desc = ""; for id, reference in pairs(object.references) do - if (type == "list" and values) then + if propertyType == "list" and values then desc = desc .."|cFFE0E000".. id .. ": |r" .. (values[reference[variable]] or "") .. "\n"; + elseif propertyType == "progressSource" then + desc = desc .."|cFFE0E000".. id .. ": |r" + local progressSource = reference[variable] + if type(progressSource) == "table" then + local trigger = progressSource[1] + if trigger == 0 then + desc = desc .. L["Manual with %i/%i"]:format(progressSource[3] or 0, progressSource[4] or 100) + else + local p = OptionsPrivate.Private.GetProgressValueConstant(progressSource) + local description = values[p] or "" + if type(description) == "string" then + desc = desc .. description + elseif type(description) == "table" + and type(description[1]) == "string" + and type(description[2]) == "string" + then + desc = desc .. description[1] .. " " .. description[2] + end + end + end + desc = desc .."\n" else - desc = desc .."|cFFE0E000".. id .. ": |r" .. (valueToString(reference[variable], type) or "") .. "\n"; + desc = desc .."|cFFE0E000".. id .. ": |r" .. (valueToString(reference[variable], propertyType) or "") .. "\n"; end end return desc; @@ -298,6 +330,7 @@ local function addControlsForChange(args, order, data, conditionVariable, totalA order = order + 1; local setValue; + local setValueTable local setValueColor; local setValueComplex; local setValueColorComplex; @@ -313,6 +346,17 @@ local function addControlsForChange(args, order, data, conditionVariable, totalA conditions[i].changes[j].value = v; WeakAuras.ClearAndUpdateOptions(data.id) end + setValueTable = function(info, v) + for id, reference in pairs(conditions[i].changes[j].references) do + local auraData = WeakAuras.GetData(id) + local conditionIndex = conditions[i].check.references[id].conditionIndex + auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = CopyTable(v) + WeakAuras.Add(auraData) + OptionsPrivate.ClearOptions(auraData.id) + end + conditions[i].changes[j].value = CopyTable(v) + WeakAuras.ClearAndUpdateOptions(data.id) + end setValueColor = function(info, r, g, b, a) for id, reference in pairs(conditions[i].changes[j].references) do local auraData = WeakAuras.GetData(id); @@ -392,6 +436,11 @@ local function addControlsForChange(args, order, data, conditionVariable, totalA WeakAuras.Add(data); WeakAuras.ClearAndUpdateOptions(data.id) end + setValueTable = function(info, v) + conditions[i].changes[j].value = CopyTable(v) + WeakAuras.Add(data) + WeakAuras.ClearAndUpdateOptions(data.id) + end setValueColor = function(info, r, g, b, a) conditions[i].changes[j].value = conditions[i].changes[j].value or {}; conditions[i].changes[j].value[1] = r; @@ -531,21 +580,92 @@ local function addControlsForChange(args, order, data, conditionVariable, totalA set = setValueColor } order = order + 1; - elseif (propertyType == "list") then + elseif (propertyType == "list" or property == "progressSource") then local values = property and allProperties.propertyMap[property] and allProperties.propertyMap[property].values; args["condition" .. i .. "value" .. j] = { type = "select", width = WeakAuras.normalWidth, values = values, - name = blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"]), - desc = descIfNoValue(data, conditions[i].changes[j], "value", propertyType, values), + name = blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"], ""), + desc = descIfNoValue(data, conditions[i].changes[j], "value", propertyType, values), order = order, get = function() return conditions[i].changes[j].value; end, - set = setValue + set = setValue, } - order = order + 1; + order = order + 1 + + if propertyType == "progressSource" then + args["condition" .. i .. "value" .. j].control = "WeakAurasTwoColumnDropdown" + args["condition" .. i .. "value" .. j].set = setValueTable + args["condition" .. i .. "value" .. j].get = function() + local v = conditions[i].changes[j].value + return OptionsPrivate.Private.GetProgressValueConstant(v) + end + + args["condition" .. i .. "progressSourceWarning" .. j] = { + type = "description", + width = WeakAuras.doubleWidth, + name = L["Note: This progress source does not provide a total value/duration. A total value/duration must be set via \"Set Maximum Progress\""], + order = order, + hidden = function() + local v = conditions[i].changes[j].value + local progressSource = OptionsPrivate.Private.AddProgressSourceMetaData(data, v) + -- Auto progress, Manual Progress or the progress source has a total property + if progressSource[2] == "auto" or progressSource[1] == 0 or progressSource[4] ~= nil then + return true + end + return false + end + } + order = order + 1 + + local function hiddenManual() + local v = conditions[i].changes[j].value + local progressSource = OptionsPrivate.Private.AddProgressSourceMetaData(data, v) + if progressSource[1] == 0 then + return false + end + return true + end + + args["condition" .. i .. "progressSoruceManualValue" .. j] = { + type = "range", + control = "WeakAurasSpinBox", + width = WeakAuras.normalWidth, + name = L["Value"], + order = order, + min = 0, + softMax = 100, + bigStep = 1, + hidden = hiddenManual, + get = function() + local v = conditions[i].changes[j].value + return v and type(v[3]) == "number" and v[3] or 0 + end, + set = setValueComplex(3) + } + order = order + 1 + + args["condition" .. i .. "progressSoruceManualTotal" .. j] = { + type = "range", + control = "WeakAurasSpinBox", + width = WeakAuras.normalWidth, + name = L["Total"], + order = order, + min = 0, + softMax = 100, + bigStep = 1, + hidden = hiddenManual, + get = function() + local v = conditions[i].changes[j].value + return v and type(v[4]) == "number" and v[4] or 100 + end, + set = setValueComplex(4) + } + order = order + 1 + end elseif (propertyType == "sound") then args["condition" .. i .. "value" .. j .. "sound_type"] = { type = "select", @@ -2515,7 +2635,7 @@ local function buildAllPotentialProperties(data, category) allProperties.propertyMap[k].type = "incompatible"; end - if (allProperties.propertyMap[k].type == "list") then + if (allProperties.propertyMap[k].type == "list" or allProperties.propertyMap[k].type == "progressSource" ) then -- Merge value lists for key, value in pairs(v.values) do if (allProperties.propertyMap[k].values[key] == nil) then diff --git a/WeakAurasOptions/GenericTrigger.lua b/WeakAurasOptions/GenericTrigger.lua index 1e7e092a09..c7247cf22d 100644 --- a/WeakAurasOptions/GenericTrigger.lua +++ b/WeakAurasOptions/GenericTrigger.lua @@ -317,6 +317,12 @@ local function GetCustomTriggerOptions(data, triggernum) test = "function", events = "table", values = "table", + total = "string", + inverse = "string", + paused = "string", + remaining = "string", + modRate = "string", + useModRate = "boolean" } local function validateCustomVariables(variables) diff --git a/WeakAurasOptions/RegionOptions/AuraBar.lua b/WeakAurasOptions/RegionOptions/AuraBar.lua index a51a651e88..cc17442921 100644 --- a/WeakAurasOptions/RegionOptions/AuraBar.lua +++ b/WeakAurasOptions/RegionOptions/AuraBar.lua @@ -411,8 +411,6 @@ local function createOptions(id, data) }, }; - options = OptionsPrivate.Private.regionPrototype.AddAdjustedDurationOptions(options, data, 36.5); - local overlayInfo = OptionsPrivate.Private.GetOverlayInfo(data); if (overlayInfo and next(overlayInfo)) then options["overlayheader"] = { @@ -476,6 +474,7 @@ local function createOptions(id, data) return { aurabar = options, + progressOptions = OptionsPrivate.commonOptions.ProgressOptions(data), position = OptionsPrivate.commonOptions.PositionOptions(id, data), }; end diff --git a/WeakAurasOptions/RegionOptions/Icon.lua b/WeakAurasOptions/RegionOptions/Icon.lua index 5f5628181e..ebc48d2a07 100644 --- a/WeakAurasOptions/RegionOptions/Icon.lua +++ b/WeakAurasOptions/RegionOptions/Icon.lua @@ -230,8 +230,7 @@ local function createOptions(id, data) name = L["Enable Swipe"], order = 11.1, desc = L["Enable the \"Swipe\" radial overlay"], - disabled = function() return not OptionsPrivate.Private.CanHaveDuration(data); end, - get = function() return OptionsPrivate.Private.CanHaveDuration(data) and data.cooldown; end + get = function() return data.cooldown; end }, inverse = { type = "toggle", @@ -239,8 +238,7 @@ local function createOptions(id, data) name = L["Inverse"], order = 11.2, desc = L["Invert the direction of progress"], - disabled = function() return not (OptionsPrivate.Private.CanHaveDuration(data) and data.cooldown); end, - get = function() return data.inverse and OptionsPrivate.Private.CanHaveDuration(data) and data.cooldown; end, + get = function() return data.inverse and data.cooldown; end, hidden = function() return not data.cooldown end }, cooldownSwipe = { @@ -249,7 +247,6 @@ local function createOptions(id, data) name = L["Show \"Swipe\""], order = 11.3, desc = "|TInterface\\AddOns\\WeakAuras\\Media\\Textures\\swipe-example:30|t\n"..L["Enable \"swipe\" part of the overlay"], - disabled = function() return not OptionsPrivate.Private.CanHaveDuration(data) end, hidden = function() return not data.cooldown end, }, cooldownEdge = { @@ -258,7 +255,6 @@ local function createOptions(id, data) name = L["Show \"Edge\""], order = 11.4, desc = "|TInterface\\AddOns\\WeakAuras\\Media\\Textures\\edge-example:30|t\n"..L["Enable \"Edge\" part of the overlay"], - disabled = function() return not OptionsPrivate.Private.CanHaveDuration(data) end, hidden = function() return not data.cooldown end, }, cooldownTextDisabled = { @@ -267,7 +263,6 @@ local function createOptions(id, data) name = L["Hide Timer Text"], order = 11.5, desc = L["A timer will automatically be displayed according to default Interface Settings (overridden by some addons).\nEnable this setting if you want this timer to be hidden, or when using a WeakAuras text to display the timer"], - disabled = function() return not OptionsPrivate.Private.CanHaveDuration(data); end, hidden = function() return not data.cooldown end, }, useCooldownModRate = { @@ -276,7 +271,6 @@ local function createOptions(id, data) name = L["Blizzard Cooldown Reduction"], order = 11.6, desc = L["Cooldown Reduction changes the duration of seconds instead of showing the real time seconds."], - disabled = function() return not OptionsPrivate.Private.CanHaveDuration(data); end, hidden = function() return not data.cooldown end, }, ccWarning = { @@ -303,6 +297,7 @@ local function createOptions(id, data) return { icon = options, + progressOptions = OptionsPrivate.commonOptions.ProgressOptions(data), position = OptionsPrivate.commonOptions.PositionOptions(id, data), }; end diff --git a/WeakAurasOptions/RegionOptions/ProgressTexture.lua b/WeakAurasOptions/RegionOptions/ProgressTexture.lua index b30ddf3cf1..a308b88848 100644 --- a/WeakAurasOptions/RegionOptions/ProgressTexture.lua +++ b/WeakAurasOptions/RegionOptions/ProgressTexture.lua @@ -298,18 +298,12 @@ local function createOptions(id, data) hidden = function() return not data.slanted or data.orientation == "CLOCKWISE" or data.orientation == "ANTICLOCKWISE" end, values = OptionsPrivate.Private.slant_mode }, - spacer = { - type = "header", - name = "", - order = 56 - }, endHeader = { type = "header", order = 100, name = "", }, }; - options = OptionsPrivate.Private.regionPrototype.AddAdjustedDurationOptions(options, data, 57); local overlayInfo = OptionsPrivate.Private.GetOverlayInfo(data); if (overlayInfo and next(overlayInfo)) then @@ -353,6 +347,7 @@ local function createOptions(id, data) return { progresstexture = options, + progressOptions = OptionsPrivate.commonOptions.ProgressOptions(data), position = OptionsPrivate.commonOptions.PositionOptions(id, data), }; end diff --git a/WeakAurasOptions/RegionOptions/StopMotion.lua b/WeakAurasOptions/RegionOptions/StopMotion.lua index 3db65194b3..e0de9b7dfb 100644 --- a/WeakAurasOptions/RegionOptions/StopMotion.lua +++ b/WeakAurasOptions/RegionOptions/StopMotion.lua @@ -565,17 +565,11 @@ local function createOptions(id, data) } }; - if OptionsPrivate.commonOptions then - return { - stopmotion = options, - position = OptionsPrivate.commonOptions.PositionOptions(id, data, 2), - }; - else - return { - stopmotion = options, - position = WeakAuras.PositionOptions(id, data, 2), - }; - end + return { + stopmotion = options, + progressOptions = OptionsPrivate.commonOptions.ProgressOptions(data), + position = OptionsPrivate.commonOptions.PositionOptions(id, data, 2), + } end local function createThumbnail() diff --git a/WeakAurasOptions/SubRegionOptions/Tick.lua b/WeakAurasOptions/SubRegionOptions/Tick.lua index fd88de7aa3..09a59cb206 100644 --- a/WeakAurasOptions/SubRegionOptions/Tick.lua +++ b/WeakAurasOptions/SubRegionOptions/Tick.lua @@ -23,31 +23,53 @@ local function createOptions(parentData, data, index, subIndex) order = 2, hasAlpha = true, }, + + tick_thickness = { + type = "range", + control = "WeakAurasSpinBox", + width = WeakAuras.normalWidth, + name = L["Thickness"], + order = 2.5, + min = 0, + softMax = 20, + step = 1, + }, + + tick_progress_source_space = { + type = "description", + name = "", + order = 3, + width = WeakAuras.normalWidth, + }, + tick_placement_mode = { type = "select", width = WeakAuras.normalWidth, name = L["Tick Mode"], - order = 3, + order = 3.1, values = OptionsPrivate.Private.tick_placement_modes, }, - tick_placement = { - type = "input", + + + tick_progress_source_space_2 = { + type = "description", + name = "", + order = 3.2, width = WeakAuras.normalWidth, - name = L["Tick Placement"], - order = 4, - validate = WeakAuras.ValidateNumeric, - desc = L["Enter in a value for the tick's placement."], }, - tick_thickness = { - type = "range", - control = "WeakAurasSpinBox", - width = WeakAuras.normalWidth, - name = L["Thickness"], + + tick_add = { + type = "execute", + name = L["Add"], order = 5, - min = 0, - softMax = 20, - step = 1, + width = WeakAuras.normalWidth, + func = function() + tinsert(data.tick_placements, 0) + WeakAuras.Add(parentData) + WeakAuras.ClearAndUpdateOptions(parentData.id) + end }, + tick_extrasDescription = { type = "execute", control = "WeakAurasExpandSmall", @@ -80,7 +102,7 @@ local function createOptions(parentData, data, index, subIndex) return description end, width = WeakAuras.doubleWidth, - order = 6, + order = 7, func = function(info, button) local collapsed = OptionsPrivate.IsCollapsed("subtext", "subtext", "tickextras" .. index, true) OptionsPrivate.SetCollapsed("subtext", "subtext", "tickextras" .. index, not collapsed) @@ -99,7 +121,7 @@ local function createOptions(parentData, data, index, subIndex) type = "toggle", width = WeakAuras.normalWidth, name = L["Automatic length"], - order = 7, + order = 8, desc = L["Matches the height setting of a horizontal bar or width for a vertical bar."], hidden = hiddentickextras, }, @@ -108,7 +130,7 @@ local function createOptions(parentData, data, index, subIndex) control = "WeakAurasSpinBox", width = WeakAuras.normalWidth, name = L["Length"], - order = 8, + order = 9, min = 0, softMax = 50, step = 1, @@ -119,14 +141,14 @@ local function createOptions(parentData, data, index, subIndex) type = "toggle", width = WeakAuras.normalWidth, name = L["Use Texture"], - order = 9, + order = 10, hidden = hiddentickextras, }, tick_blend_mode = { type = "select", width = WeakAuras.normalWidth, name = L["Blend Mode"], - order = 10, + order = 11, values = OptionsPrivate.Private.blend_types, disabled = function() return not data.use_texture end, hidden = hiddentickextras, @@ -134,7 +156,7 @@ local function createOptions(parentData, data, index, subIndex) tick_texture = { type = "input", name = L["Texture"], - order = 11, + order = 12, width = WeakAuras.doubleWidth - 0.15, disabled = function() return not data.use_texture end, hidden = hiddentickextras, @@ -143,7 +165,7 @@ local function createOptions(parentData, data, index, subIndex) type = "execute", name = L["Choose"], width = 0.15, - order = 11.5, + order = 12.5, func = function() OptionsPrivate.OpenTexturePicker(parentData, { "subRegions", index @@ -164,7 +186,7 @@ local function createOptions(parentData, data, index, subIndex) type = "toggle", width = WeakAuras.doubleWidth, name = L["Desaturate"], - order = 12, + order = 13, hidden = hiddentickextras, }, tick_rotation = { @@ -218,6 +240,69 @@ local function createOptions(parentData, data, index, subIndex) } } + for i in ipairs(data.tick_placements) do + options["tick_progress_source" .. i] = { + type = "select", + width = WeakAuras.normalWidth, + name = L["Progress Source"], + order = 4 + i / 100, + control = "WeakAurasTwoColumnDropdown", + values = OptionsPrivate.Private.GetProgressSourcesForUi(parentData, true), + get = function(info) + return OptionsPrivate.Private.GetProgressValueConstant(data.progressSources[i] or {-2, ""}) + end, + set = function(info, value) + if value then + data.progressSources = data.progressSources or {} + data.progressSources[i] = data.progressSources[i] or {} + -- Copy only trigger + property + data.progressSources[i][1] = value[1] + data.progressSources[i][2] = value[2] + else + data.progressSources[i] = nil + end + WeakAuras.Add(parentData) + end, + hidden = function() + return not(data.tick_placement_mode == "ValueOffset") + end + } + + options["tick_placement" .. i] = { + type = "input", + width = WeakAuras.normalWidth - 0.15, + name = L["Tick Placement"], + order = 4 + i / 100 + 0.001, + validate = WeakAuras.ValidateNumeric, + desc = L["Enter in a value for the tick's placement."], + get = function(info) + return data.tick_placements[i] or "" + end, + set = function(info, value) + data.tick_placements[i] = value + WeakAuras.Add(parentData) + end + } + + options["tick_placement_delete" .. i] = { + type = "execute", + width = 0.15, + name = L["Delete"], + order = 4 + i / 100 + 0.002, + func = function() + tremove(data.tick_placements, i) + WeakAuras.Add(parentData) + end, + image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\delete", + imageWidth = 24, + imageHeight = 24, + control = "WeakAurasIcon", + disabled = function() + return #data.tick_placements < 2 + end + } + end + OptionsPrivate.AddUpDownDeleteDuplicate(options, parentData, index, "subtick") return options