Layout

Layout in Famo.us is managed by Transforms and Modifiers. Transforms
encapsulate the CSS3 transform specification
which define the position, orientation and distortion of a node. While a
Transform is a static object, representing a snapshot in time, a Modifier
encapsulates varying Transforms over time. Modifiers also bundle layout
properties like size, origin and align that allow for alignment and justification.
Modifiers can be directly added to the Famo.us Scene Graph, acting on the nodes beneath them.

Overview

Transforms

Transforms correspond directly to a CSS3 transformation matrix.
A Transform combines translation, rotation, scale and skew components
into a 16-element array.

Component Description Default Value
translation [x,y,z] [0,0,0]
rotation [x,y,z] [0,0,0]
scale [x,y,z] [1,1,1]
skew [x,y,z] [0,0,0]

The Transform class provides methods to break down a Transform into these
components, and also to build up Transforms from them.

Transform Primitives

All Transforms are built up from these Transforms primitives

Transform Return Value Components Description
Transform.identity
[1,      0,      0,      0, 
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [0,0,0]
scale : [1,1,1]
skew : [0,0,0]
The identity transformation. This has no affect on layout.
Transform.translate(x,y,z)
[1,      0,      0,      0, 
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1]
 translation : [x,y,z] 
rotation : [0,0,0]
scale : [1,1,1]
skew : [0,0,0]
Translates along the x-, y- and z-axes.
Transform.scale(x,y,z)
[x,      0,      0,      0, 
0, y, 0, 0,
0, 0, z, 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [0,0,0]
scale : [x,y,z]
skew : [0,0,0]
Scales in the x-, y-, and z-axes relative to an origin.
Transform.rotateX(θ)
[1,      0,      0,      0, 
0, cos(θ),-sin(θ), 0,
0, sin(θ), cos(θ), 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [θ,0,0]
scale : [1,1,1]
skew : [0,0,0]
Rotates clock-wise in the y/z-plane (along the x-axis).
Transform.rotateY(θ)
[cos(θ), 0,      sin(θ), 0, 
0, 1, 0, 0,
sin(θ), 0, cos(θ), 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [0,θ,0]
scale : [1,1,1]
skew : [0,0,0]
Rotates clock-wise in the x/z-plane (along the y-axis).
Transform.rotateZ(θ)
[cos(θ),-sin(θ), 0,      0, 
sin(θ), cos(θ), 0, 0,
0, 0, 0, 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [0,0,θ]
scale : [1,1,1]
skew : [0,0,0]
Rotates clock-wise in the x/y-plane (along the z-axis).
Transform.skewX(θ)
[1,      0,      0,      0, 
tan(θ), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [0,0,0]
scale : [1,1,1]
skew : [θ,0,0]
Skews along the x-axis. θ gives the angle between the top and left sides.
Transform.skewY(θ)
[1,      tan(θ), 0,      0, 
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1]
 translation : [0,0,0] 
rotation : [0,0,0]
scale : [1,1,1]
skew : [0,θ,0]
Skews along the y-axis. θ gives the angle between the top and left sides.
Transform.inFront
[1,      0,      0,      0, 
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0.001, 1]
 translation : [0,0, 0.001] 
rotation : [0,0,0]
scale : [1,1,1]
skew : [0,0,0]
A small z-translation forwards toward the viewer. Useful for layering.
Transform.behind
[1,      0,      0,      0, 
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, -0.001, 1]
 translation : [0,0,-0.001] 
rotation : [0,0,0]
scale : [1,1,1]
skew : [0,0,0]
A small z-translation backwards away from the viewer. Useful for layering.

Building Transforms

Complex Transforms can be built up from the above primitives in two ways

For this guide, we will focus on the latter. See the Scene Graph guide for the former.
Transform composition is done through the .multiply method. Though .multiply
is useful for composing arbitrary transforms, it is heavyweight for simple compositions,
such as composing a translation on a rotation. Famo.us provides optimized methods for these
use cases.

Method Description
Transform.multiply(T1,T2) Equivalent to applying a transform T1 on top of a transform T2. Note: Order matters!
Transform.thenMove(T,p) Translates a transform T by p = [x,y,z].
Equivalent to Transform.multiply(Transform.translate(x,y,z), T).
Transform.moveThen(p,T) Translates by p = [x,y,z] prior to applying the Transform T.
Equivalent to Transform.multiply(T, Transform.translate(x,y,z)).
Transform.thenScale(T,s) Scales a transform T by s = [x,y,z].
Equivalent to Transform.multiply(Transform.scale(x,y,z), T).
Transform.aboutOrigin(T,p) Shifts the origin of a Transform to a new point p = [x,y,z] in pixels from the top/left.
For instance,
Transform.aboutOrigin(Transform.rotateZ(Math.PI/4), [50,100,0])
would rotate a renderable around a pivot at [50,100,0] instead of the top-left.
Transform.average(T1,T2,w) Returns a weighted average between the two transforms with weight w by averaging their rotate, translate, scale and skew components.

Breaking Down Transforms

Complex Transforms can be broken up into their individual components via the methods

Method Description
Transform.getTransform(T) Returns the translate components of the Transform T.
Transform.interpret(T) Returns an object with translate, rotate, scale, and skew keys.
Note: This is a relatively expensive operation and its use is discouraged.

Modifiers

Transforms by themselves can't be added to the Scene Graph. In order to apply
a Transform to a renderable, a Modifier is necessary. A Modifier is a node,
and can be directly added to the Scene Graph. A Modifier is best thought of as
a shell that accepts primitives, like Transforms that can modify the nodes (which
can be other Modifiers) below them either by applying a Transform, opacity
or alignment.

Below we demonstrate applying a translational Transform to a Surface 100px
in the x- and y- directions.

var surface = new Surface({
    size: [50, 50],
    properties: { background: 'red' }
});

var modifier = new Modifier({
    transform : Transform.translate(100, 100, 0)
});

context.add(modifier).add(surface);

Any Transform can be applied this way. As mentioned above, Modifiers have
convenience properties to affect alignment as well. Before we introduce this
concept, we will need to understand Famo.us sizing primitives: size, origin
and align.

Alignment & Sizing

In addition to positioning, Modifiers can also align renderables relative to a
size context. This allows users to "center" objects, or "left-justify" them, etc.
To understand how this is accomplished in Famo.us, we introduce the concepts of
size, align and origin in Modifier.

Size

Size defines a bounding-box for content. Nodes in the Scene Graph below sized
Modifiers can use this bounding size to define their container's size. The
simplest example being a Surface with size = [undefined, undefined] that
takes the size of a parenting Modifier.

In the following example, the created surface will have a size of [200, 100],
even though its width was original set to undefined.

  var sizeModifier = new Modifier({size: [200, 200]});

  var surface = new Surface({
    size: [undefined, 100],
    properties: { background : 'red' }
  });

  context.add(sizeModifier).add(surface);

This concept extends beyond Surfaces to any Famo.us renderable, such as a View.
It is especially important in Views that handle layout, such as GridLayout or
SequentialLayout. These layouts have no intrinsic notion of a boundind-box, and
need to have their size defined for them in a parenting Modifier.

Align

Layout is often easily described in terms of "top left", "bottom right", etc.
Align is a way of defining an alignment relative to a bounding-box given by a
size. Align is given by an array [x, y] of proportions between 0 and 1.
The default value for the align is top left, or [0, 0]. The following table
summarizes common alignment values.

Align Values Meaning
[0, 0] Top Left
[0.5, 0] Top Center
[1, 0] Top Right
[0.5, 0.5] Center Center
[1, 0.5] Center Right
[0.5, 1] Bottom Center
[1, 1] Bottom Right

For example, below is a way of aligning a Surface's top/left corner to the
left center of within a bounding-box of size [100, 100].

    ////////////////////////////////////
    //
    //       100 x 100 bounding box
    //                  ↙            
    // ┌──────────────┐
    // │              │
    // │              │
    // ├──────┐       │
    // │      │       │
    // ├──────┘       │
    // └──────────────┘
    //
    ////////////////////////////////////

    var surface = new Surface({
        size: [50, 30],
        properties: { background: 'red' }
    });

    var sizeModifier = new Modifier({
        size: [100, 100]
    });

    var alignModifier = new Modifier({
        align: [0, 0.5]
    });

    context.add(sizeModifier).add(alignModifier).add(surface);

Origin

In the above example, we aligned the top left corner of the Surface. What if
we want to align a different location of the Surface, such as its center? This
is done by setting the origin property of a Modifier.

While alignment is relative to parenting size, origin is relative to the renderable.
The combination of the two places the renderable's origin at the location of the
parent's alignment. This is best described visually:

    align point (+)       origin point ()
  ┌─────────────────┐        ┌───────┐
                                  
                                 
          +                └───────┘
                          (renderable)
                    
  └─────────────────┘
     (bounding box)

            align & origin ()
           ┌─────────────────┐
                 ┌───────┐  
                          
                         
                 └───────┘  
                            
           └─────────────────┘
     (renderable inside bounding box)

For example, if we want to have a Surface centered within some sized context,
we would need to place the Surface's center, on the sized context's center. In
code,

    ////////////////////////////////
    //
    //        100 x 100 bounding box
    //                ↙
    // ┌────────────┐
    // │  ┌──────┐  │
    // │  │      │  │
    // │  │      │  │
    // │  └──────┘  │
    // └────────────┘
    //
    ////////////////////////////////

    var surface = new Surface({
        size: [70, 70],
        properties: { background: 'red' }
    });

    var sizeModifier = new Modifier({
        size: [100, 100]
    });

    var centerModifier = new Modifier({
        align: [0.5, 0.5],
        origin: [0.5, 0.5]
    });

    context
        .add(sizeModifier)
        .add(centerModifier)
        .add(surface);

The default value for the origin is top left, or [0, 0]. Note: The
sizeModifier must come before the alignModifier as alignment must reference
a parent size. These modifiers can not be combined into one.

Dynamic Layout

Thus far, we have only considered setting the transform, size, origin and
align parameters as a static property of a Modifier. However, each of these
properties can animate over time. This can be done in one of two methods

Push-based animations

The StateModifier class found in Famous/modifiers/StateModifier.js is a push-based
implementation. Here, StateModifier has the methods

method description
StateModifier.setTransform(T, definition, callback) Sets the transform state.
StateModifier.setOpacity(opacity, definition, callack) Sets the opacity state.
StateModifier.setSize(size, definition, callack) Sets the size state.
StateModifier.setOrigin(origin, definition, callack) Sets the origin state.
StateModifier.setAlign(align, definition, callack) Sets the align state.

A push-based example of animating a renderable is

var surface = new Surface({
    size: [70, 70],
    properties: { background: 'red' }
});

var stateModifier = new StateModifier({
    opacity: 1
});

context.add(stateModifier).add(surface);

// animate the opacity from 1 to 0 over 500ms using a linear easing curve
stateModifier.setOpacity(
    0,
    {curve: 'linear', duration : 500},
    function() { console.log('animation finished!') }
);

Pull-based animations

The Modifier class found in Famous/core/Modifier.js is a pull-based
implementation. The Modifier itself doesn't keep any state (like what the
current Transform is); instead that state is provided externally by a getter,
which is either a function that returns the state, or an object with a .get
method that returns the state. Modifier has the methods

method description
Modifier.transformFrom(transformGetter) The transform state is pulled from the transformGetter.
Modifier.opacityFrom(opacityGetter) The opacity state is pulled from the opacityGetter.
Modifier.sizeFrom(sizeGetter) The size state is pulled from the sizeGetter.
Modifier.originFrom(originGetter) The origin state is pulled from the originGetter.
Modifier.alignFrom(alignGetter) The align state is pulled from the alignGetter.

The same example above can be recreated in a pull-based way as

var surface = new Surface({
    size: [70, 70],
    properties: { background: 'red' }
});

var modifier = new Modifier();

context.add(modifier).add(surface);

// the opacityGetter
var opacityState = new Transitionable(1);

modifier.opacityFrom(opacityState);

// animate the opacity from 1 to 0 over 500ms using a linear easing curve
opacityState.set(
    0,
    {curve : 'linear', duration : 500},
    function(){ console.log('animation finished!'); }
);