Status of this document: Experimental
Motion expression
A motion expression is functional, syntactic sugar for the creation and configuration of plans.
1. Expression namespace
expression = Tween()
Motion expressions begin with a namespace.
The purpose of a namespace is to define a lexical scope for a particular set of term functions. This allows an ecosystem of namespaces to exist where some namespaces may have similar or identical term functions. A namespace should document what its term functions do.
Pseudo-code example implementation of a tween namespace:
Tween: Family {
fn fadeIn(...) -> Term
fn fadeOut(...) -> Term
fn moveTo(...) -> Term
fn rotateBy(...) -> Term
...
}
Syntax: Families start with a capital letter. Families are nouns.
Families have term functions. A term function initiates the description of plans.
2. Terms
expression = Tween().fadeIn()
The purpose of a family's term function is to create and initialize a set of related plans.
Pseudo-code example implementation of a fade in term function:
fn Tween.fadeIn() -> TweenTerm {
return TweenTerm(previousTerm: self.previousTerm, work: function() {
let animation = TweenAnimation("opacity")
animation.from = 0
animation.to = 1
return [animation]
})
}
Term functions must be created with functions. It may be tempting to define argument-less term functions as dynamic properties (if your language supports this). This would allow motion expressions like
Tween().fadeIn
. We explicitly discourage this. Ensure that every term function is a function in order to provide consistency to the engineer.
Syntax: Term functions start with a lowercase letter. Term functions are verbs. Adjectives should be prepended with an auxiliary verb (e.g. do/be/has) to make a verb phrase.
A term function creates an instance of a term. The term carries the initialized plans.
The purpose of a term is to define a lexical scope for a particular set of modifier functions. This allows a term to define modifier functions that are relevant to its plans. A term should document what its modifier functions do.
Pseudo-code example implementation of a tween term:
TweenTerm : Term {
fn withEasingCurve(curve) -> TweenTerm
fn withDuration(seconds) -> TweenTerm
...
}
Syntax: Terms start with a capital letter. Terms functions are nouns.
Terms have modifier functions. A modifier function modifies the plans in the term.
3. Modifiers
expression = Tween().fadeIn().withEasingCurve(easeOut)
The purpose of a Term's modifier function is to configure the plans in that term.
Pseudo-code example implementation of an easing curve modifier function:
fn TweenTerm.withEasingCurve(curve) -> TweenTerm { {
return TweenTerm(previousTerm: self.previousTerm, work: function() {
let plans = self.work()
for plan in plans {
plan.easingCurve = curve
}
return plans
})
}
Modifier functions must return a new instance of the term. It may be tempting to directly modify the plans in the term and return
self
. This is not allowed and will break immutability.
Syntax: Modifier functions start with a lowercase letter. Modifier functions begin with a preposition (e.g. with/to/after).
4. Chaining
expression = Gesture().rotatable().withMaxDelta(45).and.draggable()
A motion expression is immutable, which allows the engineer to chain terms and modifications. Motion expressions can be stored and extended without affecting previous instances.
Term chaining: Terms within a family can be chained together by using the special and
object.
draggable = Gesture().draggable()
target.addPlans(draggable.plans())
# Reusing the draggable expression
target.addPlans(draggable.and.rotatable().plans())
Note:
and
is a new instance of the family object.and
can be a dynamic property (if your language supports this) or a read-only field.
Modifier chaining: Modifiers within a term can be chained together. Modifications must be applied to the term in the order in which they were specified.
fadeIn = Tween().fadeIn().withEasingCurve(easeIn)
elementA.addPlans(fadeIn.plans())
# Reusing the fadeIn expression
elementB.addPlans(fadeIn.withEasingCurve(easeOut).from(.5f).plans())
5. Generating plans
plans = expression.plans()
Every motion expression must be resolvable into an array of plans.
Successive invocations of this method should generate new plans.