Tag Archives: custom node

JavaFX 2 GameTutorial Part 2

UPDATE: Link to the source code is at the end of the blog.

Demo of a JavaFX 2 game tutorial

Atom Smasher – Game Loop Demo.

 Introduction

This is the second installment of a series of blog entries relating to a JavaFX 2 Game Tutorial. If you have not read Part 1 please see the introduction section of the JavaFX 2 Game Tutorial. To recap in Part 1, I mention some aspects of game play and a simple demo of a prototype spaceship (comprised of simple shapes) that is capable of navigating via the mouse. Disclaimer: This is a long tutorial so if you just want to run the demo just Click HERE. The demo is called Atom Smasher where you generate atoms (spheres) that collide. You may freeze the game to add more atoms. The objective is to have more than one atom alive and bouncing around. A text displays the current number of atoms floating around. Before beginning our discussion on a game loop I wanted to give you some background history about games and animation.

History

Back in the day (during the 80s-90s) many game programmers attempted to animate images has encountered the infamous screen flicker problem. This is where your sprites (graphic images) will often flash and make the game look quite awful. All monitors have a refresh rate where certain intervals the pixels will be redrawn (called vertical retrace CRTs). For example, if the refresh rate is 80 Hz it is approximately 80 times a second the screen is redrawn. If you are modifying things on the screen you can often be out of sync because of being in the middle of a refresh interval. What should you do about this? Well, actually there are two things that will help remedy this problem (double buffering & knowing when the cycle is occurring). Some clever developers created a technique called double buffering. Double buffering is a technique which consists of two surfaces where each takes turns on becoming the displayable surface and the other is an offscreen area (buffer surface). This technique is really a digital sleight of hand trick where the developer can pre calculate the sprites and their positions to be drawn on the offscreen surface. Once you are finished drawing on the offscreen buffer the code will switch it as the displayable surface. An important thing to point out is that we still have an issue due to the fact that we need to be notified when the refresh interval is about to begin the redraw process. In Java this ability is built in via the BufferStrategy API. So, where am I going with this? Sometimes explaining the past strategies will help us appreciate what we have today. Do we need to do this in JavaFX? Nope. Have no fear JavaFX is here! All of the issues that I’ve mentioned are all taken care of for us by using JavaFX’s Scene graph API. However, most games will still use the old fashion way of animating graphics and updating the game world called the ‘Game Loop’.

The Game Loop

Simply put the game loop is responsible for updating sprites (graphics), checking collision, and cleanup. Older game loops will check for key and mouse events as part of the loop. Since JavaFX abstracts events to allow the Scene or individual nodes to handle events the ability to listen to low level events aren’t necessary in our game loop. Shown below is a source code snippet of a typical game loop which will update sprites, check collisions, and cleanup sprites at each cycle. You will notice the Duration object from JavaFX 2.x which represents 60 divided by 1000 milliseconds or 60 frames per second(FPS). Each frame will call the handle() method of the JavaFX’s EventHandler interface in order to update the game world. Hypothetically, I’ve create three methods updateSprites(), checkCollisions(), and cleanupSprites() that will be invoked to handle sprites in the game.


   final Duration oneFrameAmt = Duration.millis(1000/60);
   final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,
      new EventHandler() {

      @Override
      public void handle(javafx.event.ActionEvent event) {

         // update actors
         updateSprites();

         // check for collision
         checkCollisions();

         // removed dead things
         cleanupSprites();

      }
   }); // oneFrame

   // sets the game world's game loop (Timeline)
   TimelineBuilder.create()
      .cycleCount(Animation.INDEFINITE)
      .keyFrames(oneFrame)
      .build()
      .play();

The above code snippet is really all you need to create a simple game or animation. However, you may want to take things to the next level. You may want to create a game engine that can manage sprites and the state of the game world.

Game Engine

A game engine is a fancy name for a utility or library responsible for encapsulating the game world,  running the game loop, managing sprites, physics, etc. This is essentially a small game framework that allows you to extend or reuse so you don’t have to reinvent the wheel when creating a 2D game from scratch. To fast forward I created a UML class diagram of a design of a game engine.

Shown below is Figure 1 A JavaFX Game Engine Class diagram.

Figure 1. A JavaFX 2 Game Engine Design

In Figure 1 A JavaFX 2 Game Engine Design you will notice three classes a GameWorld, SpriteManager, and Sprite. The GameWorld class is responsible for initializing the game state, executing the game loop, updating sprites, handling sprite collisions, and cleaning up. Next is the SpriteManager class which in charge of managing sprites by adding, removing, and other house keeping for collisions. Lastly, is the Sprite class which is responsible for maintaining the state of an image (Actor). In a 2D world a sprite can contain the object’s velocity, rotation, scene node or image that eventually gets rendered at each cycle (key frame/frames per second).

Just a quick reminder on UML notation:

  • Plus symbol ‘+‘ denotes that a class member is public.
  • Minus symbol ‘‘ denotes that a class member is private
  • Hash symbol ‘#‘ denotes that a class member is protected.

GameWorld

Below is the source code implementation of the GameWorld class. Click to expand. Later you will see a class diagram depicting a simple demo game that will extend the GameWorld class (see AtomSmasher).

package carlfx.gameengine;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * This application demonstrates a JavaFX 2.x Game Loop.
 * Shown below are the methods which comprise of the fundamentals to a
 * simple game loop in JavaFX:
*
 *  <strong>initialize()</strong> - Initialize the game world.
 *  <strong>beginGameLoop()</strong> - Creates a JavaFX Timeline object containing the game life cycle.
 *  <strong>updateSprites()</strong> - Updates the sprite objects each period (per frame)
 *  <strong>checkCollisions()</strong> - Method will determine objects that collide with each other.
 *  <strong>cleanupSprites()</strong> - Any sprite objects needing to be removed from play.
 *
 * @author cdea
 */
public abstract class GameWorld {

    /** The JavaFX Scene as the game surface */
    private Scene gameSurface;
    /** All nodes to be displayed in the game window. */
    private Group sceneNodes;
    /** The game loop using JavaFX's <code>Timeline</code> API.*/
    private static Timeline gameLoop;

    /** Number of frames per second. */
    private final int framesPerSecond;

    /** Title in the application window.*/
    private final String windowTitle;

    /**
     * The sprite manager.
     */
    private final SpriteManager spriteManager = new SpriteManager();

    /**
     * Constructor that is called by the derived class. This will
     * set the frames per second, title, and setup the game loop.
     * @param fps - Frames per second.
     * @param title - Title of the application window.
     */
    public GameWorld(final int fps, final String title) {
        framesPerSecond = fps;
        windowTitle = title;
        // create and set timeline for the game loop
        buildAndSetGameLoop();
    }

    /**
     * Builds and sets the game loop ready to be started.
     */
    protected final void buildAndSetGameLoop() {

        final Duration oneFrameAmt = Duration.millis(1000/getFramesPerSecond());
        final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,
            new EventHandler() {

                @Override
                public void handle(javafx.event.ActionEvent event) {

                    // update actors
                    updateSprites();

                    // check for collision
                    checkCollisions();

                    // removed dead things
                    cleanupSprites();

                }
        }); // oneFrame

        // sets the game world's game loop (Timeline)
        setGameLoop(TimelineBuilder.create()
                .cycleCount(Animation.INDEFINITE)
                .keyFrames(oneFrame)
                .build());
    }

    /**
     * Initialize the game world by update the JavaFX Stage.
     * @param primaryStage
     */
    public abstract void initialize(final Stage primaryStage);

    /**Kicks off (plays) the Timeline objects containing one key frame
     * that simply runs indefinitely with each frame invoking a method
     * to update sprite objects, check for collisions, and cleanup sprite
     * objects.
     *
     */
    public void beginGameLoop() {
        getGameLoop().play();
    }

    /**
     * Updates each game sprite in the game world. This method will
     * loop through each sprite and passing it to the handleUpdate()
     * method. The derived class should override handleUpdate() method.
     *
     */
    protected void updateSprites() {
        for (Sprite sprite:spriteManager.getAllSprites()){
            handleUpdate(sprite);
        }
    }

    /** Updates the sprite object's information to position on the game surface.
     * @param sprite - The sprite to update.
     */
    protected void handleUpdate(Sprite sprite) {
    }

    /**
     * Checks each game sprite in the game world to determine a collision
     * occurred. The method will loop through each sprite and
     * passing it to the handleCollision()
     * method. The derived class should override handleCollision() method.
     *
     */
    protected void checkCollisions() {
        // check other sprite's collisions
        spriteManager.resetCollisionsToCheck();
        // check each sprite against other sprite objects.
        for (Sprite spriteA:spriteManager.getCollisionsToCheck()){
            for (Sprite spriteB:spriteManager.getAllSprites()){
                if (handleCollision(spriteA, spriteB)) {
                    // The break helps optimize the collisions
                    //  The break statement means one object only hits another
                    // object as opposed to one hitting many objects.
                    // To be more accurate comment out the break statement.
                    break;
                }
            }
        }
    }

    /**
     * When two objects collide this method can handle the passed in sprite
     * objects. By default it returns false, meaning the objects do not
     * collide.
     * @param spriteA - called from checkCollision() method to be compared.
     * @param spriteB - called from checkCollision() method to be compared.
     * @return boolean True if the objects collided, otherwise false.
     */
    protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {
        return false;
    }

    /**
     * Sprites to be cleaned up.
     */
    protected void cleanupSprites() {
        spriteManager.cleanupSprites();
    }

    /**
     * Returns the frames per second.
     * @return int The frames per second.
     */
    protected int getFramesPerSecond() {
        return framesPerSecond;
    }

    /**
     * Returns the game's window title.
     * @return String The game's window title.
     */
    public String getWindowTitle() {
        return windowTitle;
    }

    /**
     * The game loop (Timeline) which is used to update, check collisions, and
     * cleanup sprite objects at every interval (fps).
     * @return Timeline An animation running indefinitely representing the game
     * loop.
     */
    protected static Timeline getGameLoop() {
        return gameLoop;
    }

    /**
     * The sets the current game loop for this game world.
     * @param gameLoop Timeline object of an animation running indefinitely
     * representing the game loop.
     */
    protected static void setGameLoop(Timeline gameLoop) {
        GameWorld.gameLoop = gameLoop;
    }

    /**
     * Returns the sprite manager containing the sprite objects to
     * manipulate in the game.
     * @return SpriteManager The sprite manager.
     */
    protected SpriteManager getSpriteManager() {
        return spriteManager;
    }

    /**
     * Returns the JavaFX Scene. This is called the game surface to
     * allow the developer to add JavaFX Node objects onto the Scene.
     * @return
     */
    public Scene getGameSurface() {
        return gameSurface;
    }

    /**
     * Sets the JavaFX Scene. This is called the game surface to
     * allow the developer to add JavaFX Node objects onto the Scene.
     * @param gameSurface The main game surface (JavaFX Scene).
     */
    protected void setGameSurface(Scene gameSurface) {
        this.gameSurface = gameSurface;
    }

    /**
     * All JavaFX nodes which are rendered onto the game surface(Scene) is
     * a JavaFX Group object.
     * @return Group The root containing many child nodes to be displayed into
     * the Scene area.
     */
    public Group getSceneNodes() {
        return sceneNodes;
    }

    /**
     * Sets the JavaFX Group that will hold all JavaFX nodes which are rendered
     * onto the game surface(Scene) is a JavaFX Group object.
     * @param sceneNodes The root container having many children nodes
     * to be displayed into the Scene area.
     */
    protected void setSceneNodes(Group sceneNodes) {
        this.sceneNodes = sceneNodes;
    }

}

SpriteManager

A sprite manager class is a helper class to assist the game loop to keep track of sprites. Normally a sprite manager will contain all sprites and each sprite contains a JavaFX Node that is displayed onto the Scene graph.
Shown below is the source code. Click to expand.

package carlfx.gameengine;

import java.util.*;

/**
 * Sprite manager is responsible for holding all sprite objects, and cleaning up
 * sprite objects to be removed. All collections are used by the JavaFX
 * application thread. During each cycle (animation frame) sprite management
 * occurs. This assists the user of the API to not have to create lists to
 * later be garbage collected. Should provide some performance gain.
 * @author cdea
 */
public class SpriteManager {
    /** All the sprite objects currently in play */
    private final static List GAME_ACTORS = new ArrayList<>();

    /** A global single threaded list used to check collision against other
     * sprite objects.
     */
    private final static List CHECK_COLLISION_LIST = new ArrayList<>();

    /** A global single threaded set used to cleanup or remove sprite objects
     * in play.
     */
    private final static Set CLEAN_UP_SPRITES = new HashSet<>();

    /** */
    public List getAllSprites() {
        return GAME_ACTORS;
    }

    /**
     * VarArgs of sprite objects to be added to the game.
     * @param sprites
     */
    public void addSprites(Sprite... sprites) {
        GAME_ACTORS.addAll(Arrays.asList(sprites));
    }

    /**
     * VarArgs of sprite objects to be removed from the game.
     * @param sprites
     */
    public void removeSprites(Sprite... sprites) {
        GAME_ACTORS.removeAll(Arrays.asList(sprites));
    }

    /** Returns a set of sprite objects to be removed from the GAME_ACTORS.
     * @return CLEAN_UP_SPRITES
     */
    public Set getSpritesToBeRemoved() {
        return CLEAN_UP_SPRITES;
    }

 /**
     * Adds sprite objects to be removed
     * @param sprites varargs of sprite objects.
     */
    public void addSpritesToBeRemoved(Sprite... sprites) {
        if (sprites.length > 1) {
            CLEAN_UP_SPRITES.addAll(Arrays.asList((Sprite[]) sprites));
        } else {
            CLEAN_UP_SPRITES.add(sprites[0]);
        }
    }

    /**
     * Returns a list of sprite objects to assist in collision checks.
     * This is a temporary and is a copy of all current sprite objects
     * (copy of GAME_ACTORS).
     * @return CHECK_COLLISION_LIST
     */
    public List getCollisionsToCheck() {
        return CHECK_COLLISION_LIST;
    }

    /**
     * Clears the list of sprite objects in the collision check collection
     * (CHECK_COLLISION_LIST).
     */
    public void resetCollisionsToCheck() {
        CHECK_COLLISION_LIST.clear();
        CHECK_COLLISION_LIST.addAll(GAME_ACTORS);
    }

    /**
     * Removes sprite objects and nodes from all
     * temporary collections such as:
     * CLEAN_UP_SPRITES.
     * The sprite to be removed will also be removed from the
     * list of all sprite objects called (GAME_ACTORS).
     */
    public void cleanupSprites() {

        // remove from actors list
        GAME_ACTORS.removeAll(CLEAN_UP_SPRITES);

        // reset the clean up sprites
        CLEAN_UP_SPRITES.clear();
    }
}

Sprite

The Sprite class represents an image or node to be displayed onto the JavaFX Scene graph. In a 2D game a sprite will contain additional information such as its velocity for the object as it moves across the scene area. The game loop will call the update() and collide() method at every interval of a key frame.
Shown below is the source code. Click to expand.

package carlfx.gameengine;

import java.util.ArrayList;
import java.util.List;
import javafx.animation.Animation;
import javafx.scene.Node;

/**
 * The Sprite class represents a image or node to be displayed.
 * In a 2D game a sprite will contain a velocity for the image to
 * move across the scene area. The game loop will call the update()
 * and collide() method at every interval of a key frame. A list of
 * animations can be used during different situations in the game
 * such as rocket thrusters, walking, jumping, etc.
 * @author cdea
 */
public abstract class Sprite {

    /** Animation for the node */
    public List animations = new ArrayList<>();

    /** Current display node */
    public Node node;

    /** velocity vector x direction */
    public double vX = 0;

    /** velocity vector y direction */
    public double vY = 0;

    /** dead? */
    public boolean isDead = false;

    /**
     * Updates this sprite object's velocity, or animations.
     */
    public abstract void update();

    /**
     * Did this sprite collide into the other sprite?
     *
     * @param other - The other sprite.
     * @return
     */
    public boolean collide(Sprite other) {
        return false;
    }
}

JavaFX 2 Game Loop Demo – Atom Smasher

Whew! If you’ve got this far you are one brave soul. Let’s take a small break and try out the demo I created using the game engine above.
Shown below is a Java Webstart button to launch the game demo. Later, you will see the design and source code detailing how it was created.

Requirements:

  • Java 7 or later
  • JavaFX 2.0.2 2.1 or later
  • Windows XP or later (Should be available soon for Linux/MacOS)
AtomSmasher Game loop demo

Demo

GameLoopPart2 Design

Below is a class diagram of the game demo called Atom Smasher which uses the game engine framework mentioned earlier.
Shown below is Figure 2 Atom Smasher Class Diagram.

Figure 2. Atom Smasher Class Diagram

GameLoopPart2

The GameLoopPart2 is the driver or main JavaFX application that runs the game. This creates a GameWorld object to be initialized and starts the game loop.
Shown below is the source code. Click to expand.

package carlfx;

import carlfx.gameengine.GameWorld;
import javafx.application.Application;
import javafx.stage.Stage;

/**
 * The main driver of the game.
 * @author cdea
 */
public class GameLoopPart2 extends Application {

    GameWorld gameWorld = new AtomSmasher(60, "JavaFX 2 GameTutorial Part 2 - Game Loop");
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        // setup title, scene, stats, controls, and actors.
        gameWorld.initialize(primaryStage);

        // kick off the game loop
        gameWorld.beginGameLoop();

        // display window
        primaryStage.show();
    }

}

AtomSmasher

AtomSmasher is a derived class of the GameWorld class. It creates many spheres that animate with random velocities, colors and positions. Button controls lets the user generate more ‘atoms’ (JavaFX Circle nodes). As each atom collides with one another they will invoke the implode() method that produces a fade transition animation. You will notice how easy it is to implement this game by simply implementing initialize(), handleUpdate(), handleCollision(), and cleanupSprites() methods. Once implemented the game engine does the rest. The initialize() method creates the button controls for the user. To update the sprites positions or change the game state you will implement the handleUpdate() method. To compare all sprites if they have collided with one another you will implement the handleCollision(). The last part of the game loop’s life cycle is cleaning up sprites. Cleaning up means updating the sprite manger and updating the JavaFX Scene (removing nodes).
Shown below is the source code. Click to expand.

package carlfx;

import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import java.util.Random;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import static javafx.animation.Animation.Status.RUNNING;
import static javafx.animation.Animation.Status.STOPPED;

/**
 * This is a simple game world simulating a bunch of spheres looking
 * like atomic particles colliding with each other. When the game loop begins
 * the user will notice random spheres (atomic particles) floating and
 * colliding. The user is able to press a button to generate more
 * atomic particles. Also, the user can freeze the game.
 *
 * @author cdea
 */
public class AtomSmasher extends GameWorld {
    /** Read only field to show the number of sprite objects are on the field*/
    private final static Label NUM_SPRITES_FIELD = new Label();

    public AtomSmasher(int fps, String title){
        super(fps, title);
    }

    /**
     * Initialize the game world by adding sprite objects.
     * @param primaryStage
     */
    @Override
    public void initialize(final Stage primaryStage) {
        // Sets the window title
        primaryStage.setTitle(getWindowTitle());

        // Create the scene
        setSceneNodes(new Group());
        setGameSurface(new Scene(getSceneNodes(), 640, 580));
        primaryStage.setScene(getGameSurface());

        // Create many spheres
        generateManySpheres(150);

        // Display the number of spheres visible.
        // Create a button to add more spheres.
        // Create a button to freeze the game loop.
        final Timeline gameLoop = getGameLoop();
        VBox stats = VBoxBuilder.create()
            .spacing(5)
            .translateX(10)
            .translateY(10)
            .children(HBoxBuilder.create()
                .spacing(5)
                .children(new Label("Number of Particles: "), // show no. particles
                    NUM_SPRITES_FIELD).build(),

                    // button to build more spheres
                    ButtonBuilder.create()
                        .text("Regenerate")
                        .onMousePressed(new EventHandler() {
                            @Override
                            public void handle(MouseEvent arg0) {
                                generateManySpheres(150);
                            }}).build(),

                    // button to freeze game loop
                    ButtonBuilder.create()
                        .text("Freeze/Resume")
                        .onMousePressed(new EventHandler() {

                            @Override
                            public void handle(MouseEvent arg0) {
                                switch (gameLoop.getStatus()) {
                                    case RUNNING:
                                        gameLoop.stop();
                                        break;
                                    case STOPPED:
                                        gameLoop.play();
                                        break;
                                }
                            }}).build()
            ).build(); // (VBox) stats on children

        // lay down the controls
        getSceneNodes().getChildren().add(stats);
    }

    /**
     * Make some more space spheres (Atomic particles)
     */
    private void generateManySpheres(int numSpheres) {
        Random rnd = new Random();
        Scene gameSurface = getGameSurface();
        for (int i=0; i (gameSurface.getWidth() - (circle.getRadius() * 2))) {
                newX = gameSurface.getWidth() - (circle.getRadius()  * 2);
            }

            // check for the bottom of screen the height newY is greater than height
            // minus radius times 2(height of sprite)
            double newY = rnd.nextInt((int) gameSurface.getHeight());
            if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) {
                newY = gameSurface.getHeight() - (circle.getRadius() * 2);
            }

            circle.setTranslateX(newX);
            circle.setTranslateY(newY);
            circle.setVisible(true);
            circle.setId(b.toString());

            // add to actors in play (sprite objects)
            getSpriteManager().addSprites(b);

            // add sprite's
            getSceneNodes().getChildren().add(0, b.node);

        }
    }

    /**
     * Each sprite will update it's velocity and bounce off wall borders.
     * @param sprite - An atomic particle (a sphere).
     */
    @Override
    protected void handleUpdate(Sprite sprite) {
        if (sprite instanceof Atom) {
            Atom sphere = (Atom) sprite;

            // advance the spheres velocity
            sphere.update();

            // bounce off the walls when outside of boundaries
            if (sphere.node.getTranslateX() > (getGameSurface().getWidth()  -
                sphere.node.getBoundsInParent().getWidth()) ||
                sphere.node.getTranslateX() < 0 ) {                 sphere.vX = sphere.vX * -1;             }             if (sphere.node.getTranslateY() > getGameSurface().getHeight()-
                sphere.node.getBoundsInParent().getHeight() ||
                sphere.node.getTranslateY() < 0) {
                sphere.vY = sphere.vY * -1;
            }
        }
    }

    /**
     * How to handle the collision of two sprite objects. Stops the particle
     * by zeroing out the velocity if a collision occurred.
     * @param spriteA
     * @param spriteB
     * @return
     */
    @Override
    protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {
        if (spriteA.collide(spriteB)) {
            ((Atom)spriteA).implode(this);
            ((Atom)spriteB).implode(this);
            getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB);
            return true;
        }
        return false;
    }

    /**
     * Remove dead things.
     */
    @Override
    protected void cleanupSprites() {
        // removes from the scene and backend store
        super.cleanupSprites();

        // let user know how many sprites are showing.
        NUM_SPRITES_FIELD.setText(String.valueOf(getSpriteManager().getAllSprites().size()));

    }
}

Atom

The Atom class extends from the Sprite class. An atom is a sprite that appears like a spherical object that moves across the scene. An atom will have a random radius, color, and velocity. As each atom sprite collides with another atom they will animate a fade transition (the implode() method).
Shown below is the source code. Click to expand.

package carlfx;

import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import javafx.animation.FadeTransitionBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.RadialGradientBuilder;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.util.Duration;

/**
 * A spherical looking object (Atom) with a random radius, color, and velocity.
 * When two atoms collide each will fade and become removed from the scene. The
 * method called implode() implements a fade transition effect.
 *
 * @author cdea
 */
public class Atom extends Sprite {

    public Atom(double radius, Color fill) {
        Circle sphere = CircleBuilder.create()
                .centerX(radius)
                .centerY(radius)
                .radius(radius)
                .cache(true)
                .build();

        RadialGradient rgrad = RadialGradientBuilder.create()
                    .centerX(sphere.getCenterX() - sphere.getRadius() / 3)
                    .centerY(sphere.getCenterY() - sphere.getRadius() / 3)
                    .radius(sphere.getRadius())
                    .proportional(false)
                    .stops(new Stop(0.0, fill), new Stop(1.0, Color.BLACK))
                    .build();

        sphere.setFill(rgrad);

        // set javafx node to a circle
        node = sphere;

    }

    /**
     * Change the velocity of the atom particle.
     */
    @Override
    public void update() {
        node.setTranslateX(node.getTranslateX() + vX);
        node.setTranslateY(node.getTranslateY() + vY);
    }

    @Override
    public boolean collide(Sprite other) {
        if (other instanceof Atom) {
            return collide((Atom)other);
        }
       return false;
    }

    /**
     * When encountering another Atom to determine if they collided.
     * @param other Another atom
     * @return boolean true if this atom and other atom has collided,
     * otherwise false.
     */
    private boolean collide(Atom other) {

        // if an object is hidden they didn't collide.
        if (!node.isVisible() ||
            !other.node.isVisible() ||
            this == other) {
            return false;
        }

        // determine it's size
        Circle otherSphere = other.getAsCircle();
        Circle thisSphere =  getAsCircle();
        double dx = otherSphere.getTranslateX() - thisSphere.getTranslateX();
        double dy = otherSphere.getTranslateY() - thisSphere.getTranslateY();
        double distance = Math.sqrt( dx * dx + dy * dy );
        double minDist  = otherSphere.getRadius() + thisSphere.getRadius() + 3;

        return (distance < minDist);
    }

    /**
     * Returns a node casted as a JavaFX Circle shape.
     * @return Circle shape representing JavaFX node for convenience.
     */
    public Circle getAsCircle() {
        return (Circle) node;
    }

    /**
     * Animate an implosion. Once done remove from the game world
     * @param gameWorld - game world
     */
    public void implode(final GameWorld gameWorld) {
        vX = vY = 0;
        FadeTransitionBuilder.create()
            .node(node)
            .duration(Duration.millis(300))
            .fromValue(node.getOpacity())
            .toValue(0)
            .onFinished(new EventHandler() {
                @Override
                public void handle(ActionEvent arg0) {
                    isDead = true;
                    gameWorld.getSceneNodes().getChildren().remove(node);
                }
            })
            .build()
            .play();
    }
}

Conclusion

Hopefully you’ve got a chance to understand the fundamentals of a gaming loop and later apply the knowledge by implementing a robust game engine. Although, I briefly mention collision I am saving that for Part 4 of these tutorials. Please stay tuned for Part 3 where we will get into input using the keyboard or mouse. Feel free to experiment. Let me know what you come up with. 😉

Carl

UPDATE: Link to the source code is below:

To obtain the source code please download the link to a jar file below by using the ‘Save link As‘ option in your browser. If you are on a Windows system you can change the extension from ‘jar‘ to ‘zip‘ to be easily expanded. It will contain a directory ‘src‘ with the source code.

The source code location:

http://www.jroller.com/carldea/resource/javafx2.0_games/part2source_code.jar

The published version of the source code is at the GitHub called (JFXGen) for you to clone and fork to your hearts content (It’s there for you to use for your own projects). Enjoy.

https://github.com/carldea/JFXGen

git clone http://github.com:carldea/JFXGen.git

References:

CRTshttp://en.wikipedia.org/wiki/Refresh_rate

Double bufferinghttp://en.wikipedia.org/wiki/Double_buffering#Double_buffering

Developing Games in Java by David Brackeen with Bret Barker and Laurence Vanhelsuwe. Page 56 “Getting Rid of Flicker and Tearing”
BufferStrategy API – http://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferStrategy.html

JavaFX – A Poor Man’s Form Designer

Person Form - Using Poor Man's Form Designer

Person Form

Introduction (Updated 2)

NOTE: If you’ve been here before and found this blog entry useful please note that I have updated it again (for good reason).  Let me explain why and what I’ve updated. Since I have found that I was becoming quite productive generating forms, I wanted a more decoupled approach, so I re-factored the source code to be more reusable and easily up-datable. To use the form designer for your applications you can skip down to the section called “Using the Form Designer” to help you create your own forms in no time at all! (I also had a crack at making the form a little more attractive too).

What is a Form Designer?

Software developers who build form based applications will often struggle with positioning controls dynamically while a window resizes. This is better known as “Layout Management”. Many developers will rely on GUI builders that provide a “WYSIWYG” ability allowing a designer to drag and drop components onto a canvas area. On the other hand some developers prefer to hand code GUI form screens. There are pros and cons to using both strategies; however in this article I will strike a balance by building a form designer to allow a developer to assist them in hand coding GUI form screens (sound strange, just read on). While the JavaFX community has anticipated for GUI designer tools, I couldn’t wait. So, I decided to create the “Poor Man’s Form Designer”. This simple designer tool uses the popular MigLayout by Mikael Grev and was ported over to the JavaFX platform by Dean Iverson (an author of the book “Pro JavaFX Platform”) into the JFXtras project. The PMFD‘s source code consists of one file Main.fx and just at around 370 lines of code including comments. After the Conclusion section of this article you will see the full source code to the demo and form designer. Simply click on the source code links to expand and your on your way to developing beautiful looking forms!

Poor Man's Form Designer Constraints Window

Poor Man's Form Designer Constraints Window

Note: You will notice I forgot to add column constraints. The designer now has the ability to adjust the column constraints.

Demo

Demo

Instructions:

  1. Click Launch button above
  2. Go to the MigLayout cheat sheet at http://migcalendar.com/miglayout/cheatsheet.html to keep handy when tweaking the “Form Design Constraint Editor” window.
  3. Go to the “Form Design Constraint Editor” and click the dump button. Here it will dump all the current constraints for the following areas: Layout, Column/Row and Components constraints.
  4. Go to the “Form Design Constraint Editor” window under the Component Constraint area is a combo box please select firstNameLabel, then tab to the constraint text field (just right of combo box) and type “align left“. Click Apply button to update changes.
  5. Be sure to observe the Form View window and you’ll notice the “First Name:” label is left justified beside the first name text field.
  6. Play around with other constraint areas. Cut and paste from the cheat sheet. The cheat sheet is divided in three sections: Layout Constraints, Column & Row Constraints and Component constraints. Don’t forget to hit the apply button.
  7. You’ll notice in the component constraint area when selecting a control in the combo box the previous constraint is preserved and displayed in the constraint text box.
  8. Go to the “Form Design Constraint Editor” and click the dump button. Here it will dump all the current constraints for the following areas: Layout, Column/Row and Components constraints.
  9. Copy and paste those dumped constraints to assist in your hand coded GUI Form. (See below “Using the Form Designer” section, Steps 3 & 4)

Disclaimer: While the coding details below seem strange,  you may want to visit Dean’s Pleasing Software blog entry “MigLayout for JavaFX Reloaded” regarding how to Get started with MigLayout and JavaFX. http://pleasingsoftware.blogspot.com/search?q=layout

Using the Form Designer

Step 1: Create a new JavaFX project / setup to include JFXtras libraries and MigLayout jar files

Step 2: Copy Source code poormans_form_designer.fx expand put into your project. (check package statement)

Step 3: Create a form (class) which extends MigLayout from the JFXtras project. Simply add two attributes:

  1. componentsConstraints – Sequence of String objects
  2. nodesToLayout – Sequence of Node objects

Although this seems odd, I plan to possibly create a Mixin class or a derived MigLayout class to carry these attributes. Internally each Node contains a layoutInfo which contains a MigNodeLayoutInfo object (see http://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout for details). You should notice that the nameform.fx does not have dependencies to the designer library code, thus allowing the form developer to use CustomNode if they choose.

Create a Form (see nameform.fx for full source code)

public class NameForm extends MigLayout {
  public var componentsConstraints:String[] = [];
  public var nodesToLayout:Node[] = [];

  init {

    // ... other controls
    var firstNameLabel:Label = Label {
      id: "firstNameLabel"
      text: "First Name"

      font : Font {
          size: 18
      }
    };

    var firstNameField:TextBox = TextBox {
      id:"firstNameField"
    };
    // ... other controls

   nodesToLayout = [
      sectionFullName,  // 0
      instructionsText, // 1
      firstNameLabel,   // 2
      firstNameField,   // 3
      // ... all controls
   ];
    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes below
    // +---------------------------------------------------
    // [BEGIN]

    constraints = "";
    columns = "[pref]10[fill]";
    rows = "[pref]10[pref]";
    componentsConstraints = [
      "span, growx, wrap",                // fullNameTitle 0
      "span, wrap 15px",               // instructionsText 1
      "align right",                     // firstNameLabel 2
      "span, w min:100:300, wrap",       // firstNameField 3
      "align right",                    // middleNameLabel 4
      "growx, w min:100:300, wrap",     // middleNameField 5
      "align right",                      // lastNameLabel 6
      "growx,  w min:100:300, wrap",      // lastNameField 7
      "align right",                    // suffixNameLabel 8
      " w min:100:300, wrap 15",        // suffixNameField 9
      "span, wrap",                           // dobTitle 10
      "align right",                          // dobLabel 11
      " w min:100:pref, wrap 15",             // dobField 12
      "span, wrap",                           // pobTitle 13
      "align right",                      // pobCityLabel 14
      "span, growx,  w min:100:300, wrap",  // pobCityField 15
      "align right",                    // pobCountyLabel 16
      "span, growx,  w min:100:300, wrap",  // pobCountyField 17
      "align right",                     // pobStateLabel 18
      "span, w pref:100:pref + 10, wrap",  // pobStateField 19
    ];

    // [END]
    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes above
    // +---------------------------------------------------
  }

  // IMPORTANT - This will generate mig nodes to be put
  // into your content to be displayed in the scene.
  postinit {
    content = for (i in [0.. sizeof nodesToLayout -1]) {
      migNode(nodesToLayout[i], componentsConstraints[i]);
    }
  }

}

Note: Next, will be Step 3, Launching your newly created MigLayout form into the designer. Once you are happy with the constraints you may press the “Dump” button to output the code that you will cut and paste into your form above. Place code between your [BEGIN] and [END] comment tags (Between lines 32 & 60)

Step 3: Launching your newly created MigLayout form into the designer (see Main.fx for full source code below conclusion)

Create a Main.fx to launch form into the Poor Man’s Form Designer tool. (see poormans_form_designer.fx for full source code below conclusion )

  // http://pleasingsoftware.blogspot.com/search?q=layout
  // Create name form
  def nameForm:nameform.NameForm = NameForm{
  };

  // Remove content, due to nodes placed in the migNameForm later
  delete nameForm.content;

  // Create a miglayout designer object
  def designer:MigLayoutDesigner = MigLayoutDesigner{
      layoutConstraint: nameForm.constraints
      columnsConstraint: nameForm.columns
      rowsConstraint: nameForm.rows
      componentsConstraints: nameForm.componentsConstraints
      nodesToLayout: nameForm.nodesToLayout
  };

  // launch designer
  poormans_form_designer.launch(designer, null, null);

Troubleshooting

  • Missing imports or resolving – NetBeans: Control-Shift-I . Eclipse: Control-Shift-O . In NetBeans when using script level functions or classes inside a script file just use the ‘*’ wild card. ie: import poormans_form_designer.*;
  • Script file does not contain correct package name. ie: package xyz;
  • Form does not have two attributes called nodesToLayout and componentsConstraints.
  • If Form is extending from MigLayout it should set its content using the two known attributes. ie: content = for (i in [0.. sizeof nodesToLayout -1]) {
    migNode(nodesToLayout[i], componentsConstraints[i]);
    }

Enhancements

Above you will see hand coded controls that eventually get displayed onto the Form View window (Person Form). Since the the GUI control code elements are simply sequential, I believe it would be quite easy to add, insert and remove controls dynamically onto the Form View window area.  Also, possibly a property sheet window for controls, skins and behavior swapping.

Conclusion

The main goal is to properly lay out components in order to create a nice looking forms and also to learn the popular layout framework library MigLayout with it’s constraints language syntax. Using JavaFX‘s binding allows the form designer tool to enable the user to easily tweak constraints on the fly without having to rerun an application GUI for every adjustment made during the layout process in a form view. Another interesting thing to note is that since we are in the JavaFX world we can layout shapes, custom nodes and graphics (not just regular form controls). As a reminder regarding the use of JFXtras MigLayout, users should read about the known issues, please go to JFXtras MigLayout wiki at: http://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout . Well, by the time you read this blog entry I’m sure a nice GUI form designer/builder tool will be available.

The source code is listed below (click source code link to expand):

Note: I am in the process of putting the zipped up project onto the JFXtras.org for easy downloading.

Requirements:

  • Java 6 JDK or greater
  • JavaFX 1.2.1 SDK
  • JFXtras version 0.5 jars
  • latest Miglayout jar

Main.fx –

/*
 * Main.fx
 * @author Carl Dea
 * Created on Jan 9, 2010, 9:06:36 PM
 */
package migtest3;

import javafx.stage.Stage;
import migtest3.nameform.*;
import javafx.scene.Scene;
import org.jfxtras.scene.ResizableScene;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.paint.Color;
import migtest3.poormans_form_designer.*;

function run(__ARGS__ : String[]) {

  // Create name form
  def nameForm:nameform.NameForm = NameForm{
  };

  // Remove content, due to nodes placed in the migNameForm later
  delete nameForm.content;

  // Create a miglayout designer object
  def designer:MigLayoutDesigner = MigLayoutDesigner{
      layoutConstraint: nameForm.constraints
      columnsConstraint: nameForm.columns
      rowsConstraint: nameForm.rows
      componentsConstraints: nameForm.componentsConstraints
      nodesToLayout: nameForm.nodesToLayout
  };

  // Create a custom scene. Note: this overrides default
  // scene inside launch PMFD script function.
  var newDesignerViewScene:Scene = ResizableScene {
    fill: LinearGradient {
      startX : 0.0
      startY : 0.0
      endX : 1.0
      endY : 0.0
      stops: [
        Stop {
          color : Color.rgb(251, 251, 251)
          offset: 0.0
        },
        Stop {
          color : Color.rgb(240, 240, 240)
          offset: 1.0
        },
     ]
    }
    content: [designer.dynamicMigLayout] // use the dynamicMigLayout
  }; // scene

  // create a custom Stage. Note: this overrides default
  // Stage inside launch PMFD script function.
  var newDesignerStageView = Stage {
      y:150
      x:100
      width: 490
      height: 600
      scene:newDesignerViewScene
      visible:true
  };

  // launch designer
  poormans_form_designer.launch(designer, newDesignerViewScene, newDesignerStageView);
}

nameform.fx –

/*
 * nameform.fx
 *
 * @author Carl Dea
 * Created on Jan 9, 2010, 11:03:57 AM
 */

package migtest3;

import javafx.ext.swing.SwingComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.Node;
import javafx.scene.CustomNode;
import javafx.ext.swing.SwingComboBoxItem;
import javafx.scene.Group;
import javafx.scene.shape.Rectangle;
import org.jfxtras.scene.layout.MigLayout;

public var US_STATES = [
  "AL","AK","AS","AZ","AR","CA","CO", "CT","DE","DC","FM","FL","GA","GU","HI","ID","IL","IN","IA","KS",
  "KY","LA","ME","MH","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ","NM","NY","NC","ND","MP",
  "OH","OK","OR","PW","PA","PR","RI","SC","SD","TN","TX","UT","VT","VI","VA","WA","WV","WI","WY"
];

public function comboItemSeqCreator(names:String[]):SwingComboBoxItem[]{
  for( n in names) SwingComboBoxItem{ text:n };
}

public class SectionTitleHeader extends CustomNode {
  public-init var text:String;
  public var height;
  public var width;
  public var rectangleColor:Color on replace {
    if (not FX.isInitialized(rectangleColor)){
      rectangleColor = Color.rgb(0,130,171);
    }
  };

  public override function create():Node{
    var title = Group {
      content: [
        Rectangle {
          x: 10,
          y: height - 85,
          arcHeight: 5,
          arcWidth: 5,
          width: bind width - 30,
          height: 30,
          stroke:Color.BLACK
          fill: rectangleColor
          opacity: .5
        },
        Text {
          x: 15,
          y: height - 65,
          content: text
          fill: Color.WHITE
          font: Font {
            name: "Arial Bold"
            letterSpacing: .20
            size: 20
          }
        }
      ]
    }; // title
    return title;
  }

}

public class NameForm extends MigLayout {
  public var componentsConstraints:String[] = [];
  public var nodesToLayout:Node[] = [];

  init {

    var sectionFullName:SectionTitleHeader = SectionTitleHeader {
      id:"fullNameTitle"
      text:"1  Full Name"
      width:bind sectionFullName.scene.stage.width
      height:100
      rectangleColor:Color.BLUE
    }

    def instructions:String =
      "- If you have only initials in your name, use them "
      "and enter (I/O) after the initial(s). \n"
      "- If you have no middle name, enter \"NMN\".\n"
      "- If you are \"Jr.,\" \"Sr.,\" etc. enter this in the box after your middle name.\n";

    var instructionsText:Text = Text {
      id: "instructionsText"
      content: instructions
      fill:Color.BLACK
      font : Font {
        embolden:true
        size: 14
      }
    };

    var firstNameLabel:Label = Label {
      id: "firstNameLabel"
      text: "First Name"

      font : Font {
          size: 18
      }
    };

    var firstNameField:TextBox = TextBox {
      id:"firstNameField"
    };

    var middleNameLabel:Label = Label {
      id: "middleNameLabel"
      text: "Middle Name"
      font : Font {
        size: 18
      }
    };

    var middleNameField:TextBox = TextBox {
      id:"middleNameField"
    };

    var lastNameLabel:Label = Label {
      id: "lastNameLabel"
      text: "Last Name"
      font : Font {
        size: 18
      }
    };

    var lastNameField:TextBox = TextBox {
      id:"lastNameField"
    };

    var suffixNameLabel:Label = Label {
      id: "suffixNameLabel"
      text: "Suffix"
      font : Font {
        size: 18
      }
    };

    var suffixNameField:TextBox = TextBox {
      id:"suffixNameField"
    };

    var sectionDob:SectionTitleHeader = SectionTitleHeader {
      id:"dobTitle"
      text:"2  Date of Birth"
      width: bind sectionDob.scene.stage.width
      height:100
      rectangleColor:Color.BLUE
    }

    var dobLabel:Label = Label {
      id: "dobLabel"
      text: "DOB"
      font : Font {
        size: 18
      }
    };

    var dobField:TextBox = TextBox {
      id:"dobField"
    };

    var sectionPlaceOfBirth:SectionTitleHeader = SectionTitleHeader {
      id: "pobTitle"
      text: "3  Place of Birth"
      width: bind sectionPlaceOfBirth.scene.stage.width
      height:100
      rectangleColor:Color.BLUE
    }
    var pobCityLabel:Label = Label {
      id: "pobCityLabel"
      text: "City"
      font : Font {
        size: 18
      }
    };

    var pobCityField:TextBox = TextBox {
      id:"pobCityField"
    };
    var pobCountyLabel:Label = Label {
      id: "pobCountyLabel"
      text: "County"
      font : Font {
        size: 18
      }
    };

    var pobCountyField:TextBox = TextBox {
      id:"pobCountyField"
    };

    var pobStateLabel:Label = Label {
      id: "pobStateLabel"
      text: "State"
      font : Font {
        size: 18
      }
    };
    var statesComboItems = comboItemSeqCreator(US_STATES);
    var pobStateField:SwingComboBox = SwingComboBox {
       id: "pobStateField"
       items: [statesComboItems]
       visible: true
    }

    var changeFieldsOpacity:Node[] = [
      firstNameField,
      middleNameField,
      lastNameField,
      suffixNameField,
      dobField,
      pobCityField,
      pobCountyField,
      pobStateField,
    ];
    changeOpacity(changeFieldsOpacity, .80);

   nodesToLayout = [
      sectionFullName,  // 0
      instructionsText, // 1
      firstNameLabel,   // 2
      firstNameField,   // 3
      middleNameLabel,  // 4
      middleNameField,  // 5
      lastNameLabel,    // 6
      lastNameField,    // 7
      suffixNameLabel,  // 8
      suffixNameField,  // 9
      sectionDob,       // 10
      dobLabel,         // 11
      dobField,         // 12
      sectionPlaceOfBirth,// 13
      pobCityLabel,     // 14
      pobCityField,     // 15
      pobCountyLabel,   // 16
      pobCountyField,   // 17
      pobStateLabel,    // 18
      pobStateField,    // 19
    ];

    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes below
    // +---------------------------------------------------
    // [BEGIN]

    constraints = "";
    columns = "[pref]10[fill]";
    rows = "[pref]10[pref]";
    componentsConstraints = [
      "span, growx, wrap",                // fullNameTitle 0
      "span, wrap 15px",               // instructionsText 1
      "align right",                     // firstNameLabel 2
      "span, w min:100:300, wrap",       // firstNameField 3
      "align right",                    // middleNameLabel 4
      "growx, w min:100:300, wrap",     // middleNameField 5
      "align right",                      // lastNameLabel 6
      "growx,  w min:100:300, wrap",      // lastNameField 7
      "align right",                    // suffixNameLabel 8
      " w min:100:300, wrap 15",        // suffixNameField 9
      "span, wrap",                           // dobTitle 10
      "align right",                          // dobLabel 11
      " w min:100:pref, wrap 15",             // dobField 12
      "span, wrap",                           // pobTitle 13
      "align right",                      // pobCityLabel 14
      "span, growx,  w min:100:300, wrap",  // pobCityField 15
      "align right",                    // pobCountyLabel 16
      "span, growx,  w min:100:300, wrap",  // pobCountyField 17
      "align right",                     // pobStateLabel 18
      "span, w pref:100:pref + 10, wrap",  // pobStateField 19
    ];

    // [END]
    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes above
    // +---------------------------------------------------
  }

  // IMPORTANT - This will generate mig nodes to be put
  // into your content to be displayed in the scene.
  postinit {
    content = for (i in [0.. sizeof nodesToLayout -1]) {
      migNode(nodesToLayout[i], componentsConstraints[i]);
    }
  }

}
public function changeOpacity(nodes:Node[], opacityLevel:Float):Void {
  for (n in nodes) {
    n.opacity = opacityLevel;
  }

}

poormans_form_designer.fx –

/*
 * poormans_form_designer.fx
 *
 * @author Carl Dea
 * Created on Jan 2, 2010, 8:05:06 PM
 */
package migtest3;

import javafx.ext.swing.SwingComboBox;
import javafx.ext.swing.SwingComboBoxItem;
import javafx.ext.swing.SwingComponent;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Sequences;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import org.jfxtras.scene.ResizableScene;
import org.jfxtras.scene.layout.MigLayout;
import org.jfxtras.scene.layout.MigLayout.*;
import org.jfxtras.stage.JFXDialog;

/** Title of the designer */
var designViewTitle:String = "Poor Man's Form Designer ver 0.2";

/**
 * This makes a node MigLayout capable.
 *
 */
public class MigLayoutDesigner {
  public var layoutConstraint:String = "";
  public var columnsConstraint:String = "";
  public var rowsConstraint:String = "";
  public var componentsConstraints:String[] = [];
  public var nodesToLayout:Node[];
  public var migNodesToLayout:Node[] = bind generateMigNodes(nodesToLayout, componentsConstraints);
  protected var dynamicMigLayout:MigLayout;

  postinit {
    dynamicMigLayout = createBindableMigLayout();
  }

//  public abstract function createMigLayoutNode():Void;
  /**
   * Creates a bound MigLayout.
   */
  bound public function createBindableMigLayout() {
    MigLayout {
        constraints: bind layoutConstraint
        columns: bind columnsConstraint
        rows: bind rowsConstraint
        content: bind migNodesToLayout
    }
  }
}

/**
 * Generates a sequence of Nodes wrapped with migNode function.
 */
bound public function generateMigNodes(nodes:Node[], componentConstraintStrings:String[]){
    for (i in [0.. sizeof nodes -1]) {
      migNode(nodes[i], componentConstraintStrings[i]);
    }
}

/**
 * @param migForm - a MigLayoutCapable form. Inherits from the mixin MigLayoutCapable.
 * @param designerViewScene - The designer View of the Scene. If null one will be created.
 * @param designerViewStage - The designer View of the Stage (Window). If null one will be created.
 */
public function launch(migForm:MigLayoutDesigner, designerViewScene:Scene, designerViewStage:Stage) {

  // List Comprehensions - Strings representing nodes' id
  var listIds = for( n in migForm.nodesToLayout) n.id;

  // List comprehensions - SwingComboBoxItems
  var comboboxItems = for( n in listIds) SwingComboBoxItem{ text:n };

  // Combo Box control with ids of each widget on form.
  var idListBox:SwingComboBox = SwingComboBox {
    text : bind componentIdSelection with inverse;
    items: [comboboxItems]
  }

  // select first one as default
  idListBox.selectedIndex = 0;

  ////////////////////////////////////////////////////
  // Layout constraints section
  ////////////////////////////////////////////////////
  var layoutConstraintLabel:Label = Label {
    text: "Layout Constraint"
     font: Font {
        embolden:true
        size: 20
     }
  }
  var layoutConstraintTextBox:TextBox = TextBox {
    text: migForm.layoutConstraint
    selectOnFocus: true
  }

  // Updates the layout constraint when pressed
  var layoutConstraintApplyButton:Button = Button {
    text: "Apply"
    action: function() {
        migForm.layoutConstraint = layoutConstraintTextBox.text;
    }
  }

  ////////////////////////////////////////////////////
  // Column constraints section
  ////////////////////////////////////////////////////
  var columnsConstraintLabel:Label = Label {
    text: "Columns Constraint"
     font: Font {
        embolden:true
        size: 20
     }
  }

  // Constraint textbox
  var columnsConstraintTextBox:TextBox = TextBox {
    text: migForm.columnsConstraint
    columns: 30
  };

  // Updates the columns constraint when pressed
  var columnsConstraintApplyButton:Button = Button {
     text: "Apply"
     action: function() {
        migForm.columnsConstraint = columnsConstraintTextBox.text;
     }
  };

  ////////////////////////////////////////////////////
  // Row constraints section
  ////////////////////////////////////////////////////
  var rowsConstraintLabel:Label = Label {
    text: "Rows Constraint"
     font: Font {
        embolden:true
        size: 20
     }
  }

  // Constraint textbox
  var rowsConstraintTextBox:TextBox = TextBox {
    text: migForm.rowsConstraint
    columns: 30
  };

  // Updates the rows constraint when pressed
  var rowsConstrainApplyButton:Button = Button {
     text: "Apply"
     action: function() {
        migForm.rowsConstraint = rowsConstraintTextBox.text;
     }
  };

  ////////////////////////////////////////////////////
  // Component constraints section
  ////////////////////////////////////////////////////
  var componentsConstraintLabel:Label = Label {
     text: "Nodes Constraints"
     font: Font {
        embolden:true
        size: 20
     }
  };

  // Constraint textbox
  var componentConstraintTextBox:TextBox = TextBox {
      columns: 30
  };

  var componentIdSelection: String on replace {
      componentConstraintTextBox.text = migForm.componentsConstraints [
        Sequences.indexByIdentity(listIds, componentIdSelection)
      ];
  };

  var componentConstraintApplyButton:Button = Button {
     text: "Apply"
     action: function() {
        for (i in [0.. sizeof migForm.nodesToLayout -1]) {
           if (migForm.nodesToLayout[i].id.equalsIgnoreCase(componentIdSelection)) {
              migForm.componentsConstraints[i] = componentConstraintTextBox.text;
              break;
           }
        }
     } // action
  };

  ////////////////////////////////////////////////////
  // MigLayout Dump constraints section
  ////////////////////////////////////////////////////
  var miglayoutDumpLabel:Label = Label {
     text: "MigLayout Constraints Dump"
     font: Font {
        embolden:true
        size: 20
     }
  };

  var constraintsDumpButton:Button = Button {
     text: "Dump"
     action: function() {
        //var miglayout:MigLayout = personFormScene.content[0] as MigLayout;
        var buffer:String;
        var migStructure:String;
        buffer = "constraints = \"{migForm.layoutConstraint}\";\n"
           "columns = \"{migForm.columnsConstraint}\"; \n"
           "rows = \"{migForm.rowsConstraint}\";\n"
           "componentsConstraints = [\n";

        for (i in [0.. sizeof listIds -1]) {
          //buffer2 = "{buffer2}\"  id: {listIds[i]} - {migForm.componentsConstraints[i]}\n";
          var strElement = "\"{migForm.componentsConstraints[i]}\", // {listIds[i]} {i} \n";
          var spaces = generateSpaces(55 - strElement.length());
          buffer = "{buffer}  \"{migForm.componentsConstraints[i]}\", {spaces} // {listIds[i]} {i} \n";
        }
        buffer = "{buffer}];\n";
        constraintsDumpTextArea.setText("{buffer}");
     }
  };

  // set up a swing wrapped text area box to hold dumped constraints.
  var constraintsDumpTextArea:JTextArea = new JTextArea();
  constraintsDumpTextArea.setColumns(40);
  constraintsDumpTextArea.setRows(20);
  constraintsDumpTextArea.setAutoscrolls(true);
  var sp = new JScrollPane(constraintsDumpTextArea);
  var constraintsDumpTextAreaJfx = SwingComponent.wrap(sp);

  // constrains for the constraint configure window.
  var editorComponentsConstraints = [
     "wrap",
     "growx",
     "wrap",
     "span, newline 15px, wrap",
     "growx",
     "wrap",
     "span, newline 15px, wrap",
     "growx",
     "wrap",
     "span, newline 15px, wrap",
     "growx",
     "grow",
     "",
     "newline 20px",
     "span, align right, wrap",
     "span, growx"
  ];

  var inputPanelToLayout = [
      layoutConstraintLabel,    // 0
      layoutConstraintTextBox,  // 1
      layoutConstraintApplyButton,// 2
      columnsConstraintLabel,   // 3
      columnsConstraintTextBox, // 4
      columnsConstraintApplyButton,// 5
      rowsConstraintLabel,      // 6
      rowsConstraintTextBox,    // 7
      rowsConstrainApplyButton, // 8
      componentsConstraintLabel,// 9
      idListBox,                // 10
      componentConstraintTextBox,// 11
      componentConstraintApplyButton, // 12
      miglayoutDumpLabel,       // 13
      constraintsDumpButton,    // 14
      constraintsDumpTextAreaJfx,// 15
    ];

// convert to miglayout type nodes wrapped.
var editorMigNodesToLayout:Node[] = bind generateMigNodes(inputPanelToLayout, editorComponentsConstraints);

// check if user of the api sent in a Scene or not.
var newDesignerViewScene:Scene = designerViewScene;
  if (designerViewScene == null) {
    newDesignerViewScene = ResizableScene {
      fill:  LinearGradient {
        startX : 0.0
        startY : 0.0
        endX : 1.0
        endY : 0.0
        stops: [
          Stop {
            color : Color.DARKTURQUOISE
            offset: 0.0
          },
          Stop {
            color : Color.WHITE
            offset: 1.0
          },
        ] // stops
      } // fill
      content: [migForm.dynamicMigLayout] // content
    }; // scene
  }

  // check if user of the api sent in a Stage or not.
  var newDesignerStageView:Stage = designerViewStage;

  if (designerViewStage == null) {
    // Form View (Person Form)
    newDesignerStageView = Stage {
      y:150
      x:100
      width: 500
      height: 500
      title: designViewTitle
      scene: newDesignerViewScene
      visible:true
      opacity: .94
    }
  } else {
    newDesignerStageView.scene = newDesignerViewScene;
    if ("".equals(newDesignerStageView.title.trim())) {
      newDesignerStageView.title = designViewTitle;
    }
  }

  // Generate Scene for design view if one doesn't exist
  var constraintInputWindowScene:Scene = ResizableScene {
     fill: Color.GRAY

     content: [
        MigLayout {
           content:bind inputPanelToLayout
        },
     ] // content
  };

  // Form Design Constraint Editor.
  var constraintInputWindow:JFXDialog = JFXDialog{
     title: "Form Design Constraints Editor"
     owner:newDesignerStageView
     x: newDesignerStageView.x + newDesignerStageView.width
     y: 150
     modal:false
     visible:true
     scene:constraintInputWindowScene
     height:500
     width: 600
  }
}

/** Assisting dump button to display constraints nicely
 * to ease cut and paste in to Form for the user.
 * @param numSpaces
 */
function generateSpaces(numSpaces:Integer):String {
  var spaces:String = "";
  for (i in [1..numSpaces]) {
    spaces = "{spaces} ";
  }
  return spaces;
}

References

MigLayout by Dean Iverson – Pleasing Softwarehttp://pleasingsoftware.blogspot.com/search?q=layout

JavaFX and Layouts by Amy Fowlerhttp://weblogs.java.net/blog/aim/archive/2009/09/10/javafx12-layout

JFXtras & MigLayouthttp://jfxtras.googlecode.com/svn/site/javadoc/release-0.5/org.jfxtras.scene.layout/org.jfxtras.scene.layout.MigLayout.html

MigLayout Cheatsheet http://migcalendar.com/miglayout/cheatsheet.html

JFXtras – Miglayout wikihttp://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout

Swing GUI Builder (formerly Project Matisse)http://netbeans.org/features/java/swing.html

Abeille Form Designerhttps://abeille.dev.java.net/

JForm Designerhttp://www.formdev.com/

WindowBuilderhttp://www.instantiations.com/windowbuilder/

Swing tutorialhttp://java.sun.com/docs/books/tutorial/uiswing/

SWThttp://www.eclipse.org/swt/

Swing layouthttp://java.sun.com/docs/books/tutorial/uiswing/layout/using.html

Swing GUI Builder (formerly Project Matisse)

JavaFX to JavaScript and Back Part 2

Tons of Bouncy Balls

Tons of Bouncy Balls

Introduction

In the second installment of the series “JavaFX to JavaScript and Back” we will look at code! If you remember what we discussed in my previous post of Part 1 showing a demo of many bouncy balls. If you want to skip to the source code go to demo and click on download project.

Newcomers

If you are new and just getting started with JavaFX I strongly suggest JavaFX.com or Java.sun.com. If you are like me, one who knows enough to be dangerous please skip to Getting Started. Newcomers should take a look at books in the References section at the end of this article. Another awesome resource is the “Free” 15-Week JavaFX Programming (with Passion!) Online Course w/ Sang Shin & James Weaver.

Assumptions

  • NetBeans 6.5.1 is installed with JavaFX 1.2 pluggin.
  • Java SDK 1.6 update 14
  • Know basic JavaScript
  • Know basic HTML

Getting started

To give you a quick recap of the scenarios:

  1. Create Bouncy Balls (JavaScript to JavaFX)
  2. Remove Bouncy Balls (JavaFX to JavaScript)

Create a JavaFX Applet

Step 1: Create a Ball (Custom Node)
First create a class called Ball that extends a CustomNode. The key is overriding the create() method of the CustomNode class. We first create a Circle with a RadialGradient fill. Next we create a Group containing the circle in its content attribute. Then add the onMousePressed function to the group to detect a mouse pressed event which will remove this ball’s instance from the Scene (Game Area) and decrements the numBalls variable which updates the Web Page’s form element Number of Balls. Finally we return the group (ball variable).

class Ball extends CustomNode {
    public var velocity:Velocity;
    public var centerX:Integer=100;
    public var centerY:Integer=100;
    public var radius:Integer;
    public var fill:Color;
    protected override function create(): Node {
        var circle:Circle = Circle {
                centerX: centerX, centerY: centerY
                radius: radius
                fill: RadialGradient {
                        centerX: centerX - radius / 3
                        centerY: centerY - radius / 3

                        radius: radius
                        proportional: false
                        stops: [
                            Stop {
                                offset: 0.0
                                color: getRndColor()
                            },
                            Stop {
                                offset: 1.0
                                color: Color.BLACK
                            }
                        ] // stops
                } // RadialGradient
        } // circle
        var ball = Group{
            content:[circle];
            // remove ball
            onMousePressed: function( e: MouseEvent ):Void {
                delete this from gameArea;
                numBalls--;
            }
        } // Group

        return ball;
    } // create()
} // Ball

Step 2: Create initial ball to be put in Scene

You should notice I’ve created a few script level functions which conveniently return random values to create a Ball node. I won’t list those function for brevity. The random generated attributes are: velocity, centerX, centerY, radius, circle.fill = (RadialGradient 1st stop Color [Step 1 – Line 19])

// create an initial ball to float around.
var initialBallRadius = getRndRadius();
var rndCenter:Point = getRndCenter(500, 400, initialBallRadius);
var firstBall = Ball{
    velocity:getRndVelocity()
    centerX: rndCenter.x
    centerY: rndCenter.y
    radius: initialBallRadius
};

Step 3: Create the Scene to display Balls
The variable gameArea is a sequence of Nodes. You will notice the ball instance from Step 2 is the first node in the sequence. The scene’s content attribute is bound to the gameArea variable. During run time as nodes are added and removed from the game area the scene will dynamically update the visible nodes.

var gameArea:Node[] = [firstBall, toggleAnimationButton, mainRectRegion];
var scene:Scene = Scene {
        content: bind gameArea
        fill:Color.TRANSPARENT
};

Step 4: Create the Stage
This is the top level container which holds the scene.

Stage {
    title: "Application title"
    width: 500
    height: 400
    opacity: bind mainScreenOpacity;
    scene: scene
}

Step 5: Create a game or animation loop using the Timeline class
Here we create a Timeline instance that will run indefinitely (Timeline.INDEFINITE) with a single key frame (KeyFrame) which periodically updates each bouncy ball’s attributes which eventually renders each ball node with a new x and y position based on the velocity. Also the Timeline (gameLoop) is started immediately using the play() function.

var gameLoop:Timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames : [
        KeyFrame {
            time: 1s / 50
            canSkip : true
            action: function() {
                for (node:Node in gameArea) {
                    if (not (node instanceof Ball)){
                        continue; // don't update non balls
                    }
                    var ball = node as Ball;
                    var xMin = ball.boundsInParent.minX;
                    var yMin = ball.boundsInParent.minY;
                    var xMax = ball.boundsInParent.maxX;
                    var yMax = ball.boundsInParent.maxY;

                    // Collision - boundaries
                    if (xMin  scene.width){
                       ball.velocity.xVelocity = ball.velocity.xVelocity * -1;
                    }
                    if (yMin  scene.height){
                       ball.velocity.yVelocity = ball.velocity.yVelocity * -1;
                    }

                    ball.translateX = ball.translateX + ball.velocity.xVelocity;
                    ball.translateY = ball.translateY + ball.velocity.yVelocity;
                }
            } // action
        } //
    ]
};
gameLoop.play();

Step 6: Create the AppletStageExtension
This is the reference to the JavaFX applet which would assist with browser functionality such as the evaluation of JavaScript code.

var applet: AppletStageExtension;

Step 7: Create the numBalls variable keeping track of adds and removes.
The numBalls variable has a trigger (on replace) that updates the HTML page’s input text field “numBalls“. You should notice the id attribute of the tag is name “numBalls“. An id is a way to uniquely locate the form element in the HTML DOM (in this case a reference to the input field. When numBalls variable changes the trigger will evaluate the JavaScript code “document.getElementById(‘numBalls’).value = {numBalls}”. What this does is populate the form element field’s ‘id’ called “numBalls” whenever the JavaFX variable numBalls is updated.

var numBalls:Integer = 1 on replace {
    applet.eval("document.getElementById('numBalls').value = {numBalls}");
};

Step 8: Create a JavaFX function to add lots of balls

The HTML Web page will have a JavaScript function that would call the JavaFX function below when the user presses the Add Ball(s) button.  javafxtojavascriptandback8You will notice the FX.deferAction() call, which ensures updates to JavaFX classes are happening on the JavaFX main processing thread. This function will instantiate num balls inserts into the game area (Scene content) and increments the numBalls (trigger will update Web page’s form element).

// This function is called from the JavaScript button and numBallsToAdd field
function addBalls(num:Integer) : Void {
    FX.deferAction(function() : Void {
        for( i in [1..num]){
            var curBallRadius = getRndRadius();
            var rndCenter:Point = getRndCenter(500, 400, curBallRadius);
            var b = Ball{
                velocity:getRndVelocity()
                centerX: rndCenter.x
                centerY: rndCenter.y
                radius: curBallRadius
                fill: getRndColor()
            };
            insert b into gameArea;
            numBalls++;
        }
    });
}

Create a HTML Web page containing the JavaFX applet

Step 9: Create HTML Web page to display JavaFX applet

Run in Browser

Run in Browser

After creating the project in NetBeans select project / properties / Run (categories) / Application Execution Model – Run in Browser.

Step 10: Build Project

Building the project which will create files in the {PROJECT_HOME}/dist directory.

Step 11: Copy originally NetBeans created HTML file of the dist directory to another name

Use the copied and renamed HTML file and not the original because when the build process occurs it will overwrite the original HTML generated file.

Step 12: Modify HTML code to contain JavaScript to call JavaFX applet.

    javafx(
        {
              archive: "JavaFXtoJavaScript.jar",
              draggable: true,
              width: 500,
              height: 400,
              code: "javafxtojavascript.Main",
              name: "JavaFXtoJavaScript",
			  id: "ballApp"
        },
		{ isApplet: "true" }
    );

function addBalls() {
	document.ballApp.script.addBalls(document.getElementById('numBallsToAdd').value);
}

Step 13: Modify HTML to contain Form Elements.

<form>
<table>
<tr><th>Add balls to Scene :</th>
<td><input id=”numBallsToAdd” name=”numBallsToAdd” value=”1″/></td>
<td><input onClick=”addBalls();” type=”button” name=”addButton” value=”Add Ball(s)”/></td>
</tr>
<tr>
<th>Number of balls: </th>
<td><input id=”numBalls” name=”numBalls” READONLY/></td>
</tr>
</table>
<br/>
</form>

Running Applet in Browser through NetBeans

Step 14: Run application or hit F6 key

This will launch the JavaFX applet into the browser from the original HTML page created from the build. Go to the address and modify the URL (Just the file name) to use the modified copy which would run the JavaFX applet along with the form elements added from step 12 – 13.

Conclusion

Although the example only used simple attributes of the AppletStageExtension there are other properties and events to explore such as onAppletRestored, showDocument(), etc.  Hopefully we can get away from HTML and just have rich client applications, however I don’t think the browser is going away anytime soon. So we can happily go from JavaFX to JavaScript and Back!

Enjoy. Let me know what you think.

References:

Pro JavaFX Platform by Jim Weaver, Weiqi Gao, Stephen Chin, and Dean Iverson
JavaFX: Developing Rich Internet Applications by Jim Clarke, Jim Connors and Eric J. Bruno
Essential JavaFX by Gail Anderson and Paul Anderson

numBalls

JavaFX to JavaScript and Back Part 1

Introduction

One of the most impressive features that I’ve seen relating to JavaFX, is the ability of an Applet to interact with the browser via JavaScript and HTML. The browser can also interact back with JavaFX code. There are plenty of examples out there but I’d like to add one more to the pile of cool examples. It may be cool using AJAX concepts with JavaScript & HTML, but using the concept with JavaFX is just amazing. The possibilities are endless. “JavaFX to JavaScript and Back Part 1” will demonstrate what the application will do and “JavaFX to JavaScript and Back Part 2” will be on steps on how-to do it.

While I was a kid growing up, I would go to the bowling alley to get a 25 cent(US) bouncy ball from the vending machine. I’ve lost many a bouncy ball in the parking lot from bouncing them as hard as I could. In this example I will create a 500 by 400 pixel sized bouncy ball containment field in JavaFX. This is to prevent them from ever leaving me like they did when I was a kid. 😦

Note: Thanks to Stephen Chin an author of the book Pro JavaFX Platform and WidgetFX for allowing me to host The Bouncy Ball Containment Field Click Here to run!

(Tip: If in Firefox just middle mouse click link to launch in another tab)

Initial Ball added to ball world

Here is an example of an HTML page with an embedded JavaFX applet with HTML form elements below for data input to create bouncy balls. The demo allows a user to specify the number of balls to add to the scene called the “Add balls to Scene” field.  When the “Add Ball(s)” button is pressed the JavaScript code will pass the number of balls to the JavaFX public script level function called addBalls() which will create bouncy balls. When balls are added the JavaFX Script code will talk back to the HTML read-only field called “Number of balls” to show the current number of balls in the containment field (Scene). On the JavaFX side of things the location, velocity, size and color of the balls will be generated randomly and rendered on the scene.  You should notice the new JavaFX 1.2 Toggle Button component on the upper left. It is used to stop and  start the animation of the bouncing of the balls (Timeline pause & play to be exact). The first use case scenario is add balls which depicts the JavaScript to JavaFX data flow. The second use case scenario is display the current number of bouncy balls in the containment field which depicts the JavaFX to JavaScript data flow.

Balls addBalls(lots of balls)

This shows when the user specifies a whole bunch of balls to add.

Button to use JavaScript function addBalls()
Button to use JavaScript function addBalls()

A ball can be removed from the scene by just clicking an individual ball.  The field “Number of balls” input field is really a read-only field that is updated from the JavaFX side whenever balls are added and removed from the bouncy ball containment field.

More Bouncy Balls

By adding many balls I wanted to see how well it would perform.

Tons of Bouncy Balls

Here I add balls, lots of balls! The bouncy ball containment field now holds 400 bouncy balls. Although I might not be doing things efficiently I noticed my CPU was performing adequately. The utilization was between 41% to 46% (older Dell hyper-threading Inspiron XPS laptop Pentium(R) 4 CPU 3.40 GHz) using Windows XP SP 3. It was a smooth animation with no flicker. The Ball class is a Custom Node which contains a Circle with an effect called Radial Gradient to give it a 3D sphere look.

Dragging JavaFX Applet out of Web Page

By using the Alt key along with mouse drag the JavaFX applet will appear to be pulled out of the browser’s Web page and out onto the desktop.

JavaFX Applet living outside of browser

By default, when you drag the applet out of the browser onto your desktop a small “close button” appears on the upper right corner of your application. When the user clicks the “close button” box the JavaFX applet will snap right back into the browser. Another thing to note is that when the JavaFX applet is running outside the browser and the user subsequently closes the browser the applet will remain running independent of the browser.   This introduces an interesting ability that adds a new dimension to usability on the desktop.  As the user becomes drawn into the application experience the need to be attached to the Web page will be obsolete. Another cool effect that I have added to the Application is transparency or opacity level when the mouse cursor is exiting or entering the scene area. When the user moves the mouse away from the application the screen will fade to a subtle transparency. When the user moves the mouse cursor into the application screen area the background becomes fully opaque (white in this case).

Fully Opaque when mouse moves into application

Fully Opaque when mouse moves into application

This fading effect helps the user transition from one application to another.

The last, item of the desktop experience to mention is the Java JNLP mechanism that asks the user if he or she wants to create a short-cut icon for the application to launch at a later time.

Create short-cut dialog

Create short-cut dialog

To reproduce the dialog asking for a shortcut, you would drag the applet out of browser, then close tab or browser application and then a dialog will appear asking to create the short-cut.

Conclusion

Building the bouncy ball containment field was extremely fun. I learned a lot about boundsInParent which taught me that it is the rectangular region after all transformations to the node has been applied when adding balls to the Scene. As the future of the Web 2.0 apps grow users will demand richer interfaces and better content. I believe JavaFX has made great strides in desktop development and also increasing the usability factor of desktop applications by leaps and bounds. In Part 2 of “JavaFX to JavaScript and Back” I will detail a step-by-step tutorial on how to build the JavaFX applet you see here. Let me know what you think. Feedback is always welcome!