#lang racket/base (require (for-syntax racket/base) racket/list racket/class racket/stxparam racket/contract/base slideshow/pict "tag-pict.rkt") #| TODO - [lcr]bl alignments... not sure about [lcr]tl - document composer contract - generalize ppict-add to ppict-add* (put 'next support there) - find a way to support slide animation |# ;; ============================================================ ;; Progressive Picts #| A ppict contains a pict and a placer (or #f). A placer = (placer (pict (listof (U pict real #f)) -> ppict)) In a placer function's arguments: a number means to change the separation spacing a #f is just ignored FIXME: clarify, for following or including current gap? |# (struct ppict pict (placer)) (define (mk-ppict p placer) (ppict (pict-draw p) (pict-width p) (pict-height p) (pict-ascent p) (pict-descent p) (list (make-child p 0 0 1 1 0 0)) #f (pict-last p) placer)) (define (ppict-pict dp) (child-pict (car (pict-children dp)))) ;; ---- (define-syntax-parameter ppict-do-state (lambda (stx) (raise-syntax-error #f "used out of context" stx))) ;; ppict-go : pict placer -> ppict (define (ppict-go dp pl) (cond [(ppict? dp) (mk-ppict (ppict-pict dp) pl)] [(pict? dp) (mk-ppict dp pl)])) ;; ppict-add : ppict (U pict real #f 'next) ... -> ppict (define (ppict-add dp . parts) (let-values ([(final intermediates) (ppict-add/internal 'ppict-add dp parts)]) final)) ;; ppict-add* : ppict (U pict real #f 'next) ... -> (values ppict (listof pict)) (define (ppict-add* dp . parts) (ppict-add/internal 'ppict-add* dp parts)) ;; ppict-add/internal : symbol pict (listof (U pict real #f 'next)) ;; -> (values pict (listof pict) ;; In second return value, one pict per 'next occurrence. ;; FIXME: avoid applying ghost to previously ghosted pict? (define (ppict-add/internal who base parts) (unless (ppict? base) (error who "missing placer")) (let ([placer (ppict-placer base)] [base-pict (ppict-pict base)] [elem-chunks ;; (listof (listof pict?)) ;; length is N+1, where N is number of 'next in chunk ;; ghosted before visible (let elab ([chunk parts]) (cond [(and (pair? chunk) (eq? 'next (car chunk))) (let ([elab-rest (elab (cdr chunk))]) (cons (map ghost* (car elab-rest)) elab-rest))] [(and (pair? chunk) (not (eq? 'next (car chunk)))) (for/list ([elem-chunk (in-list (elab (cdr chunk)))]) (cons (car chunk) elem-chunk))] [(null? chunk) (list null)]))]) (let out-loop ([chunks elem-chunks] [rpicts null]) (cond [(null? (cdr chunks)) (values (send placer place base-pict (car chunks)) (reverse rpicts))] [else (out-loop (cdr chunks) (cons (send placer place base-pict (car chunks)) rpicts))])))) ;; ---- (define (placer? x) (is-a? x placer<%>)) (define (refpoint-placer? x) (is-a? x refpoint%)) (define (merge-refpoints x y) (send x take-y-from y)) (define placer<%> (interface () ;; place : pict (listof (U pict real #f)) -> pict place)) (define refpoint% (class* object% (placer<%>) (init-field xa ya depxy halign valign compose [sep 0] [cont? #f]) (super-new) (define/public (place scene picts) (define-values (dx dy) (let-values ([(depx depy) (if depxy (depxy scene) (values 0 0))]) (values (+ depx xa) (+ depy ya)))) (define-values (newpict newsep) (apply-compose compose sep (cons (and cont? (blank 0)) picts))) (define newscene (pin-over/align scene dx dy halign valign newpict)) (cond [(and (eq? valign 't) (eq? compose (halign->vcompose halign))) ;; ie, going top-down and compose is the natural compose for this align (mk-ppict newscene (new refpoint% (xa dx) (ya (+ dy (pict-height newpict))) (depxy #f) (halign halign) (valign valign) (compose compose) (sep newsep) (cont? #t)))] [(and (eq? halign 'l) (eq? compose (valign->hcompose valign))) ;; ie, going left-right and compose is the natural compose ... (mk-ppict newscene (new refpoint% (xa (+ dx (pict-width newpict))) (ya dy) (depxy #f) (halign halign) (valign valign) (compose compose) (sep newsep) (cont? #t)))] [else newscene])) (define/public (take-y-from other) (new refpoint% (xa xa) (ya (get-field ya other)) (depxy (let ([odepxy (get-field depxy other)]) (lambda (base) (let-values ([(x _y) (if depxy (depxy base) (values 0 0))] [(_x y) (if odepxy (odepxy base) (values 0 0))]) (values x y))))) (halign halign) (valign valign) (compose compose) (sep sep) (cont? cont?))))) ;; -- (define (grid cols rows col row [align 'cc] #:abs-x [abs-x 0] #:abs-y [abs-y 0] #:compose [compose (halign->vcompose (align->h align))]) ;; row, column indexes are 1-based (define halign (align->h align)) (define valign (align->v align)) (define xfrac (/ (+ (sub1 col) (align->frac halign)) cols)) (define yfrac (/ (+ (sub1 row) (align->frac valign)) rows)) (new refpoint% (xa abs-x) (ya abs-y) (depxy (lambda (p) (values (* xfrac (pict-width p)) (* yfrac (pict-height p))))) (halign halign) (valign valign) (compose compose))) (define (coord xfrac yfrac [align 'cc] #:abs-x [abs-x 0] #:abs-y [abs-y 0] #:compose [compose (halign->vcompose (align->h align))]) (define halign (align->h align)) (define valign (align->v align)) (new refpoint% (xa abs-x) (ya abs-y) (depxy (lambda (p) (values (* xfrac (pict-width p)) (* yfrac (pict-height p))))) (halign halign) (valign valign) (compose compose))) ;; ---- (define cascade% (class* object% (placer<%>) (init-field step-x0 step-y0) (super-new) (define/public (place scene elems) (for ([e (in-list elems)]) (when (real? e) (error 'cascade "spacing changes not allowed: ~e" e))) (let* ([picts (filter pict? elems)] [max-w (apply max 1 (map pict-width picts))] ;; avoid 0 [max-h (apply max 1 (map pict-height picts))] ;; avoid 0 [auto-step-x (/ (- (pict-width scene) max-w) (+ 1 (length picts)))] [auto-step-y (/ (- (pict-height scene) max-h) (+ 1 (length picts)))] [step-x (if (eq? step-x0 'auto) auto-step-x step-x0)] [step-y (if (eq? step-y0 'auto) auto-step-y step-y0)] [bbox (blank max-w max-h)] [newscene (for/fold ([scene scene]) ([pict (in-list picts)] [i (in-naturals 1)]) (pin-over scene (* i step-x) (* i step-y) (cc-superimpose bbox pict)))]) ;; Can't continue a cascade, since depends on number of picts. ;; FIXME: If step is given rather than computed, then we can. newscene)))) ;; cascade : ... -> placer (define (cascade [step-x0 'auto] [step-y0 'auto]) ;; Auto cascade by largest bounding box. ;; FIXME: add align arg, determines position of each pict w/in bbox (new cascade% (step-x0 step-x0) (step-y0 step-y0))) (define tile% (class* object% (placer<%>) (init-field cols rows [start-at 0]) (super-new) (define/public (place scene elems) (for ([e (in-list elems)]) (when (real? e) (error 'tile "spacing changes not allowed: ~e" e))) (let* ([picts (filter pict? elems)] [scene-w (pict-width scene)] [scene-h (pict-height scene)] [dx (/ scene-w cols)] [dy (/ scene-h rows)] [newscene (for/fold ([scene scene]) ([pict (in-list picts)] [i (in-naturals start-at)]) (let ([r (quotient i cols)] [c (remainder i cols)]) (pin-over/align scene (+ (/ dx 2) (* c dx)) (+ (/ dy 2) (* r dy)) 'c 'c pict)))]) (mk-ppict newscene (new tile% (cols cols) (rows rows) (start-at (+ start-at (length picts))))))))) (define (tile cols rows) (new tile% (cols cols) (rows rows))) ;; at-find-pict : ... -> placer (define (at-find-pict path [find cc-find] [align 'cc] #:abs-x [abs-x 0] #:abs-y [abs-y 0] #:compose [compose (halign->vcompose (align->h align))]) (define halign (align->h align)) (define valign (align->v align)) (new refpoint% (xa abs-x) (ya abs-y) (depxy (lambda (p) (let ([pict-path (if (tag-path? path) (find-tag p path) path)]) (unless pict-path (error 'at-find-path "failed finding ~e" path)) (find p pict-path)))) (halign halign) (valign valign) (compose compose))) (define (pin-over/align scene x y halign valign pict) (let ([localrefx (* (pict-width pict) (align->frac halign))] [localrefy (* (pict-height pict) (align->frac valign))]) (pin-over scene (- x localrefx) (- y localrefy) pict))) ;; ---- ;; apply-compose : compose real (listof (U #f pict real)) -> (values pict real) ;; Returns composed pict and last given separator num in elems (or init-sep, if none) (define (apply-compose compose init-sep elems) (define (start-loop sep elems) (cond [(and (pair? elems) (real? (car elems))) (start-loop (car elems) (cdr elems))] [(and (pair? elems) (pict? (car elems))) (join-loop (car elems) sep (cdr elems))] [(null? elems) (blank 0)])) (define (join-loop base sep elems) (cond [(and (pair? elems) (real? (car elems))) (join-loop base (car elems) (cdr elems))] [(and (pair? elems) (pict? (car elems))) (join-loop (compose sep base (car elems)) sep (cdr elems))] [(null? elems) base])) (values (start-loop init-sep (filter values elems)) (last (cons init-sep (filter real? elems))))) ;; ---- (define (align->frac align) (case align ((t l) 0) ((c) 1/2) ((b r) 1))) (define (align->h align) (case align ((lt lc lb) 'l) ((ct cc cb) 'c) ((rt rc rb) 'r))) (define (align->v align) (case align ((lt ct rt) 't) ((lc cc rc) 'c) ((lb cb rb) 'r))) (define (halign->vcompose halign) (case halign ((l) vl-append) ((c) vc-append) ((r) vr-append))) (define (valign->hcompose align) (case align ((t) ht-append) ((c) hc-append) ((b) hb-append))) ;; ---- (define (ghost* x) (if (pict? x) (ghost x) x)) ;; ============================================================ ;; Exports (define align/c (or/c 'lt 'ct 'rt 'lc 'cc 'rc 'lb 'cb 'rb)) (provide (all-defined-out))