package com.rialvalue.layouts {
import flash.events.TimerEvent;
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.utils.Timer;
import mx.core.ILayoutElement;
import mx.core.IVisualElement;
import mx.core.UIComponent;
import spark.layouts.supportClasses.LayoutBase;
public class CoverflowLayout extends LayoutBase {
private static const ANIMATION_DURATION:int = 700;
private static const ANIMATION_STEPS:int = 24; // fps
private static const RIGHT_SIDE:int = -1;
private static const LEFT_SIDE:int = 1;
private var finalMatrixs:Vector.<Matrix3D>;
private var centerX:Number;
private var centerY:Number;
private var transitionTimer:Timer;
private var _elementRotation:Number;
private var _selectedItemProximity:Number;
private var _selectedIndex:int;
private var _depthDistance:Number;
private var _perspectiveProjectionX:Number;
private var _perspectiveProjectionY:Number;
private var _focalLength:Number = 300;
private var _horizontalDistance:Number = 100;
public function set perspectiveProjectionX(value:Number):void {
_perspectiveProjectionX = value;
invalidateTarget();
}
public function set perspectiveProjectionY(value:Number):void {
_perspectiveProjectionY = value;
invalidateTarget();
}
public function set focalLength(value:Number):void {
_focalLength = value;
invalidateTarget();
}
public function set elementRotation(value:Number):void {
_elementRotation = value;
invalidateTarget();
}
public function set horizontalDistance(value:Number):void {
_horizontalDistance = value;
invalidateTarget();
}
public function set depthDistance(value:Number):void {
_depthDistance = value;
invalidateTarget();
}
public function set selectedItemProximity(value:Number):void {
_selectedItemProximity = value;
invalidateTarget();
}
public function set selectedIndex(value:Number):void {
_selectedIndex = value;
if (target) {
target.invalidateDisplayList();
target.invalidateSize();
}
}
private function invalidateTarget():void {
if (target) {
target.invalidateDisplayList();
target.invalidateSize();
}
}
private function centerPerspectiveProjection(width:Number, height:Number):void {
_perspectiveProjectionX = _perspectiveProjectionX != -1 ? _perspectiveProjectionX : width / 2;
_perspectiveProjectionY = _perspectiveProjectionY != -1 ? _perspectiveProjectionY : height / 2;
var perspectiveProjection:PerspectiveProjection = new PerspectiveProjection();
perspectiveProjection.projectionCenter = new Point(_perspectiveProjectionX, _perspectiveProjectionY);
perspectiveProjection.focalLength = _focalLength;
target.transform.perspectiveProjection = perspectiveProjection;
}
private function positionCentralElement(element:ILayoutElement, width:Number, height:Number):Matrix3D {
element.setLayoutBoundsSize(NaN, NaN, false);
var matrix:Matrix3D = new Matrix3D();
var elementWidth:Number = element.getLayoutBoundsWidth(false);
var elementHeight:Number = element.getLayoutBoundsHeight(false);
centerX = (width - elementWidth) / 2;
centerY = (height - elementHeight) / 2;
matrix.appendTranslation(centerX, centerY, -_selectedItemProximity);
element.setLayoutBoundsSize(NaN, NaN, false);
if (element is IVisualElement) {
IVisualElement(element).depth = 10;
}
return matrix;
}
private function positionLateralElement(element:ILayoutElement, index:int, side:int):Matrix3D {
element.setLayoutBoundsSize(NaN, NaN, false);
var matrix:Matrix3D = new Matrix3D();
var elementWidth:Number = element.getLayoutBoundsWidth(false);
var elementHeight:Number = element.getLayoutBoundsHeight(false);
var zPosition:Number = index * _depthDistance;
if (side == RIGHT_SIDE) {
matrix.appendTranslation(-elementWidth, 0, 0);
matrix.appendRotation(side * _elementRotation, Vector3D.Y_AXIS);
matrix.appendTranslation(2 * elementWidth - _horizontalDistance, 0, 0);
} else {
matrix.appendRotation(side * _elementRotation, Vector3D.Y_AXIS);
}
matrix.appendTranslation(centerX - side * (index) * _horizontalDistance, centerY, zPosition);
if (element is IVisualElement) {
IVisualElement(element).depth = -zPosition;
}
return matrix;
}
override public function updateDisplayList(width:Number, height:Number):void {
var i:int = 0;
var j:int = 0;
var numElements:int = target.numElements;
var matrix:Matrix3D;
if (numElements > 0) {
centerPerspectiveProjection(width, height);
finalMatrixs = new Vector.<Matrix3D>(numElements);
var midElement:int = _selectedIndex == -1 ? Math.ceil(numElements / 2) : _selectedIndex;
matrix = positionCentralElement(target.getVirtualElementAt(midElement), width, height);
finalMatrixs[midElement] = matrix;
for (i = midElement - 1; i >= 0; i--) {
matrix = positionLateralElement(target.getVirtualElementAt(i), midElement - i, LEFT_SIDE);
finalMatrixs[i] = matrix;
}
for (j = 0, i = midElement + 1; i < numElements; i++, j++) {
matrix = positionLateralElement(target.getVirtualElementAt(i), j, RIGHT_SIDE);
finalMatrixs[i] = matrix;
}
playTransition();
}
}
private function playTransition():void {
if (transitionTimer) {
transitionTimer.stop();
transitionTimer.reset();
} else {
transitionTimer = new Timer(ANIMATION_DURATION / ANIMATION_STEPS, ANIMATION_STEPS);
transitionTimer.addEventListener(TimerEvent.TIMER, animationTickHandler);
transitionTimer.addEventListener(TimerEvent.TIMER_COMPLETE, animationTimerCompleteHandler);
}
transitionTimer.start();
}
private function animationTickHandler(event:TimerEvent):void {
var numElements:int = target.numElements;
var initialMatrix:Matrix3D;
var finalMatrix:Matrix3D;
var element:ILayoutElement;
for (var i:int = 0; i < numElements; i++) {
finalMatrix = finalMatrixs[i];
element = target.getVirtualElementAt(i);
initialMatrix = UIComponent(element).transform.matrix3D;
initialMatrix.interpolateTo(finalMatrix, 0.2);
element.setLayoutMatrix3D(initialMatrix, false);
}
}
private function animationTimerCompleteHandler(event:TimerEvent):void {
finalMatrixs = null;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="800"
minHeight="480"
xmlns:local="*"
creationComplete="init()"
xmlns:layouts="com.rialvalue.layouts.*"
viewSourceURL="srcview/index.html">
<fx:Metadata>
[SWF(width="800", height="480")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import mx.events.CollectionEvent;
[Bindable]
private var presets:ArrayCollection;
private function init():void
{
dp.disableAutoUpdate();
dp2.disableAutoUpdate();
for (var i:int=1; i < 32; i++)
{
dp.addItem("photos/photo_" + i + ".jpg");
}
for (i=0; i < 10; i++)
{
dp2.addItem("photos/photo_" + i + ".jpg");
}
dp.enableAutoUpdate();
dp2.enableAutoUpdate();
initPresets();
}
private function initPresets():void
{
presets=new ArrayCollection();
presets.disableAutoUpdate();
presets.addItem({label: "Preset1", projectionX: -1, projectionY: -1, focalLength: 300, hDistance: 103, depth: 1, rotation: -70, proximity: 75});
presets.addItem({label: "Preset2", projectionX: -1, projectionY: -1, focalLength: 300, hDistance: 100, depth: 1, rotation: -45, proximity: 30});
presets.addItem({label: "Preset3", projectionX: -1, projectionY: 60, focalLength: 300, hDistance: 87, depth: 55, rotation: 0, proximity: 30});
presets.addItem({label: "Preset4", projectionX: 468, projectionY: 298, focalLength: 327, hDistance: 88, depth: 50, rotation: -45, proximity: 30});
presets.addItem({label: "Preset5", projectionX: 236, projectionY: -6, focalLength: 416, hDistance: 36, depth: 100, rotation: 0, proximity: 30});
presets.enableAutoUpdate();
presetsSelector.selectedIndex=0;
}
]]>
</fx:Script>
<fx:Declarations>
<s:ArrayCollection id="dp"/>
<s:ArrayCollection id="dp2"/>
<fx:Component id="renderer1">
<mx:Image source="{data}"/>
</fx:Component>
<fx:Component id="renderer2">
<local:PodRenderer autoDrawBackground="false"/>
</fx:Component>
</fx:Declarations>
<s:layout>
<s:VerticalLayout horizontalAlign="center"
verticalAlign="middle"/>
</s:layout>
<s:List dataProvider="{ dataSelector.selectedItem.data }"
width="750"
height="375"
id="list"
selectedIndex="@{ selectedSlider.value}"
itemRenderer="{rendererSelector.selectedItem.data}">
<s:layout>
<layouts:CoverflowLayout id="coverflow"
selectedIndex="{ list.selectedIndex }"
horizontalDistance="{horizontalDistance.value}"
selectedItemProximity="{selectedItemProximity.value}"
depthDistance="{depthDistance.value}"
elementRotation="{elementRotation.value}"
focalLength="{focalLength.value}"
perspectiveProjectionX="{projectionX.value}"
perspectiveProjectionY="{projectionY.value}"/>
</s:layout>
</s:List>
<s:HGroup width="850">
<s:VGroup width="50%">
<mx:Form>
<mx:FormItem label="Selected Item">
<s:HSlider id="selectedSlider"
minimum="0"
maximum="{ dp.length}"
stepSize="1"
value="5"/>
</mx:FormItem>
<mx:FormItem label="Presets">
<s:ComboBox id="presetsSelector"
dataProvider="{presets}"/>
</mx:FormItem>
<mx:FormItem label="Perspectivice Projection center"
direction="vertical">
<s:HSlider id="projectionX"
minimum="-1000"
maximum="1000"
value="{presetsSelector.selectedItem.projectionX}"/>
<s:HSlider id="projectionY"
minimum="-1000"
maximum="1000"
value="{presetsSelector.selectedItem.projectionY}"/>
</mx:FormItem>
<mx:FormItem label="Element rotation">
<s:HSlider id="elementRotation"
minimum="-90"
maximum="0"
stepSize="1"
value="{presetsSelector.selectedItem.rotation}"/>
</mx:FormItem>
</mx:Form>
</s:VGroup>
<s:VGroup width="50%">
<mx:Form>
<mx:FormItem label="Renderer">
<s:ComboBox id="rendererSelector"
selectedIndex="0">
<s:dataProvider>
<s:ArrayCollection>
<fx:Object label="renderer1"
data="{renderer1}"/>
<fx:Object label="renderer2"
data="{renderer2}"/>
</s:ArrayCollection>
</s:dataProvider>
</s:ComboBox>
</mx:FormItem>
<mx:FormItem label="Focal length">
<s:HSlider id="focalLength"
minimum="1"
maximum="1000"
value="{presetsSelector.selectedItem.focalLength}"/>
</mx:FormItem>
<mx:FormItem label="Horizontal distance">
<s:HSlider id="horizontalDistance"
minimum="0"
maximum="200"
stepSize="1"
value="{presetsSelector.selectedItem.hDistance}"/>
</mx:FormItem>
<mx:FormItem label="Depth distance">
<s:HSlider id="depthDistance"
minimum="1"
maximum="200"
stepSize="1"
value="{presetsSelector.selectedItem.depth}"/>
</mx:FormItem>
<mx:FormItem label="Selected Item proximity">
<s:HSlider id="selectedItemProximity"
minimum="0"
maximum="200"
stepSize="1"
value="{presetsSelector.selectedItem.proximity}"/>
</mx:FormItem>
<s:ComboBox id="dataSelector"
selectedIndex="0">
<s:dataProvider>
<s:ArrayCollection>
<fx:Object label="dp1"
data="{dp}"/>
<fx:Object label="dp2"
data="{dp2}"/>
</s:ArrayCollection>
</s:dataProvider>
</s:ComboBox>
</mx:FormItem>
</mx:Form>
</s:VGroup>
</s:HGroup>
</s:Application>
PodRenderer:
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:ai="http://ns.adobe.com/ai/2009" xmlns:d="http://ns.adobe.com/fxg/2008/dt"
xmlns:flm="http://ns.adobe.com/flame/2008" xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<!-- Generate with Adobe Catalyst CS5 -->
<s:states>
<s:State name="up"/>
<s:State name="overed"/>
</s:states>
<s:Group id="group2" x="0" blendMode="normal" x.overed="18" y.overed="0" y.up="10">
<s:Group id="group1" x="0" y="132" blendMode="normal" d:userLabel="shadowAndReflect" y.overed="142">
<s:Group id="group3" x="32" y="8" ai:spare="1" d:userLabel="reflectGroup" luminosityClip="true" maskType="luminosity"
x.overed="14">
<s:mask>
<s:Group x="-9.761" y="-2.774">
<s:Rect width="159.361" height="149.932">
<s:fill>
<s:LinearGradient scaleX="149.932" x="79.6807" y="0" rotation="90">
<s:GradientEntry color="#FFFFFF" ratio="0"/>
<s:GradientEntry ratio="1"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
</s:Group>
</s:mask>
<s:Rect width="140" height="140" x="0" y="0" alpha="0.77" d:id="6" d:userLabel="reflect" flm:variant="5"
height.up="72">
<s:fill>
<s:LinearGradient scaleX="140.03" x="0" y="70.0146">
<s:GradientEntry color="#F5F7F9" ratio="0"/>
<s:GradientEntry color="#E0E2E4" ratio="0.026793"/>
<s:GradientEntry color="#D2D5D7" ratio="0.0560065"/>
<s:GradientEntry color="#CCCFD1" ratio="0.0882261"/>
<s:GradientEntry color="#CACDCF" ratio="0.131868"/>
<s:GradientEntry color="#D7D9DB" ratio="0.895604"/>
<s:GradientEntry color="#E0E2E4" ratio="0.940392"/>
<s:GradientEntry color="#F5F7F9" ratio="1"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
</s:Group>
<s:BitmapImage id="bitmapimage1" source="@Embed('assets/itemShadow.png')" x="0" y="0" alpha="0.49" d:id="7"
d:userLabel="shadow" flm:variant="7" height.overed="17" smooth="true" width.overed="165"/>
</s:Group>
<s:Group id="group4" x="32" y="0" d:userLabel="Item"
x.overed="14">
<s:Rect width="140" height="140" x="0" y="0" d:id="8" d:userLabel="background" flm:variant="8">
<s:fill>
<s:LinearGradient scaleX="140.03" x="0" y="70.0142">
<s:GradientEntry color="#F5F7F9" ratio="0"/>
<s:GradientEntry color="#E0E2E4" ratio="0.026793"/>
<s:GradientEntry color="#D2D5D7" ratio="0.0560065"/>
<s:GradientEntry color="#CCCFD1" ratio="0.0882261"/>
<s:GradientEntry color="#CACDCF" ratio="0.131868"/>
<s:GradientEntry color="#D7D9DB" ratio="0.895604"/>
<s:GradientEntry color="#E0E2E4" ratio="0.940392"/>
<s:GradientEntry color="#F5F7F9" ratio="1"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Path
data="M 102.053 0 L 9.976 0 C 4.475 0 0 4.537 0 10.112 L 0 11.319 C 0 16.898 4.475 21.438 9.976 21.438 L 102.053 21.438 C 107.551 21.438 112.025 16.898 112.025 11.319 L 112.025 10.112 C 112.025 4.537 107.551 0 102.053 0 L 102.053 0 Z"
x="13.661" y="110.022" alpha="0.55" d:id="10" d:userLabel="backgroundAmount" flm:variant="10" winding="nonZero" includeIn="overed">
<s:fill>
<s:LinearGradient scaleX="21.438" x="56.013" y="0" rotation="90">
<s:GradientEntry color="#929EA1" ratio="0"/>
<s:GradientEntry color="#A8B1B3" ratio="0.11423"/>
<s:GradientEntry color="#B7BEBF" ratio="0.229583"/>
<s:GradientEntry color="#C0C6C7" ratio="0.346308"/>
<s:GradientEntry color="#C3C8C9" ratio="0.467033"/>
<s:GradientEntry color="#C4C9CA" ratio="0.704834"/>
<s:GradientEntry color="#CBCFD0" ratio="0.823039"/>
<s:GradientEntry color="#DEE0E1" ratio="0.920046"/>
<s:GradientEntry color="#FFFFFF" ratio="1"/>
</s:LinearGradient>
</s:fill>
</s:Path>
<s:Rect width="140" height="34" x="0" y="0" d:id="12" d:userLabel="backgroundHeader" flm:variant="12">
<s:fill>
<s:LinearGradient scaleX="139.94" x="0" y="16.8472">
<s:GradientEntry color="#004F4F" ratio="0"/>
<s:GradientEntry color="#003A32" ratio="0.0489363"/>
<s:GradientEntry color="#002B1C" ratio="0.0961603"/>
<s:GradientEntry color="#00210E" ratio="0.141323"/>
<s:GradientEntry color="#001D09" ratio="0.181319"/>
<s:GradientEntry color="#002715" ratio="0.316858"/>
<s:GradientEntry color="#003B2F" ratio="0.510989"/>
<s:GradientEntry color="#003224" ratio="0.662366"/>
<s:GradientEntry color="#001D09" ratio="0.857143"/>
<s:GradientEntry color="#002D20" ratio="0.915877"/>
<s:GradientEntry color="#004F4F" ratio="1"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Rect width="140" height="1" x="0" y="0" d:userLabel="headerPath">
<s:fill>
<s:SolidColor color="#008F96"/>
</s:fill>
</s:Rect>
</s:Group>
</s:Group>
</s:ItemRenderer>