﻿// Slide.Show, version 1.0
// Copyright © Vertigo Software, Inc.
// This source is subject to the Microsoft Public License (Ms-PL).
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx.
// All other rights reserved.

/// <reference path="SlideShow.js" />

/*******************************************
 * class: SlideShow.Transition
 *******************************************/
SlideShow.Transition = function(control)
{
	/// <summary>Provides a base class for transitions.</summary>
	/// <param name="control">The Slide.Show control.</param>
	
	SlideShow.Transition.base.constructor.call(this);
	
	this.control = control;
	this.state = "Stopped";
};

SlideShow.extend(SlideShow.Object, SlideShow.Transition,
{
	begin: function(fromImage, toImage)
	{
		/// <summary>Begins the transition between the specified images.</summary>
		/// <param name="fromImage">The initial image.</param>
		/// <param name="toImage">The final image.</param>
		
		this.state = "Started";
		this.fromImage = fromImage;
		this.toImage = toImage;
		this.outStoryboardComplete = false;
		this.inStoryboardComplete = false;
	},
	
	complete: function()
	{
		/// <summary>Cleans up after the transition and fires the "complete" event.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.fromImage.root.visibility = "Collapsed";
		this.toImage.root.visibility = "Visible";
		
		this.state = "Stopped";
		this.fireEvent("complete");
	},
	
	addStoryboard: function(slideImage, targetName, targetProperty, animationXaml)
	{
		/// <summary>Adds a storyboard to the specified SlideImage control.</summary>
		/// <param name="slideImage">The SlideImage control.</param>
		/// <param name="targetName">The TargetName value for the added storyboard.</param>
		/// <param name="targetProperty">The TargetProperty value for the added storyboard.</param>
		/// <param name="animationXaml">The animation XAML for the added storyboard.</param>
		/// <returns>The added storyboard.</returns>
		
		var storyboardXaml = '<Storyboard xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="TransitionStoryboard" Storyboard.TargetName="' + targetName + '" Storyboard.TargetProperty="' + targetProperty + '">' + animationXaml + '</Storyboard>';
		var storyboard = this.control.host.content.createFromXaml(storyboardXaml);
		slideImage.root.resources.add(storyboard);
		return storyboard;
	},
	
	createClippingPath: function(geometryXaml)
	{
		/// <summary>Creates a clipping path containing a geometry group with the specified XAML.</summary>
		/// <param name="geometryXaml">The geometry XAML representing the clipping path for the canvas.</param>
		/// <returns>The clipping path.</returns>
		
		var clipXaml = '<Canvas.Clip xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><GeometryGroup>' + geometryXaml + '</GeometryGroup></Canvas.Clip>';
		return this.control.host.content.createFromXaml(clipXaml);
	},
	
	onOutStoryboardComplete: function(sender, e)
	{
		/// <summary>Cleans up the completed storyboards and fires the "complete" event.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.outStoryboardComplete = true;
		
		if (this.inStoryboardComplete)
			this.complete();
	},
	
	onInStoryboardComplete: function(sender, e)
	{
		/// <summary>Cleans up the completed storyboards and fires the "complete" event.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.inStoryboardComplete = true;
		
		if (this.outStoryboardComplete)
			this.complete();
	}	
});

/*******************************************
 * class: SlideShow.NoTransition
 *******************************************/
SlideShow.NoTransition = function(control)
{
	/// <summary>A transition which simply switches the current image with a new one.</summary>
	/// <param name="control">The Slide.Show control.</param>
	
	SlideShow.NoTransition.base.constructor.call(this, control);
};

SlideShow.extend(SlideShow.Transition, SlideShow.NoTransition,
{
	begin: function(fromImage, toImage)
	{
		/// <summary>Begins the transition between the specified images.</summary>
		/// <param name="fromImage">The initial image.</param>
		/// <param name="toImage">The final image.</param>
		
		SlideShow.NoTransition.base.begin.call(this, fromImage, toImage);
		
		fromImage.root.visibility = "Collapsed";
		toImage.root.visibility = "Visible";	
		this.complete();
	}
});

/*******************************************
 * class: SlideShow.FadeTransition
 *******************************************/
SlideShow.FadeTransition = function(control, options)
{
	/// <summary>A transition which fades in a new image and fades out the current one.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="options">The options for the transition.</param>
	
	SlideShow.FadeTransition.base.constructor.call(this, control);
	
	SlideShow.merge(this.options,
	{
		direction: "InOut",
		duration: 0.8
	});
	
	this.setOptions(options);
};

SlideShow.extend(SlideShow.Transition, SlideShow.FadeTransition,
{
	begin: function(fromImage, toImage)
	{
		/// <summary>Begins the transition between the specified images.</summary>
		/// <param name="fromImage">The initial image.</param>
		/// <param name="toImage">The final image.</param>
		
		SlideShow.FadeTransition.base.begin.call(this, fromImage, toImage);
		
		switch (this.options.direction.toLowerCase())
		{
			case "in":
				fromImage.root.visibility = "Collapsed";
				toImage.root.visibility = "Visible";
				break;
			
			case "inout":
				fromImage.root.visibility = "Visible";
				toImage.root.visibility = "Visible";
				break;
			
			default:
				throw new Error("Invalid direction: " + this.options.direction);
		}
		
		// out storyboard
		var outAnimationXaml = '<DoubleAnimation Duration="0:0:' + this.options.duration + '" From="1" To="0" />';
		var outStoryboard = this.addStoryboard(fromImage, fromImage.image.name, "Opacity", outAnimationXaml);
		outStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onOutStoryboardComplete));
		outStoryboard.begin();
		
		// in storyboard
		var inAnimationXaml = '<DoubleAnimation Duration="0:0:' + this.options.duration + '" From="0" To="1" />';
		var inStoryboard = this.addStoryboard(toImage, toImage.image.name, "Opacity", inAnimationXaml);
		inStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onInStoryboardComplete));
		inStoryboard.begin();
	}
});

/*******************************************
 * class: SlideShow.SlideTransition
 *******************************************/
SlideShow.SlideTransition = function(control, options)
{
	/// <summary>A transition which slides in a new image and slides out the current one.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="options">The options for the transition.</param>
	
	SlideShow.SlideTransition.base.constructor.call(this, control);
	
	SlideShow.merge(this.options,
	{
		direction: "Left",
		duration: 0.8
	});
	
	this.setOptions(options);
};

SlideShow.extend(SlideShow.Transition, SlideShow.SlideTransition,
{
	begin: function(fromImage, toImage)
	{
		/// <summary>Begins the transition between the specified images.</summary>
		/// <param name="fromImage">The initial image.</param>
		/// <param name="toImage">The final image.</param>
		
		SlideShow.SlideTransition.base.begin.call(this, fromImage, toImage);
		
		var fromImageToValue, toImageFromValue, targetProperty;
		
		switch (this.options.direction.toLowerCase())
		{
			case "left":
				fromImageToValue = -this.control.root.width;
				toImageFromValue = this.control.root.width;
				targetProperty = "(Canvas.Left)";
				break;
			
			case "right":
				fromImageToValue = this.control.root.width;
				toImageFromValue = -this.control.root.width;
				targetProperty = "(Canvas.Left)";
				break;
			
			case "up":
				fromImageToValue = -this.control.root.height;
				toImageFromValue = this.control.root.height;
				targetProperty = "(Canvas.Top)";
				break;
			
			case "down":
				fromImageToValue = this.control.root.height;
				toImageFromValue = -this.control.root.height;
				targetProperty = "(Canvas.Top)";
				break;
			
			default:
				throw new Error("Invalid direction: " + this.options.direction);
		}
		
		fromImage.root.visibility = "Visible";
		toImage.root.visibility = "Visible";
		
		// out storyboard
		var outAnimationXaml =
			'<DoubleAnimationUsingKeyFrames>' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,1" KeyTime="0:0:0" Value="0" />' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,1" KeyTime="0:0:' + this.options.duration + '" Value="' + fromImageToValue + '" />' +
			'</DoubleAnimationUsingKeyFrames>';
		
		var outStoryboard = this.addStoryboard(fromImage, fromImage.image.name, targetProperty, outAnimationXaml);
		outStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onOutStoryboardComplete));
		outStoryboard.begin();	
		
		// in storyboard
		var inAnimationXaml =
			'<DoubleAnimationUsingKeyFrames>' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,1" KeyTime="0:0:0" Value="' + toImageFromValue + '" />' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,1" KeyTime="0:0:' + this.options.duration + '" Value="0" />' +
			'</DoubleAnimationUsingKeyFrames>';
		
		var inStoryboard = this.addStoryboard(toImage, toImage.image.name, targetProperty, inAnimationXaml);
		inStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onInStoryboardComplete));
		inStoryboard.begin();	
	}
});

/*******************************************
 * class: SlideShow.WipeTransition
 *******************************************/
SlideShow.WipeTransition = function(control, options)
{
	/// <summary>A transition which wipes a new image over the current one.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="options">The options for the transition.</param>
	
	SlideShow.WipeTransition.base.constructor.call(this, control);
	
	SlideShow.merge(this.options,
	{
		direction: "Left",
		duration: 0.8
	});
	
	this.setOptions(options);
};

SlideShow.extend(SlideShow.Transition, SlideShow.WipeTransition,
{
	begin: function(fromImage, toImage)
	{
		/// <summary>Begins the transition between the specified images.</summary>
		/// <param name="fromImage">The initial image.</param>
		/// <param name="toImage">The final image.</param>
		
		SlideShow.WipeTransition.base.begin.call(this, fromImage, toImage);
		
		var fromClippingPathTo, toClippingPathTop, toClippingPathLeft, toClippingPathFrom, targetProperty;
		
		switch (this.options.direction.toLowerCase())
		{
			case "left":
				fromClippingPathTo = -fromImage.parent.root.width;
				toClippingPathTop = 0;
				toClippingPathLeft = toImage.parent.root.width;
				toClippingPathFrom = toImage.parent.root.width;
				targetProperty = "X";
				break;
			
			case "right":
				fromClippingPathTo = fromImage.parent.root.width;
				toClippingPathTop = 0;
				toClippingPathLeft = -toImage.parent.root.width;
				toClippingPathFrom = -toImage.parent.root.width;
				targetProperty = "X";
				break;
			
			case "up":
				fromClippingPathTo = -fromImage.parent.root.height;
				toClippingPathTop = toImage.parent.root.height;
				toClippingPathLeft = 0;
				toClippingPathFrom = toImage.parent.root.height;
				targetProperty = "Y";
				break;
			
			case "down":
				fromClippingPathTo = fromImage.parent.root.height;
				toClippingPathTop = -toImage.parent.root.height;
				toClippingPathLeft = 0;
				toClippingPathFrom = -toImage.parent.root.height;
				targetProperty = "Y";
				break;
			
			default:
				throw new Error("Invalid direction: " + this.options.direction);
		}
		
		// from image
		var fromGeometryXaml =
			'<RectangleGeometry xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Rect="0,0,' + fromImage.parent.root.width + "," + fromImage.parent.root.height + '">' +
			'	<RectangleGeometry.Transform>' +
			'		<TranslateTransform x:Name="VisibleTransform" X="0" Y="0" />' +
			'	</RectangleGeometry.Transform>' +
			'</RectangleGeometry>';
		
		fromImage.root.clip = this.createClippingPath(fromGeometryXaml);
		fromImage.root.visibility = "Visible";
				
		// to image
		var toGeometryXaml =
			'<RectangleGeometry xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Rect="0,0,' + toImage.parent.root.width + "," + toImage.parent.root.height + '">' +
			'	<RectangleGeometry.Transform>' +
			'		<TranslateTransform x:Name="VisibleTransform" X="' + toClippingPathLeft + '" Y="' + toClippingPathTop + '" />' +
			'	</RectangleGeometry.Transform>' +
			'</RectangleGeometry>';
		
		toImage.root.clip = this.createClippingPath(toGeometryXaml);
		toImage.root.visibility = "Visible";
		
		fromImage.addEventListener("sizeChange", SlideShow.createDelegate(this, this.onSlideImageSizeChanged));
		toImage.addEventListener("sizeChange", SlideShow.createDelegate(this, this.onSlideImageSizeChanged));
		
		// out storyboard
		var outAnimationXaml =
			'<DoubleAnimationUsingKeyFrames>' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,0" KeyTime="0:0:0" Value="0" />' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,1" KeyTime="0:0:' + this.options.duration + '" Value="' + fromClippingPathTo + '" />' +
			'</DoubleAnimationUsingKeyFrames> ';
		
		this.outStoryboard = this.addStoryboard(fromImage, "VisibleTransform", targetProperty, outAnimationXaml);
		this.outStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onOutStoryboardComplete));
		this.outStoryboard.begin();
		
		// in storyboard
		var inAnimationXaml =
			'<DoubleAnimationUsingKeyFrames>' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,0" KeyTime="0:0:0" Value="' + toClippingPathFrom + '" />' +
			'	<SplineDoubleKeyFrame KeySpline="0,0 0,1" KeyTime="0:0:' + this.options.duration + '" Value="0" />' +
			'</DoubleAnimationUsingKeyFrames> ';
		
		this.inStoryboard = this.addStoryboard(toImage, "VisibleTransform", targetProperty, inAnimationXaml);
		this.inStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onInStoryboardComplete));
		this.inStoryboard.begin();
	},
	
	onSlideImageSizeChanged: function(sender)
	{
		/// <summary>Handles the event fired when the slide image control is resized.</summary>
		/// <summary>Handles the event fired when the control is resized.</summary>

		if (this.state = "Started")
		{
			this.outStoryboard.seek('0:0:' + this.options.duration);
			this.inStoryboard.seek('0:0:' + this.options.duration);
			SlideShow.WipeTransition.base.complete.call(this);
		}

		var clipGeometry = sender.root.clip.children.getItem(0);
		clipGeometry.rect = '0,0,' + sender.parent.root.width + ',' + sender.parent.root.height;
	}
});

/*******************************************
 * class: SlideShow.ShapeTransition
 *******************************************/
SlideShow.ShapeTransition = function(control, options)
{
	/// <summary>A transition which cuts to a new image from the current one using a shape.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="options">The options for the transition.</param>
	
	SlideShow.ShapeTransition.base.constructor.call(this, control);
	
	SlideShow.merge(this.options,
	{
		shape: "Circle",
		direction: "Out",
		duration: 0.8
	});
	
	this.setOptions(options);
};

SlideShow.extend(SlideShow.Transition, SlideShow.ShapeTransition,
{
	begin: function(fromImage, toImage)
	{
		/// <summary>Begins the transition between the specified images.</summary>
		/// <param name="fromImage">The initial image.</param>
		/// <param name="toImage">The final image.</param>
		
		SlideShow.ShapeTransition.base.begin.call(this, fromImage, toImage);
		
		switch (this.options.shape.toLowerCase())
		{
			case "circle":
				var direction = this.options.direction.toLowerCase();
				
				if (direction != "out" && direction != "in")
					throw new Error("Invalid direction: " + direction);
				
				var maxSize = Math.max(this.control.root.width, this.control.root.height);
				var clipFrom = (direction == "out") ? 0 : maxSize;
				var clipTo = (direction == "out") ? maxSize : 0;
				
				var simpleXaml = '<EllipseGeometry xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="CircleBehaviorPath" Center="' + (this.control.root.width / 2) + ',' + (this.control.root.height / 2) + '" RadiusX="' + clipFrom + '" RadiusY="' + clipFrom + '" />';
				var complexXaml = simpleXaml + '<RectangleGeometry Rect="0,0,' + this.control.root.width + ',' + this.control.root.height + '" />';
				
				if (direction == "out")
				{
					fromImage.root.clip = this.createClippingPath(complexXaml);
					toImage.root.clip = this.createClippingPath(simpleXaml);
				}
				else
				{
					fromImage.root.clip = this.createClippingPath(simpleXaml);
					toImage.root.clip = this.createClippingPath(complexXaml);
				}
				
				fromImage.root.visibility = "Visible";
				toImage.root.visibility = "Visible";
				
				fromImage.addEventListener("sizeChange", SlideShow.createDelegate(this, this.onSlideImageSizeChanged));
				toImage.addEventListener("sizeChange", SlideShow.createDelegate(this, this.onSlideImageSizeChanged));
				
				// out storyboard
				var animationXaml =
					'<DoubleAnimation Storyboard.TargetProperty="RadiusX" Duration="0:0:' + this.options.duration + '" To="' + clipTo + '" />' +
					'<DoubleAnimation Storyboard.TargetProperty="RadiusY" Duration="0:0:' + this.options.duration + '" To="' + clipTo + '" />';
				
				this.outStoryboard = this.addStoryboard(fromImage, "CircleBehaviorPath", "RadiusX", animationXaml);
				this.outStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onOutStoryboardComplete));
				this.outStoryboard.begin();
				
				// in storyboard
				this.inStoryboard = this.addStoryboard(toImage, "CircleBehaviorPath", "RadiusX", animationXaml);
				this.inStoryboard.addEventListener("Completed", SlideShow.createDelegate(this, this.onInStoryboardComplete));
				this.inStoryboard.begin();
				
				break;

			default:
				throw new Error("Invalid shape: " + this.options.shape.toLowerCase());
		}
	},

	onSlideImageSizeChanged: function(sender)
	{
		/// <summary>Handles the event fired when the slide image control is resized.</summary>

		if (this.state = "Started")
		{
			this.outStoryboard.stop();
			this.inStoryboard.stop();
			SlideShow.ShapeTransition.base.complete.call(this);
		}

		var clipGeometry = sender.root.clip.children.getItem(0);
		var maxSize = Math.max(this.control.root.width, this.control.root.height);
		var center = (sender.parent.root.width / 2) + ',' + (sender.parent.root.height / 2);
		clipGeometry.center = center;
		clipGeometry.radiusX = maxSize;
		clipGeometry.radiusY = maxSize;
	}
});
