#lang scheme/base
(require scheme/class
scheme/local
scheme/bool
mred
htdp/error
htdp/image
(only-in lang/htdp-beginner image?)
mrlib/cache-image-snip
lang/prim
(for-syntax scheme/base))
(require mrlib/gif)
(require mzlib/runtime-path)
(require mrlib/bitmap-label
string-constants)
(provide (all-from-out htdp/image))
(provide
nw:rectangle place-image empty-scene scene+line )
(provide run-animation run-saveable-animation end-of-time )
(provide
key-event? key=? )
(provide-higher-order-primitive
run-simulation (_ _ _ create-scene) )
(provide
run-movie )
(define (nw:rectangle width height mode color)
(check-pos 'rectangle width "first")
(check-pos 'rectangle height "second")
(check-mode 'rectangle mode "third")
(check-color 'rectangle color "fourth")
(put-pinhole (rectangle width height mode color) 0 0))
(define (place-image image x y background)
(check-image 'place-image image "first")
(check-arg 'place-image (number? x) 'integer "second" x)
(check-arg 'place-image (number? y) 'integer "third" y)
(check-image 'place-image background "fourth")
(let ([x (number->integer x)]
[y (number->integer y)])
(place-image0 image x y (put-pinhole background 0 0))))
(define (empty-scene width height)
(check-pos 'empty-scene width "first")
(check-pos 'empty-scene height "second")
(put-pinhole
(overlay (rectangle width height 'solid 'white)
(rectangle width height 'outline 'black))
0 0))
(define (scene+line img x0 y0 x1 y1 c)
(check-arg 'scene+line (scene? img) "scene" "first" "plain image")
(check-arg 'scene+line (number? x0) "number" "second" x0)
(check-arg 'scene+line (number? y0) "number" "third" y0)
(check-arg 'scene+line (number? x1) "number" "fourth" x1)
(check-arg 'scene+line (number? y1) "number" "fifth" y1)
(let ([x0 (number->integer x0)]
[x1 (number->integer x1)]
[y0 (number->integer y0)]
[y1 (number->integer y1)])
(add-line-to-scene0 img x0 y0 x1 y1 c)))
(define (coerce x) (inexact->exact (floor x)))
(define *the-delta* 0.0)
(define add-event void)
(define (key-event? k)
(or (char? k) (symbol? k)))
(define (key=? k m)
(check-arg 'key=? (key-event? k) 'KeyEvent "first" k)
(check-arg 'key=? (key-event? m) 'KeyEvent "first" m)
(eqv? k m))
(define-struct handler (func))
(define-struct (tick-handler handler) ())
(define-struct (redraw-handler handler) ())
(define-struct (key-handler handler) ())
(define-struct (mouse-handler handler) ())
(define-struct (stop-when-handler handler) ())
(define (on-tick handle-tick)
(check-proc 'on-tick handle-tick 1 "on-tick" "one argument")
(make-tick-handler handle-tick))
(define (on-redraw handle-redraw)
(check-proc 'on-redraw handle-redraw 1 "on-redraw" "one argument")
(make-redraw-handler handle-redraw))
(define (on-key handle-key)
(check-proc 'on-key handle-key 2 "on-key" "two arguments")
(make-key-handler handle-key))
(define (on-mouse handle-mouse)
(check-proc 'on-mouse handle-mouse 4 "on-mouse" "four arguments")
(make-mouse-handler handle-mouse))
(define (stop-when finished?)
(check-proc 'stop-when finished? 1 "stop-when" "one argument")
(make-stop-when-handler finished?))
(define (end-of-time s)
(set-stop-when-callback (lambda (w) #t))
(printf "end of time: ~a~n" s)
the-world)
(provide-higher-order-primitive on-tick (handle-tick))
(provide-higher-order-primitive on-redraw (handle-redraw))
(provide-higher-order-primitive on-key (handle-key))
(provide-higher-order-primitive on-mouse (handle-mouse))
(provide-higher-order-primitive stop-when (finished?))
(define (extract-one-handler test? L default error-handler)
(let ((found-handler (extract-one-helper test? L error-handler)))
(if (not found-handler)
default
(handler-func found-handler))))
(define (extract-one-helper test? L error-handler)
(cond ((null? L) #f)
((test? (car L))
(if (extract-one-helper test? (cdr L) error-handler)
(error-handler)
(car L)))
(else (extract-one-helper test? (cdr L) error-handler))))
(define (run-animation w h init-world delta . handlers)
(run-animation0 #f w h init-world delta handlers))
(define (run-saveable-animation w h init-world delta . handlers)
(run-animation0 #t w h init-world delta handlers))
(define (run-animation0 recording? w h init-world delta handlers)
(check-pos 'run-animation w "width")
(check-pos 'run-animation h "height")
(check-arg 'run-animation
(and (number? delta) (<= 0 delta 1000))
"number [of seconds] between 0 and 1000"
"fourth"
delta)
(install-world delta init-world)
(set! *the-delta* delta) (let ([w (coerce w)]
[h (coerce h)]
[redraw-proc
(extract-one-handler
redraw-handler? handlers
(lambda (pic) pic) (lambda () (error 'run-animation "Too many redraw handlers")))]
[timer-proc
(extract-one-handler
tick-handler? handlers
(lambda (world) world) (lambda () (error 'run-animation "Too many tick handlers")))]
[char-proc
(extract-one-handler
key-handler? handlers
(lambda (world key) world) (lambda () (error 'run-animation "Too many key handlers")))]
[mouse-proc
(extract-one-handler
mouse-handler? handlers
(lambda (world x y event) world) (lambda () (error 'run-animation "Too many mouse handlers")))]
[quit-proc
(extract-one-handler
stop-when-handler? handlers
(lambda (world) #f) (lambda () (error 'run-animation "Too many stop detectors")))]
)
(set-and-show-frame w h recording?)
(set! add-event (if recording? add-event0 void))
(set-redraw-callback
(lambda (world)
(place-image (put-pinhole (redraw-proc world) 0 0)
0 0
(empty-scene w h))))
(set-timer-callback timer-proc)
(set-key-callback char-proc (current-eventspace))
(set-mouse-callback mouse-proc (current-eventspace))
(set-stop-when-callback quit-proc)
(send the-time start
(let* ([w (ceiling (* 1000 the-delta))])
(if (exact? w) w (inexact->exact w))))
(redraw-callback)
#t
))
(define (run-movie movie)
(check-arg 'run-movie (list? movie) "list (of images)" "first" movie)
(for-each (lambda (cand)
(check-image 'run-movie cand "first" "list of images"))
movie)
(let* ([fst (car movie)]
[wdt (image-width fst)]
[hgt (image-height fst)])
(run-animation wdt hgt movie 1/27
(on-redraw car)
(on-tick cdr)
(stop-when (compose null? cdr)))))
(define run-simulation
(lambda x
(define args (length x))
(if (or (= args 5) (= args 4))
(apply run-simulation0 x)
(error 'run-simulation msg-run-simulation))))
(define msg-run-simulation
(string-append
"consumes 4 or 5 arguments:\n"
"-- (run-simulation <width> <height> <rate> <world-to-world-function>)\n"
"-- (run-simulation <width> <height> <rate> <world-to-world-function> <create-animated-gif?>)\n"
"see Help Desk."))
(define run-simulation0
(case-lambda
[(width height rate f record?)
(check-pos 'run-simulation width "first")
(check-pos 'run-simulation height "second")
(check-arg 'run-simulation (number? rate) 'number "third" rate)
(check-proc 'run-simulation f 1 "fourth" "one argument")
(check-arg 'run-simulation (boolean? record?) 'number "fifth [and optional]" record?)
(run-animation0 record? width height 1 rate (list (on-redraw f) (on-tick add1)))]
[(width height rate f)
(run-simulation width height rate f #f)]))
(define (check-pos tag c rank)
(check-arg tag (and (number? c) (> (coerce c) 0))
"positive integer" rank c))
(define (check-image tag i rank . other-message)
(if (and (pair? other-message) (string? (car other-message)))
(check-arg tag (image? i) (car other-message) rank i)
(check-arg tag (image? i) "image" rank i)))
(define (check-scene tag i rank)
(if (image? i)
(unless (scene? i)
(error tag "scene expected, given image whose pinhole is at (~s,~s) instead of (0,0)"
(pinhole-x i) (pinhole-y i)))
(check-arg tag #f "image" rank i)))
(define (scene? i) (and (= 0 (pinhole-x i)) (= 0 (pinhole-y i))))
(define (check-color tag width rank)
(check-arg tag (or (symbol? width) (string? width))
"color symbol or string" rank width))
(define (check-mode tag s rank)
(check-arg tag (or (eq? s 'solid)
(eq? s 'outline)
(string=? "solid" s)
(string=? "outline" s)) "mode (solid or outline)" rank s))
(define (place-image0 image x y scene)
(define sw (image-width scene))
(define sh (image-height scene))
(define ns (overlay/xy scene x y image))
(define nw (image-width ns))
(define nh (image-height ns))
(if (and (= sw nw) (= sh nh)) ns (shrink ns 0 0 sw sh)))
(define (place-image0 image x y scene)
(define sw (image-width scene))
(define sh (image-height scene))
(define ns (overlay/xy scene x y image))
(define nw (image-width ns))
(define nh (image-height ns))
(if (and (= sw nw) (= sh nh)) ns (shrink ns 0 0 (- sw 1) (- sh 1))))
(define (add-line-to-scene0 img x0 y0 x1 y1 c)
(define w (image-width img))
(define h (image-height img))
(cond
[(and (<= 0 x0) (< x0 w) (<= 0 x1) (< x1 w) (<= 0 y0) (< y0 w) (<= 0 y1) (< y1 w))
(add-line img x0 y0 x1 y1 c)]
[(= x0 x1) (if (<= 0 x0 w) (add-line img x0 (app y0 h) x0 (app y1 h) c) img)]
[(= y0 y1) (if (<= 0 y0 h) (add-line img (app x0 w) y0 (app x1 w) y0 c) img)]
[else
(local ((define lin (points->line x0 y0 x1 y1))
(define dir (direction x0 y0 x1 y1))
(define-values (upp low lft rgt) (intersections lin w h))
(define (add x y) (add-line img x0 y0 x y c)))
(cond
[(and (< 0 x0 w) (< 0 y0 h)) (case dir
[(upper-left) (if (number? upp) (add upp 0) (add 0 lft))]
[(lower-left) (if (number? low) (add low h) (add 0 lft))]
[(upper-right) (if (number? upp) (add upp 0) (add h rgt))]
[(lower-right) (if (number? low) (add low h) (add w rgt))]
[else (error 'dir "contract violation: ~e" dir)])]
[(and (< 0 x1 w) (< 0 y1 h)) (add-line-to-scene0 img x1 y1 x0 y0 c)]
[else
(cond
[(and (number? upp) (number? low)) (add-line img upp 0 low h c)]
[(and (number? upp) (number? lft)) (add-line img upp 0 0 lft c)]
[(and (number? upp) (number? rgt)) (add-line img upp 0 w rgt c)]
[(and (number? low) (number? lft)) (add-line img low h 0 lft c)]
[(and (number? low) (number? rgt)) (add-line img low h w rgt c)]
[(and (number? lft) (number? rgt)) (add-line img 0 lft w rgt c)]
[else img])]))]))
(define (app y h)
(cond
[(and (<= 0 y) (< y h)) y]
[(< y 0) 0]
[else (- h 1)]))
(define (direction x0 y0 x1 y1)
(string->symbol
(string-append
(if (<= y0 y1) "lower" "upper") "-" (if (<= x0 x1) "right" "left"))))
(define-struct lyne (slope y0))
(define (points->line x0 y0 x1 y1)
(local ((define slope (/ (- y1 y0) (- x1 x0))))
(make-lyne slope (- y0 (* slope x0)))))
(define (of ln x) (+ (* (lyne-slope ln) x) (lyne-y0 ln)))
(define (intersections l w h)
(values
(opt (X l 0) w) (opt (X l h) w) (opt (lyne-y0 l) h) (opt (of l w) h)))
(define (opt z lft) (if (<= 0 z lft) z false))
(define (X ln h) (/ (- h (lyne-y0 ln)) (lyne-slope ln)))
(define unique-world (cons 1 1))
(define the-world unique-world)
(define the-world0 unique-world)
(define (install-world delta w)
(reset-event-history)
(set! the-delta delta)
(set! the-world w)
(set! the-world0 w)
(vw-setup))
(define the-delta 1000)
(define visible-world #f)
(define (vw-setup)
(set! visible-world (new pasteboard%))
(send visible-world set-cursor (make-object cursor% 'arrow)))
(define (vw-init?) (is-a? visible-world pasteboard%))
(define (update-frame pict)
(send visible-world begin-edit-sequence)
(send visible-world lock #f)
(let ([s (send visible-world find-first-snip)])
(when s
(send visible-world delete s)))
(let ([c (send visible-world get-canvas)])
(let-values ([(px py)
(if (is-a? pict cache-image-snip%)
(send pict get-pinhole)
(values 0 0))]
[(cw ch)
(send c get-client-size)])
(send visible-world insert (send pict copy) (- px) (- py))))
(send visible-world lock #t)
(send visible-world end-edit-sequence))
(define (set-and-show-frame w h animated-gif)
(define the-play-back-custodian (make-custodian))
(define frame (create-frame the-play-back-custodian))
(when animated-gif
(add-stop-and-image-buttons frame the-play-back-custodian))
(add-editor-canvas frame visible-world w h)
(send frame show #t))
(define (create-frame the-play-back-custodian)
(new (class frame%
(super-new)
(define/augment (on-close)
(custodian-shutdown-all the-play-back-custodian)
(callback-stop!)))
(label "DrScheme")
(stretchable-width #f)
(stretchable-height #f)
(style '(no-resize-border metal))))
(define IMAGES "Images")
(define-runtime-path s:pth '(lib "break.png" "icons"))
(define-runtime-path i:pth '(lib "file.gif" "icons"))
(define (add-stop-and-image-buttons frame the-play-back-custodian)
(define p (new horizontal-pane% [parent frame][alignment '(center center)]))
(define S ((bitmap-label-maker (string-constant break-button-label) s:pth) '_))
(define I ((bitmap-label-maker IMAGES i:pth) '_))
(define stop-button
(new button% [parent p] [label S] [style '(border)]
[callback (lambda (this-button e)
(callback-stop!)
(send this-button enable #f)
(send image-button enable #t))]))
(define image-button
(new button% [parent p] [enabled #f] [label I] [style '(border)]
[callback (lambda (b e)
(parameterize ([current-custodian the-play-back-custodian])
(define th (thread play-back))
(send b enable #f)))]))
(void))
(define (add-editor-canvas frame visible-world w h)
(define c
(new (class editor-canvas%
(super-new)
(define/override (on-char e) (key-callback (send e get-key-code)))
(define/override (on-event e) (mouse-callback e)))
(parent frame)
(editor visible-world)
(style '(no-hscroll no-vscroll))
(horizontal-inset INSET)
(vertical-inset INSET)))
(send c min-client-width (+ w INSET INSET))
(send c min-client-height (+ h INSET INSET))
(send c focus))
(define INSET 5)
(define TICK 'tick)
(define MOUSE 'mouse)
(define KEY 'key)
(define event-history '())
(define (reset-event-history)
(set! event-history '()))
(define (add-event0 type . stuff)
(set! event-history (cons (cons type stuff) event-history)))
(define (play-back)
(define (world-transition world fst)
(case (car fst)
[(tick) (timer-callback0 world)]
[(key) (key-callback0 world (cadr fst))]
[(mouse) (mouse-callback0 world (cadr fst) (caddr fst) (cadddr fst))]
[else (error 'play-back "bad type of event: ~s" fst)]))
(define total (+ (length event-history) 1))
(define image-count 0)
(define bitmap-list '())
(define (save-image img)
(define-values (w h) (send img get-size))
(define (make-bitmap)
(define bm (make-object bitmap% w h))
(define dc (make-object bitmap-dc% bm))
(send dc clear)
(send img draw dc 0 0 0 0 w h 0 0 #f)
bm)
(define bm (make-bitmap))
(set! bitmap-list (cons bm bitmap-list))
(set! image-count (+ image-count 1))
(send bm save-file (format "i~a.png" image-count) 'png))
(define target:dir
(let* ([cd (current-directory)]
[dd (get-directory "Select directory for images" #f cd)])
(if dd dd cd)))
(parameterize ([current-directory target:dir])
(let replay ([ev (reverse event-history)][world the-world0])
(define img (redraw-callback0 world))
(update-frame (text (format "~a/~a created" image-count total) 18 'red))
(save-image img)
(cond
[(null? ev)
(update-frame (text (format "creating ~a" ANIMATED-GIF-FILE) 18 'red))
(create-animated-gif (reverse bitmap-list))
(update-frame img)]
[else
(let ([world1 (world-transition world (car ev))])
(replay (cdr ev) world1))]))))
(define (create-animated-gif bitmap-list)
(define intv (if (> +inf.0 *the-delta* 0) (inexact->exact (floor (* 100 *the-delta*))) 5))
(when (file-exists? ANIMATED-GIF-FILE)
(delete-file ANIMATED-GIF-FILE))
(write-animated-gif bitmap-list intv ANIMATED-GIF-FILE #:one-at-a-time? #t))
(define ANIMATED-GIF-FILE "i-animated.gif")
(define-syntax (define-callback stx)
(syntax-case stx ()
[(_ n msg (f esp ...) para body ...)
(let* ([n:str (symbol->string (syntax-e (syntax n)))]
[callback (lambda (before after)
(string->symbol
(string-append before n:str "-callback" after)))]
[name (datum->syntax stx (callback "" ""))]
[name0 (datum->syntax stx (callback "" "0"))]
[set-name (datum->syntax stx (callback "set-" ""))])
#`(define-values (#,name #,name0 #,set-name)
(values
void void
(lambda (f esp ...)
(when (callback-set? #,name)
(error (format "the ~a has already been specified") msg))
(set! #,name0 f)
(set! #,name (lambda para body ...))))))]))
(define (callback-stop!)
(send the-time stop)
(set! timer-callback void)
(set! mouse-callback void)
(set! key-callback void)
(set! stop-when-callback (lambda (w) #f))
(set! redraw-callback void))
(define (callback-set? cb) (not (eq? cb void)))
(define the-time (new timer% [notify-callback (lambda () (timer-callback))]))
(define-callback timer "tick-event hander" (f) ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(set! the-world (f the-world))
(add-event TICK)
(redraw-callback)))
(define-callback redraw "redraw function" (f) ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(define result (f the-world))
(define fname (object-name f))
(define tname (if fname fname 'your-redraw-function))
(if (image? result)
(check-result tname scene? "scene" result
(format "image with pinhole at (~s,~s)"
(pinhole-x result) (pinhole-y result)))
(check-result tname (lambda (x) (image? x)) "scene" result))
(update-frame result)
(when (stop-when-callback)
(callback-stop!))
))
(define-callback stop-when "is end of world check" (f) ()
(define result (f the-world))
(define fname (object-name f))
(define tname (if fname fname 'your-redraw-function))
(check-result fname boolean? "boolean" result)
result)
(set-stop-when-callback (lambda (w) #f))
(define-callback key "key-event handler" (f evt-space) (e)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(set! the-world (f the-world e))
(add-event KEY e)
(redraw-callback))))))
(define-callback mouse "mouse event handler" (f evt-space) (e)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(define x (- (send e get-x) INSET))
(define y (- (send e get-y) INSET))
(define m (mouse-event->symbol e))
(set! the-world (f the-world x y m))
(add-event MOUSE x y m)
(redraw-callback))))))
(define (mouse-event->symbol e)
(cond [(send e button-down?) 'button-down]
[(send e button-up?) 'button-up]
[(send e dragging?) 'drag]
[(send e moving?) 'move]
[(send e entering?) 'enter]
[(send e leaving?) 'leave]
[else (error 'on-mouse-event
(format
"Unknown event type: ~a"
(send e get-event-type)))]))
(define (exn-handler e)
(callback-stop!)
(raise e))
(define (break-handler . _)
(printf "animation stopped")
(callback-stop!)
the-world)
(define (number->integer x)
(inexact->exact (floor x)))