| // 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 |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // 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*>( |
| transition_property_value.get()); |
| |
| // 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. |
| // https://www.w3.org/TR/2013/WD-css3-transitions-20131119/#transition-property-property |
| for (int i = static_cast<int>(property_name_list->value().size()) - 1; |
| i >= 0; --i) { |
| PropertyKey current_list_property = |
| property_name_list->value()[static_cast<size_t>(i)]; |
| 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*>( |
| destination_computed_style.transition_duration().get()); |
| 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. |
| return; |
| } |
| |
| // 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) { |
| UpdateTransitionForProperty( |
| *iter, current_time, source_computed_style.GetPropertyValue(*iter), |
| destination_computed_style.GetPropertyValue(*iter), |
| destination_computed_style); |
| } |
| } |
| |
| 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: |
| // https://www.w3.org/TR/css3-transitions/#reversing |
| // 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 = |
| old_transition.Evaluate(current_time); |
| |
| // 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, |
| &new_reversing_adjusted_start_value, |
| &new_reversing_shortening_factor); |
| |
| 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, |
| new_reversing_shortening_factor); |
| } |
| |
| } // 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 |
| // https://www.w3.org/TR/css3-transitions/#starting |
| |
| // 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()); |
| |
| // The property is only animated if its transition duration is greater than 0. |
| const TimeListValue* transition_duration = |
| base::polymorphic_downcast<const TimeListValue*>( |
| transition_style.transition_duration().get()); |
| |
| // 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 = |
| transition_index < 0 |
| ? base::TimeDelta() |
| : transition_duration->get_item_modulo_size(transition_index); |
| |
| if (!duration.is_zero()) { |
| if (duration.InMilliseconds() != 0 && !start_value->Equals(*end_value)) { |
| TimeListValue* delay_list = base::polymorphic_downcast<TimeListValue*>( |
| transition_style.transition_delay().get()); |
| const base::TimeDelta& delay = |
| delay_list->get_item_modulo_size(transition_index); |
| |
| TimingFunctionListValue* timing_function_list = |
| base::polymorphic_downcast<TimingFunctionListValue*>( |
| transition_style.transition_timing_function().get()); |
| const scoped_refptr<TimingFunction>& timing_function = |
| timing_function_list->get_item_modulo_size(transition_index); |
| |
| // 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 = |
| transitions_.GetTransitionForProperty(property); |
| |
| 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. |
| transitions_.UpdateTransitionForProperty( |
| 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. |
| transitions_.UpdateTransitionForProperty( |
| property, |
| 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. |
| // https://www.w3.org/TR/css3-transitions/#transitionend |
| const Transition* transition = |
| transitions_.GetTransitionForProperty(property); |
| if (transition != NULL) { |
| if (current_time >= transition->EndTime()) { |
| transitions_.RemoveTransitionForProperty(property); |
| } |
| } |
| } |
| } 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)) { |
| transitions_.RemoveTransitionForProperty(property); |
| } |
| } |
| } |
| |
| const Transition* TransitionSet::GetTransitionForProperty( |
| PropertyKey property) const { |
| return transitions_.GetTransitionForProperty(property); |
| } |
| |
| void TransitionSet::Clear() { transitions_.Clear(); } |
| |
| 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) { |
| event_handler_->OnTransitionRemoved(iter->second); |
| } |
| } |
| } |
| |
| 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) { |
| event_handler_->OnTransitionRemoved(found->second); |
| } |
| found->second = transition; |
| } else { |
| transitions_.insert(std::make_pair(property, transition)); |
| } |
| |
| if (event_handler_ != NULL) { |
| event_handler_->OnTransitionStarted(transition); |
| } |
| } |
| |
| void TransitionSet::TransitionMap::RemoveTransitionForProperty( |
| PropertyKey property) { |
| InternalTransitionMap::iterator found = transitions_.find(property); |
| DCHECK(found != transitions_.end()); |
| |
| if (event_handler_ != NULL) { |
| event_handler_->OnTransitionRemoved(found->second); |
| } |
| |
| transitions_.erase(found); |
| } |
| |
| void TransitionSet::TransitionMap::Clear() { |
| if (event_handler_ != NULL) { |
| for (auto& transition : transitions_) { |
| event_handler_->OnTransitionRemoved(transition.second); |
| } |
| } |
| |
| transitions_.clear(); |
| } |
| |
| namespace { |
| base::LazyInstance<TransitionSet> g_empty_transition_set = |
| LAZY_INSTANCE_INITIALIZER; |
| } // namespace |
| |
| const TransitionSet* TransitionSet::EmptyTransitionSet() { |
| return &g_empty_transition_set.Get(); |
| } |
| |
| } // namespace cssom |
| } // namespace cobalt |