Bug 373266 - base support for animated theme widgets to nsnativetheme. r=roc

This commit is contained in:
Jim Mathies 2012-03-07 09:29:21 -06:00
parent 33fb391942
commit 39f254973a
4 changed files with 403 additions and 18 deletions

View File

@ -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<nsIObserverService> 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

View File

@ -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,

View File

@ -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<FadeData> 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<nsIObserverService> 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<nsIObserverService> 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<nsISupports*>(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<nsISupports*>(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;
}

View File

@ -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<nsITimer> mAnimatedContentTimer;
// Render refresh list - nsIContent contains the content
// that will be invalidated when mAnimatedContentTimer fires.
// Cleared on every call to mAnimatedContentTimer Notify.
nsAutoTArray<nsCOMPtr<nsIContent>, 20> mAnimatedContentList;
// Fade list data - nsISupportsHashKey contains the nsIContent
// associated with an active fade.
nsClassHashtable<nsISupportsHashKey, FadeData> mAnimatedFadesList;
};