diff --git a/widget/gtk2/nsNativeThemeGTK.cpp b/widget/gtk2/nsNativeThemeGTK.cpp index 59650f33f26..130b460bb1f 100644 --- a/widget/gtk2/nsNativeThemeGTK.cpp +++ b/widget/gtk2/nsNativeThemeGTK.cpp @@ -67,8 +67,7 @@ #include "gfxPlatformGtk.h" #include "gfxGdkNativeRenderer.h" -NS_IMPL_ISUPPORTS_INHERITED2(nsNativeThemeGTK, nsNativeTheme, nsITheme, - nsIObserver) +NS_IMPL_ISUPPORTS_INHERITED1(nsNativeThemeGTK, nsNativeTheme, nsITheme) static int gLastGdkError; @@ -82,7 +81,7 @@ nsNativeThemeGTK::nsNativeThemeGTK() // We have to call moz_gtk_shutdown before the event loop stops running. nsCOMPtr obsServ = mozilla::services::GetObserverService(); - obsServ->AddObserver(this, "xpcom-shutdown", false); + obsServ->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes)); memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates)); @@ -95,14 +94,10 @@ NS_IMETHODIMP nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) { - if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { moz_gtk_shutdown(); - } else { - NS_NOTREACHED("unexpected topic"); - return NS_ERROR_UNEXPECTED; } - - return NS_OK; + return nsNativeTheme::Observe(aSubject, aTopic, aData); } void diff --git a/widget/gtk2/nsNativeThemeGTK.h b/widget/gtk2/nsNativeThemeGTK.h index 5deb30e1ac3..2557db73e1d 100644 --- a/widget/gtk2/nsNativeThemeGTK.h +++ b/widget/gtk2/nsNativeThemeGTK.h @@ -46,12 +46,12 @@ #include "gtkdrawing.h" class nsNativeThemeGTK: private nsNativeTheme, - public nsITheme, - public nsIObserver { + public nsITheme { public: NS_DECL_ISUPPORTS_INHERITED - - NS_DECL_NSIOBSERVER + // Also implemented by nsNativeTheme + NS_SCRIPTABLE NS_IMETHOD Observe(nsISupports *aSubject, const char * aTopic, + const PRUnichar * aData); // The nsITheme interface. NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext, diff --git a/widget/xpwidgets/nsNativeTheme.cpp b/widget/xpwidgets/nsNativeTheme.cpp index c2a29b9afbf..7c4209b9f90 100644 --- a/widget/xpwidgets/nsNativeTheme.cpp +++ b/widget/xpwidgets/nsNativeTheme.cpp @@ -54,13 +54,15 @@ #include "nsProgressFrame.h" #include "nsMenuFrame.h" #include "mozilla/dom/Element.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" nsNativeTheme::nsNativeTheme() : mAnimatedContentTimeout(PR_UINT32_MAX) { } -NS_IMPL_ISUPPORTS1(nsNativeTheme, nsITimerCallback) +NS_IMPL_ISUPPORTS2(nsNativeTheme, nsITimerCallback, nsIObserver) nsIPresShell * nsNativeTheme::GetPresShell(nsIFrame* aFrame) @@ -565,6 +567,150 @@ nsNativeTheme::QueueAnimatedContentForRefresh(nsIContent* aContent, return true; } +inline bool +IsFadeIn(nsNativeTheme::FadeState aState) +{ + return (aState == nsNativeTheme::FADE_IN || + aState == nsNativeTheme::FADE_IN_FINISHED); +} + +inline bool +IsFadeOut(nsNativeTheme::FadeState aState) +{ + return (aState == nsNativeTheme::FADE_OUT); +} + +bool +nsNativeTheme::QueueAnimatedContentRefreshForFade(nsIContent* aContent, + FadeState aFadeDirection, + PRUint32 aMinimumFrameRate, + PRUint32 aMilliseconds, + PRUint32 aUserData) +{ + NS_ASSERTION(aContent, "Null pointer!"); + NS_ASSERTION((aFadeDirection == FADE_IN || + aFadeDirection == FADE_OUT), "Bad initial fade direction."); + + // Initialize our hash table and setup an observer for freeing its contents + // on shutdown. + if (NS_FAILED(InitFadeList())) + return false; + + // Note, QueueAnimatedContentForRefresh failures in here can result in + // content getting stuck in mAnimatedFadesList until shutdown, so we + // warn loudly. Generally this should never happen. + + FadeData* pFade = mAnimatedFadesList.Get(aContent); + if (pFade) { + // Update the user data + pFade->SetUserData(aUserData); + + // Check for direction changes and update our fade data accordingly. + if (IsFadeIn(pFade->GetState()) != IsFadeIn(aFadeDirection)) { + if (pFade->GetState() != FADE_IN_FINISHED) { + // The amount of time we spent getting here equals the amount of + // time we spend getting back out. + pFade->Reset(pFade->TimeoutUsed(), aFadeDirection); + } else { + // Reset to transition timeout passed in. + //PRUint32 timeout = + // PR_IntervalToMilliseconds(PR_IntervalNow()) + aMilliseconds; + //pFade->Reset(timeout, aFadeDirection); + pFade->Reset(TimeDuration::FromMilliseconds(aMilliseconds), + aFadeDirection); + } + } + + // Check for a timeout + if (pFade->GetTimeout() < TimeStamp::Now()) { + // If timed out and it's a fade up, set state to finished. We keep the + // fade data around until a corresponding fade out completes or the + // underlying frame is destroyed. + if (IsFadeIn(pFade->GetState())) { + pFade->FadeInFinished(); + // Create a heartbeat (1 sec) animation timer so if the underlying + // frame is destroyed, Notify will free the content. + if (!QueueAnimatedContentForRefresh(aContent, 1)) { + NS_WARNING("QueueAnimatedContentForRefresh failed???"); + return false; + } + } else if (IsFadeOut(pFade->GetState())) { + // If timed out and it's a fade out, clear it, we're done. + mAnimatedFadesList.Remove(aContent); + // Fire one last time to get the base graphic painted. + if (!QueueAnimatedContentForRefresh(aContent, aMinimumFrameRate)) { + NS_WARNING("QueueAnimatedContentForRefresh failed???"); + return false; + } + } + } else { + // fading.. + if (!QueueAnimatedContentForRefresh(aContent, aMinimumFrameRate)) { + NS_WARNING("QueueAnimatedContentForRefresh failed???"); + return false; + } + } + return true; + } + + // If we don't have a fade put together a FadeData, store it in + // mAnimatedFadesList, and kick things off. + TimeStamp timeout = TimeStamp::Now() + + TimeDuration::FromMilliseconds(aMilliseconds); + nsAutoPtr newFade(new FadeData(timeout, aFadeDirection, aUserData)); + if (!newFade) { + NS_WARNING("Out of memory!"); + return false; + } + // Call QueueAnimatedContentForRefresh to kick off the fade animation. + if (!QueueAnimatedContentForRefresh(aContent, aMinimumFrameRate)) { + NS_WARNING("QueueAnimatedContentForRefresh failed???"); + return false; + } + mAnimatedFadesList.Put(aContent, newFade); + newFade.forget(); + + return true; +} + +// mAnimatedFadesList management + +nsresult +nsNativeTheme::InitFadeList() +{ + if (mAnimatedFadesList.IsInitialized()) + return NS_OK; + if (!mAnimatedFadesList.Init()) + return NS_ERROR_UNEXPECTED; + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + nsresult rv = NS_ERROR_UNEXPECTED; + if (obsSvc) { + rv = obsSvc->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + } + return rv; +} + +NS_IMETHODIMP +nsNativeTheme::Observe(nsISupports* aSubject, const char* aTopic, + const PRUnichar* aData) +{ + if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { + mAnimatedFadesList.Clear(); + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + nsresult rv = NS_ERROR_UNEXPECTED; + if (obsSvc) { + rv = obsSvc->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + NS_ASSERTION(NS_SUCCEEDED(rv), + "nsNativeTheme RemoveObserver failed, this may cause a leak."); + } + return NS_OK; +} + +// mAnimatedContentTimer callback for QueueAnimatedContentForRefresh + NS_IMETHODIMP nsNativeTheme::Notify(nsITimer* aTimer) { @@ -578,14 +724,90 @@ nsNativeTheme::Notify(nsITimer* aTimer) nsIFrame* frame = mAnimatedContentList[index]->GetPrimaryFrame(); if (frame) { frame->InvalidateOverflowRect(); + } else { + // If this content has fade data associated with it, and the + // frame has gone away, free the data and cancel the fade. + if (mAnimatedFadesList.IsInitialized()) { + mAnimatedFadesList.Remove(mAnimatedContentList[index]); + } } } mAnimatedContentList.Clear(); mAnimatedContentTimeout = PR_UINT32_MAX; + return NS_OK; } +// Fade helpers + +nsNativeTheme::FadeData* +nsNativeTheme::GetFade(nsIContent* aContent) +{ + if (!aContent || !mAnimatedFadesList.IsInitialized()) + return nsnull; + return mAnimatedFadesList.Get(reinterpret_cast(aContent)); +} + +nsNativeTheme::FadeState +nsNativeTheme::GetFadeState(nsIContent* aContent) +{ + FadeData* pFade = GetFade(aContent); + if (!pFade) + return FADE_NOTACTIVE; + return pFade->GetState(); +} + +PRUint32 +nsNativeTheme::GetFadeTicks(nsIContent* aContent) +{ + FadeData* pFade = GetFade(aContent); + if (!pFade) + return 0; + return pFade->GetTicks(); +} + +double +nsNativeTheme::GetFadeAlpha(nsIContent* aContent) +{ + return ((double)GetFadeTicks(aContent))/TICK_MAX; +} + +PRUint32 +nsNativeTheme::GetFadeUserData(nsIContent* aContent) +{ + FadeData* pFade = GetFade(aContent); + if (!pFade) + return 0; + return pFade->GetUserData(); +} + +void +nsNativeTheme::SetFadeUserData(nsIContent* aContent, PRUint32 aUserData) +{ + FadeData* pFade = GetFade(aContent); + if (pFade) { + pFade->SetUserData(aUserData); + } +} + +void +nsNativeTheme::CancelFade(nsIContent* aContent) +{ + if (aContent && mAnimatedFadesList.IsInitialized()) { + mAnimatedFadesList.Remove(reinterpret_cast(aContent)); + } +} + +void +nsNativeTheme::FinishFadeIn(nsIContent* aContent) +{ + FadeData* pFade = GetFade(aContent); + if (pFade) { + pFade->FadeInFinished(); + } +} + nsIFrame* nsNativeTheme::GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame, bool aNextSibling) @@ -607,3 +829,4 @@ nsNativeTheme::GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame, return nsnull; return sibling; } + diff --git a/widget/xpwidgets/nsNativeTheme.h b/widget/xpwidgets/nsNativeTheme.h index e121f87a94e..9abaf618b0f 100644 --- a/widget/xpwidgets/nsNativeTheme.h +++ b/widget/xpwidgets/nsNativeTheme.h @@ -49,18 +49,27 @@ #include "nsEventStates.h" #include "nsTArray.h" #include "nsITimer.h" +#include "nsClassHashtable.h" +#include "nsIObserver.h" +#include "mozilla/TimeStamp.h" class nsIContent; class nsIFrame; class nsIPresShell; class nsPresContext; -class nsNativeTheme : public nsITimerCallback +class nsNativeTheme : + public nsITimerCallback, + public nsIObserver { + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + protected: NS_DECL_ISUPPORTS NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIOBSERVER enum ScrollbarButtonType { eScrollbarButton_UpTop = 0, @@ -189,14 +198,172 @@ class nsNativeTheme : public nsITimerCallback bool GetCheckedOrSelected(nsIFrame* aFrame, bool aCheckSelected); bool GetIndeterminate(nsIFrame* aFrame); - bool QueueAnimatedContentForRefresh(nsIContent* aContent, - PRUint32 aMinimumFrameRate); - nsIFrame* GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame, bool aNextSibling); + // Queue a themed element for a redraw after a set interval. + bool QueueAnimatedContentForRefresh(nsIContent* aContent, + PRUint32 aMinimumFrameRate); + + /* + * Simple two phase animations on themed widgets - 'Fades' transition from + * a base state to a highlighted state and back to the base state, at which + * point data associated with the fade is freed. + * + * Important notes: + * + * Consumers are responsible for triggering refresh calls by calling + * QueueAnimatedContentRefreshForFade on each redraw. + * + * Consumers are also responsible for switching fade transitions from + * FADE_IN/FADE_IN_FINISHED to FADE_OUT through calls to QACRFF. Failing + * to do so keeps content / fade data stored in mAnimatedFadesList until + * the content's underlying frame is destroyed or the application closes. + */ + + // Fade states + typedef enum FadeState { + FADE_NOTACTIVE = 0, // Fade state not found, fade complete + FADE_IN = 1, // Fading in + FADE_IN_FINISHED = 2, // Fade-in is finished, waiting for fade-out + FADE_OUT = 3, // Fading out + }; + + /* + * QueueAnimatedContentRefreshForFade - creates a new fade or requests a + * refresh on an existing fade in progress. + * + * aContent The themed content element the animation is associated + * with. + * aFadeDirection The current direction of the fade. Valid values are + * FADE_IN or FADE_OUT. + * aMinimumFrameRate The minimum frame rate requested (30 is typical). Value + * is passed to QueueAnimatedContentForRefresh to trigger a + * refresh. + * aMilliseconds Duration of the fade-in or fade-out transition. + * aUserData Generic consumer data storage for state across rendering + * of individual frames. Updated on every call. + */ + bool + QueueAnimatedContentRefreshForFade(nsIContent* aContent, + FadeState aFadeDirection, + PRUint32 aMinimumFrameRate, + PRUint32 aMilliseconds, + PRUint32 aUserData = 0); + + // Max ticks returned by FadeData->GetTicks(). + #define TICK_MAX 100.0 + + // Internal data structure for storing fade data + class FadeData + { + public: + /* + * FadeData() + * aTimeout now + duration + * aState FADE_IN or FADE_OUT + * aUserData intial value for user data + */ + FadeData(TimeStamp aTimeout, FadeState aState, PRUint32 aUserData) : + mTimeout(aTimeout), + mStartTime(TimeStamp::Now()), + mState(aState), + mUserData(aUserData) { + } + ~FadeData() {} + + /* + * Reset - resets the to a new timeout value and direction. + * aTimeout msec(now) + duration + * aState FADE_IN or FADE_OUT + */ + void Reset(TimeDuration aTimeout, FadeState aState) { + NS_ASSERTION((aState == FADE_IN || aState == FADE_OUT), + "Bad fade direction."); + mStartTime = TimeStamp::Now(); + mTimeout = TimeStamp::Now() + aTimeout; + mState = aState; + } + + /* + * GetTicks - returns the number of ticks in this animation where + * ticks >= 0 && ticks <= TICK_MAX. FADE_IN has increasing ticks, + * FADE_OUT decreasing. + */ + PRUint32 GetTicks() { + TimeStamp now = TimeStamp::Now(); + if (now >= mTimeout) { + return (mState == FADE_OUT ? 0 : (PRUint32)TICK_MAX); + } + TimeDuration diff = now - mStartTime; + PRUint32 tick = + (PRUint32)ceil((diff / (mTimeout - mStartTime)) * TICK_MAX); + // we want ticks to ascend and descend according to the direction. + if (mState == FADE_OUT) { + tick = (PRUint32)abs(tick - TICK_MAX); + } + return tick; + } + + /* + * TimeoutUsed - for fades that have not completes, returns the + * amount of time used thus far in the current transition in msec. + */ + TimeDuration TimeoutUsed() { + TimeDuration used = TimeStamp::Now() - mStartTime; + TimeDuration totalTime = mTimeout - mStartTime; + return NS_MIN(used, totalTime); + } + + /* + * Misc. data getters/setters + */ + TimeStamp GetTimeout() { return mTimeout; } + FadeState GetState() { return mState; } + void FadeInFinished() { mState = FADE_IN_FINISHED; } + PRUint32 GetUserData() { return mUserData; } + void SetUserData(PRUint32 aUserData) { mUserData = aUserData; } + + private: + TimeStamp mTimeout; + TimeStamp mStartTime; + FadeState mState; + PRUint32 mUserData; + }; + + /* + * nsNativeTheme fade data helpers + */ + + // Retrieves the FadeData object associated with this content, or null. + FadeData* GetFade(nsIContent* aContent); + // Retrieves the current fade state or FADE_NOTACTIVE. + FadeState GetFadeState(nsIContent* aContent); + // Retrieves the current tick count for a fade transition or 0. Ticks + // range from 0 -> TICK_MAX. For FADE_IN transitions ticks increase, + // for FADE_OUT transitions ticks decrease. + PRUint32 GetFadeTicks(nsIContent* aContent); + // Retrieves the alpha value (0->1) corresponding to the current tick + // count for a fade transition, or 0. + double GetFadeAlpha(nsIContent* aContent); + // Get/set consumer data. Valid across each call to QACRFF. + PRUint32 GetFadeUserData(nsIContent* aContent); + void SetFadeUserData(nsIContent* aContent, PRUint32 aUserData); + // Cancel an active fade and free its resources. + void CancelFade(nsIContent* aContent); + // Mark a fade as FADE_IN_FINISHED. + void FinishFadeIn(nsIContent* aContent); + private: + nsresult InitFadeList(); + PRUint32 mAnimatedContentTimeout; nsCOMPtr mAnimatedContentTimer; + // Render refresh list - nsIContent contains the content + // that will be invalidated when mAnimatedContentTimer fires. + // Cleared on every call to mAnimatedContentTimer Notify. nsAutoTArray, 20> mAnimatedContentList; + // Fade list data - nsISupportsHashKey contains the nsIContent + // associated with an active fade. + nsClassHashtable mAnimatedFadesList; };