mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 16:25:38 +00:00
Bug 1244590
- Part 7: Calculate paced spacing. r=birtles
Calculate the paced spacing for each Keyframes by the context element. Use the algorithm in the spec. MozReview-Commit-ID: HFWQwoKhKWt --HG-- extra : rebase_source : 40112eb1f722179cfc0a4ccbd188e22bc7af9b9b
This commit is contained in:
parent
95267f5a26
commit
34e916b2cb
@ -475,7 +475,7 @@ KeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
|
||||
// the specified spacing mode when we generate computed animation property
|
||||
// values from the keyframes since both operations require a style context
|
||||
// and need to be performed whenever the style context changes.
|
||||
KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::distribute);
|
||||
KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
|
||||
|
||||
if (mAnimation && mAnimation->IsRelevant()) {
|
||||
nsNodeUtils::AnimationChanged(mAnimation);
|
||||
@ -526,6 +526,12 @@ KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext)
|
||||
KeyframeUtils::GetComputedKeyframeValues(mKeyframes, mTarget->mElement,
|
||||
aStyleContext);
|
||||
|
||||
if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
|
||||
KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::paced,
|
||||
mEffectOptions.mPacedProperty,
|
||||
computedValues);
|
||||
}
|
||||
|
||||
properties =
|
||||
KeyframeUtils::GetAnimationPropertiesFromKeyframes(mKeyframes,
|
||||
computedValues,
|
||||
|
@ -30,6 +30,10 @@ namespace mozilla {
|
||||
//
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// This is used while calculating paced spacing. If the keyframe is not pacable,
|
||||
// we set its cumulative distance to kNotPaceable, so we can use this to check.
|
||||
const double kNotPaceable = -1.0;
|
||||
|
||||
// For the aAllowList parameter of AppendStringOrStringSequence and
|
||||
// GetPropertyValuesPairs.
|
||||
enum class ListAllowance { eDisallow, eAllow };
|
||||
@ -392,7 +396,19 @@ RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
|
||||
nsIDocument* aDocument);
|
||||
|
||||
static void
|
||||
DistributeRange(const Range<Keyframe>& aKeyframes);
|
||||
DistributeRange(const Range<Keyframe>& aSpacingRange,
|
||||
const Range<Keyframe>& aRangeToAdjust);
|
||||
|
||||
static void
|
||||
DistributeRange(const Range<Keyframe>& aSpacingRange);
|
||||
|
||||
static void
|
||||
PaceRange(const Range<Keyframe>& aKeyframes,
|
||||
const Range<double>& aCumulativeDistances);
|
||||
|
||||
static nsTArray<double>
|
||||
GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
|
||||
nsCSSProperty aProperty);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
//
|
||||
@ -460,12 +476,26 @@ KeyframeUtils::GetKeyframesFromObject(JSContext* aCx,
|
||||
|
||||
/* static */ void
|
||||
KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
|
||||
SpacingMode aSpacingMode)
|
||||
SpacingMode aSpacingMode,
|
||||
nsCSSProperty aProperty,
|
||||
nsTArray<ComputedKeyframeValues>& aComputedValues)
|
||||
{
|
||||
if (aKeyframes.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<double> cumulativeDistances;
|
||||
if (aSpacingMode == SpacingMode::paced) {
|
||||
MOZ_ASSERT(IsAnimatableProperty(aProperty),
|
||||
"Paced property should be animatable");
|
||||
|
||||
cumulativeDistances = GetCumulativeDistances(aComputedValues, aProperty);
|
||||
// Reset the computed offsets if using paced spacing.
|
||||
for (Keyframe& keyframe : aKeyframes) {
|
||||
keyframe.mComputedOffset = Keyframe::kComputedOffsetNotSet;
|
||||
}
|
||||
}
|
||||
|
||||
// If the first keyframe has an unspecified offset, fill it in with 0%.
|
||||
// If there is only a single keyframe, then it gets 100%.
|
||||
if (aKeyframes.Length() > 1) {
|
||||
@ -479,28 +509,87 @@ KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
|
||||
|
||||
// Fill in remaining missing offsets.
|
||||
const Keyframe* const last = aKeyframes.cend() - 1;
|
||||
RangedPtr<Keyframe> keyframeA(aKeyframes.begin(), aKeyframes.Length());
|
||||
const RangedPtr<Keyframe> begin(aKeyframes.begin(), aKeyframes.Length());
|
||||
RangedPtr<Keyframe> keyframeA = begin;
|
||||
while (keyframeA != last) {
|
||||
// Find keyframe A and keyframe B *between* which we will apply spacing.
|
||||
RangedPtr<Keyframe> keyframeB = keyframeA + 1;
|
||||
while (keyframeB.get()->mOffset.isNothing() && keyframeB != last) {
|
||||
while (keyframeB->mOffset.isNothing() && keyframeB != last) {
|
||||
++keyframeB;
|
||||
}
|
||||
keyframeB.get()->mComputedOffset = keyframeB.get()->mOffset.valueOr(1.0);
|
||||
keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
|
||||
|
||||
// Fill computed offsets in (keyframe A, keyframe B).
|
||||
if (aSpacingMode == SpacingMode::distribute) {
|
||||
// Bug 1276573: Use the new constructor accepting two RangedPtr<T>
|
||||
// arguments, so we can make the code simpler.
|
||||
DistributeRange(Range<Keyframe>(keyframeA.get(),
|
||||
keyframeB - keyframeA + 1));
|
||||
} else {
|
||||
// TODO
|
||||
MOZ_ASSERT(false, "not implement yet");
|
||||
}
|
||||
// a) Find Paced A (first paceable keyframe) and
|
||||
// Paced B (last paceable keyframe) in [keyframe A, keyframe B].
|
||||
RangedPtr<Keyframe> pacedA = keyframeA;
|
||||
while (pacedA < keyframeB &&
|
||||
cumulativeDistances[pacedA - begin] == kNotPaceable) {
|
||||
++pacedA;
|
||||
}
|
||||
RangedPtr<Keyframe> pacedB = keyframeB;
|
||||
while (pacedB > keyframeA &&
|
||||
cumulativeDistances[pacedB - begin] == kNotPaceable) {
|
||||
--pacedB;
|
||||
}
|
||||
// As spec says, if there is no paceable keyframe
|
||||
// in [keyframe A, keyframe B], we let Paced A and Paced B refer to
|
||||
// keyframe B.
|
||||
if (pacedA > pacedB) {
|
||||
pacedA = pacedB = keyframeB;
|
||||
}
|
||||
// b) Apply distributing offsets in (keyframe A, Paced A] and
|
||||
// [Paced B, keyframe B).
|
||||
DistributeRange(Range<Keyframe>(keyframeA.get(),
|
||||
keyframeB - keyframeA + 1),
|
||||
Range<Keyframe>((keyframeA + 1).get(),
|
||||
pacedA - keyframeA));
|
||||
DistributeRange(Range<Keyframe>(keyframeA.get(),
|
||||
keyframeB - keyframeA + 1),
|
||||
Range<Keyframe>(pacedB.get(),
|
||||
keyframeB - pacedB));
|
||||
// c) Apply paced offsets to each paceable keyframe in (Paced A, Paced B).
|
||||
// We pass the range [Paced A, Paced B] since PaceRange needs the end
|
||||
// points of the range in order to calculate the correct offset.
|
||||
PaceRange(Range<Keyframe>(pacedA.get(), pacedB - pacedA + 1),
|
||||
Range<double>(&cumulativeDistances[pacedA - begin],
|
||||
pacedB - pacedA + 1));
|
||||
// d) Fill in any computed offsets in (Paced A, Paced B) that are still
|
||||
// not set (e.g. because the keyframe was not paceable, or because the
|
||||
// cumulative distance between paceable properties was zero).
|
||||
for (RangedPtr<Keyframe> frame = pacedA + 1; frame < pacedB; ++frame) {
|
||||
if (frame->mComputedOffset != Keyframe::kComputedOffsetNotSet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RangedPtr<Keyframe> start = frame - 1;
|
||||
RangedPtr<Keyframe> end = frame + 1;
|
||||
while (end < pacedB &&
|
||||
end->mComputedOffset == Keyframe::kComputedOffsetNotSet) {
|
||||
++end;
|
||||
}
|
||||
DistributeRange(Range<Keyframe>(start.get(), end - start + 1));
|
||||
frame = end;
|
||||
}
|
||||
}
|
||||
keyframeA = keyframeB;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes)
|
||||
{
|
||||
nsTArray<ComputedKeyframeValues> emptyArray;
|
||||
ApplySpacing(aKeyframes, SpacingMode::distribute, eCSSProperty_UNKNOWN,
|
||||
emptyArray);
|
||||
}
|
||||
|
||||
/* static */ nsTArray<ComputedKeyframeValues>
|
||||
KeyframeUtils::GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
|
||||
dom::Element* aElement,
|
||||
@ -1358,22 +1447,203 @@ RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
|
||||
}
|
||||
|
||||
/**
|
||||
* Evenly distribute the computed offsets in (A, B). We should pass the
|
||||
* range keyframes in [A, B] and use A, B to calculate computed offsets in
|
||||
* (A, B).
|
||||
* Evenly distribute the computed offsets in (A, B).
|
||||
* We pass the range keyframes in [A, B] and use A, B to calculate distributing
|
||||
* computed offsets in (A, B). The second range, aRangeToAdjust, is passed, so
|
||||
* we can know which keyframe we want to apply to. aRangeToAdjust should be in
|
||||
* the range of aSpacingRange.
|
||||
*
|
||||
* @param aKeyframes The sequence of keyframes between whose endpoints we should
|
||||
* apply distribute spacing.
|
||||
* @param aSpacingRange The sequence of keyframes between whose endpoints we
|
||||
* should apply distribute spacing.
|
||||
* @param aRangeToAdjust The range of keyframes we want to apply to.
|
||||
*/
|
||||
static void
|
||||
DistributeRange(const Range<Keyframe>& aKeyframes)
|
||||
DistributeRange(const Range<Keyframe>& aSpacingRange,
|
||||
const Range<Keyframe>& aRangeToAdjust)
|
||||
{
|
||||
const size_t n = aKeyframes.length() - 1;
|
||||
const double startOffset = aKeyframes[0].mComputedOffset;
|
||||
const double diffOffset = aKeyframes[n].mComputedOffset - startOffset;
|
||||
for (size_t i = 1; i < n; ++i) {
|
||||
aKeyframes[i].mComputedOffset = startOffset + double(i) / n * diffOffset;
|
||||
MOZ_ASSERT(aRangeToAdjust.start() >= aSpacingRange.start() &&
|
||||
aRangeToAdjust.end() <= aSpacingRange.end(),
|
||||
"Out of range");
|
||||
const size_t n = aSpacingRange.length() - 1;
|
||||
const double startOffset = aSpacingRange[0].mComputedOffset;
|
||||
const double diffOffset = aSpacingRange[n].mComputedOffset - startOffset;
|
||||
for (auto iter = aRangeToAdjust.start();
|
||||
iter != aRangeToAdjust.end();
|
||||
++iter) {
|
||||
size_t index = iter - aSpacingRange.start();
|
||||
iter->mComputedOffset = startOffset + double(index) / n * diffOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of DistributeRange to apply distribute spacing to all keyframes in
|
||||
* between the endpoints of the given range.
|
||||
*
|
||||
* @param aSpacingRange The sequence of keyframes between whose endpoints we
|
||||
* should apply distribute spacing.
|
||||
*/
|
||||
static void
|
||||
DistributeRange(const Range<Keyframe>& aSpacingRange)
|
||||
{
|
||||
// We don't need to apply distribute spacing to keyframe A and keyframe B.
|
||||
DistributeRange(aSpacingRange,
|
||||
Range<Keyframe>((aSpacingRange.start() + 1).get(),
|
||||
aSpacingRange.end() - aSpacingRange.start()
|
||||
- 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply paced spacing to all paceable keyframes in between the endpoints of the
|
||||
* given range.
|
||||
*
|
||||
* @param aKeyframes The range of keyframes between whose endpoints we should
|
||||
* apply paced spacing. Both endpoints should be paceable, i.e. the
|
||||
* corresponding elements in |aCumulativeDist| should not be kNotPaceable.
|
||||
* Within this function, we refer to the start and end points of this range
|
||||
* as Paced A and Paced B respectively in keeping with the notation used in
|
||||
* the spec.
|
||||
* @param aCumulativeDistances The sequence of cumulative distances of the paced
|
||||
* property as returned by GetCumulativeDistances(). This acts as a
|
||||
* parallel range to |aKeyframes|.
|
||||
*/
|
||||
static void
|
||||
PaceRange(const Range<Keyframe>& aKeyframes,
|
||||
const Range<double>& aCumulativeDistances)
|
||||
{
|
||||
MOZ_ASSERT(aKeyframes.length() == aCumulativeDistances.length(),
|
||||
"Range length mismatch");
|
||||
|
||||
const size_t len = aKeyframes.length();
|
||||
// If there is nothing between the end points, there is nothing to space.
|
||||
if (len < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double distA = *(aCumulativeDistances.start());
|
||||
const double distB = *(aCumulativeDistances.end() - 1);
|
||||
MOZ_ASSERT(distA != kNotPaceable && distB != kNotPaceable,
|
||||
"Both Paced A and Paced B should be paceable");
|
||||
|
||||
// If the total distance is zero, we should fall back to distribute spacing.
|
||||
// The caller will fill-in any keyframes without a computed offset using
|
||||
// distribute spacing so we can just return here.
|
||||
if (distA == distB) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RangedPtr<Keyframe> pacedA = aKeyframes.start();
|
||||
const RangedPtr<Keyframe> pacedB = aKeyframes.end() - 1;
|
||||
MOZ_ASSERT(pacedA->mComputedOffset != Keyframe::kComputedOffsetNotSet &&
|
||||
pacedB->mComputedOffset != Keyframe::kComputedOffsetNotSet,
|
||||
"Both Paced A and Paced B should have valid computed offsets");
|
||||
|
||||
// Apply computed offset.
|
||||
const double offsetA = pacedA->mComputedOffset;
|
||||
const double diffOffset = pacedB->mComputedOffset - offsetA;
|
||||
const double initialDist = distA;
|
||||
const double totalDist = distB - initialDist;
|
||||
for (auto iter = pacedA + 1; iter != pacedB; ++iter) {
|
||||
size_t k = iter - aKeyframes.start();
|
||||
if (aCumulativeDistances[k] == kNotPaceable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double dist = aCumulativeDistances[k] - initialDist;
|
||||
iter->mComputedOffset = offsetA + diffOffset * dist / totalDist;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cumulative distances for the paced property.
|
||||
*
|
||||
* @param aValues The computed values returned by GetComputedKeyframeValues.
|
||||
* @param aPacedProperty The paced property.
|
||||
* @return The cumulative distances for the paced property. The length will be
|
||||
* the same as aValues.
|
||||
*/
|
||||
static nsTArray<double>
|
||||
GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
|
||||
nsCSSProperty aPacedProperty)
|
||||
{
|
||||
// a) If aPacedProperty is a shorthand property, get its components.
|
||||
// Otherwise, just add the longhand property into the set.
|
||||
size_t pacedPropertyCount = 0;
|
||||
nsCSSPropertySet pacedPropertySet;
|
||||
bool isShorthand = nsCSSProps::IsShorthand(aPacedProperty);
|
||||
if (isShorthand) {
|
||||
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPacedProperty,
|
||||
CSSEnabledState::eForAllContent) {
|
||||
pacedPropertySet.AddProperty(*p);
|
||||
++pacedPropertyCount;
|
||||
}
|
||||
} else {
|
||||
pacedPropertySet.AddProperty(aPacedProperty);
|
||||
pacedPropertyCount = 1;
|
||||
}
|
||||
|
||||
// b) Search each component (shorthand) or the longhand property, and
|
||||
// calculate the cumulative distances of paceable keyframe pairs.
|
||||
const size_t len = aValues.Length();
|
||||
nsTArray<double> cumulativeDistances(len);
|
||||
// cumulativeDistances is a parallel array to |aValues|, so set its length to
|
||||
// the length of |aValues|.
|
||||
cumulativeDistances.SetLength(len);
|
||||
ComputedKeyframeValues prevPacedValues;
|
||||
size_t preIdx = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
// Find computed values of the paced property.
|
||||
ComputedKeyframeValues pacedValues;
|
||||
for (const PropertyStyleAnimationValuePair& pair : aValues[i]) {
|
||||
if (pacedPropertySet.HasProperty(pair.mProperty)) {
|
||||
pacedValues.AppendElement(pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Check we have values for all the paceable longhand components.
|
||||
if (pacedValues.Length() != pacedPropertyCount) {
|
||||
// This keyframe is not paceable, assign kNotPaceable and skip it.
|
||||
cumulativeDistances[i] = kNotPaceable;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevPacedValues.IsEmpty()) {
|
||||
// This is the first paceable keyframe so its cumulative distance is 0.0.
|
||||
cumulativeDistances[i] = 0.0;
|
||||
} else {
|
||||
double dist = 0.0;
|
||||
if (isShorthand) {
|
||||
// Apply the distance by the square root of the sum of squares of
|
||||
// longhand component distances.
|
||||
for (size_t propIdx = 0; propIdx < pacedPropertyCount; ++propIdx) {
|
||||
nsCSSProperty prop = prevPacedValues[propIdx].mProperty;
|
||||
MOZ_ASSERT(pacedValues[propIdx].mProperty == prop,
|
||||
"Property mismatch");
|
||||
|
||||
double componentDistance = 0.0;
|
||||
if (StyleAnimationValue::ComputeDistance(
|
||||
prop,
|
||||
prevPacedValues[propIdx].mValue,
|
||||
pacedValues[propIdx].mValue,
|
||||
componentDistance)) {
|
||||
dist += componentDistance * componentDistance;
|
||||
}
|
||||
}
|
||||
dist = sqrt(dist);
|
||||
} else {
|
||||
// If the property is longhand, we just use the 1st value.
|
||||
// If ComputeDistance() fails, |dist| will remain zero so there will be
|
||||
// no distance between the previous paced value and this value.
|
||||
StyleAnimationValue::ComputeDistance(aPacedProperty,
|
||||
prevPacedValues[0].mValue,
|
||||
pacedValues[0].mValue,
|
||||
dist);
|
||||
}
|
||||
cumulativeDistances[i] = cumulativeDistances[preIdx] + dist;
|
||||
}
|
||||
prevPacedValues.SwapElements(pacedValues);
|
||||
preIdx = i;
|
||||
}
|
||||
return cumulativeDistances;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -86,9 +86,25 @@ public:
|
||||
*
|
||||
* @param aKeyframes The set of keyframes to adjust.
|
||||
* @param aSpacingMode The spacing mode to apply.
|
||||
* @param aProperty The paced property. Only used when |aSpacingMode| is
|
||||
* SpacingMode::paced. In all other cases it is ignored and hence may be
|
||||
* any value, e.g. eCSSProperty_UNKNOWN.
|
||||
* @param aComputedValues The set of computed keyframe values as returned by
|
||||
* GetComputedKeyframeValues. Only used when |aSpacingMode| is
|
||||
* SpacingMode::paced. In all other cases this parameter is unused and may
|
||||
* be any value including an empty array.
|
||||
*/
|
||||
static void ApplySpacing(nsTArray<Keyframe>& aKeyframes,
|
||||
SpacingMode aSpacingMode);
|
||||
SpacingMode aSpacingMode,
|
||||
nsCSSProperty aProperty,
|
||||
nsTArray<ComputedKeyframeValues>& aComputedValues);
|
||||
|
||||
/**
|
||||
* Wrapper for ApplySpacing to simplify using distribute spacing.
|
||||
*
|
||||
* @param aKeyframes The set of keyframes to adjust.
|
||||
*/
|
||||
static void ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes);
|
||||
|
||||
/**
|
||||
* Converts an array of Keyframe objects into an array of AnimationProperty
|
||||
|
Loading…
Reference in New Issue
Block a user