public void InvalidParameters()
        {
            var objectA   = new AnimatableObject("ObjectA");
            var objectB   = new AnimatableObject("ObjectA");
            var property  = new AnimatableProperty <float>();
            var animation = new SingleFromToByAnimation();
            var objects   = new[] { objectA, objectB };

            var manager = new AnimationManager();

            // Should throw exception.
            Assert.That(() => { manager.IsAnimated((IAnimatableObject)null); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.IsAnimated((IAnimatableProperty)null); }, Throws.TypeOf <ArgumentNullException>());

            // Should throw exception.
            Assert.That(() => { manager.CreateController(null, objects); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.CreateController(animation, (IEnumerable <IAnimatableObject>)null); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.CreateController(null, objectA); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.CreateController(animation, (IAnimatableObject)null); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.CreateController(null, property); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.CreateController(animation, (IAnimatableProperty)null); }, Throws.TypeOf <ArgumentNullException>());

            // Should throw exception.
            Assert.That(() => { manager.StartAnimation(null, objects); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.StartAnimation(animation, (IEnumerable <IAnimatableObject>)null); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.StartAnimation(null, objectA); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.StartAnimation(animation, (IAnimatableObject)null); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.StartAnimation(null, property); }, Throws.TypeOf <ArgumentNullException>());
            Assert.That(() => { manager.StartAnimation(animation, (IAnimatableProperty)null); }, Throws.TypeOf <ArgumentNullException>());

            // Should not throw exception.
            Assert.That(() => manager.StopAnimation((IEnumerable <IAnimatableObject>)null), Throws.Nothing);
            Assert.That(() => manager.StopAnimation((IAnimatableObject)null), Throws.Nothing);
            Assert.That(() => manager.StopAnimation((IAnimatableProperty)null), Throws.Nothing);
        }
        public void ShouldReturnNullWhenTypeDoesNotMatch()
        {
            var widthProperty = new AnimatableProperty <float>();
            var animatable    = new AnimatableObject("Object");

            animatable.Properties.Add("Width", widthProperty);

            Assert.IsNull(animatable.GetAnimatableProperty <string>("Width"));
        }
        public void ShouldReturnNullWhenNameIsNotFound()
        {
            var widthProperty = new AnimatableProperty <float>();
            var animatable    = new AnimatableObject("Object");

            animatable.Properties.Add("Width", widthProperty);

            Assert.IsNull(animatable.GetAnimatableProperty <float>("WrongName"));
        }
        public void ShouldRemoveAnimationsIfInactive()
        {
            var obj      = new AnimatableObject("TestObject");
            var property = new AnimatableProperty <float>();

            obj.Properties.Add("Value", property);

            var animation = new SingleFromToByAnimation
            {
                From           = 100.0f,
                To             = 200.0f,
                TargetProperty = "Value",
            };

            var manager     = new AnimationManager();
            var controllerA = manager.StartAnimation(animation, obj);

            controllerA.AutoRecycle();
            controllerA.UpdateAndApply();
            Assert.AreEqual(100.0f, property.Value);
            Assert.IsTrue(controllerA.IsValid);

            manager.Update(TimeSpan.FromSeconds(0.5));
            manager.ApplyAnimations();
            Assert.AreEqual(150.0f, property.Value);
            Assert.IsTrue(controllerA.IsValid);

            // Replace animation instance with new instance.
            var controllerB = manager.StartAnimation(animation, obj);

            controllerB.AutoRecycle();
            controllerB.UpdateAndApply();
            Assert.AreEqual(100.0f, property.Value);
            Assert.IsTrue(controllerA.IsValid);
            Assert.IsTrue(controllerB.IsValid);

            // controllerA should be removed automatically.
            // (Note: Cleanup is done incrementally, not every frame.
            // It is okay if it takes a few updates.)
            manager.Update(TimeSpan.FromSeconds(0.1));
            manager.Update(TimeSpan.FromSeconds(0.1));
            manager.Update(TimeSpan.FromSeconds(0.1));
            manager.Update(TimeSpan.FromSeconds(0.1));
            manager.Update(TimeSpan.FromSeconds(0.1));
            manager.ApplyAnimations();
            Assert.AreEqual(150.0f, property.Value);
            Assert.IsFalse(controllerA.IsValid);
            Assert.IsTrue(controllerB.IsValid);
        }
        public void ShouldReturnAllAnimatableProperties()
        {
            var widthProperty  = new AnimatableProperty <float>();
            var heightProperty = new AnimatableProperty <float>();
            var textProperty   = new AnimatableProperty <string>();
            var animatable     = new AnimatableObject("Object");

            animatable.Properties.Add("Width", widthProperty);
            animatable.Properties.Add("Height", heightProperty);
            animatable.Properties.Add("Text", textProperty);

            Assert.That(animatable.GetAnimatedProperties(), Has.Member(widthProperty));
            Assert.That(animatable.GetAnimatedProperties(), Has.Member(heightProperty));
            Assert.That(animatable.GetAnimatedProperties(), Has.Member(textProperty));
        }
        public void ShouldRemoveAnimationsIfTargetsAreGarbageCollected()
        {
            var obj      = new AnimatableObject("TestObject");
            var property = new AnimatableProperty <float>();

            obj.Properties.Add("Value", property);

            var animation = new SingleFromToByAnimation
            {
                From           = 100.0f,
                To             = 200.0f,
                TargetProperty = "Value",
            };

            var manager    = new AnimationManager();
            var controller = manager.StartAnimation(animation, obj);

            controller.AutoRecycle();
            controller.UpdateAndApply();
            Assert.AreEqual(100.0f, property.Value);

            manager.Update(TimeSpan.FromSeconds(0.5));
            manager.ApplyAnimations();
            Assert.AreEqual(150.0f, property.Value);

            // Garbage-collect target object.
            obj      = null;
            property = null;
            GC.Collect();

            // Controller should be still valid, because AnimationManager.Update() needs
            // to be called first.
            Assert.IsTrue(controller.IsValid);

            manager.Update(TimeSpan.FromSeconds(0.1));
            manager.ApplyAnimations();

            // Animation instance should now be recycled.
            Assert.IsFalse(controller.IsValid);
        }
        public void AnimateObjects()
        {
            var objectA    = new AnimatableObject("ObjectA");
            var propertyA1 = new AnimatableProperty <float> {
                Value = 10.0f
            };

            objectA.Properties.Add("Value", propertyA1);
            var propertyA2 = new AnimatableProperty <float> {
                Value = 20.0f
            };

            objectA.Properties.Add("Value2", propertyA2);

            var objectB   = new AnimatableObject("ObjectB");
            var propertyB = new AnimatableProperty <float> {
                Value = 30.0f
            };

            objectB.Properties.Add("Value", propertyB);

            var objectC   = new AnimatableObject("ObjectC");
            var propertyC = new AnimatableProperty <float> {
                Value = 40.0f
            };

            objectC.Properties.Add("Value", propertyC);

            var animationA1 = new SingleFromToByAnimation // Should be assigned to ObjectA
            {
                From           = 100.0f,
                To             = 200.0f,
                TargetObject   = "ObjectXyz",             // Ignored because ObjectA is selected by animationGroup1.
                TargetProperty = "Value",                 // Required.
            };
            var animationA2 = new SingleFromToByAnimation // Should be assigned to ObjectA
            {
                From           = 200.0f,
                To             = 300.0f,
                TargetObject   = "ObjectB",                // Ignored because ObjectA is selected by animationGroup1.
                TargetProperty = "Value",                  // Required.
            };
            var animationA3 = new Vector3FromToByAnimation // Ignored because of incompatible type.
            {
                From           = new Vector3(300.0f),
                To             = new Vector3(400.0f),
                TargetObject   = "ObjectA",
                TargetProperty = "Value",
            };
            var animationA4 = new SingleFromToByAnimation // Ignored because TargetProperty is not set.
            {
                From           = 400.0f,
                To             = 500.0f,
                TargetObject   = "",
                TargetProperty = "",
            };
            var animationGroupA = new TimelineGroup {
                TargetObject = "ObjectA"
            };

            animationGroupA.Add(animationA1);
            animationGroupA.Add(animationA2);
            animationGroupA.Add(animationA3);
            animationGroupA.Add(animationA4);

            var animationB1 = new SingleFromToByAnimation // Should be assigned to ObjectB
            {
                From           = 100.0f,
                To             = 200.0f,
                TargetObject   = "ObjectB",
                TargetProperty = "Value",
            };
            var animationA5 = new SingleFromToByAnimation
            {
                From           = 600.0f,
                To             = 700.0f,
                TargetObject   = "",
                TargetProperty = "Value",
            };

            var animationGroupRoot = new TimelineGroup();

            animationGroupRoot.Add(animationGroupA);
            animationGroupRoot.Add(animationB1);
            animationGroupRoot.Add(animationA5);

            var manager = new AnimationManager();

            // CreateController()
            var controller = manager.CreateController(animationGroupRoot, new[] { objectA, objectB, objectC });

            Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[0]).Property);
            Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[1]).Property);
            Assert.AreEqual(null, ((AnimationInstance <Vector3>)controller.AnimationInstance.Children[0].Children[2]).Property);
            Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[3]).Property);
            Assert.AreEqual(propertyB, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property);
            Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[2]).Property);
            Assert.AreEqual(10.0f, propertyA1.Value);
            Assert.AreEqual(20.0f, propertyA2.Value);
            Assert.AreEqual(30.0f, propertyB.Value);
            Assert.AreEqual(40.0f, propertyC.Value);
            Assert.IsFalse(manager.IsAnimated(objectA));
            Assert.IsFalse(manager.IsAnimated(propertyA1));
            Assert.IsFalse(manager.IsAnimated(propertyA2));
            Assert.IsFalse(manager.IsAnimated(objectB));
            Assert.IsFalse(manager.IsAnimated(propertyB));
            Assert.IsFalse(manager.IsAnimated(objectC));
            Assert.IsFalse(manager.IsAnimated(propertyC));

            controller.Start();
            controller.UpdateAndApply();
            Assert.AreEqual(600.0f, propertyA1.Value);
            Assert.AreEqual(20.0f, propertyA2.Value);
            Assert.AreEqual(100.0f, propertyB.Value);
            Assert.AreEqual(40.0f, propertyC.Value);
            Assert.IsTrue(manager.IsAnimated(objectA));
            Assert.IsTrue(manager.IsAnimated(propertyA1));
            Assert.IsFalse(manager.IsAnimated(propertyA2));
            Assert.IsTrue(manager.IsAnimated(objectB));
            Assert.IsTrue(manager.IsAnimated(propertyB));
            Assert.IsFalse(manager.IsAnimated(objectC));
            Assert.IsFalse(manager.IsAnimated(propertyC));

            controller.Stop();
            controller.UpdateAndApply();
            Assert.AreEqual(10.0f, propertyA1.Value);
            Assert.AreEqual(20.0f, propertyA2.Value);
            Assert.AreEqual(30.0f, propertyB.Value);
            Assert.AreEqual(40.0f, propertyC.Value);
            Assert.IsFalse(manager.IsAnimated(objectA));
            Assert.IsFalse(manager.IsAnimated(propertyA1));
            Assert.IsFalse(manager.IsAnimated(propertyA2));
            Assert.IsFalse(manager.IsAnimated(objectB));
            Assert.IsFalse(manager.IsAnimated(propertyB));
            Assert.IsFalse(manager.IsAnimated(objectC));
            Assert.IsFalse(manager.IsAnimated(propertyC));

            // StartAnimation()
            controller = manager.StartAnimation(animationGroupRoot, new[] { objectA, objectB, objectC });
            controller.UpdateAndApply();
            Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[0]).Property);
            Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[1]).Property);
            Assert.AreEqual(null, ((AnimationInstance <Vector3>)controller.AnimationInstance.Children[0].Children[2]).Property);
            Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[3]).Property);
            Assert.AreEqual(propertyB, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property);
            Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[2]).Property);
            Assert.AreEqual(600.0f, propertyA1.Value);
            Assert.AreEqual(20.0f, propertyA2.Value);
            Assert.AreEqual(100.0f, propertyB.Value);
            Assert.AreEqual(40.0f, propertyC.Value);
            Assert.IsTrue(manager.IsAnimated(objectA));
            Assert.IsTrue(manager.IsAnimated(propertyA1));
            Assert.IsFalse(manager.IsAnimated(propertyA2));
            Assert.IsTrue(manager.IsAnimated(objectB));
            Assert.IsTrue(manager.IsAnimated(propertyB));
            Assert.IsFalse(manager.IsAnimated(objectC));
            Assert.IsFalse(manager.IsAnimated(propertyC));

            manager.StopAnimation(new[] { objectA, objectB, objectC });
            manager.UpdateAndApplyAnimation(new[] { objectA, objectB, objectC });
            Assert.AreEqual(10.0f, propertyA1.Value);
            Assert.AreEqual(20.0f, propertyA2.Value);
            Assert.AreEqual(30.0f, propertyB.Value);
            Assert.AreEqual(40.0f, propertyC.Value);
            Assert.IsFalse(manager.IsAnimated(objectA));
            Assert.IsFalse(manager.IsAnimated(propertyA1));
            Assert.IsFalse(manager.IsAnimated(propertyA2));
            Assert.IsFalse(manager.IsAnimated(objectB));
            Assert.IsFalse(manager.IsAnimated(propertyB));
            Assert.IsFalse(manager.IsAnimated(objectC));
            Assert.IsFalse(manager.IsAnimated(propertyC));
        }
        public void AnimateObject()
        {
            var obj      = new AnimatableObject("TestObject");
            var property = new AnimatableProperty <float> {
                Value = 10.0f
            };

            obj.Properties.Add("Value", property);
            var property2 = new AnimatableProperty <float> {
                Value = 20.0f
            };

            obj.Properties.Add("Value2", property2);

            var animationA = new SingleFromToByAnimation
            {
                From           = 100.0f,
                To             = 200.0f,
                TargetObject   = "ObjectA", // Should be ignored.
                TargetProperty = "Value",
            };
            var animationB = new SingleFromToByAnimation
            {
                From           = 200.0f,
                To             = 300.0f,
                TargetObject   = "ObjectB", // Should be ignored.
                TargetProperty = "",
            };
            var animationGroup = new TimelineGroup();

            animationGroup.Add(animationA);
            animationGroup.Add(animationB);

            var manager = new AnimationManager();

            // Should assign animationA to 'obj'.
            var controller = manager.CreateController(animationGroup, obj);

            Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[0]).Property);
            Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property);
            Assert.IsFalse(manager.IsAnimated(property));
            Assert.IsFalse(manager.IsAnimated(property2));
            Assert.IsFalse(manager.IsAnimated(obj));

            // When started then animationA should be active.
            controller.Start();
            controller.UpdateAndApply();
            Assert.AreEqual(100.0f, property.Value);
            Assert.AreEqual(20.0f, property2.Value);
            Assert.IsTrue(manager.IsAnimated(obj));
            Assert.IsTrue(manager.IsAnimated(property));
            Assert.IsFalse(manager.IsAnimated(property2));

            controller.Stop();
            controller.UpdateAndApply();
            Assert.AreEqual(10.0f, property.Value);
            Assert.AreEqual(20.0f, property2.Value);
            Assert.IsFalse(manager.IsAnimated(obj));
            Assert.IsFalse(manager.IsAnimated(property));
            Assert.IsFalse(manager.IsAnimated(property2));

            // Same test for AnimationManager.StartAnimation()
            controller = manager.StartAnimation(animationGroup, obj);
            controller.UpdateAndApply();
            Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[0]).Property);
            Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property);
            Assert.AreEqual(100.0f, property.Value);
            Assert.AreEqual(20.0f, property2.Value);
            Assert.IsTrue(manager.IsAnimated(obj));
            Assert.IsTrue(manager.IsAnimated(property));
            Assert.IsFalse(manager.IsAnimated(property2));

            manager.StopAnimation(obj);
            manager.UpdateAndApplyAnimation(obj);
            Assert.AreEqual(10.0f, property.Value);
            Assert.AreEqual(20.0f, property2.Value);
            Assert.IsFalse(manager.IsAnimated(obj));
            Assert.IsFalse(manager.IsAnimated(property));
            Assert.IsFalse(manager.IsAnimated(property2));
        }
        public void NameShouldBeSet()
        {
            var animatableProperty = new AnimatableObject("Name of object");

            Assert.AreEqual("Name of object", animatableProperty.Name);
        }