276 lines
11 KiB
Racket
276 lines
11 KiB
Racket
#lang scribble/doc
|
|
@(require "ss.ss"
|
|
(for-label slideshow/play
|
|
slideshow/code))
|
|
|
|
@title[#:tag "play"]{Animations}
|
|
|
|
@defmodule[slideshow/play]{The @racketmodname[slideshow/play] module
|
|
provides tools for generating animations as multiple, automatically
|
|
advanced slides.}
|
|
|
|
Many of the tools are based on a function that takes a number between
|
|
@racket[0.0] and @racket[1.0] inclusive and produces a
|
|
pict. The pict produced for the input @racket[0.0] is
|
|
the starting image of the animation, and the pict produced for
|
|
@racket[1.0] is the ending image, while intermediate values produced
|
|
intermediate images. For example,
|
|
|
|
@racketblock[
|
|
(lambda (n)
|
|
(cellophane (t "Hello") n))
|
|
]
|
|
|
|
corresponds to an animation that fades in the word ``Hello.''
|
|
|
|
@; --------------------------------------------------
|
|
|
|
@section{Generating Animated Slides}
|
|
|
|
@defproc[(play [gen ((real-in 0.0 1.0) . -> . pict?)]
|
|
[#:steps steps exact-positive-integer? 10]
|
|
[#:delay delay-secs real? 0.05]
|
|
[#:skip-first? skip-first? any/c #f]
|
|
[#:title title (or/c string? pict? #f
|
|
((real-in 0.0 1.0) . -> . (or/c string? pict? #f)))
|
|
#f]
|
|
[#:name name (or/c string? #f
|
|
((real-in 0.0 1.0) . -> . (or/c string? #f)))
|
|
title]
|
|
[#:layout layout (or/c 'auto 'center 'top 'tall) 'auto])
|
|
void?]{
|
|
|
|
Generates @math{@racket[steps]+1} slides by calling @racket[gen] on
|
|
equally-spaced values from @racket[0.0] (inclusve) to @racket[1.0]
|
|
(exclusive). Except for the first of the slides, each slide has a
|
|
timeout of @racket[delay-secs], so that the next slide appears
|
|
automatically.
|
|
|
|
Normally, @racket[play] is called via @racket[play-n], which
|
|
effectively calls @racket[gen] on @racket[1.0] without a timeout to
|
|
complete the animation and stop the auto-advance of slides. The
|
|
@racket[play-n] function also manages with multi-step animations.
|
|
|
|
If @racket[skip-first?] is @racket[#f], then one fewer slide is
|
|
generated, because @racket[gen] is not called on @racket[0.0].
|
|
|
|
The @racket[title], @racket[name], and @racket[layout] arguments are
|
|
passed on to @racket[slide], at least when @racket[title] and/or
|
|
@racket[name] are not functions. When @racket[title] or @racket[name]
|
|
is a function, the function is applied to the value used to produce
|
|
the slide content, and the resulting title or name is passed on to
|
|
@racket[slide].
|
|
|
|
In condensed mode (i.e., when @racket[condense?] is @racket[#t]), any
|
|
slide that would be registered with a timeout is instead skipped.}
|
|
|
|
|
|
@defproc[(play-n [gen* (() (listof (real-in 0.0 1.0)) . ->* . pict?)]
|
|
[#:steps steps (or/c exact-positive-integer?
|
|
(improper-listof exact-positive-integer?))
|
|
10]
|
|
[#:delay delay-secs real? 0.05]
|
|
[#:skip-first? skip-first? any/c #f]
|
|
[#:skip-last? skip-last? any/c #f]
|
|
[#:title title (or/c string? pict? #f
|
|
((real-in 0.0 1.0) . -> . (or/c string? pict? #f)))
|
|
#f]
|
|
[#:name name (or/c string? #f
|
|
((real-in 0.0 1.0) . -> . (or/c string? #f)))
|
|
title]
|
|
[#:layout layout (or/c 'auto 'center 'top 'tall) 'auto])
|
|
void?]{
|
|
|
|
Generates a sequence of slides by calling @racket[gen*] with, for each
|
|
of its arguments, numbers from @racket[0.0] to @racket[1.0]. If
|
|
@racket[gen*] accepts @math{n} arguments, then result is a sequence of
|
|
animations with a pause (i.e., not auto-advanced) between each of
|
|
@math{n} segments.
|
|
|
|
If @racket[gen*] accepts a single argument, then @racket[play-n] is
|
|
like @racket[play], except that @racket[gen*] is also called with
|
|
@racket[1.0] to generate a slide with no timeout. If @racket[gen*]
|
|
accepts multiple arguments, then slides are generated by calling
|
|
@racket[gen*] with the first argument varying from @racket[0.0] to
|
|
@racket[1.0] while all other arguments are @racket[0.0]. Then, the
|
|
first argument is held at @racket[1.0] while the second argument varies
|
|
from @racket[0.0] to @racket[1.0], and so on.
|
|
|
|
For example,
|
|
|
|
@racketblock[
|
|
(play-n
|
|
(lambda (n1 n2)
|
|
(cellophane (t "Hello")
|
|
(* n1 (- 1.0 n2)))))
|
|
]
|
|
|
|
generates an animation to fade in the word ``Hello,'' and then pauses
|
|
for a manual advance, and then fades ``Hello'' back out.
|
|
|
|
If @racket[skip-first?] is @racket[#t], then the very first slide of
|
|
the sequence is skipped. Similarly, if @racket[skip-last?] is
|
|
@racket[#t], then the last slide of the sequence is skipped.
|
|
|
|
The @racket[steps] argument controls how many steps happen in each
|
|
phase on the animation. If it is a number, then that number is used for
|
|
each phase. If it is a pair of two numbers, then the first number is used
|
|
for the first phase, and the second number is used for the rest of the
|
|
phases. Similarly, if it is @racket[(cons num_1 (cons num_2 num_3))],
|
|
@racket[num_1] and @racket[num_2] are used for the first two phases
|
|
and @racket[num_3] is used for the rest.
|
|
|
|
The @racket[delay-msecs], @racket[title],
|
|
@racket[name], and @racket[layout] arguments are passed on to
|
|
@racket[play] for each of the @math{n} segments of animation.}
|
|
|
|
|
|
@defproc[(animate-slide [element (flat-rec-contract elem/c
|
|
(or/c pict? 'next 'alts
|
|
(listof (listof elem/c))))]
|
|
...)
|
|
(() (listof (real-in 0.0 1.0)) . ->* . pict?)]{
|
|
|
|
Accepts slide content similar to @racket[slide] with @racket['next]
|
|
and @racket['alts] and produces a procedure suitable for use with
|
|
@racket[play-n]. The result is similar to using @racket[slide], but
|
|
with fades for @racket['next] and @racket['alts] transitions (to
|
|
better fit the style, perhaps, of surrounding animations).}
|
|
|
|
@; --------------------------------------------------
|
|
|
|
@section{Animation Helpers}
|
|
|
|
@defproc[(fade-pict [n (real-in 0.0 1.0)] [p1 pict?] [p2 pict?]
|
|
[#:combine combine (pict? pict? . -> . pict?) cc-superimpose])
|
|
pict?]{
|
|
|
|
Interpolates @racket[p1] and @racket[p2], where the result with
|
|
@racket[n] as @racket[0.0] is @racket[p1], and the result with
|
|
@racket[n] as @racket[1.0] is @racket[p2]. For intermediate points,
|
|
@racket[p1] fades out while @racket[p2] fades in as @racket[n] changes
|
|
from @racket[0.0] to @racket[1.0]. At the same time, the width and
|
|
height of the generated pict are intermediate between
|
|
@racket[p1] and @racket[p2], and the relative baselines and last
|
|
pict correspondingly morph within the bounding box.
|
|
|
|
The @racket[combine] argument determines how @racket[p1] and
|
|
@racket[p2] are aligned for morphing. For example, if @racket[p1] and
|
|
@racket[p2] both contain multiple lines of text with the same line
|
|
height but different number of lines, then using
|
|
@racket[ctl-superimpose] would keep the ascent line in a fixed
|
|
location relative to the top of the resulting pict as the rest of the
|
|
shape morphs around it.}
|
|
|
|
@defproc[(fade-around-pict [n (real-in 0.0 1.0)]
|
|
[p1 pict?]
|
|
[make-p2 (pict? . -> . pict?)])
|
|
pict?]{
|
|
|
|
Similar to @racket[fade-pict], but the target is not a fixed
|
|
@racket[_p2], but instead a function @racket[make-p2] that takes a
|
|
@racket[launder]ed @racket[ghost] of @racket[p1] and places it into a
|
|
larger scene. Also, @racket[p1] does not fade out as @racket[n]
|
|
increases; instead, @racket[p1] is placed wherever its ghost appears
|
|
in the result of @racket[make-p2].
|
|
|
|
For example,
|
|
|
|
@RACKETBLOCK[
|
|
(lambda (n)
|
|
(fade-around-pict n
|
|
(code x)
|
|
(lambda (g) (code (+ #,x 1)))))
|
|
]
|
|
|
|
animates the wrapping of @racket[x] with a @racket[(+ .... 1)] form.}
|
|
|
|
@defproc[(slide-pict [base pict?]
|
|
[p pict?]
|
|
[p-from pict?]
|
|
[p-to pict?]
|
|
[n (in-real 0.0 1.0)])
|
|
pict?]{
|
|
|
|
Pins @racket[p] onto @racket[base], sliding from @racket[p-from] to
|
|
@racket[p-to] (which are picts within @racket[base]) as
|
|
@racket[n] goes from @racket[0.0] to @racket[1.0]. The top-left
|
|
locations of @racket[p-from] and @racket[p-to] determine the placement
|
|
of the top-left of @racket[p].
|
|
|
|
The @racket[p-from] and @racket[p-to] picts are typically
|
|
@racket[launder]ed @racket[ghost]s of @racket[p] within @racket[base],
|
|
but they can be any picts within @racket[base].}
|
|
|
|
@; --------------------------------------------------
|
|
|
|
@section{Merging Animations}
|
|
|
|
@defproc[(sequence-animations [gen ((real-in 0.0 1.0) . ->* . pict?)]
|
|
...)
|
|
((real-in 0.0 1.0) . ->* . pict?)]{
|
|
|
|
Converts a list of @racket[gen] functions into a single function that
|
|
uses each @racket[gen] in sequence.}
|
|
|
|
@defproc[(reverse-animations [gen ((real-in 0.0 1.0) . ->* . pict?)]
|
|
...)
|
|
((real-in 0.0 1.0) . ->* . pict?)]{
|
|
|
|
Converts a list of @racket[gen] functions into a single function that
|
|
run @racket[(sequence-animations gen ...)] in reverse.}
|
|
|
|
@; --------------------------------------------------
|
|
|
|
@section{Stretching and Squashing Time}
|
|
|
|
@deftogether[(
|
|
@defproc[(fast-start [n (in-real 0.0 1.0)]) (in-real 0.0 1.0)]
|
|
@defproc[(fast-end [n (in-real 0.0 1.0)]) (in-real 0.0 1.0)]
|
|
@defproc[(fast-edges [n (in-real 0.0 1.0)]) (in-real 0.0 1.0)]
|
|
@defproc[(fast-middle [n (in-real 0.0 1.0)]) (in-real 0.0 1.0)]
|
|
)]{
|
|
|
|
Monotonically but non-uniformly maps @racket[n] with fixed
|
|
points at @racket[0.0] and @racket[1.0].
|
|
|
|
The @racket[fast-start] mapping is convex, so that
|
|
|
|
@racketblock[(slide-pict _base p _p1 _p2 (fast-start n))]
|
|
|
|
appears to move quickly away from @racket[_p1] and then slowly as it
|
|
approaches @racket[_p2], assuming that @racket[n] increases uniformly.
|
|
|
|
The @racket[fast-end] mapping is concave, so that
|
|
|
|
@racketblock[(slide-pict _base _p _p1 _p2 (fast-start _n))]
|
|
|
|
appears to move slowly away from @racket[_p1] and then quicly as it
|
|
approaches @racket[_p2], assuming that @racket[_n] increases uniformly.
|
|
|
|
The @racket[fast-edges] mapping is convex at first and concave at the
|
|
end, so that
|
|
|
|
@racketblock[(slide-pict _base _p _p1 _p2 (fast-start _n))]
|
|
|
|
appears to move quickly away from @racket[_p1], then more slowly, and
|
|
then quickly again near @racket[_p2], assuming that @racket[_n] increases
|
|
uniformly.
|
|
|
|
The @racket[fast-middle] mapping is concave at first and convex at the
|
|
end, so that
|
|
|
|
@racketblock[(slide-pict _base _p _p1 _p2 (fast-start _n))]
|
|
|
|
appears to move slowly away from @racket[_p1], then more quickly, and
|
|
then slowly again near @racket[_p2], assuming that @racket[_n] increases
|
|
uniformly.}
|
|
|
|
@defproc[(split-phase [n (in-real 0.0 1.0)])
|
|
(values (in-real 0.0 1.0) (in-real 0.0 1.0))]{
|
|
|
|
Splits the progression of @racket[n] from @racket[0.0] to @racket[1.0]
|
|
into a progression from @racket[(values 0.0 0.0)] to @racket[(values
|
|
1.0 0.0)] and then @racket[(values 1.0 0.0)] to @racket[(values 1.0
|
|
1.0)].}
|