The Animation Editor

A Code Sculpture in GTK+

Lion Kimbro : Projects : Animation Editor

Audience

This page is written for people who are interested in tools development, but have little experience, and people who are interested in GTK+.

I am writing this because I believe in the Golden Rule.

I wish people wrote stuff like this for me, so I'll write stuff like this for others. It's always nice to read about your peers efforts, and to have something to learn from and mentally chew on.

I also wished that Google had turned up information on GnomeCanvas bugs. When Google eats this page, it will. I will have saved others from having to discover GnomeCanvas bugs on their own. Heheheheh. {;D}=

Motivation

I believe that tool construction is one of the most useful works programmers perform. A good system with bad tools will be underutilized, but people seem to find great ways of using good tools, even if encumbered by a poor system.

My experience writing tools is limited to a level editor for a game I wrote when I was 9 in BASIC. I made extensive use of the LOCATE command, a command for positioning the cursor. I used $INKEY to make it so that when you typed a character, it put it beneath the cursor. That's about it.

This is something I wanted to remedy.

I decided to write a "code sculpture" (some play code written to try out a new language, library, or concept) to play with user interfaces, and to build skill with GTK+, my favorite Free widget library.

I decided to write an Animation Editor, a tool for linking images together into an animation.

Process

  1. UI Design - Drawing the UI on Paper
  2. Sizing, Dimensions, and Spacing Determination
  3. Coordinates Determination
  4. Programming
  5. Tweaking
UI Design - Drawing the UI on Paper

First I designed the editor in my mind, and drew it on paper:

The X's are delete buttons. The squares with dots in the middle are images for the animation. The left/right arrows manipulate a number (between the arrows), which represents how long the image will be played in the animation. The picture of the guy running on the left is the running animation, continuously playing.

Sizing, Dimensions, and Spacing Determination

I labeled the widgets and important dimensions.

Usually with GTK+ development, you don't need to determine dimensions, sizes, and spacings. However, my editor had a custom UI within a GnomeCanvas, so I needed to be able to control dimensions.

Here's what the spacing and dimension code looked like:

# UI constants #

SA = 10
SB = 10
SC = 10
SD = 10
SE = 10
SF = 10
SG = 5

XVBOR = 10
XHBOR = 40
XHIGH = 20
XWIDE = 20

WVBOR = 10
WHBOR = 10
WHIGH = 30
WWIDE = 20

IHIGH = 60
IWIDE = 80

AVBOR    = 10
AHBORIN  = 20
AHBOROUT = 10
AHIGH    = 30
AWIDE    = 20

SA,SB,SC,SD,SE,SF, and SG, are spacings. X stands for "Delete X", W for "Wedge", "I" for "Image", and "A" for "Arrow". VBOR is vertical border and HBOR is horizontal border.

Coordinates Determination

After the sizes, dimensions, and spacings were determined, I converted them to coordinates. Why the two step process? Why not just start with purely physical coordinates? It is easy to manipulate logical coordinates, but interrelated physical coordinates are difficult to deal with. I would rather tweak logical coordinates, and have the physical coordinates automatically calculated.

Here's the physical coordinates determination code:

# calculations from UI constants #

Y0  = 0
Y1  = Y0  + SA
Y2  = Y1  + XVBOR
Y3  = Y2  + XHIGH
Y4  = Y3  + XVBOR
Y5  = Y4  + SB
Y6  = Y5  + (IHIGH/2)
Y7  = Y5  + IHIGH
Y8  = Y7  + SC
Y9  = Y8  + AVBOR
Y10 = Y9  + (AHIGH/2)
Y11 = Y9  + AHIGH
Y12 = Y11 + AVBOR
Y13 = Y12 + SD

X0  = 0
X8  = SE+AHBORIN+AWIDE+AHBOROUT+SF
X16 = 2*X8

def offc( ammount ):
    return (X8 - ammount,
            X8 + ammount)

(X7,X9) = offc( XWIDE/2 )
(X6,X10) = offc( SE )
(X5,X11) = offc( (XWIDE/2)+XHBOR )
(X4,X12) = offc( SE + AHBORIN )
(X3,X13) = offc( IWIDE/2 )
(X2,X14) = offc( SE+AHBORIN+AWIDE )
(X1,X15) = offc( SE+AHBORIN+AWIDE+AHBOROUT )

WEDGE_WIDTH  = WWIDE + (2*WHBOR)
WEDGE_HEIGHT = WHIGH + (2*WVBOR)

Programming

Finally, came the programming.

Coding was smooth, save two problems.

First, the GnomeCanvas is broken in several places in ways that my previous play with GnomeCanvas didn't reveal.

The X and Y Attributes on GnomeCanvasGroups are Fucked. You'd think changing X and Y would move the GnomeCanvasGroup, right? Sometimes it does. Sometimes it doesn't. Some times it sort of does.

When it "sort of does", it moves half of itself, but will leave entrails behind. An image here, a line over there, all sorts of stuff. Also, events are only captured after the move if there is overlap with it's previous location. It's complicated, but trust me: Don't move GnomeCanvasGroups more than so far if you value your ability to receive events.

Use .move instead. .move only supports relative addressing, but hey..! It works. I write more about GnomeCanvas later on in the "GnomeCanvas Has Some Errors: Why?" section.

Second, I never wrote file selection code. Oh no! Why not? Because I had no idea how. I write more about this in the "Design vs. Iteration" section later on, but briefly, I needed to play file selection before I got myself into design and implementation. Had I done that, I would have been quite capable of designing for it. But as it was, I didn't know how to use it, and sat confused upon reaching the time to implement it.

Which is a shame, because without being able to select files, I can't make the frames look different than one another. And without different frames, I can't really have an animation.

So, the coding didn't entirely work. It was difficult where GnomeCanvas didn't work as expected, and failed where I had failed to design due to a lack of prior play.

Read the full source code for the application, and download the null image as well if you'd like to execute it. Put them in the same directory, and then type "python anim5.python"

Tweaking

Tweaking the application at the end was quite entertaining. I gave the buttons rectangle sections behind them, so that the mouse roll-overs didn't require that you get the mouse right on top of the X's and arrows. Manipulating the logical dimension values was a lot of fun too.

Tweaking is so much fun, I think it could go on for hours. I think this is a large part of the reason why design can be so much more quicker than iteration: After each iteration comes a tweaking phase, and that can be time consuming.

Parting Thoughts

Here are some thoughts that came to me during development:

Design vs. Iteration

Generally in my life, I've been skeptical of design. The question in my mind has always been, "If you design, what happens when you suddenly find out, half way through your project, that the API doesn't work like you expect? Or that you forgot something important?"

Design was for giant luser corporations that could affort to waste big money. So I focused entirely on play and iterative development.

The problem is, I keep bumping into this wall, and when I look at the code of people who I admire, people who have crossed this wall, I see evidence of beautiful design.

The problem that I have found with iterative development, is that it is slow. Iterative work works as a series of micro-designs, each with their own micro-design, quick implementation, and micro-test. There is pause before and after each iterative work. Design, on the other hand, works quite rapidly, and in one blast. You draw a large number of symbols on paper, do some calculations, type in some magical numbers into the computer, and wha-lah. You have a complete system.

The tradeoffs between design and iteration are quite analogous to the tradeoffs between the network world's high MTU (design) and low MTU (iteration). High MTU brings you greater throughput, but less interactivity. Low MTU brings you greater interactivity, but less throughput.

This was my first experience in quite some time with intentional design, and all in all, I will say it was quite impressive.

I find it interesting that where I failed was precisely where my design dropped off: The file interface. I had neglected to consider the file interface, (though I knew in the back of my mind that it might pose a problem.)

Does this justify my original fear of design: "If you design, what happens when you suddenly find out, half way through your project, that the API doesn't work like you expect? Or that you forgot something important?" I would say that while my fear certainly applies, that it shows not there is a fault in design, but rather, that I needed some more play (which is iterative). That is, the use of design was not improper, but rather, that the complementary element of iteration had fallen short.

My mind had falsely decided that development was either an iterative process, or a design process. Now I believe that it is a function of both. To go beyond the sort of hippyish egalitarian feel, let me say precisely how I feel about the function of play (iteration) and design:

The role of play is to familiarize the developer with an API. The role of play is to find out where the landmines are in the API (such as the "x" and "y" attributes in the GnomeCanvasGroup), what works (the moveto command in the GnomeCanvasGroup), to discover the capabilities of the system, to discover how the API is to be used, to figure out the culture around the API, and to stretch the imagination.

The role of design is to create something fast. Design allows you to meditate briefly on a particular act, and then commit it with precision and speed. Design is fast, because there is a minimum of reconsideration, rewrite, and fiddling.

I think it is interesting to note that play and design contain one another in different scales. Each piece of play is a micro design, as we consider what to do, and then do it. Every design is a piece of a larger play; It is not uncommon to hear people say things like, "You have to design and implement a system 5 times before you really start to get it right," which is basically what play is like, just on a much larger scale.

My UI Sense Sucked

I designed the UI with a mouse in mind. Perhaps this is because I was drawing on a piece of paper, and thinking purely visually, rather than interactively.

If I did this again, I would write it with the keyboard user in mind, rather than the mouse user. Left and right would move a frame selector left and right. Up and down would increment and decrement the current value. I don't know- maybe control left and control right would add new frames to the left and right of the current frame. Control-X would cut the current frame.

GnomeCanvas Has Some Errors: Why?

I kept wondering, "How can it be that GnomeCanvas has these bugs?" Is this Not a Free Software Project? Is it not immortal? How can it be?!

I have a few theories:

  1. Nobody cares about GnomeCanvas in GNOME 1.4. They're all working on GnomeCanvas in 2.0
  2. Nobody is using GnomeCanvas, so nobody has fixed the bugs yet.
  3. Whoever's managing GnomeCanvas is mean and won't take peoples patches.
  4. Whoever's managing GnomeCanvas is demented, and thinks that the way it works is the desired behavior.
  5. Everyone who discovered the same problems with GnomeCanvasGroup has the same dillema as I have: They can fixing it, but have no reason to believe that if they do, their patch will be accepted. They write to the gtk-app-dev list about this, but receive no response.

I really don't hope that any of these theories are right. Unfortunately, I think that one of them has to be correct. There are various ways to correct the situation, the problem is figuring out which reason is the actual reason for the situation.

That was Fun!

Failure to write a useful application didn't make me sad; Again, this is a code sculpture, not a serious application. It's just for fun and learning, both of which came to me in abundance.