| Paste number 70096: | my own documentation for hacking on gordon |
| Pasted by: | fusss |
| When: | 7 months, 3 weeks ago |
| Share: | Tweet this! | http://paste.lisp.org/+1I34 |
| Channel: | #lisp |
| Paste contents: |
All you need to start hacking flash with gordon and the spec.
Flash, or "SWF" files are binary files that contains both data (fonts, video, audio, animation sequences,
other binary data) and "code" (compiled action script.) So in effect, both code and data are binaries;
the bitmaps are binary bitmaps, audio is in MP3 codec, animation sequences consist of images, lines,
shapes, etc. Flash files can load and embed other flash files, HTML files or sometimes PDF (only recently).
Since flash was mainly for animation, a single SWF binary is often called a "movie", which doesn't
make sense if it only contains compiled bytecode.
Flash files consist of a header and a sequence of tags. The header is just a few bytes long, fairly easy
to parse, and you will never need to know about it if you're using Gordon. It's the TAGS that are most important.
Every SWF file consists of a sequence of tags. Each tag contains a small tag-header and tag data. The tag-header
defines the length and type of data, and tag-data contains the binary encoding of the data. You don't worry about
the tag-header as it's generated by gordon, but you define the tag data.
There are two types of tags, defenition tags and control tags.
Defenition tags contain the content of the SWF file; image, sound, font, shapes, etc. A flash movie consists of
a series of frames; the first frame starts right after the file-header, and each subsequent frame starts where the
last ended. Each frame is terminated with the ShowFrame tag. With in each frame, you can define all the elements that
need to appear on the screen, in order. Flash will read the defenitions one by one and add them to the DisplayList,
when ShowFrame is encountered, flash will start playing your frame. In Pseudo-code:
START-MOVIE
Define Label :text "Hello World" :id 1
PlaceObject Label :x 0 :y 0 :x-max n :y-max m :id 1
ShowFrame
End
each element you create has a unique ID called the CHARACTER ID by flash docs (the element itself is called a character.)
The character is the unique ID that defines it in the file. If you create a line in flash, you give it a character
(or :id in gordon sources, as you can see in the :id argument to Define Label above) any resource you create will have
a character id.
Everytime you define an element and you give it a unique character ID you're said to be instantiating it. The character
ID goes to an array of character IDs, each uniquely defining an instantiated character. Character ID zero is unique and
should not be used.
The defenition tags are: DefineShape, DefineSound, DefineFont, DefineText, DefineLabel, DefineMorphShape, etc.
Control tags are tags which specify how things should behave or appear. They "manipulate rendered instances of characters".
Control tags are usually passed a character ID to tell them which character to act on. In the above pseudo-code,
Place Object is passed :id 1 to tell it which character to place on the screen. In Flash, objects are created first,
placed on the screen later. One important DEFENITION tag defines a character "button" element. It's an abstract shape
you can impose on any visible element, and it defines a a hit area for the mouse on the screen (buttons don't have
to be square, and they don't have a particular shape. The button is just a generic area, which
most often overlaps another predefined shape, and instructs flash to change the mouse pointer when the mouse enters
this area.) The button DEFENITION tag is passed an :id number as well, to tell it which area on the screen to make
clickable.
The control tags are: PlaceObject, ShowFrame, StartSound, EndTag, etc.
Various tags in flash have new and deprecated version. The newer versions have a number after them, like PlaceObject-2.
The larger the number the newer the tag. Currently the -2 suffixed tags are used of the numbered tags.
Character instances are placed in a display list in the order they're defined. Along with a character ID, character
instances also have a depth (:depth argument in gordon) which specifies their z-ordering. Higher depth value are placed on
top of lower depth values. Negative depth values are reserved for flash. Zero depth value is the initial surface.
Characters also have color matrices associated with them; color matrices with alpha channels (a-color in gordon) can
set transparency levels and can reveal or conceal other characters laying beneath the current character instance.
Button characters are associated with displayable characters by an area/shape matrix. Any region can be made clickable
by assigning a button character to it. This is done by passing the area's character ID to the button during its defenition
(in gordon this is done with the :characters keyword which accepts a list of records, each of those records has a
character-id slot which can be set to the character id of the element we want to make clickable)
Let's look at a gordon example, the usual hello world example:
(asdf:oos 'asdf:load-op :gordon)
(use-package :gordon)
(defparameter font-id 64000)
(defun hello-world ()
(with-open-file (fontstream "freeserif.fo" :direction :input :element-type '(unsigned-byte 8))
(with-movie (m "hello.swf"
:width 500
:height 500
:frame-rate 12
:bgcolor *white*)
(add-movie-object m fontstream)
(add-tag m (make-tag-define-edit-text :initial-text "Hello World" :id 1
:font-id font-id :font-height 40 :border t
:bounds (make-rect :xmin 0 :ymin 0
:xmax 400
:ymax 100)
:color *a-blue*))
(add-tag m (make-tag-place-object2 :depth 1 :id 1 :matrix
(make-matrix :translate-x 0 :translate-y 0)))
(add-tag m (make-tag-showframe))
(add-tag m (make-tag-end)))))
Let's go through the code. First we load Gordon and use its package. The font-id is given a high enough constant
to avoid conflicting with any low-numbered character id (font-id is a character id for the font character we
are about to instantiate)
WITH-OPEN-FILE is used to open the font file we're going to use to display text on the screen. The font freeserif.fo
is included with gordon, but you can use any other font you want. Gordon output is binary data, 8-bit octets to be
exact, so be sure to open the file stream as "8-bit unsigned-byte". The default is a Lisp character, which is a bit
more complex than flat ascii or utf8.
WITH-MOVIE is a convenience macro that takes care of opening a file for the movie we're about to create; the first
argument 'm' is bound to the movie-file stream. :Width and :height keywords tells us gordon the demensions of the movie
we want to create. Is it a smallish flash movie for the web or a full-screen kiosk application? You can discover
the rest of the init-args from the gordon sources.
Now we start to have fun. ADD-MOVIE-OBJECT is one of the first tags we see. Remember how tags were broken into defenition
and control tags? try to guess what each is in the code ;-) ADD-MOVIE-OBJECT adds an object to the movie, in this case
binary font data. It's just a convenience wrapper, but it's very convenient. If you want to know what happens underneath,
an Object tag is created and the font data from the file is populated in it. Typically, tags are added in gordon with
ADD-TAG. In this case, the font data are just stored, but they're not added to the DisplayList. Think of it as importing
data, but it's not acted on it yet.
The next two ADD-TAG expressions are the meat of the example. Each tag is made with a MAKE-TAG-* constructor, but all
tags are added to the display list with ADD-TAG. The first creates a defenition tag for an EDIT-TEXT to display our
greeting. The second creates a control tag.
After that we instantiate the ShowFrame tag and add it to the display list. This effectively terminates the frame
and sends what we have defined above to the screen. The last tag ends the file.
Well, so far we only have one frame, let's make things a little interesting and make a two-frame animation. We will
add another EDIT-TEXT with a different string, "How are you?", and continously flip between the two.
How to call flash functions and methods from Lisp.
I added ActionCallFunction and ActionCallMethod to gordon and now I'm able to call ActionScript functions.
What you need to do is push arguments on to the stack right to left (right-most arg first). Push the count
of arguments. Push the function name you wanna call. Then add the ActionCallFunction tag.
For example, to multiply two numbers 4 5 (i.e. 4*5):
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "4"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "5"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "2"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "*"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-call-function))))
Gordon will generate byte-code for you. You do this with in the definition of a frame and
generated code will run when it hits the display list.
You can store the result in a variable by push a variable name, the value and adding the ActionSetVariable tag.
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "_product")))) ;; new
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "4"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "5"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "2"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "*"))))
(add-tag m (make-tag-do-action :records (list (make-action-record-call-function))))
;; the product is on the stack top and the variable name "_product" is right beneath it.
(add-tag m (make-tag-do-action :records (list (make-action-record-set-variable))))
You can tie the result to a TEXT-ENTRY so that it display this value by passing it the variable name
as a :variable-name initarg. E.g.
(add-tag m (make-tag-define-edit-text :initial-text "" :id 1
:variable-name "_product" ;; <-- looky looky
:font-id font-id
:font-height 20 :border t
:bounds (make-rect :xmin 0 :ymin 0
:xmax 400
:ymax 100)))
Another example displays the date:
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "_time_now")))) ;; <-- var to bind
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "0")))) ;; no arguments for function
(add-tag m (make-tag-do-action :records (list (make-action-record-push :value "Date")))) ;; function is Date()
(add-tag m (make-tag-do-action :records (list (make-action-record-call-function)))) ;; call it
(add-tag m (make-tag-do-action :records (list (make-action-record-set-variable)))) ;; SETQ
The changes I made to Gordon to make this happen are as follows:
In actions.lisp, append the following lines.
(add-action-record-type action-record-set-variable
29
()
(lambda (x) nil))
(add-action-record-type action-record-get-time
52
()
(lambda (x) nil))
(add-action-record-type action-record-call-function
61
()
(lambda (x) nil))
In packages.lisp, add them to the export list.
#:make-action-record-get-time
#:make-action-record-call-function
#:make-action-record-set-variable
This paste has no annotations.