#lang scribble/doc @(require scribble/manual scribblings/icons (for-label scheme/gui "animated-canvas.ss")) @title[#:tag "top"]{Animated Canvas} @(author+email @tt{M. Douglas Williams} "m.douglas.williams@gmail.com") This library provides an @scheme[animates-canvas%] class that specializes the MrEd @scheme[canvas%] class to provide a simple double-buffered animation capability in PLT Scheme. A simple demonstration program is also provided. Everything in this library is exported by a single module: @defmodule[(planet williams/animated-canvas/animated-canvas)] @table-of-contents[] @section{Theory of Operation} @(margin-note finger "This section assumes you are familiar with the " @scheme[canvas%] " class.") An animated canvas uses two bitmaps to provide a double-buffered animation capability. This is implemented by a new class @scheme[animated-canvas%] that specializes the @scheme[canvas%] class. At any specific time, one bitmap, the @tech{background bitmap}, is being used for all drawing operations and the other bitmap, the @tech{foreground bitmap}, is being used to paint the canvas. The @scheme[swap-bitmaps] method is used to swap the background and foreground bitmaps. When the bitmaps are swapped, the contents of the new foreground bitmap --- the old background bitmap --- is displayed on the canvas. The new background bitmap --- the old foreground bitmap --- is automatically cleared unless specifically prevented by the @scheme['no-autoclear] style option. The device context returned by the animated canvases's @scheme[get-dc] method is the device context of the background bitmap. This value is not valid across calls to @scheme[swap-bitmaps]. Therefore, it is important to re-retrieve, via @scheme[get-dc], the device context across bitmap swaps. The animated canvas also supports resizing on the canvas, which automatically resizes the background buffer after the bitmaps are swapped. @section{Interface} @defclass[animated-canvas% canvas% (canvas<%>)]{ An @scheme[animated-canvas%] object is a specialized @scheme[canvas%] object for animated drawings. Most of the following description is from the GUI: PLT Graphics Toolkit reference manual with additions as noted for @scheme[animated-canvas%]. Changes that are specific to the @scheme[animated-canvas%] are in bold face. @defconstructor[([parent (or/c (is-a?/c frame%) (is-a?/c dialog%) (is-a?/c panel%) (is-a?/c pane%))] [style (listof (one-of/c 'border 'control-border 'combo 'vscroll 'hscroll 'resize-corner 'gl 'no-autoclear 'transparent 'no-focus 'deleted)) null] [paint-callback ((is-a?/c canvas%) (is-a?/c dc<%>) . -> . any) void] [label (or/c label-string? false/c) #f] [gl-config (or/c (is-a?/c gl-config%) false/c) #f] [enabled any/c #t] [vert-margin (integer-in 0 1000) 0] [horiz-margin (integer-in 0 1000) 0] [min-width (integer-in 0 10000) _graphical-minimum-width] [min-height (integer-in 0 10000) _graphical-minimum-height] [stretchable-width any/c #t] [stretchable-height any/c #t])]{ The @scheme[style] argument indicates one or more of the following styles: @itemize{ @item{@scheme['border] --- gives the canvas a thin border} @item{@scheme['control-border] --- gives the canvas a border that is like a @scheme[text-field%] control} @item{@scheme['combo] --- gives the canvas a combo button that is like a @scheme[combo-field%] control; this style is intended for use with @scheme['control-border] and not with @scheme['hscroll] or @scheme['vscroll]} @item{@scheme['hscroll] --- enables horizontal scrolling (initially visible but inactive)} @item{@scheme['vscroll] --- enables vertical scrolling (initially visible but inactive)} @item{@scheme['resize-corner] --- leaves room for a resize control at the canvas's bottom right when only one scrollbar is visible} @item{@scheme['gl] --- @italic{obsolete} (every canvas is an OpenGL context where supported)} @item{@scheme['no-autoclear] --- @bold{prevents automatic erasing of the background bitmap after calls to @method[canvas% swap-bitmaps]}} @item{@scheme['transparent] --- @bold{ignored for an animated canvas}} @item{@scheme['no-focus] --- prevents the canvas from accepting the keyboard focus when the canvas is clicked, or when the @method[window<%> focus] method is called} @item{@scheme['deleted] --- creates the canvas as initially hidden and without affecting @scheme[parent]'s geometry; the canvas can be made active later by calling @scheme[parent]'s @method[area-container<%> add-child] method} } The @scheme['hscroll] and @scheme['vscroll] styles create a canvas with an initially inactive scrollbar. The scrollbars are activated with either @method[canvas% init-manual-scrollbars] or @method[canvas% init-auto-scrollbars], and they can be hidden and re-shown with @method[canvas% show-scrollbars]. @bold{The @scheme[paint-callback] argument is ignored for an animated canvas.} The @scheme[label] argument names the canvas for @method[window<%> get-label], but it is not displayed with the canvas. The @scheme[gl-config] argument determines properties of an OpenGL context for this canvas, as obtained through the canvas's drawing context. See also @method[canvas<%> get-dc] and @xmethod[dc<%> get-gl-context].} @defmethod[(get-dc) (is-a?/c dc<%>)]{ Returns the device context of the background bitmap. This value changes across calls to @method[animated-canvas% swap-bitmaps].} @defmethod[(swap-bitmaps) any]{ Swaps the background and foreground bitmaps, displays the new foreground bitmap, and clears the new background bitmap (unless explicitly requested not to do so).} } @section[#:tag "example"]{Example Animation} This section contains an example animated graphics program that uses the @scheme[animated-canvas%] class. @schememod[ scheme/gui (code:comment " Lines animated canvas example.") (code:comment " Draws moving line similar to those in the Qix video game.") (require (planet williams/animated-canvas/animated-canvas)) (define-struct qix-segment (x1 y1 x2 y2)) (define-struct qix (x1 y1 x-dot1 y-dot1 x2 y2 x-dot2 y-dot2 color segments) #:mutable) (define (random-color) (make-object color% (random 256) (random 256) (random 256))) (define max-segments 12) (define (draw-qix the-qix) (define (draw-segment dc x1 y1 x2 y2 color) (send dc set-pen color 0 'solid) (send dc draw-line x1 y1 x2 y2)) (let ((dc (send canvas get-dc))) (for-each (lambda (qix) (for-each (lambda (segment) (draw-segment dc (qix-segment-x1 segment) (qix-segment-y1 segment) (qix-segment-x2 segment) (qix-segment-y2 segment) (qix-color qix))) (qix-segments qix))) the-qix) (send canvas swap-bitmaps))) (define (move-qix the-qix) (define (update-position-x x x-dot) (set! x (+ x x-dot)) (cond ((< x 0) (set! x (- x)) (set! x-dot (- x-dot))) ((> x (send canvas get-width)) (set! x (- (* 2 (send canvas get-width)) x)) (set! x-dot (- x-dot)))) (values x x-dot)) (define (update-position-y y y-dot) (set! y (+ y y-dot)) (cond ((< y 0) (set! y (- y)) (set! y-dot (- y-dot))) ((> y (send canvas get-height)) (set! y (- (* 2 (send canvas get-height)) y)) (set! y-dot (- y-dot)))) (values y y-dot)) (for-each (lambda (qix) (code:comment " Update the qix position") (let-values (((x x-dot)(update-position-x (qix-x1 qix) (qix-x-dot1 qix)))) (set-qix-x1! qix x) (set-qix-x-dot1! qix x-dot)) (let-values (((y y-dot)(update-position-y (qix-y1 qix) (qix-y-dot1 qix)))) (set-qix-y1! qix y) (set-qix-y-dot1! qix y-dot)) (let-values (((x x-dot)(update-position-x (qix-x2 qix) (qix-x-dot2 qix)))) (set-qix-x2! qix x) (set-qix-x-dot2! qix x-dot)) (let-values (((y y-dot)(update-position-y (qix-y2 qix) (qix-y-dot2 qix)))) (set-qix-y2! qix y) (set-qix-y-dot2! qix y-dot)) (code:comment " Add a new segment to the segment list") (set-qix-segments! qix (append (qix-segments qix) (list (make-qix-segment (qix-x1 qix) (qix-y1 qix) (qix-x2 qix) (qix-y2 qix))))) (code:comment " Remove old segments") (when (> (length (qix-segments qix)) max-segments) (set-qix-segments! qix (cdr (qix-segments qix))))) the-qix)) (define break? #f) (define (main n-qix) (begin-busy-cursor) (send run-button enable #f) (set! break? #f) (let ((the-qix (let-values (((w h) (send canvas get-client-size))) (build-list n-qix (lambda (i) (make-qix (random w) (random h) (- (random 20) 10) (- (random 20) 10) (random w) (random h) (- (random 20) 10) (- (random 20) 10) (random-color) '())))))) (let loop () (let ((t (current-milliseconds))) (draw-qix the-qix) (move-qix the-qix) (sleep/yield (max 0.0 (/ (- 10.0 (- (current-milliseconds) t)) 1000.0))) (unless break? (loop))))) (send run-button enable #t) (end-busy-cursor)) (code:comment " GUI elements") (define frame (instantiate frame% ("Animated Canvas Demo"))) (define panel (instantiate horizontal-panel% (frame) (alignment '(right center)) (stretchable-height #f))) (define run-button (instantiate button% ("Run" panel) (horiz-margin 4) (callback (lambda (b e) (main (send slider get-value)))))) (define stop (instantiate button% ("Stop" panel) (horiz-margin 4) (callback (lambda (b e) (set! break? #t))))) (define slider (instantiate slider% ("Number of qix" 1 100 frame) (init-value 10) (style '(horizontal)))) (define canvas (instantiate animated-canvas% (frame) (style '(border)) (min-width 800) (min-height 600))) (send frame show #t) ] The following is a screen shot of the lines example. @image["images/lines.png"] @section{Issues and To Do} TBD