Skip to main content

JavaFX Doodle #3: A Paper Cutout Demo

Posted by joshy on March 13, 2008 at 4:27 PM PDT

I just flew back from Australia where I spoke at the Sydney tech days and boy are my arms tired. Actually, it's more my legs than my arms, and technically I arrived before I left which is pretty weird... but anyway, I'm back now. I'm exhausted and don't have my photos in order yet so the Sydney post will have to wait until next week. For now, however, I thought I'd share with you Doodle #3, which is one of the demos I showed in Sydney.

A Paper Cutout is a style of application common in the Flash world. It has a character of some sort with a bunch of pieces of clothing or other accessories next to it. You can think of it as the modern equivalent of a paper doll or Mr. Potato head. One of my favorites it the South Park character builder. You can run my humble little version of it here.



Paper Cutout: run it

I built this demo in about an hour. I spent 10 minutes writing the code and 50 minutes badly drawing the artwork in Photoshop. When you run the demo you can move the pieces around and drop them on the canvas. Notice the drop shadow below the piece when you drag it. The code to do this is very simple. Each of the pieces is just a PNG image drawn in Photoshop. To add the movement I subclassed ImageView to create the Cutout class.

class Cutout extends ImageView {
    attribute x: Number;
    attribute y: Number;
    attribute url: String;
    attribute movable: Boolean;
}

attribute Cutout.movable = true;
attribute Cutout.image = Image { url: bind url };
attribute Cutout.transform = bind translate(x,y);

The code above is pretty straight forward. The subclass adds x and y attributes which are bound to the transform, meaning the transform will update whenever the x or y coordinate changes. The image is bound to the url, so when you set the url attribute the real image will be loaded automatically. Now we just need some event handlers:

attribute Cutout.onMousePressed = operation(e:CanvasMouseEvent) {
    this.filter = ShadowFilter { distance: 10, radius: 10, opacity: 0.7 };
};
attribute Cutout.onMouseReleased  = operation(e:CanvasMouseEvent) {
    this.filter = null;
};
attribute Cutout.onMouseDragged = operation(e:CanvasMouseEvent) {
    if(movable) {
        this.x += e.dragTranslation.x;
        this.y += e.dragTranslation.y;
    }
};

When the mouse is pressed it will turn on a shadow filter. When the mouse is released it will turn the shadow off. When you drag the mouse it will update the x and y attributes. The JavaFX API saves you from the pain of coordinate conversion by providing a pre-converted dragTranslation point. Just add it to the x and y attributes and you are off to go. Notice the if(movable) test. This is to turn off dragging if necessary, which is required for the image of the main character in the code below.

Now just combine them in a MainCanvas group and you are done.

class MainCanvas extends CompositeNode {  }
operation MainCanvas.composeNode() {
    return Group {
        transform: [translate(50,60)],
        content:  [
            Cutout { x: 200, y: 0, url: "paperdoll/frog.png", movable: false  },
            Cutout { x: 5, y: 0, url: "paperdoll/hat.png" },
            Cutout { x: 5, y: 80, url: "paperdoll/patch.png" },
            Cutout { x: 5, y: 140, url: "paperdoll/moustache.png" },
            Cutout { x: 40, y: 190, url: "paperdoll/goatee.png" },
            ]
    };
}

So that's doodle number three. Almost entirely declarative and quite simple. See you next week.

   - Josh

Resources

Related Topics >>

Comments

as nice to read as always! i'm still not all that sure if i should learn javafx. so please, keep those articles coming and i'll be convinced in no time :) what i really like about javafx, but makes me feel unsafe as java dev at the same time, is the sheer power some commands seem to have, for instance attribute Cutout.transform = bind translate(x,y); apperently that binds cutout.transform to a translate object, so how does the translate object know that x and y were updated? is there some kind of implizit binding going on here, or am i reading the command the wrong way? and another question: how would i apply a second transform that would be bound to another value, say a rotational transformation bound to an angle.

the bind keyword is the magic that makes it happen. The transform will be updated if anything after the bind is changed. 'translate' is a function which takes x and y. 'bind' sets up a binding which will monitor x and y for changes. If either of those changes then translate will update. When that changes then the transform will be updated. Adding a second transform is easy. the Node.transform property is actually an array (a sequence). You could do it like this: "Cutout.transform = bind [translate(x,y), rotate(a,b,r)];" where a and b are x&y values and r is the angle.

davebsoft: The short answer is, it doesn't yet but it will. The long answer is, the current JavaFX runtime libraries require local access due to bugs or accessing functionality in ways that it shouldn't. We are working on a new version of the runtime which will not require such access. When that is available I will update all of my doodles to the new version.

Josh, this is cool, and I like being able to run your stuff via JNLP, by why don't they run in the sandbox? I trust you, but I prefer not to give unrestricted access to my machine to programs that don't need it.

thanks for the answer, the Cutout.transform being an array is quite amazing for me too, it's almost too easy :) Especiall considering the fact that you can assign a non array value to it (i guess javafx is doing an automatic conversion?) Na, screw all this, I'm worrying way to much how javafx can do all this things that are a pain to get done in java, while i should just grab a copy of the next javafx book, forget all i know about java and start enjoying :)