| /* |
| * Copyright 2016 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 <string> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "cobalt/math/rect_f.h" |
| #include "cobalt/math/size_f.h" |
| #include "cobalt/math/transform_2d.h" |
| #include "cobalt/render_tree/animations/animate_node.h" |
| #include "cobalt/render_tree/brush.h" |
| #include "cobalt/render_tree/color_rgba.h" |
| #include "cobalt/render_tree/composition_node.h" |
| #include "cobalt/render_tree/font.h" |
| #include "cobalt/render_tree/glyph_buffer.h" |
| #include "cobalt/render_tree/image.h" |
| #include "cobalt/render_tree/image_node.h" |
| #include "cobalt/render_tree/matrix_transform_node.h" |
| #include "cobalt/render_tree/node_visitor.h" |
| #include "cobalt/render_tree/rect_node.h" |
| #include "cobalt/render_tree/text_node.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace cobalt { |
| namespace render_tree { |
| namespace animations { |
| |
| using ::cobalt::math::SizeF; |
| using ::cobalt::math::RectF; |
| |
| // Helper function to create an animation set for a render tree that consists |
| // of only one animation targeting only one node in the render tree. |
| template <typename T> |
| scoped_refptr<AnimateNode> CreateSingleAnimation( |
| const scoped_refptr<T>& target, |
| typename Animation<T>::Function anim_function) { |
| AnimateNode::Builder animations_builder; |
| animations_builder.Add(target, anim_function); |
| return new AnimateNode(animations_builder, target); |
| } |
| |
| class ImageFake : public Image { |
| public: |
| const math::Size& GetSize() const OVERRIDE { return size_; } |
| |
| private: |
| math::Size size_; |
| }; |
| |
| // The following tests ensure that simple single time-independent animations |
| // work fine on single-node render trees of different types. |
| void AnimateText(TextNode::Builder* text_node, base::TimeDelta time_elapsed) { |
| UNREFERENCED_PARAMETER(time_elapsed); |
| text_node->color = ColorRGBA(.5f, .5f, .5f); |
| } |
| TEST(AnimateNodeTest, EnsureSingleTextNodeAnimates) { |
| scoped_refptr<TextNode> text_node( |
| new TextNode(math::Vector2dF(0, 0), new GlyphBuffer(RectF(0, 0, 1, 1)), |
| ColorRGBA(1.0f, 1.0f, 1.0f))); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| CreateSingleAnimation(text_node, base::Bind(&AnimateText)); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| TextNode* animated_text_node = |
| dynamic_cast<TextNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_text_node); |
| EXPECT_EQ(ColorRGBA(.5f, .5f, .5f), animated_text_node->data().color); |
| } |
| |
| void AnimateImage(ImageNode::Builder* image_node, |
| base::TimeDelta time_elapsed) { |
| UNREFERENCED_PARAMETER(time_elapsed); |
| image_node->destination_rect = RectF(2.0f, 2.0f); |
| } |
| TEST(AnimateNodeTest, EnsureSingleImageNodeAnimates) { |
| scoped_refptr<ImageNode> image_node( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| CreateSingleAnimation(image_node, base::Bind(&AnimateImage)); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| ImageNode* animated_image_node = |
| dynamic_cast<ImageNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect); |
| } |
| |
| void AnimateRect(RectNode::Builder* rect_node, base::TimeDelta time_elapsed) { |
| UNREFERENCED_PARAMETER(time_elapsed); |
| rect_node->rect = RectF(2.0f, 2.0f); |
| } |
| TEST(AnimateNodeTest, EnsureSingleRectNodeAnimates) { |
| scoped_refptr<RectNode> rect_node(new RectNode( |
| RectF(1.0f, 1.0f), |
| scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| CreateSingleAnimation(rect_node, base::Bind(&AnimateRect)); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| RectNode* animated_rect_node = |
| dynamic_cast<RectNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_rect_node); |
| EXPECT_TRUE(animated_rect_node); |
| EXPECT_EQ(RectF(2.0f, 2.0f), animated_rect_node->data().rect); |
| } |
| |
| // Test that time is being passed through correctly and that we can animate |
| // the same tree repeatedly with different time values to produce different |
| // results. |
| void AnimateImageAddWithTime(ImageNode::Builder* image_node, |
| base::TimeDelta time_elapsed) { |
| image_node->destination_rect.Outset( |
| static_cast<float>(time_elapsed.InSecondsF()), |
| static_cast<float>(time_elapsed.InSecondsF())); |
| } |
| TEST(AnimateNodeTest, SingleImageNodeAnimatesOverTimeRepeatedlyCorrectly) { |
| scoped_refptr<ImageNode> image_node( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| CreateSingleAnimation(image_node, base::Bind(&AnimateImageAddWithTime)); |
| |
| scoped_refptr<Node> animated_render_tree; |
| ImageNode* animated_image_node; |
| |
| animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_FLOAT_EQ(3.0f, animated_image_node->data().destination_rect.width()); |
| |
| animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(2)); |
| animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_FLOAT_EQ(5.0f, animated_image_node->data().destination_rect.width()); |
| |
| animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(4)); |
| animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_FLOAT_EQ(9.0f, animated_image_node->data().destination_rect.width()); |
| } |
| |
| // Test that nodes with multiple animations targeting them work correctly, and |
| // that the animations are applied in the correct order. |
| void AnimateImageScaleWithTime(ImageNode::Builder* image_node, |
| base::TimeDelta time_elapsed) { |
| math::SizeF image_size = image_node->destination_rect.size(); |
| image_size.Scale(static_cast<float>(time_elapsed.InSecondsF()), |
| static_cast<float>(time_elapsed.InSecondsF())); |
| image_node->destination_rect.set_size(image_size); |
| } |
| TEST(AnimateNodeTest, MultipleAnimationsTargetingOneNodeApplyInCorrectOrder) { |
| scoped_refptr<ImageNode> image_node( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| |
| // Here we add 2 animations both targeting the same ImageNode. We expect |
| // them to be applied in the order that they appear in the list. We test this |
| // by issuing both an add and then a multiply to the image node's destination |
| // size property, so that the result depends on the order of operations. |
| AnimationList<ImageNode>::Builder animation_list_builder; |
| animation_list_builder.animations.push_back( |
| base::Bind(&AnimateImageAddWithTime)); |
| animation_list_builder.animations.push_back( |
| base::Bind(&AnimateImageScaleWithTime)); |
| |
| AnimateNode::Builder animations_builder; |
| animations_builder.Add(image_node, |
| make_scoped_refptr(new AnimationList<ImageNode>( |
| animation_list_builder.Pass()))); |
| scoped_refptr<AnimateNode> with_animations( |
| new AnimateNode(animations_builder, image_node)); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(3)); |
| |
| ImageNode* animated_image_node = |
| dynamic_cast<ImageNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_image_node); |
| |
| EXPECT_FLOAT_EQ(21.0f, animated_image_node->data().destination_rect.width()); |
| } |
| |
| // Test that animating a transform node correctly adjusts parameters but |
| // leaves its child node untouched. |
| void AnimateTransform(MatrixTransformNode::Builder* transform_node, |
| base::TimeDelta time_elapsed) { |
| UNREFERENCED_PARAMETER(time_elapsed); |
| transform_node->transform = math::TranslateMatrix(2.0f, 2.0f); |
| } |
| TEST(AnimateNodeTest, AnimatingTransformNodeDoesNotAffectSourceChild) { |
| scoped_refptr<ImageNode> image_node( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| scoped_refptr<MatrixTransformNode> transform_node( |
| new MatrixTransformNode(image_node, math::Matrix3F::Identity())); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform)); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| |
| MatrixTransformNode* animated_transform_node = |
| dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_transform_node); |
| |
| EXPECT_EQ(math::TranslateMatrix(2.0f, 2.0f), |
| animated_transform_node->data().transform); |
| EXPECT_EQ(image_node.get(), animated_transform_node->data().source.get()); |
| } |
| |
| // Test that animating a child correctly adjusts sub-node parameters but |
| // not parent node parameters, though both child and parent are different nodes |
| // now. The animated child's sibling should be left untouched. |
| TEST(AnimateNodeTest, |
| AnimatingCompositionChildNodeAffectsParentAsWellButNotSibling) { |
| scoped_refptr<ImageNode> image_node_a( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| scoped_refptr<ImageNode> image_node_b( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| CompositionNode::Builder composition_node_builder; |
| composition_node_builder.AddChild(image_node_a); |
| composition_node_builder.AddChild(image_node_b); |
| scoped_refptr<CompositionNode> composition_node( |
| new CompositionNode(composition_node_builder.Pass())); |
| |
| AnimateNode::Builder animation_builder; |
| animation_builder.Add(image_node_a, base::Bind(&AnimateImage)); |
| scoped_refptr<AnimateNode> with_animations = |
| new AnimateNode(animation_builder, composition_node); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| |
| CompositionNode* animated_composition_node = |
| dynamic_cast<CompositionNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_composition_node); |
| |
| ImageNode* animated_image_node_a = dynamic_cast<ImageNode*>( |
| animated_composition_node->data().children()[0].get()); |
| EXPECT_TRUE(animated_image_node_a); |
| EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node_a->data().destination_rect); |
| |
| ImageNode* animated_image_node_b = dynamic_cast<ImageNode*>( |
| animated_composition_node->data().children()[1].get()); |
| EXPECT_EQ(image_node_b.get(), animated_image_node_b); |
| } |
| |
| TEST(AnimateNodeTest, SimpleSubAnimateNode) { |
| // Test that we can properly animate trees that built upon multiple |
| // AnimateNodes. |
| scoped_refptr<ImageNode> image_node( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| scoped_refptr<AnimateNode> image_animation = |
| CreateSingleAnimation(image_node, base::Bind(&AnimateImage)); |
| |
| scoped_refptr<MatrixTransformNode> transform_node( |
| new MatrixTransformNode(image_animation, math::Matrix3F::Identity())); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform)); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| |
| MatrixTransformNode* animated_transform_node = |
| dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_transform_node); |
| |
| // Check that the matrix transform node is animated. |
| EXPECT_EQ(math::TranslateMatrix(2.0f, 2.0f), |
| animated_transform_node->data().transform); |
| |
| // Check also that the image node is animated. |
| ImageNode* animated_image_node = |
| dynamic_cast<ImageNode*>(animated_transform_node->data().source.get()); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect); |
| } |
| |
| TEST(AnimateNodeTest, SubAnimateNodeWithTwoAncestors) { |
| // Test that we can properly animate trees that built upon multiple |
| // AnimateNodes. |
| scoped_refptr<ImageNode> image_node( |
| new ImageNode(make_scoped_refptr(new ImageFake()), RectF(1.0f, 1.0f))); |
| scoped_refptr<AnimateNode> image_animation = |
| CreateSingleAnimation(image_node, base::Bind(&AnimateImage)); |
| |
| // This node is used to make it so there is more than one node on the path |
| // between the two AnimateNodes. |
| scoped_refptr<MatrixTransformNode> transform_node_a( |
| new MatrixTransformNode(image_animation, math::Matrix3F::Identity())); |
| |
| scoped_refptr<MatrixTransformNode> transform_node_b( |
| new MatrixTransformNode(transform_node_a, math::Matrix3F::Identity())); |
| |
| scoped_refptr<AnimateNode> with_animations = |
| new AnimateNode(transform_node_b); |
| |
| scoped_refptr<Node> animated_render_tree = |
| with_animations->Apply(base::TimeDelta::FromSeconds(1)); |
| |
| MatrixTransformNode* animated_transform_node = |
| dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); |
| EXPECT_TRUE(animated_transform_node); |
| |
| // Check that the image node is animated. |
| MatrixTransformNode* animated_dummy_transform_node = |
| dynamic_cast<MatrixTransformNode*>( |
| animated_transform_node->data().source.get()); |
| EXPECT_TRUE(animated_dummy_transform_node); |
| |
| ImageNode* animated_image_node = dynamic_cast<ImageNode*>( |
| animated_dummy_transform_node->data().source.get()); |
| EXPECT_TRUE(animated_image_node); |
| EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect); |
| } |
| |
| } // namespace animations |
| } // namespace render_tree |
| } // namespace cobalt |