Add support for animation of text-shadow and -moz-box-shadow (the first complex value types that we animate). (Bug 523196) r=dholbert sr=bzbarsky

This commit is contained in:
L. David Baron 2009-10-21 06:53:46 -04:00
parent f85d2aae1c
commit 4c0fa8e623
6 changed files with 331 additions and 10 deletions

View File

@ -1143,8 +1143,8 @@ CSS_PROP_BORDER(
mBoxShadow,
eCSSType_ValueList,
kBoxShadowTypeKTable,
CSS_PROP_NO_OFFSET,
eStyleAnimType_None)
offsetof(nsStyleBorder, mBoxShadow),
eStyleAnimType_Shadow)
CSS_PROP_POSITION(
-moz-box-sizing,
box_sizing,
@ -2308,8 +2308,8 @@ CSS_PROP_TEXT(
mTextShadow,
eCSSType_ValueList,
nsnull,
CSS_PROP_NO_OFFSET,
eStyleAnimType_None)
offsetof(nsStyleText, mTextShadow),
eStyleAnimType_Shadow)
CSS_PROP_TEXT(
text-transform,
text_transform,

View File

@ -95,6 +95,9 @@ enum nsStyleAnimType {
// nsStyleSVGPaint values
eStyleAnimType_PaintServer,
// nsRefPtr<nsCSSShadowArray> values
eStyleAnimType_Shadow,
// property not animatable
eStyleAnimType_None
};

View File

@ -51,6 +51,7 @@
#include "nsICSSLoader.h"
#include "nsCSSDataBlock.h"
#include "nsCSSDeclaration.h"
#include "nsCSSStruct.h"
#include "prlog.h"
#include <math.h>
@ -152,6 +153,67 @@ nsStyleAnimation::ComputeDistance(const Value& aStartValue,
diffG * diffG + diffB * diffB);
break;
}
case eUnit_Shadow: {
// Call AddWeighted to make us lists of the same length.
Value normValue1, normValue2;
if (!AddWeighted(1.0, aStartValue, 0.0, aEndValue, normValue1) ||
!AddWeighted(0.0, aStartValue, 1.0, aEndValue, normValue2)) {
success = PR_FALSE;
break;
}
const nsCSSValueList *shadow1 = normValue1.GetCSSValueListValue();
const nsCSSValueList *shadow2 = normValue2.GetCSSValueListValue();
double distance = 0.0f;
while (shadow1 && shadow2) {
nsCSSValue::Array *array1 = shadow1->mValue.GetArrayValue();
nsCSSValue::Array *array2 = shadow2->mValue.GetArrayValue();
for (PRUint32 i = 0; i < 4; ++i) {
NS_ABORT_IF_FALSE(array1->Item(i).GetUnit() == eCSSUnit_Pixel,
"unexpected unit");
NS_ABORT_IF_FALSE(array2->Item(i).GetUnit() == eCSSUnit_Pixel,
"unexpected unit");
double diff = array1->Item(i).GetFloatValue() -
array2->Item(i).GetFloatValue();
distance += diff * diff;
}
const nsCSSValue& color1 = array1->Item(4);
const nsCSSValue& color2 = array2->Item(4);
const nsCSSValue& inset1 = array1->Item(5);
const nsCSSValue& inset2 = array2->Item(5);
// There are only two possible states of the inset value:
// (1) GetUnit() == eCSSUnit_Null
// (2) GetUnit() == eCSSUnit_Enumerated &&
// GetIntValue() == NS_STYLE_BOX_SHADOW_INSET
NS_ABORT_IF_FALSE(color1.GetUnit() == color2.GetUnit() &&
inset1 == inset2,
"AddWeighted should have failed");
if (color1.GetUnit() != eCSSUnit_Null) {
nsStyleAnimation::Value color1Value
(color1.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
nsStyleAnimation::Value color2Value
(color2.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
double colorDistance;
#ifdef DEBUG
PRBool ok =
#endif
nsStyleAnimation::ComputeDistance(color1Value, color2Value,
colorDistance);
NS_ABORT_IF_FALSE(ok, "should not fail");
distance += colorDistance * colorDistance;
}
shadow1 = shadow1->mNext;
shadow2 = shadow2->mNext;
}
NS_ABORT_IF_FALSE(!shadow1 && !shadow2, "lists of different lengths");
aDistance = sqrt(distance);
break;
}
case eUnit_Null:
case eUnit_None:
success = PR_FALSE;
@ -175,6 +237,73 @@ inline PRUint8 ClampColor(double aColor)
return NSToIntRound(aColor);
}
static PRBool
AddShadowItems(double aCoeff1, const nsCSSValue &aValue1,
double aCoeff2, const nsCSSValue &aValue2,
nsCSSValueList **&aResultTail)
{
// X, Y, Radius, Spread, Color, Inset
NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Array,
"wrong unit");
NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Array,
"wrong unit");
nsCSSValue::Array *array1 = aValue1.GetArrayValue();
nsCSSValue::Array *array2 = aValue2.GetArrayValue();
nsRefPtr<nsCSSValue::Array> resultArray = nsCSSValue::Array::Create(6);
if (!resultArray) {
return PR_FALSE;
}
for (PRUint32 i = 0; i < 4; ++i) {
NS_ABORT_IF_FALSE(array1->Item(i).GetUnit() == eCSSUnit_Pixel,
"unexpected unit");
NS_ABORT_IF_FALSE(array2->Item(i).GetUnit() == eCSSUnit_Pixel,
"unexpected unit");
double pixel1 = array1->Item(i).GetFloatValue();
double pixel2 = array2->Item(i).GetFloatValue();
resultArray->Item(i).SetFloatValue(aCoeff1 * pixel1 + aCoeff2 * pixel2,
eCSSUnit_Pixel);
}
const nsCSSValue& color1 = array1->Item(4);
const nsCSSValue& color2 = array2->Item(4);
const nsCSSValue& inset1 = array1->Item(5);
const nsCSSValue& inset2 = array2->Item(5);
if (color1.GetUnit() != color2.GetUnit() ||
inset1.GetUnit() != inset2.GetUnit()) {
// We don't know how to animate between color and no-color, or
// between inset and not-inset.
return PR_FALSE;
}
if (color1.GetUnit() != eCSSUnit_Null) {
nsStyleAnimation::Value color1Value
(color1.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
nsStyleAnimation::Value color2Value
(color2.GetColorValue(), nsStyleAnimation::Value::ColorConstructor);
nsStyleAnimation::Value resultColorValue;
#ifdef DEBUG
PRBool ok =
#endif
nsStyleAnimation::AddWeighted(aCoeff1, color1Value, aCoeff2, color2Value,
resultColorValue);
NS_ABORT_IF_FALSE(ok, "should not fail");
resultArray->Item(4).SetColorValue(resultColorValue.GetColorValue());
}
NS_ABORT_IF_FALSE(inset1 == inset2, "should match");
resultArray->Item(5) = inset1;
nsCSSValueList *resultItem = new nsCSSValueList;
if (!resultItem) {
return PR_FALSE;
}
resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array);
*aResultTail = resultItem;
aResultTail = &resultItem->mNext;
return PR_TRUE;
}
PRBool
nsStyleAnimation::AddWeighted(double aCoeff1, const Value& aValue1,
double aCoeff2, const Value& aValue2,
@ -242,6 +371,53 @@ nsStyleAnimation::AddWeighted(double aCoeff1, const Value& aValue1,
aResultValue.SetColorValue(resultColor);
break;
}
case eUnit_Shadow: {
// This is implemented according to:
// http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
// and the third item in the summary of:
// http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
const nsCSSValueList *shadow1 = aValue1.GetCSSValueListValue();
const nsCSSValueList *shadow2 = aValue2.GetCSSValueListValue();
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList **resultTail = getter_Transfers(result);
while (shadow1 && shadow2) {
if (!AddShadowItems(aCoeff1, shadow1->mValue,
aCoeff2, shadow2->mValue,
resultTail)) {
return PR_FALSE;
}
shadow1 = shadow1->mNext;
shadow2 = shadow2->mNext;
}
if (shadow1 || shadow2) {
const nsCSSValueList *longShadow;
double longCoeff;
if (shadow1) {
longShadow = shadow1;
longCoeff = aCoeff1;
} else {
longShadow = shadow2;
longCoeff = aCoeff2;
}
while (longShadow) {
// Passing coefficients that add to less than 1 produces the
// desired result of interpolating "0 0 0 transparent" with
// the current shadow.
if (!AddShadowItems(longCoeff, longShadow->mValue,
0.0, longShadow->mValue,
resultTail)) {
return PR_FALSE;
break;
}
longShadow = longShadow->mNext;
}
}
aResultValue.SetCSSValueListValue(result.forget(), eUnit_Shadow,
PR_TRUE);
break;
}
case eUnit_Null:
case eUnit_None:
success = PR_FALSE;
@ -402,6 +578,12 @@ nsStyleAnimation::UncomputeValue(nsCSSProperty aProperty,
SetColorValue(aComputedValue.GetColorValue());
}
break;
case eUnit_Shadow:
NS_ABORT_IF_FALSE(nsCSSProps::kTypeTable[aProperty] ==
eCSSType_ValueList, "type mismatch");
*static_cast<nsCSSValueList**>(aSpecifiedValue) =
aComputedValue.GetCSSValueListValue();
break;
default:
return PR_FALSE;
}
@ -630,6 +812,54 @@ nsStyleAnimation::ExtractComputedValue(nsCSSProperty aProperty,
}
return PR_FALSE;
}
case eStyleAnimType_Shadow: {
const nsCSSShadowArray *shadowArray =
*static_cast<const nsRefPtr<nsCSSShadowArray>*>(
StyleDataAtOffset(styleStruct, ssOffset));
if (!shadowArray) {
aComputedValue.SetCSSValueListValue(nsnull, eUnit_Shadow, PR_TRUE);
return PR_TRUE;
}
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList **resultTail = getter_Transfers(result);
for (PRUint32 i = 0, i_end = shadowArray->Length(); i < i_end; ++i) {
const nsCSSShadowItem *shadow = shadowArray->ShadowAt(i);
// X, Y, Radius, Spread, Color, Inset
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(6);
arr->Item(0).SetFloatValue(
nsPresContext::AppUnitsToFloatCSSPixels(shadow->mXOffset),
eCSSUnit_Pixel);
arr->Item(1).SetFloatValue(
nsPresContext::AppUnitsToFloatCSSPixels(shadow->mYOffset),
eCSSUnit_Pixel);
arr->Item(2).SetFloatValue(
nsPresContext::AppUnitsToFloatCSSPixels(shadow->mRadius),
eCSSUnit_Pixel);
// NOTE: This code sometimes stores mSpread: 0 even when
// the parser would be required to leave it null.
arr->Item(3).SetFloatValue(
nsPresContext::AppUnitsToFloatCSSPixels(shadow->mSpread),
eCSSUnit_Pixel);
if (shadow->mHasColor) {
arr->Item(4).SetColorValue(shadow->mColor);
}
if (shadow->mInset) {
arr->Item(5).SetIntValue(NS_STYLE_BOX_SHADOW_INSET,
eCSSUnit_Enumerated);
}
nsCSSValueList *resultItem = new nsCSSValueList;
if (!resultItem) {
return PR_FALSE;
}
resultItem->mValue.SetArrayValue(arr, eCSSUnit_Array);
*resultTail = resultItem;
resultTail = &resultItem->mNext;
}
aComputedValue.SetCSSValueListValue(result.forget(), eUnit_Shadow,
PR_TRUE);
return PR_TRUE;
}
case eStyleAnimType_None:
NS_NOTREACHED("shouldn't use on non-animatable properties");
}
@ -682,6 +912,9 @@ nsStyleAnimation::Value::operator=(const Value& aOther)
case eUnit_Color:
mValue.mColor = aOther.mValue.mColor;
break;
case eUnit_Shadow:
mValue.mCSSValueList = aOther.mValue.mCSSValueList
? aOther.mValue.mCSSValueList->Clone() : nsnull;
}
return *this;
@ -740,9 +973,27 @@ nsStyleAnimation::Value::SetColorValue(nscolor aColor)
mValue.mColor = aColor;
}
void
nsStyleAnimation::Value::SetCSSValueListValue(nsCSSValueList *aValueList,
Unit aUnit,
PRBool aTakeOwnership)
{
FreeValue();
NS_ASSERTION(IsCSSValueListUnit(aUnit), "bad unit");
mUnit = aUnit;
if (aTakeOwnership) {
mValue.mCSSValueList = aValueList;
} else {
mValue.mCSSValueList = aValueList ? aValueList->Clone() : nsnull;
}
}
void
nsStyleAnimation::Value::FreeValue()
{
if (IsCSSValueListUnit(mUnit)) {
delete mValue.mCSSValueList;
}
}
PRBool
@ -765,6 +1016,9 @@ nsStyleAnimation::Value::operator==(const Value& aOther) const
return mValue.mFloat == aOther.mValue.mFloat;
case eUnit_Color:
return mValue.mColor == aOther.mValue.mColor;
case eUnit_Shadow:
return nsCSSValueList::Equal(mValue.mCSSValueList,
aOther.mValue.mCSSValueList);
}
NS_NOTREACHED("incomplete case");

View File

@ -52,6 +52,7 @@ class nsCSSDeclaration;
class nsIContent;
class nsPresContext;
class nsStyleContext;
struct nsCSSValueList;
/**
* Utility class to handle animated style values
@ -173,7 +174,10 @@ public:
* for some types this means that the void* is pointing to memory
* owned by the nsStyleAnimation::Value. (For all complex types, the
* nsStyleAnimation::Value owns the necessary objects so that the
* caller does not need to do anything to free them.)
* caller does not need to do anything to free them. However, this
* means that callers using the void* variant must keep
* |aComputedValue| alive longer than the structure into which they've
* filled the value.)
*
* @param aProperty The property whose value we're uncomputing.
* @param aPresContext The presentation context for the document in
@ -215,7 +219,8 @@ public:
eUnit_Coord,
eUnit_Percent,
eUnit_Float,
eUnit_Color
eUnit_Color,
eUnit_Shadow // nsCSSValueList* (may be null)
};
class Value {
@ -225,6 +230,7 @@ public:
nscoord mCoord;
float mFloat;
nscolor mColor;
nsCSSValueList* mCSSValueList;
} mValue;
public:
Unit GetUnit() const {
@ -254,6 +260,10 @@ public:
NS_ASSERTION(mUnit == eUnit_Color, "unit mismatch");
return mValue.mColor;
}
nsCSSValueList* GetCSSValueListValue() const {
NS_ASSERTION(IsCSSValueListUnit(mUnit), "unit mismatch");
return mValue.mCSSValueList;
}
explicit Value(Unit aUnit = eUnit_Null) : mUnit(aUnit) {
NS_ASSERTION(aUnit == eUnit_Null || aUnit == eUnit_Normal ||
@ -279,6 +289,8 @@ public:
void SetPercentValue(float aPercent);
void SetFloatValue(float aFloat);
void SetColorValue(nscolor aColor);
void SetCSSValueListValue(nsCSSValueList *aValue, Unit aUnit,
PRBool aTakeOwnership);
Value& operator=(const Value& aOther);
@ -288,6 +300,10 @@ public:
private:
void FreeValue();
static PRBool IsCSSValueListUnit(Unit aUnit) {
return aUnit == eUnit_Shadow;
}
};
};

View File

@ -64,7 +64,11 @@ using mozilla::TimeDuration;
struct ElementPropertyTransition
{
nsCSSProperty mProperty;
nsStyleAnimation::Value mStartValue, mEndValue;
// We need to have mCurrentValue as a member of this structure because
// the result of the calls to |Interpolate| might hold data that this
// object's owning style rule needs to keep alive (after calling
// UncomputeValue on it in MapRuleInfoInto).
nsStyleAnimation::Value mStartValue, mEndValue, mCurrentValue;
TimeStamp mStartTime; // actual start plus transition delay
// data from the relevant nsTransition
@ -227,12 +231,11 @@ ElementTransitionsStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
double valuePortion =
pt.mTimingFunction.GetSplineValue(timePortion);
nsStyleAnimation::Value value;
#ifdef DEBUG
PRBool ok =
#endif
nsStyleAnimation::Interpolate(pt.mStartValue, pt.mEndValue,
valuePortion, value);
valuePortion, pt.mCurrentValue);
NS_ABORT_IF_FALSE(ok, "could not interpolate values");
void *prop =
@ -241,7 +244,7 @@ ElementTransitionsStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
ok =
#endif
nsStyleAnimation::UncomputeValue(pt.mProperty, aRuleData->mPresContext,
value, prop);
pt.mCurrentValue, prop);
NS_ABORT_IF_FALSE(ok, "could not store computed value");
}
}

View File

@ -47,6 +47,7 @@ function any_unit_to_num(str)
}
var supported_properties = {
"-moz-box-shadow": [ test_shadow_transition ],
"-moz-column-gap": [ test_length_transition ],
"-moz-column-rule-color": [ test_color_transition ],
"-moz-column-width": [ test_length_transition ],
@ -99,6 +100,7 @@ var supported_properties = {
"stroke-opacity" : [ test_float_zeroToOne_transition ],
"stroke-width": [ test_length_transition, test_percent_transition ],
"text-indent": [ test_length_transition, test_percent_transition ],
"text-shadow": [ test_shadow_transition ],
"top": [ test_length_transition, test_percent_transition ],
"vertical-align": [ test_length_transition, test_percent_transition ],
"width": [ test_length_transition, test_percent_transition ],
@ -275,6 +277,49 @@ function test_color_transition(prop) {
"color-valued property " + prop + ": interpolation of colors");
}
function test_shadow_transition(prop) {
var spreadStr = (prop == "-moz-box-shadow") ? " 0px" : "";
div.style.setProperty("-moz-transition-property", "none", "");
div.style.setProperty(prop, "none", "");
is(cs.getPropertyValue(prop), "none",
"shadow-valued property " + prop + ": computed value before transition");
div.style.setProperty("-moz-transition-property", prop, "");
div.style.setProperty(prop, "4px 8px 3px red", "");
is(cs.getPropertyValue(prop), "rgba(255, 0, 0, 0.5) 2px 4px 1.5px" + spreadStr,
"shadow-valued property " + prop + ": interpolation of shadows");
div.style.setProperty("-moz-transition-property", "none", "");
div.style.setProperty(prop, "green 4px 4px, 2px 2px blue", "");
is(cs.getPropertyValue(prop), "rgb(0, 128, 0) 4px 4px 0px" + spreadStr + ", rgb(0, 0, 255) 2px 2px 0px" + spreadStr,
"shadow-valued property " + prop + ": computed value before transition");
div.style.setProperty("-moz-transition-property", prop, "");
div.style.setProperty(prop, "8px 8px 8px red", "");
is(cs.getPropertyValue(prop), "rgb(128, 64, 0) 6px 6px 4px" + spreadStr + ", rgba(0, 0, 255, 0.5) 1px 1px 0px" + spreadStr,
"shadow-valued property " + prop + ": interpolation of shadows");
if (prop == "-moz-box-shadow") {
div.style.setProperty(prop, "8px 8px 8px red inset", "");
is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px inset",
"shadow-valued property " + prop + ": non-interpolable cases");
div.style.setProperty(prop, "8px 8px 8px 8px red inset", "");
is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 4px inset",
"shadow-valued property " + prop + ": interpolation of spread");
// Leave in same state whether in the |if| or not.
div.style.setProperty(prop, "8px 8px 8px red", "");
is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px",
"shadow-valued property " + prop + ": non-interpolable cases");
}
var defaultColor = cs.getPropertyValue("color") + " ";
div.style.setProperty(prop, "2px 2px 2px", "");
is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
"shadow-valued property " + prop + ": non-interpolable cases");
div.style.setProperty(prop, "4px 8px 6px", "");
is(cs.getPropertyValue(prop), defaultColor + "3px 5px 4px" + spreadStr,
"shadow-valued property " + prop + ": interpolation without color");
}
</script>
</pre>
</body>