blob: 6edeae6e052ed04df0ea029f7d39db081d19480a [file] [log] [blame]
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* See the License for the specific language governing permissions and
* limitations under the License.
#include "cobalt/cssom/css_transition_set.h"
#include <algorithm>
#include <cmath>
#include "base/lazy_instance.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/cssom/css_computed_style_data.h"
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/cssom/property_key_list_value.h"
#include "cobalt/cssom/property_value.h"
#include "cobalt/cssom/time_list_value.h"
#include "cobalt/cssom/timing_function_list_value.h"
namespace cobalt {
namespace cssom {
namespace {
// Returns the index at which the property should be animated. If the property
// should not be animated, -1 is returned.
int GetPropertyTransitionIndex(
PropertyKey property,
const scoped_refptr<const PropertyValue>& transition_property_value) {
if (transition_property_value == KeywordValue::GetNone()) {
return -1;
} else {
const PropertyKeyListValue* property_name_list =
base::polymorphic_downcast<const PropertyKeyListValue*>(
// The LAST reference to the given property in the transition-property list
// is the one we use to determine the property's index, so start searching
// for it from the back to the front.
for (int i = static_cast<int>(property_name_list->value().size()) - 1;
i >= 0; --i) {
PropertyKey current_list_property =
if (current_list_property == kAllProperty ||
current_list_property == property) {
return i;
return -1;
} // namespace
TransitionSet::TransitionSet(EventHandler* event_handler)
: transitions_(event_handler) {}
void TransitionSet::UpdateTransitions(
const base::TimeDelta& current_time,
const CSSComputedStyleData& source_computed_style,
const CSSComputedStyleData& destination_computed_style) {
const TimeListValue* transition_duration =
base::polymorphic_downcast<const TimeListValue*>(
if (transition_duration->value().size() == 1 &&
transition_duration->value()[0] == base::TimeDelta() &&
transitions_.IsEmpty()) {
// No need to process transitions if all their durations are set to
// 0, the initial value, so this will happen often.
// For each animatable property, check to see if there are any transitions
// assigned to it. If so, check to see if there are any existing transitions
// that must be updated, otherwise introduce new transitions.
const PropertyKeyVector& animatable_properties = GetAnimatableProperties();
for (PropertyKeyVector::const_iterator iter = animatable_properties.begin();
iter != animatable_properties.end(); ++iter) {
*iter, current_time, source_computed_style.GetPropertyValue(*iter),
namespace {
void SetReversingValues(
const base::TimeDelta& current_time, const Transition& old_transition,
const scoped_refptr<PropertyValue>& new_start_value,
const scoped_refptr<PropertyValue>& new_end_value,
scoped_refptr<PropertyValue>* new_reversing_adjusted_start_value,
float* new_reversing_shortening_factor) {
// This value is calculated as explained here:
// These calculations make a pleasant experience when reversing a transition
// half-way through by making the reverse transition occur over half as much
// time.
if (old_transition.reversing_adjusted_start_value()->Equals(*new_end_value)) {
*new_reversing_shortening_factor =
std::min<float>(1.0f, std::max<float>(0.0f,
std::abs(old_transition.Progress(current_time) *
old_transition.reversing_shortening_factor() +
1 - old_transition.reversing_shortening_factor())));
*new_reversing_adjusted_start_value = old_transition.end_value();
} else {
*new_reversing_adjusted_start_value = new_start_value;
*new_reversing_shortening_factor = 1.0f;
base::TimeDelta ScaleTimeDelta(const base::TimeDelta& time_delta, float scale) {
return base::TimeDelta::FromMicroseconds(static_cast<int64>(
time_delta.InSecondsF() * scale * base::Time::kMicrosecondsPerSecond));
// Given that a new transition has started on a CSS value that had an active old
// transition occurring on it, this function creates a new transition based on
// the old transition's values (so that we can smoothly transition out of the
// middle of an old transition).
Transition CreateTransitionOverOldTransition(
PropertyKey property, const base::TimeDelta& current_time,
const Transition& old_transition, const base::TimeDelta& duration,
const base::TimeDelta& delay,
const scoped_refptr<TimingFunction>& timing_function,
const scoped_refptr<PropertyValue>& end_value) {
// Since we're updating an old transtion, we'll need to know the animated
// CSS style value from the old transition at this point in time.
scoped_refptr<PropertyValue> current_value_within_old_transition =
// If the new transition is a reversal fo the old transition, we need to
// setup reversing_adjusted_start_value and reversing_shortening_factor so
// that they can be used to reduce the new transition's duration.
scoped_refptr<PropertyValue> new_reversing_adjusted_start_value;
float new_reversing_shortening_factor;
SetReversingValues(current_time, old_transition,
current_value_within_old_transition, end_value,
base::TimeDelta with_reversing_delay =
delay < base::TimeDelta()
? ScaleTimeDelta(delay, new_reversing_shortening_factor)
: delay;
return Transition(
property, current_value_within_old_transition, end_value, current_time,
ScaleTimeDelta(duration, new_reversing_shortening_factor),
with_reversing_delay, timing_function, new_reversing_adjusted_start_value,
} // namespace
void TransitionSet::UpdateTransitionForProperty(
PropertyKey property, const base::TimeDelta& current_time,
const scoped_refptr<PropertyValue>& start_value,
const scoped_refptr<PropertyValue>& end_value,
const CSSComputedStyleData& transition_style) {
// This method essentially implements the logic defined at
// Get the index of this property in the transition-property list, so we
// can know if the property should be animated (i.e. it is animated if it
// is in the list) and also how to match up transition-duration and other
// transition attributes to it.
int transition_index = GetPropertyTransitionIndex(
property, transition_style.transition_property());
if (transition_index != -1) {
// The property should be animated, though we check now to see if the
// corresponding transition-duration is set to 0 or not, since 0 implies
// that it would not be animated.
const TimeListValue* transition_duration =
base::polymorphic_downcast<const TimeListValue*>(
// Get animation parameters by using the transition_index modulo the
// specific property sizes. This is inline with the specification which
// states that list property values should be repeated to calculate values
// for indices larger than the list's size.
base::TimeDelta duration =
if (duration.InMilliseconds() != 0 && !start_value->Equals(*end_value)) {
TimeListValue* delay_list = base::polymorphic_downcast<TimeListValue*>(
const base::TimeDelta& delay =
TimingFunctionListValue* timing_function_list =
const scoped_refptr<TimingFunction>& timing_function =
// The property has been modified and the transition should be animated.
// We now check if an active transition for this property already exists
// or not.
const Transition* existing_transition =
if (existing_transition &&
current_time < existing_transition->EndTime()) {
// A transition is already occurring, so we handle this case a bit
// differently depending on if we're reversing the previous transition
// or starting a completely different one.
property, CreateTransitionOverOldTransition(
property, current_time, *existing_transition,
duration, delay, timing_function, end_value));
} else {
// There is no transition on the object currently, so create a new
// one for it.
Transition(property, start_value, end_value, current_time, duration,
delay, timing_function, start_value, 1.0f));
} else {
// Check if there is an existing transition for this property and see
// if it has completed yet. If so, remove it from the list of
// transformations.
// TODO: Fire off a transitionend event.
const Transition* transition =
if (transition != NULL) {
if (current_time >= transition->EndTime()) {
} else {
// The property is not in the transition-property list, thus it should no
// longer be animated. Remove the transition if it exists. It does
// not generate a transitionend event, it does not pass go, it does not
// collect $200.
if (transitions_.GetTransitionForProperty(property)) {
const Transition* TransitionSet::GetTransitionForProperty(
PropertyKey property) const {
return transitions_.GetTransitionForProperty(property);
TransitionSet::TransitionMap::TransitionMap(EventHandler* event_handler)
: event_handler_(event_handler) {}
TransitionSet::TransitionMap::~TransitionMap() {
if (event_handler_ != NULL) {
for (InternalTransitionMap::iterator iter = transitions_.begin();
iter != transitions_.end(); ++iter) {
const Transition* TransitionSet::TransitionMap::GetTransitionForProperty(
PropertyKey property) const {
InternalTransitionMap::const_iterator found = transitions_.find(property);
if (found == transitions_.end()) return NULL;
return &found->second;
void TransitionSet::TransitionMap::UpdateTransitionForProperty(
PropertyKey property, const Transition& transition) {
InternalTransitionMap::iterator found = transitions_.find(property);
if (found != transitions_.end()) {
if (event_handler_ != NULL) {
found->second = transition;
} else {
transitions_.insert(std::make_pair(property, transition));
if (event_handler_ != NULL) {
void TransitionSet::TransitionMap::RemoveTransitionForProperty(
PropertyKey property) {
InternalTransitionMap::iterator found = transitions_.find(property);
DCHECK(found != transitions_.end());
if (event_handler_ != NULL) {
namespace {
base::LazyInstance<TransitionSet> g_empty_transition_set =
} // namespace
const TransitionSet* TransitionSet::EmptyTransitionSet() {
return &g_empty_transition_set.Get();
} // namespace cssom
} // namespace cobalt