| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 18 matching lines...) Expand all Loading... |
| 29 */ | 29 */ |
| 30 | 30 |
| 31 #include "config.h" | 31 #include "config.h" |
| 32 #include "core/animation/KeyframeEffectModel.h" | 32 #include "core/animation/KeyframeEffectModel.h" |
| 33 | 33 |
| 34 #include "core/animation/TimedItem.h" | 34 #include "core/animation/TimedItem.h" |
| 35 #include "wtf/text/StringHash.h" | 35 #include "wtf/text/StringHash.h" |
| 36 | 36 |
| 37 namespace WebCore { | 37 namespace WebCore { |
| 38 | 38 |
| 39 bool Keyframe::compareOffsets(const RefPtrWillBeRawPtr<Keyframe>& a, const RefPt
rWillBeRawPtr<Keyframe>& b) | 39 Keyframe::Keyframe() |
| 40 : m_offset(nullValue()) |
| 41 , m_composite(AnimationEffect::CompositeReplace) |
| 42 , m_easing(LinearTimingFunction::preset()) |
| 43 { } |
| 44 |
| 45 Keyframe::Keyframe(const Keyframe& copyFrom) |
| 46 : m_offset(copyFrom.m_offset) |
| 47 , m_composite(copyFrom.m_composite) |
| 48 , m_easing(copyFrom.m_easing) |
| 40 { | 49 { |
| 41 return a->offset() < b->offset(); | 50 ASSERT(m_easing); |
| 51 for (PropertyValueMap::const_iterator iter = copyFrom.m_propertyValues.begin
(); iter != copyFrom.m_propertyValues.end(); ++iter) |
| 52 setPropertyValue(iter->key, iter->value.get()); |
| 42 } | 53 } |
| 43 | 54 |
| 44 PropertySet KeyframeEffectModelBase::properties() const | 55 void Keyframe::setEasing(PassRefPtr<TimingFunction> easing) |
| 56 { |
| 57 ASSERT(easing); |
| 58 m_easing = easing; |
| 59 } |
| 60 |
| 61 void Keyframe::setPropertyValue(CSSPropertyID property, const AnimatableValue* v
alue) |
| 62 { |
| 63 m_propertyValues.add(property, const_cast<AnimatableValue*>(value)); |
| 64 } |
| 65 |
| 66 void Keyframe::clearPropertyValue(CSSPropertyID property) |
| 67 { |
| 68 m_propertyValues.remove(property); |
| 69 } |
| 70 |
| 71 const AnimatableValue* Keyframe::propertyValue(CSSPropertyID property) const |
| 72 { |
| 73 ASSERT(m_propertyValues.contains(property)); |
| 74 return m_propertyValues.get(property); |
| 75 } |
| 76 |
| 77 PropertySet Keyframe::properties() const |
| 78 { |
| 79 // This is not used in time-critical code, so we probably don't need to |
| 80 // worry about caching this result. |
| 81 PropertySet properties; |
| 82 for (PropertyValueMap::const_iterator iter = m_propertyValues.begin(); iter
!= m_propertyValues.end(); ++iter) |
| 83 properties.add(*iter.keys()); |
| 84 return properties; |
| 85 } |
| 86 |
| 87 PassRefPtrWillBeRawPtr<Keyframe> Keyframe::cloneWithOffset(double offset) const |
| 88 { |
| 89 RefPtrWillBeRawPtr<Keyframe> theClone = clone(); |
| 90 theClone->setOffset(offset); |
| 91 return theClone.release(); |
| 92 } |
| 93 |
| 94 void Keyframe::trace(Visitor* visitor) |
| 95 { |
| 96 visitor->trace(m_propertyValues); |
| 97 } |
| 98 |
| 99 KeyframeEffectModel::KeyframeEffectModel(const KeyframeVector& keyframes) |
| 100 : m_keyframes(keyframes) |
| 101 { |
| 102 } |
| 103 |
| 104 PropertySet KeyframeEffectModel::properties() const |
| 45 { | 105 { |
| 46 PropertySet result; | 106 PropertySet result; |
| 47 if (!m_keyframes.size()) { | 107 if (!m_keyframes.size()) { |
| 48 return result; | 108 return result; |
| 49 } | 109 } |
| 50 result = m_keyframes[0]->properties(); | 110 result = m_keyframes[0]->properties(); |
| 51 for (size_t i = 1; i < m_keyframes.size(); i++) { | 111 for (size_t i = 1; i < m_keyframes.size(); i++) { |
| 52 PropertySet extras = m_keyframes[i]->properties(); | 112 PropertySet extras = m_keyframes[i]->properties(); |
| 53 for (PropertySet::const_iterator it = extras.begin(); it != extras.end()
; ++it) { | 113 for (PropertySet::const_iterator it = extras.begin(); it != extras.end()
; ++it) { |
| 54 result.add(*it); | 114 result.add(*it); |
| 55 } | 115 } |
| 56 } | 116 } |
| 57 return result; | 117 return result; |
| 58 } | 118 } |
| 59 | 119 |
| 60 PassOwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation> > > Ke
yframeEffectModelBase::sample(int iteration, double fraction, double iterationDu
ration) const | 120 PassOwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation> > > Ke
yframeEffectModel::sample(int iteration, double fraction, double iterationDurati
on) const |
| 61 { | 121 { |
| 62 ASSERT(iteration >= 0); | 122 ASSERT(iteration >= 0); |
| 63 ASSERT(!isNull(fraction)); | 123 ASSERT(!isNull(fraction)); |
| 64 ensureKeyframeGroups(); | 124 ensureKeyframeGroups(); |
| 65 ensureInterpolationEffect(); | 125 ensureInterpolationEffect(); |
| 66 | 126 |
| 67 return m_interpolationEffect->getActiveInterpolations(fraction, iterationDur
ation); | 127 return m_interpolationEffect->getActiveInterpolations(fraction, iterationDur
ation); |
| 68 } | 128 } |
| 69 | 129 |
| 70 KeyframeEffectModelBase::KeyframeVector KeyframeEffectModelBase::normalizedKeyfr
ames(const KeyframeVector& keyframes) | 130 KeyframeEffectModel::KeyframeVector KeyframeEffectModel::normalizedKeyframes(con
st KeyframeVector& keyframes) |
| 71 { | 131 { |
| 72 // keyframes [beginIndex, endIndex) will remain after removing all keyframes
if they are not | 132 // keyframes [beginIndex, endIndex) will remain after removing all keyframes
if they are not |
| 73 // loosely sorted by offset, and after removing keyframes with positional of
fset outide [0, 1]. | 133 // loosely sorted by offset, and after removing keyframes with positional of
fset outide [0, 1]. |
| 74 size_t beginIndex = 0; | 134 size_t beginIndex = 0; |
| 75 size_t endIndex = keyframes.size(); | 135 size_t endIndex = keyframes.size(); |
| 76 | 136 |
| 77 // Becomes the most recent keyframe with an explicit offset. | 137 // Becomes the most recent keyframe with an explicit offset. |
| 78 size_t lastIndex = endIndex; | 138 size_t lastIndex = endIndex; |
| 79 double lastOffset = std::numeric_limits<double>::quiet_NaN(); | 139 double lastOffset = std::numeric_limits<double>::quiet_NaN(); |
| 80 | 140 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 124 result[lastIndex + j]->setOffset(lastOffset + (offset -
lastOffset) * j / (i - lastIndex)); | 184 result[lastIndex + j]->setOffset(lastOffset + (offset -
lastOffset) * j / (i - lastIndex)); |
| 125 } | 185 } |
| 126 lastIndex = i; | 186 lastIndex = i; |
| 127 lastOffset = offset; | 187 lastOffset = offset; |
| 128 } | 188 } |
| 129 } | 189 } |
| 130 } | 190 } |
| 131 return result; | 191 return result; |
| 132 } | 192 } |
| 133 | 193 |
| 134 | 194 void KeyframeEffectModel::ensureKeyframeGroups() const |
| 135 void KeyframeEffectModelBase::ensureKeyframeGroups() const | |
| 136 { | 195 { |
| 137 if (m_keyframeGroups) | 196 if (m_keyframeGroups) |
| 138 return; | 197 return; |
| 139 | 198 |
| 140 m_keyframeGroups = adoptPtrWillBeNoop(new KeyframeGroupMap); | 199 m_keyframeGroups = adoptPtrWillBeNoop(new KeyframeGroupMap); |
| 141 const KeyframeVector keyframes = normalizedKeyframes(getFrames()); | 200 const KeyframeVector keyframes = normalizedKeyframes(getFrames()); |
| 142 for (KeyframeVector::const_iterator keyframeIter = keyframes.begin(); keyfra
meIter != keyframes.end(); ++keyframeIter) { | 201 for (KeyframeVector::const_iterator keyframeIter = keyframes.begin(); keyfra
meIter != keyframes.end(); ++keyframeIter) { |
| 143 const Keyframe* keyframe = keyframeIter->get(); | 202 const Keyframe* keyframe = keyframeIter->get(); |
| 144 PropertySet keyframeProperties = keyframe->properties(); | 203 PropertySet keyframeProperties = keyframe->properties(); |
| 145 for (PropertySet::const_iterator propertyIter = keyframeProperties.begin
(); propertyIter != keyframeProperties.end(); ++propertyIter) { | 204 for (PropertySet::const_iterator propertyIter = keyframeProperties.begin
(); propertyIter != keyframeProperties.end(); ++propertyIter) { |
| 146 CSSPropertyID property = *propertyIter; | 205 CSSPropertyID property = *propertyIter; |
| 147 KeyframeGroupMap::iterator groupIter = m_keyframeGroups->find(proper
ty); | 206 KeyframeGroupMap::iterator groupIter = m_keyframeGroups->find(proper
ty); |
| 148 PropertySpecificKeyframeGroup* group; | 207 PropertySpecificKeyframeGroup* group; |
| 149 if (groupIter == m_keyframeGroups->end()) | 208 if (groupIter == m_keyframeGroups->end()) |
| 150 group = m_keyframeGroups->add(property, adoptPtrWillBeNoop(new P
ropertySpecificKeyframeGroup)).storedValue->value.get(); | 209 group = m_keyframeGroups->add(property, adoptPtrWillBeNoop(new P
ropertySpecificKeyframeGroup)).storedValue->value.get(); |
| 151 else | 210 else |
| 152 group = groupIter->value.get(); | 211 group = groupIter->value.get(); |
| 153 | 212 |
| 154 ASSERT(keyframe->composite() == AnimationEffect::CompositeReplace); | 213 ASSERT(keyframe->composite() == AnimationEffect::CompositeReplace); |
| 155 group->appendKeyframe(keyframe->createPropertySpecificKeyframe(prope
rty)); | 214 group->appendKeyframe(adoptPtrWillBeNoop( |
| 215 new PropertySpecificKeyframe(keyframe->offset(), keyframe->easin
g(), keyframe->propertyValue(property), keyframe->composite()))); |
| 156 } | 216 } |
| 157 } | 217 } |
| 158 | 218 |
| 159 // Add synthetic keyframes. | 219 // Add synthetic keyframes. |
| 160 for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_
keyframeGroups->end(); ++iter) { | 220 for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_
keyframeGroups->end(); ++iter) { |
| 161 iter->value->addSyntheticKeyframeIfRequired(this); | 221 iter->value->addSyntheticKeyframeIfRequired(); |
| 162 iter->value->removeRedundantKeyframes(); | 222 iter->value->removeRedundantKeyframes(); |
| 163 } | 223 } |
| 164 } | 224 } |
| 165 | 225 |
| 166 void KeyframeEffectModelBase::ensureInterpolationEffect() const | 226 void KeyframeEffectModel::ensureInterpolationEffect() const |
| 167 { | 227 { |
| 168 if (m_interpolationEffect) | 228 if (m_interpolationEffect) |
| 169 return; | 229 return; |
| 170 m_interpolationEffect = InterpolationEffect::create(); | 230 m_interpolationEffect = InterpolationEffect::create(); |
| 171 | 231 |
| 172 for (KeyframeGroupMap::const_iterator iter = m_keyframeGroups->begin(); iter
!= m_keyframeGroups->end(); ++iter) { | 232 for (KeyframeGroupMap::const_iterator iter = m_keyframeGroups->begin(); iter
!= m_keyframeGroups->end(); ++iter) { |
| 173 const PropertySpecificKeyframeVector& keyframes = iter->value->keyframes
(); | 233 const PropertySpecificKeyframeVector& keyframes = iter->value->keyframes
(); |
| 174 ASSERT(keyframes[0]->composite() == AnimationEffect::CompositeReplace); | 234 ASSERT(keyframes[0]->composite() == AnimationEffect::CompositeReplace); |
| 235 const AnimatableValue* start; |
| 236 const AnimatableValue* end = keyframes[0]->value(); |
| 175 for (size_t i = 0; i < keyframes.size() - 1; i++) { | 237 for (size_t i = 0; i < keyframes.size() - 1; i++) { |
| 176 ASSERT(keyframes[i + 1]->composite() == AnimationEffect::CompositeRe
place); | 238 ASSERT(keyframes[i + 1]->composite() == AnimationEffect::CompositeRe
place); |
| 239 start = end; |
| 240 end = keyframes[i + 1]->value(); |
| 177 double applyFrom = i ? keyframes[i]->offset() : (-std::numeric_limit
s<double>::infinity()); | 241 double applyFrom = i ? keyframes[i]->offset() : (-std::numeric_limit
s<double>::infinity()); |
| 178 double applyTo = i == keyframes.size() - 2 ? std::numeric_limits<dou
ble>::infinity() : keyframes[i + 1]->offset(); | 242 double applyTo = i == keyframes.size() - 2 ? std::numeric_limits<dou
ble>::infinity() : keyframes[i + 1]->offset(); |
| 179 if (applyTo == 1) | 243 if (applyTo == 1) |
| 180 applyTo = std::numeric_limits<double>::infinity(); | 244 applyTo = std::numeric_limits<double>::infinity(); |
| 181 | 245 m_interpolationEffect->addInterpolation( |
| 182 m_interpolationEffect->addInterpolation(keyframes[i]->createInterpol
ation(iter->key, keyframes[i + 1].get()), | 246 LegacyStyleInterpolation::create( |
| 247 AnimatableValue::takeConstRef(start), |
| 248 AnimatableValue::takeConstRef(end), iter->key), |
| 183 keyframes[i]->easing(), keyframes[i]->offset(), keyframes[i + 1]
->offset(), applyFrom, applyTo); | 249 keyframes[i]->easing(), keyframes[i]->offset(), keyframes[i + 1]
->offset(), applyFrom, applyTo); |
| 184 } | 250 } |
| 185 } | 251 } |
| 186 } | 252 } |
| 187 | 253 |
| 188 bool KeyframeEffectModelBase::isReplaceOnly() | 254 bool KeyframeEffectModel::isReplaceOnly() |
| 189 { | 255 { |
| 190 ensureKeyframeGroups(); | 256 ensureKeyframeGroups(); |
| 191 for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_
keyframeGroups->end(); ++iter) { | 257 for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_
keyframeGroups->end(); ++iter) { |
| 192 const PropertySpecificKeyframeVector& keyframeVector = iter->value->keyf
rames(); | 258 const PropertySpecificKeyframeVector& keyframeVector = iter->value->keyf
rames(); |
| 193 for (size_t i = 0; i < keyframeVector.size(); ++i) { | 259 for (size_t i = 0; i < keyframeVector.size(); ++i) { |
| 194 if (keyframeVector[i]->composite() != AnimationEffect::CompositeRepl
ace) | 260 if (keyframeVector[i]->composite() != AnimationEffect::CompositeRepl
ace) |
| 195 return false; | 261 return false; |
| 196 } | 262 } |
| 197 } | 263 } |
| 198 return true; | 264 return true; |
| 199 } | 265 } |
| 200 | 266 |
| 201 void KeyframeEffectModelBase::trace(Visitor* visitor) | 267 void KeyframeEffectModel::trace(Visitor* visitor) |
| 202 { | 268 { |
| 203 visitor->trace(m_keyframes); | 269 visitor->trace(m_keyframes); |
| 204 visitor->trace(m_interpolationEffect); | 270 visitor->trace(m_interpolationEffect); |
| 205 #if ENABLE_OILPAN | 271 #if ENABLE_OILPAN |
| 206 visitor->trace(m_keyframeGroups); | 272 visitor->trace(m_keyframeGroups); |
| 207 #endif | 273 #endif |
| 208 } | 274 } |
| 209 | 275 |
| 210 Keyframe::PropertySpecificKeyframe::PropertySpecificKeyframe(double offset, Pass
RefPtr<TimingFunction> easing, AnimationEffect::CompositeOperation composite) | 276 KeyframeEffectModel::PropertySpecificKeyframe::PropertySpecificKeyframe(double o
ffset, PassRefPtr<TimingFunction> easing, const AnimatableValue* value, Composit
eOperation composite) |
| 211 : m_offset(offset) | 277 : m_offset(offset) |
| 212 , m_easing(easing) | 278 , m_easing(easing) |
| 213 , m_composite(composite) | 279 , m_composite(composite) |
| 214 { | 280 { |
| 281 m_value = AnimatableValue::takeConstRef(value); |
| 215 } | 282 } |
| 216 | 283 |
| 217 void KeyframeEffectModelBase::PropertySpecificKeyframeGroup::appendKeyframe(Pass
OwnPtr<PropertySpecificKeyframe> keyframe) | 284 KeyframeEffectModel::PropertySpecificKeyframe::PropertySpecificKeyframe(double o
ffset, PassRefPtr<TimingFunction> easing, PassRefPtrWillBeRawPtr<AnimatableValue
> value, CompositeOperation composite) |
| 285 : m_offset(offset) |
| 286 , m_easing(easing) |
| 287 , m_value(value) |
| 288 , m_composite(composite) |
| 289 { |
| 290 ASSERT(!isNull(m_offset)); |
| 291 } |
| 292 |
| 293 PassOwnPtrWillBeRawPtr<KeyframeEffectModel::PropertySpecificKeyframe> KeyframeEf
fectModel::PropertySpecificKeyframe::cloneWithOffset(double offset) const |
| 294 { |
| 295 return adoptPtrWillBeNoop(new PropertySpecificKeyframe(offset, m_easing, m_v
alue.get(), m_composite)); |
| 296 } |
| 297 |
| 298 void KeyframeEffectModel::PropertySpecificKeyframe::trace(Visitor* visitor) |
| 299 { |
| 300 visitor->trace(m_value); |
| 301 } |
| 302 |
| 303 void KeyframeEffectModel::PropertySpecificKeyframeGroup::appendKeyframe(PassOwnP
trWillBeRawPtr<PropertySpecificKeyframe> keyframe) |
| 218 { | 304 { |
| 219 ASSERT(m_keyframes.isEmpty() || m_keyframes.last()->offset() <= keyframe->of
fset()); | 305 ASSERT(m_keyframes.isEmpty() || m_keyframes.last()->offset() <= keyframe->of
fset()); |
| 220 m_keyframes.append(keyframe); | 306 m_keyframes.append(keyframe); |
| 221 } | 307 } |
| 222 | 308 |
| 223 void KeyframeEffectModelBase::PropertySpecificKeyframeGroup::removeRedundantKeyf
rames() | 309 void KeyframeEffectModel::PropertySpecificKeyframeGroup::removeRedundantKeyframe
s() |
| 224 { | 310 { |
| 225 // As an optimization, removes keyframes in the following categories, as | 311 // As an optimization, removes keyframes in the following categories, as |
| 226 // they will never be used by sample(). | 312 // they will never be used by sample(). |
| 227 // - End keyframes with the same offset as their neighbor | 313 // - End keyframes with the same offset as their neighbor |
| 228 // - Interior keyframes with the same offset as both their neighbors | 314 // - Interior keyframes with the same offset as both their neighbors |
| 229 // Note that synthetic keyframes must be added before this method is | 315 // Note that synthetic keyframes must be added before this method is |
| 230 // called. | 316 // called. |
| 231 ASSERT(m_keyframes.size() >= 2); | 317 ASSERT(m_keyframes.size() >= 2); |
| 232 for (int i = m_keyframes.size() - 1; i >= 0; --i) { | 318 for (int i = m_keyframes.size() - 1; i >= 0; --i) { |
| 233 double offset = m_keyframes[i]->offset(); | 319 double offset = m_keyframes[i]->offset(); |
| 234 bool hasSameOffsetAsPreviousNeighbor = !i || m_keyframes[i - 1]->offset(
) == offset; | 320 bool hasSameOffsetAsPreviousNeighbor = !i || m_keyframes[i - 1]->offset(
) == offset; |
| 235 bool hasSameOffsetAsNextNeighbor = i == static_cast<int>(m_keyframes.siz
e() - 1) || m_keyframes[i + 1]->offset() == offset; | 321 bool hasSameOffsetAsNextNeighbor = i == static_cast<int>(m_keyframes.siz
e() - 1) || m_keyframes[i + 1]->offset() == offset; |
| 236 if (hasSameOffsetAsPreviousNeighbor && hasSameOffsetAsNextNeighbor) | 322 if (hasSameOffsetAsPreviousNeighbor && hasSameOffsetAsNextNeighbor) |
| 237 m_keyframes.remove(i); | 323 m_keyframes.remove(i); |
| 238 } | 324 } |
| 239 ASSERT(m_keyframes.size() >= 2); | 325 ASSERT(m_keyframes.size() >= 2); |
| 240 } | 326 } |
| 241 | 327 |
| 242 void KeyframeEffectModelBase::PropertySpecificKeyframeGroup::addSyntheticKeyfram
eIfRequired(const KeyframeEffectModelBase* context) | 328 void KeyframeEffectModel::PropertySpecificKeyframeGroup::addSyntheticKeyframeIfR
equired() |
| 243 { | 329 { |
| 244 ASSERT(!m_keyframes.isEmpty()); | 330 ASSERT(!m_keyframes.isEmpty()); |
| 245 if (m_keyframes.first()->offset() != 0.0) | 331 if (m_keyframes.first()->offset() != 0.0) |
| 246 m_keyframes.insert(0, m_keyframes.first()->neutralKeyframe(0, nullptr)); | 332 m_keyframes.insert(0, adoptPtrWillBeNoop(new PropertySpecificKeyframe(0,
nullptr, AnimatableValue::neutralValue(), CompositeAdd))); |
| 247 if (m_keyframes.last()->offset() != 1.0) | 333 if (m_keyframes.last()->offset() != 1.0) |
| 248 appendKeyframe(m_keyframes.last()->neutralKeyframe(1, nullptr)); | 334 appendKeyframe(adoptPtrWillBeNoop(new PropertySpecificKeyframe(1, nullpt
r, AnimatableValue::neutralValue(), CompositeAdd))); |
| 249 } | 335 } |
| 250 | 336 |
| 251 void KeyframeEffectModelBase::PropertySpecificKeyframeGroup::trace(Visitor* visi
tor) | 337 void KeyframeEffectModel::PropertySpecificKeyframeGroup::trace(Visitor* visitor) |
| 252 { | 338 { |
| 253 #if ENABLE(OILPAN) | 339 #if ENABLE_OILPAN |
| 254 visitor->trace(m_keyframes); | 340 visitor->trace(m_keyframes); |
| 255 #endif | 341 #endif |
| 256 } | 342 } |
| 257 | 343 |
| 258 template <> | |
| 259 bool KeyframeEffectModel<AnimatableValueKeyframe>::isAnimatableValueKeyframeEffe
ctModel() const { return true; } | |
| 260 | |
| 261 template <> | |
| 262 bool KeyframeEffectModel<StringKeyframe>::isStringKeyframeEffectModel() const {
return true; } | |
| 263 | |
| 264 } // namespace | 344 } // namespace |
| OLD | NEW |