Thread Tools Display Modes
03-21-13, 03:27 AM   #1
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Why won't my animations play more than once?

I'm trying to give the WatchFrame a nice fade-in on mouseover, and fade-out on exit. However, not only do the animations not actually change the WatchFrame's opacity, they will each only play once.

Based on my debug prints and the yellow hover background I added for testing, I'm seeing that mouseover detection is working perfectly (I can't use OnEnter and OnLeave directly because the WatchFrame itself isn't mouse-enabled and I don't want it to be), but I'm only seeing "fadeIn" and "fadeOut" printed once, the first times the mouse enters and leaves the frame, respectively.

No errors are raised at any point.

Clearly I'm missing something, but what? Can someone who's actually worked with animations before take a look at this and offer any suggestions?

Code:
local MIN_ALPHA = 0.25
local MAX_ALPHA = 1

local hasMouseFocus = false

local GetMouseFocus = GetMouseFocus
local WorldFrame = WorldFrame

local fadeGroup = WatchFrame:CreateAnimationGroup()
WatchFrame.fadeGroup = fadeGroup

local fadeIn = fadeGroup:CreateAnimation("Alpha")
fadeIn:SetChange(MAX_ALPHA - MIN_ALPHA)
fadeIn:SetDuration(0.2)
--fadeIn:SetStartDelay(0.1)
--fadeIn:SetSmoothing("OUT")
fadeIn:SetScript("OnPlay", function(self) print("fadeIn") end)
fadeGroup.fadeIn = fadeIn

local fadeOut = fadeGroup:CreateAnimation("Alpha")
fadeOut:SetChange(MIN_ALPHA - MAX_ALPHA)
fadeOut:SetDuration(0.6)
--fadeOut:SetStartDelay(0.5)
--fadeOut:SetSmoothing("IN")
fadeOut:SetScript("OnPlay", function(self) print("fadeOut") end)
fadeGroup.fadeOut = fadeOut

WatchFrame:SetAlpha(MIN_ALPHA)

local bg = WatchFrame:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(true)
bg:SetTexture(1, 1, 0, 0.25)
bg:Hide()

local descendants = setmetatable({}, { __index = function(t, f)
	local parent = f:GetParent()
	while parent do
		if parent == WatchFrame then
			t[f] = true
			return true
		end
		parent = parent:GetParent()
	end
	t[f] = false
	return false
end })

WatchFrame:HookScript("OnUpdate", function(self, elapsed)
	local mouseFocus = GetMouseFocus()
	if not mouseFocus then
		return
	end
	local gotMouseFocus = mouseFocus == self or descendants[mouseFocus] or (mouseFocus == WorldFrame and self:IsMouseOver())
	if gotMouseFocus == hasMouseFocus then
		return
	end
	hasMouseFocus = gotMouseFocus
	if gotMouseFocus then
		print("OnEnter")
		--fadeGroup:Finish()
		--self:SetAlpha(MIN_ALPHA)
		fadeIn:Play()
		bg:Show()
	else
		print("OnLeave")
		--fadeGroup:Finish()
		--self:SetAlpha(MAX_ALPHA)
		fadeOut:Play()
		bg:Hide()
	end
end)
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
03-21-13, 07:45 AM   #2
Dridzt
A Pyroguard Emberseer
 
Dridzt's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 1,360
Edit: Ignore my original post, you don't want looping, I'm re-reading your post now :P

Ok I know what's the problem you're using :Play() on the animation instead of the animationgroup.
You need 2 animationgroups, one with a fadein animation and one with a fadeout.

Last edited by Dridzt : 03-21-13 at 08:24 AM.
  Reply With Quote
03-21-13, 08:01 AM   #3
ravagernl
Proceritate Corporis
Premium Member
AddOn Author - Click to view addons
Join Date: Feb 2006
Posts: 1,176
This may be obvious information and I do not know any more then you regarding AnimationGroups, but have you checked the output of Animation:IsDone() / :IsStopped / :IsPlaying / :IsPaused?
  Reply With Quote
03-21-13, 08:28 AM   #4
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,327
I'm just starting to fidget around with animations and this is the only way I can get the code to work. I cleaned up the triggering code to just depend on WatchFrame:IsMouseOver(). I also added in some lines to stop the previous animation and switch to the new one if someone decides to rapidly move the mouse over and back out repeatedly. It makes the animation less prone to spasming in that condition.

Lua Code:
  1. local MIN_ALPHA=0.25;
  2. local MAX_ALPHA=1;
  3.  
  4. local FadeInAnimGroup=WatchFrame:CreateAnimationGroup();
  5. local FadeInAnim=FadeInAnimGroup:CreateAnimation("Alpha");
  6. FadeInAnim:SetChange(MAX_ALPHA-MIN_ALPHA);
  7. FadeInAnim:SetDuration(0.2);
  8. FadeInAnimGroup:SetScript("OnFinished",function(self) self:GetParent():SetAlpha(MAX_ALPHA); end);
  9.  
  10. local FadeOutAnimGroup=WatchFrame:CreateAnimationGroup();
  11. local FadeOutAnim=FadeOutAnimGroup:CreateAnimation("Alpha");
  12. FadeOutAnim:SetChange(MIN_ALPHA-MAX_ALPHA);
  13. FadeOutAnim:SetDuration(0.2);
  14. FadeOutAnimGroup:SetScript("OnFinished",function(self) self:GetParent():SetAlpha(MIN_ALPHA); end);
  15.  
  16. WatchFrame:SetAlpha(MIN_ALPHA);
  17. local MouseOver=false;
  18. WatchFrame:HookScript("OnUpdate",function(self)
  19.     local ismouseover=self:IsMouseOver();
  20.     if ismouseover~=MouseOver then
  21.         MouseOver=ismouseover;
  22.         if ismouseover then
  23.             FadeOutAnimGroup:Stop();
  24.             self:SetAlpha(MIN_ALPHA);
  25.             FadeInAnimGroup:Play();
  26.         else
  27.             FadeInAnimGroup:Stop();
  28.             self:SetAlpha(MAX_ALPHA);
  29.             FadeOutAnimGroup:Play();
  30.         end
  31.     end
  32. end);
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
03-21-13, 02:08 PM   #5
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
The problem with using only WatchFrame:IsMouseOver() is that then it triggers when I mouse over an open UI panel (like the quest log) that's above the WatchFrame, which is really annoying... I actually plan to add a delay on mouseover so it won't trigger while moving the mouse between two adjacent UI panels either.

I'll try using two animation groups... if you can't play individual animations, though, why do they have :Play() methods?

Edit: Okay, two animations works, but I also had to add OnFinished scripts to set the alpha to the final alpha, or the animation just resets it to the starting alpha after it's done... seems strange. I also removed the pre-Play SetAlpha calls so it's even less visibly responsive to mouseover/mouseout spamming, and added negative insets to IsMouseOver to better handle the gap between the WatchFrame and the quest POI buttons.

Here is the final code if anyone wants to use it for something:
Code:
local MIN_ALPHA = 0.4
local MAX_ALPHA = 1

local hasMouseFocus = false

local GetMouseFocus = GetMouseFocus
local WorldFrame = WorldFrame

WatchFrame:SetAlpha(MIN_ALPHA)

local fadeIn = WatchFrame:CreateAnimationGroup()
fadeIn:SetScript("OnFinished", function(self) WatchFrame:SetAlpha(MAX_ALPHA) end)
fadeIn.anim = fadeIn:CreateAnimation("Alpha")
fadeIn.anim:SetChange(MAX_ALPHA - MIN_ALPHA)
fadeIn.anim:SetDuration(0.2)
fadeIn.anim:SetStartDelay(0.1)
fadeIn.anim:SetSmoothing("OUT")
WatchFrame.FadeInAnimation = fadeIn

local fadeOut = WatchFrame:CreateAnimationGroup()
fadeOut:SetScript("OnFinished", function(self) WatchFrame:SetAlpha(MIN_ALPHA) end)
fadeOut.anim = fadeOut:CreateAnimation("Alpha")
fadeOut.anim:SetChange(MIN_ALPHA - MAX_ALPHA)
fadeOut.anim:SetDuration(0.4)
fadeOut.anim:SetStartDelay(0.2)
fadeOut.anim:SetSmoothing("IN")
WatchFrame.FadeOutAnimation = fadeOut

local descendants = setmetatable({}, { __index = function(t, f)
	local parent = f:GetParent()
	while parent do
		if parent == WatchFrame then
			t[f] = true
			return true
		end
		parent = parent:GetParent()
	end
	t[f] = false
	return false
end })

WatchFrame:HookScript("OnUpdate", function(self, elapsed)
	local mouseFocus = GetMouseFocus()
	if not mouseFocus then
		return
	end
	local gotMouseFocus = mouseFocus == self or descendants[mouseFocus] or (mouseFocus == WorldFrame and self:IsMouseOver(10, -10, -10, 10))
	if gotMouseFocus == hasMouseFocus then
		return
	end
	hasMouseFocus = gotMouseFocus
	fadeOut:Stop()
	fadeIn:Stop()
	if gotMouseFocus then
		fadeIn:Play()
	else
		fadeOut:Play()
	end
end)
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.

Last edited by Phanx : 03-21-13 at 04:27 PM.
  Reply With Quote
03-21-13, 09:48 PM   #6
Dridzt
A Pyroguard Emberseer
 
Dridzt's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 1,360
I think you can solve your "reset" problem without an OnFinished script if you :SetLooping("NONE") on each of your animation groups.

Also a trick to get it to go from whatever alpha it's at when your mouseover state changes is to (for each alpha animation) change to :SetAlpha(MIN_ALPHA-WatchFrame:GetAlpha()) for the fadeOut and :SetAlpha(MAX_ALPHA-WatchFrame:GetAlpha()) for the fadeIn.
  Reply With Quote
03-23-13, 07:46 PM   #7
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
I'll give that a try, thanks.

If I'm setting the change based on the current alpha, I should probably just be able to use one animation and also set the duration and delays based on whether it's supposed to be fading in or out, right?
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
03-23-13, 09:14 PM   #8
pelf
Sentient Plasmoid
 
pelf's Avatar
Premium Member
Join Date: May 2008
Posts: 133
Well, can the out animation trigger before the in animation finishes?
  Reply With Quote
03-24-13, 02:52 AM   #9
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Yes, but if it does, the "in" animation gets :Stop()ped before the "out" animation gets :Play()ed, so they're never playing at the same time.

Edit:
Using :SetLooping("NONE") on the animation group did nothing -- it still reset to the starting alpha after the animation finished -- so I guess I'll have to keep the OnFinish script.

Here's the working code, adjusted to use just one animation, with the properties set dynamically:
Code:
local FADE_IN_ALPHA = 1
local FADE_IN_DELAY = 0.1
local FADE_IN_DURATION = 0.2

local FADE_OUT_ALPHA = 0.4
local FADE_OUT_DELAY = 0.2
local FADE_OUT_DURATION = 0.4

local GetMouseFocus = GetMouseFocus
local WorldFrame = WorldFrame

local fadeGroup = WatchFrame:CreateAnimationGroup()
local fadeAnim = fadeGroup:CreateAnimation("Alpha")
fadeGroup:SetLooping("NONE")
fadeGroup:SetScript("OnFinished", function(self) WatchFrame:SetAlpha(self.targetAlpha) end)
WatchFrame:SetAlpha(FADE_OUT_ALPHA)

local descendants = setmetatable({}, { __index = function(t, f)
	local parent = f:GetParent()
	while parent do
		if parent == WatchFrame then
			t[f] = true
			return true
		end
		parent = parent:GetParent()
	end
	t[f] = false
	return false
end })

local hasMouseFocus = false
WatchFrame:HookScript("OnUpdate", function(self, elapsed)
	local mouseFocus = GetMouseFocus()
	if not mouseFocus then
		return
	end
	local gotMouseFocus = mouseFocus == self or descendants[mouseFocus] or (mouseFocus == WorldFrame and self:IsMouseOver(10, -10, -10, 10))
	if gotMouseFocus == hasMouseFocus then
		return
	end
	hasMouseFocus = gotMouseFocus
	fadeGroup:Stop()
	if gotMouseFocus then
		-- Fade in
		local a = floor(self:GetAlpha() * 100 + 0.5) / 100
		local d = FADE_IN_ALPHA - a
		local t = (FADE_IN_DURATION * d) / (FADE_IN_ALPHA - FADE_OUT_ALPHA)
		if d < 0.05 or t < 0.05 then
			-- Don't bother animating
			return self:SetAlpha(FADE_IN_ALPHA)
		end
		fadeAnim:SetChange(d)
		fadeAnim:SetStartDelay(a == FADE_OUT_ALPHA and FADE_IN_DELAY or 0)
		fadeAnim:SetDuration(t)
		fadeGroup.targetAlpha = FADE_IN_ALPHA
		fadeGroup:Play()
	else
		-- Fade out
		local a = floor(self:GetAlpha() * 100 + 0.5) / 100
		local d = a - FADE_OUT_ALPHA
		local t = (FADE_OUT_DURATION * d) / (FADE_IN_ALPHA - FADE_OUT_ALPHA)
		if d < 0.05 or t < 0.05 then
			-- Don't bother animating
			return self:SetAlpha(FADE_OUT_ALPHA)
		end
		fadeAnim:SetChange(-d)
		fadeAnim:SetStartDelay(a == FADE_IN_ALPHA and FADE_OUT_DELAY or 0)
		fadeAnim:SetDuration(t)
		fadeGroup.targetAlpha = FADE_OUT_ALPHA
		fadeGroup:Play()
	end
end)
Next step will be to add a second level of fading to full transparency in combat, and then to generalize it to be applicable to any frame!
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.

Last edited by Phanx : 03-24-13 at 05:24 AM.
  Reply With Quote
03-24-13, 10:55 AM   #10
pelf
Sentient Plasmoid
 
pelf's Avatar
Premium Member
Join Date: May 2008
Posts: 133
On a content-related note ... would it be unreasonable to store values that are intended to be constant in a table with a no-write metatable set on it? Being sure constants aren't being unintentionally modified would make me feel a bit better about naming them as if they actually were constant .
  Reply With Quote
03-24-13, 11:14 AM   #11
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
They're constants by convention and, unlike members of a class, their naming convention should deter you as a programmer from setting a different value to them - there is also zero overhead this way, whereas using a metatable to prevent you from doing something silly will have some.
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
03-24-13, 11:16 AM   #12
pelf
Sentient Plasmoid
 
pelf's Avatar
Premium Member
Join Date: May 2008
Posts: 133
Originally Posted by Torhal View Post
They're constants by convention and, unlike members of a class, their naming convention should deter you as a programmer from setting a different value to them - there is also zero overhead this way, whereas using a metatable to prevent you from doing something silly will have some.
Yeah, I understand that ... and when coding in C/++ you can also pass reference as non-const even if you know you're not going to modify them . Would there be anything wrong with a Constants table for the purposes of peace of mind?
  Reply With Quote
03-24-13, 12:23 PM   #13
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
Other than the cost of a table lookup? No.
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
03-24-13, 02:54 PM   #14
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
If I were turning this into an addon for public consumption, I'd definitely store them in a table so they could be modified by an in-game options UI, but for personal use? No reason to waste memory and clutter up the code with a table structure.
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
03-24-13, 05:57 PM   #15
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
OnFinished scripts are needed. The animation does not keep the final state. To bad I have no time atm to try it. Sth like event bubbling would be nice to have. To do it you need to bubble the mouse event through the framestack. Not sure but I thinksth like /fstack is sth to look into. It lists the framestack for each point inside UIParent. Interesting topic.
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Why won't my animations play more than once?


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off