racket/collects/unstable/scribblings/gui/pslide.scrbl

428 lines
16 KiB
Racket

#lang scribble/doc
@(require scribble/base
scribble/manual
scribble/eval
"../utils.rkt"
(for-label racket/base
slideshow
unstable/gui/ppict
unstable/gui/pslide
unstable/gui/pict))
@title[#:tag "ppict"]{Progressive Picts and Slides}
@unstable[@author+email["Ryan Culpepper" "ryanc@racket-lang.org"]]
@(define the-eval (make-base-eval))
@(the-eval '(require slideshow/pict unstable/gui/ppict unstable/gui/private/tag-pict))
@section[#:tag "ppicts"]{Progressive Picts}
@defmodule[unstable/gui/ppict]
A @deftech{progressive pict} or ``ppict'' is a kind of @racket[pict]
that has an associated ``pict placer,'' which generally represents a
position and alignment. New picts can be placed on the progressive
pict by calling @racket[ppict-add], and the placer can be updated by
calling @racket[ppict-go]. The @racket[ppict-do] form provides a
compact notation for sequences of those two operations.
@deftogether[[
@defform[(ppict-do base-expr ppict-do-fragment ...)]
@defform/subs[(ppict-do* base-expr ppic-do-fragment ...)
([ppict-do-fragment (code:line #:go placer-expr)
(code:line #:set pict-expr)
(code:line #:next)
(code:line #:alt (ppict-do-fragment ...))
(code:line elem-expr)])
#:contracts ([base-expr pict?]
[placer-expr placer?]
[pict-expr pict?]
[elem-expr (or/c pict? real? #f)])]]]{
Builds a pict (and optionally a list of intermediate picts)
progressively. The @racket[ppict-do] form returns only the final pict;
any uses of @racket[#:next] are ignored. The @racket[ppict-do*] form
returns two values: the final pict and a list of all partial picts
emitted due to @racket[#:next] (the final pict is not included).
A @racket[#:go] fragment changes the current placer. A @racket[#:set]
fragment replaces the current pict state altogether with a new
computed pict. A @racket[#:next] fragment saves a pict including only
the contents emitted so far (but whose alignment takes into account
picts yet to come). A @racket[#:alt] fragment saves the current pict
state, executes the sub-sequence that follows, saves the result (as if
the sub-sequence ended with @racket[#:next]), then restores the saved
pict state before continuing.
The @racket[elem-expr]s are interpreted by the current placer. A
numeric @racket[elem-expr] usually represents a spacing change, but
some placers do not support them. A spacing change only affects added
picts up until the next placer is installed; when a new placer is
installed, the spacing is reset, usually to @racket[0].
The @racket[ppict-do-state] form tracks the current state of the
pict. It is updated before a @racket[#:go] or @racket[#:set] fragment
or before a sequence of @racket[elem-expr]s. It is not updated in the
middle of a chain of @racket[elem-expr]s, however.
@examples[#:eval the-eval
(define base
(ppict-do (colorize (rectangle 200 200) "gray")
#:go (coord 1/2 1/2 'cc)
(colorize (hline 200 1) "gray")
#:go (coord 1/2 1/2 'cc)
(colorize (vline 1 200) "gray")))
base
]
The use of @racket[ppict-do] in the defnition of @racket[base] above
is equivalent to
@racketblock[
(let* ([pp (colorize (rectangle 200 200) "gray")]
[pp (ppict-go pp (coord 1/2 1/2 'cc))]
[pp (ppict-add pp (colorize (hline 200 1) "gray"))]
[pp (ppict-go pp (coord 1/2 1/2 'cc))]
[pp (ppict-add pp (colorize (vline 1 200) "gray"))])
pp)
]
@examples[#:eval the-eval
(define circles-down-1
(ppict-do base
#:go (grid 2 2 2 1 'ct)
10
(circle 20)
(circle 20)
30
(circle 20)))
circles-down-1
(define circles-down-2
(ppict-do circles-down-1
(colorize (circle 20) "red")
40
(colorize (circle 20) "red")))
(code:line (inset circles-down-2 20) (code:comment "draws outside its bounding box"))
(inset (clip circles-down-2) 20)
(ppict-do base
#:go (coord 0 0 'lt)
(tag-pict (circle 20) 'circA)
#:go (coord 1 1 'rb)
(tag-pict (circle 20) 'circB)
#:set (let ([p ppict-do-state])
(pin-arrow-line 10 p
(find-tag p 'circA) rb-find
(find-tag p 'circB) lt-find)))
(let-values ([(final intermediates)
(ppict-do* base
#:go (coord 1/4 1/2 'cb)
(text "shapes:")
#:go (coord 1/2 1/2 'lb)
#:alt [(circle 20)]
#:alt [(rectangle 20 20)]
(text "and more!"))])
(append intermediates (list final)))
]
More examples of @racket[ppict-do] are scattered throughout this
section.
}
@defidform[ppict-do-state]{
Tracks the current state of a @racket[ppict-do] or @racket[ppict-do*]
form.
}
@defproc[(ppict? [x any/c]) boolean?]{
Returns @racket[#t] if @racket[x] is a @tech{progressive pict},
@racket[#f] otherwise.
}
@defproc[(ppict-go [p pict?] [pl placer?]) ppict?]{
Creates a @tech{progressive pict} with the given base pict @racket[p]
and the placer @racket[pl].
}
@deftogether[[
@defproc[(ppict-add [pp ppict?]
[elem (or/c pict? real? #f 'next)] ...)
pict?]
@defproc[(ppict-add* [pp ppict?]
[elem (or/c pict? real? #f 'next)] ...)
(values pict? (listof pict?))]]]{
Creates a new pict by adding each @racket[elem] pict on top of
@racket[pp] according to @racket[pp]'s placer. The result pict may or
may not be a @tech{progressive pict}, depending on the placer
used. The @racket[ppict-add] function only the final pict; any
occurrences of @racket['next] are ignored. The @racket[ppict-add*]
function returns two values: the final pict and a list of all partial
picts emitted due to @racket['next] (the final pict is not included).
An @racket[elem] that is a real number changes the spacing for
subsequent additions. A @racket[elem] that is @racket[#f] is
discarded; it is permitted as a convenience for conditionally
including sub-picts. Note that @racket[#f] is not equivalent to
@racket[(blank 0)], since the latter will cause spacing to be added
around it.
}
@defproc[(placer? [x any/c]) boolean?]{
Returns @racket[#t] if @racket[x] is a placer, @racket[#f] otherwise.
}
@defproc[(refpoint-placer? [x any/c]) boolean?]{
Returns @racket[#t] if @racket[x] is a placer based on a reference
point, @racket[#f] otherwise.
}
@defproc[(coord [rel-x real?]
[rel-y real?]
[align (or/c 'lt 'ct 'rt 'lc 'cc 'rc 'lb 'cb 'rb) 'cc]
[#:abs-x abs-x real? 0]
[#:abs-y abs-y real? 0]
[#:compose composer procedure? #, @elem{computed from @racket[align]}])
refpoint-placer?]{
Returns a placer that places picts according to @racket[rel-x] and
@racket[rel-y], which are interpeted as fractions of the width and
height of the base @tech{progressive pict}. That is, @racket[0],
@racket[0] is the top left corner of the base's bounding box, and
@racket[1], @racket[1] is the bottom right. Then @racket[abs-x] and
@racket[abs-y] offsets are added to get the final reference point.
Additions are aligned according to @racket[align], a symbol whose name
consists of a horizontal alignment character followed by a vertical
alignment character. For example, if @racket[align] is @racket['lt],
the pict is placed so that its left-top corner is at the reference
point; if @racket[align] is @racket['rc], the pict is placed so that
the center of its bounding box's right edge coincides with the
reference point.
By default, if there are multiple picts to be placed, they are
vertically appended, aligned according to the horizontal component of
@racket[align]. For example, if @racket[align] is @racket['cc], the
default @racket[composer] is @racket[vc-append]; for @racket['lt], the
default @racket[composer] is @racket[vl-append]. The spacing is
initially @racket[0].
@examples[#:eval the-eval
(ppict-do base
#:go (coord 1/2 1/2 'rb)
(colorize (circle 20) "red")
#:go (coord 1/2 1/2 'lt)
(colorize (circle 20) "darkgreen"))
(ppict-do base
#:go (coord 1 0 'rt #:abs-x -5 #:abs-y 10)
50 (code:comment "change spacing")
(text "abc")
(text "12345")
0 (code:comment "and again")
(text "ok done"))
(ppict-do base
#:go (coord 0 0 'lt #:compose ht-append)
(circle 10)
(circle 20)
(circle 30))
]
}
@defproc[(grid [cols exact-positive-integer?]
[rows exact-positive-integer?]
[col exact-integer?]
[row exact-integer?]
[align (or/c 'lt 'ct 'rt 'lc 'cc 'rc 'lb 'cb 'rb) 'cc]
[#:abs-x abs-x real? 0]
[#:abs-y abs-y real? 0]
[#:compose composer procedure? #, @elem{computed from @racket[align]}])
refpoint-placer?]{
Returns a placer that places picts according to a position in a
virtual grid. The @racket[row] and @racket[col] indexes are numbered
starting at @racket[1].
Uses of @racket[grid] can be translated into uses of @racket[coord],
but the translation depends on the alignment. For example,
@racket[(grid 2 2 1 1 'lt)] is equivalent to @racket[(coord 0 0 'lt)],
but @racket[(grid 2 2 1 1 'rt)] is equivalent to @racket[(coord 1/2 0 'rt)].
@examples[#:eval the-eval
(define none-for-me-thanks
(ppict-do base
#:go (grid 2 2 1 1 'lt)
(text "You do not like")
(colorize (text "green eggs and ham?") "darkgreen")))
none-for-me-thanks
(ppict-do none-for-me-thanks
#:go (grid 2 2 2 1 'rb)
(colorize (text "I do not like them,") "red")
(text "Sam-I-am."))
]
}
@defproc[(cascade [step-x (or/c real? 'auto) 'auto]
[step-y (or/c real? 'auto) 'auto])
placer?]{
Returns a placer that places picts by evenly spreading them diagonally
across the base pict in ``cascade'' style. This placer does not
support changing the spacing by including a real number within the
pict sequence.
When a list picts is to be placed, their bounding boxes are normalized
to the maximum width and height of all picts in the list; each pict is
centered in its new bounding box. The picts are then cascaded so there
is @racket[step-x] space between each of the picts' left edges; there
is also @racket[step-x] space between the base pict's left edge and
the first pict's left edge. Similarly for @racket[step-y] and the
vertical spacing.
If @racket[step-x] or @racket[step-y] is @racket['auto], the spacing
between the centers of the picts to be placed is determined
automatically so that the inter-pict spacing is the same as the
spacing between the last pict and the base.
@examples[#:eval the-eval
(ppict-do base
#:go (cascade)
(colorize (filled-rectangle 100 100) "red")
(colorize (filled-rectangle 100 100) "blue"))
(ppict-do base
#:go (cascade 40 20)
(colorize (filled-rectangle 100 100) "red")
(colorize (filled-rectangle 100 100) "blue"))
]
}
@defproc[(tile [cols exact-positive-integer?]
[rows exact-positive-integer?])
placer?]{
Returns a placer that places picts by tiling them in a grid
@racket[cols] columns wide and @racket[rows] rows high.
@examples[#:eval the-eval
(ppict-do base
#:go (tile 2 2)
(circle 50)
(rectangle 50 50)
(jack-o-lantern 50)
(standard-fish 50 30 #:color "red"))
]
}
@defproc[(at-find-pict [find-path (or/c tag-path? pict-path?)]
[finder procedure? cc-find]
[align (or/c 'lt 'ct 'rt 'lc 'cc 'rc 'lb 'cb 'rb) 'cc]
[#:abs-x abs-x real? 0]
[#:abs-y abs-y real? 0]
[#:compose composer procedure? #, @elem{computed from @racket[align]}])
refpoint-placer?]{
Returns a placer that places picts according to a reference point
based on an existing pict within the base.
@examples[#:eval the-eval
(ppict-do base
#:go (cascade)
(tag-pict (standard-fish 40 20 #:direction 'right #:color "red") 'red-fish)
(tag-pict (standard-fish 50 30 #:direction 'left #:color "blue") 'blue-fish)
#:go (at-find-pict 'red-fish rc-find 'lc #:abs-x 10)
(text "red fish"))
]
}
@defproc[(merge-refpoints [x-placer refpoint-placer?]
[y-placer refpoint-placer?])
refpoint-placer?]{
Returns a placer like @racket[x-placer] except that the y-coordinate of its
reference point is computed by @racket[y-placer].
@examples[#:eval the-eval
(ppict-do base
#:go (cascade)
(tag-pict (standard-fish 40 20 #:direction 'right #:color "red") 'red-fish)
(tag-pict (standard-fish 50 30 #:direction 'left #:color "blue") 'blue-fish)
#:go (merge-refpoints (coord 1 0 'rc)
(at-find-pict 'red-fish))
(text "red fish"))
]
}
@section[#:tag "pslide"]{Progressive Slides}
@defmodule[unstable/gui/pslide]
@defform[(pslide ppict-do-fragment ...)]{
Produce slide(s) using @tech{progressive picts}. See @racket[ppict-do]
for an explanation of @racket[ppict-do-fragment]s.
Note that like @racket[slide] but unlike @racket[ppict-do*], the
number of slides produced is one greater than the number of
@racket[#:next] uses; that is, a slide is created for the final pict.
Remember to include @racket[gap-size] after updating the current
placer if you want @racket[slide]-like spacing.
@examples[#:eval the-eval
(eval:alts (pslide #:go (coord 0 0 'lt)
(t "You do not like")
(colorize (t "green eggs and ham?") "darkgreen")
#:next
#:go (coord 1 1 'rb)
(colorize (t "I do not like them,") "red")
(t "Sam-I-am."))
(let-values ([(final slides0)
(ppict-do* (colorize (filled-rectangle 200 150) "white")
#:go (coord 1/20 1/20 'lt) ;; for margins
(text "You do not like")
(colorize (text "green eggs and ham?")
"darkgreen")
#:next
#:go (coord 19/20 19/20 'rb) ;; for margins
(colorize (text "I do not like them,") "red")
(text "Sam-I-am.")
#:next)])
(let ([slides
(inset
(vl-append -10
(colorize (text "slides" '(bold . roman)) "white")
(inset (apply hc-append 20 slides0) 15))
5)])
(cc-superimpose
(colorize (filled-rectangle (pict-width slides) (pict-height slides))
"darkgray")
slides))))
]
Note that the text is not flush against the sides of the slide,
because @racket[pslide] uses a base pict the size of the client
area, excluding the margins.
}
@defparam[pslide-base-pict make-base-pict (-> pict)]{
Controls the initial pict used by @racket[pslide]. The default value
is
@racketblock[
(lambda () (blank client-w client-h))
]
}
@defparam[pslide-default-placer placer placer?]{
Controls the initial placer used by @racket[pslide]. The default value
is
@racketblock[
(coord 1/2 1/2 'cc)
]
}
@(close-eval the-eval)