« Seeing Spots at Java Mobile & Embedded Developer Days | Main | Want to Learn Compiled JavaFX Script? New Tutorial Available »

January 29, 2008

Spinning Wheel Got to Go 'Round - A Compiled JavaFX Script Example

At Java Mobile and Embedded Developer Days last week I met a lot of great people.  One of these was Kevin Nilson, the leader of the Silicon Valley Web Developer JUG.  He relayed the idea (based upon something used by the No Fluff Just Stuff gang) of creating an application in JavaFX Script that consists of a wheel that has the names of JUG attendees on it.  The wheel would revolve and land on a name, who would then receive a prize.  Today's post is a first cut at this, which also demonstrates several compiled JavaFX Script features.  Here's a screenshot of the application:

Winnerwheeljfx_3  

When the program starts up, a wheel with some fictitious names appears.  The user can click the dot in the center of the circle and enter a list of names (up to 60) in a dialog box, shown below:

Winnerwheeljfx_dialog

Clicking the OK button causes the names entered to appear in the wheel, as shown in the screenshot above.  Clicking the arrow on the left causes the wheel to spin, landing on a name, and showing a dialog box with the winner:

Winnerwheeljfx_winnerdialog

Presumably, since the JUG is in California, the rock bands in this list regularly attend the Silicon Valley JUGs :-) 

The Source Code with a Caveat

Here's the source code for the application, but please keep in mind that the Key-Frame animation capability in compiled JavaFX Script is still under development.  The syntax planned is very succinct, as referred to in the Key-Frame Animation post in Chris Oliver's Weblog.  The syntax shown here uses Key-Frame animation, but is more verbose.  It also exposes an underlying compiled JavaFX Script feature (pointers), that won't be exposed when the Key-Frame animation implementation is fully baked.

With that in mind, here is the source for our WinnerWheelJFX custom component.  Notice that it extends CompositeNode, so it is a graphical component:

/*
*  WinnerWheelJFX.fx - A wheel with names on it that spins
*                      and lands on a random name.  This will
*                      be used initially by Java Users Groups
*                      to give a away door prizes.  It will be
*                      improved upon over time, serving as an
*                      example of compiled JavaFX 2D graphics
*                      and animation.
*
*                      Note: The compiled JavaFX Script Key-Frame
*                      implementation isn't complete, so the
*                      animation syntax is more verbose than it
*                      will be in the near future.
*
*  Initially developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*/
import javafx.ui.*;
import javafx.ui.animation.*;
import javafx.ui.canvas.*;
import java.io.BufferedReader;
import java.io.StringReader;
import java.lang.Math;
import java.lang.System;
import com.sun.javafx.runtime.PointerFactory;

public class WinnerWheelJFX extends CompositeNode {
  public attribute names:String[];
  public attribute chosenName:String;
  public attribute chosenIdx:Integer;
  public attribute running:Boolean = true;
 
  private static attribute maxNames = 60;
  private attribute dlg:Dialog;
  private attribute rand:Number;
  private attribute stopValue = 1100;
  private attribute a:Integer on replace (olda) {
    chosenIdx = (((a * rand) % 360) / 360.0 * sizeof names).intValue();
    chosenName = names[chosenIdx];
    if (a >= stopValue) {
      running = false;
      MessageDialog {
        title: "And the Winner Is..."
        visible: true
        message: chosenName
      }
    }
  }
  private attribute pf = PointerFactory {};
  private attribute bpa = bind pf.make(a);
  private attribute pa = bpa.unwrap();
  private attribute interpolate = NumberValue.LINEAR;
  private attribute t =
    Timeline {
      keyFrames: [
        KeyFrame {
          keyTime: 0s;
          keyValues:
            NumberValue {
              target: pa;
              value: 0;
              interpolate: bind interpolate
            }
        },
        KeyFrame {
          keyTime: 10s;
          keyValues:
            NumberValue {
              target: pa;
              value: 700;
              interpolate: bind interpolate
            }
        },
        KeyFrame {
          keyTime: 20s;
          keyValues:
            NumberValue {
              target: pa;
              value: stopValue
              interpolate: bind interpolate
            }
        }
      ]
    };

  public function spin() {
    running = true;
    rand = Math.random() + .5;
    interpolate = NumberValue.LINEAR;
    t.start();
  }
  public function composeNode():Node {
    var margin = 20;
    var canvas = getCanvas();
    var cX = bind canvas.width / 2;
    var cY = bind canvas.height / 2;
    var rad = bind Math.min(cX, cY) - margin;
    var origX = bind cX - rad;
    var origY = bind cY - rad;
    return
      Group {
        content: [
          Polygon {
            var spinFillColor = Color.PURPLE
            points: bind [
              cX - rad,
              cY,
              origX / 2,
              cY - (origX / 4),
              origX / 2,
              cY + (origX / 4)
            ]
            cursor: Cursor.HAND
            fill: bind spinFillColor
            onMouseEntered:
              function(cme:CanvasMouseEvent):Void {
                spinFillColor = Color.YELLOW;
              }
            onMouseExited:
              function(cme:CanvasMouseEvent):Void {
                spinFillColor = Color.PURPLE;
              }
            onMouseClicked:
              function(cme:CanvasMouseEvent):Void {
                spin();
              }
          },
          Circle {
            var editFillColor = Color.RED
            cx: bind cX
            cy: bind cY
            radius: bind rad / 30
            cursor: Cursor.HAND
            fill: bind editFillColor
            onMouseEntered:
              function(cme:CanvasMouseEvent):Void {
                editFillColor = Color.BLUE;
              }
            onMouseExited:
              function(cme:CanvasMouseEvent):Void {
                editFillColor = Color.RED;
              }
            onMouseClicked:
              function(cme:CanvasMouseEvent):Void {
                dlg = Dialog {
                  var ta = TextArea {
                    rows: 10
                    columns: 20
                    background: Color.WHITE
                    text: ""
                  }
                  modal: true
                  title: "Enter Up to {maxNames} Names"
                  visible: true
                  content: ta
                  buttons: [
                    Button {
                      text: "OK"
                      defaultButton: true
                      action:
                        function():Void {
                          names = [];
                          var peopleStr:String = ta.text;
                          var br = new BufferedReader((new StringReader(peopleStr)));
                          var line:String;
                          var i = 0;
                          while ((line = br.readLine()) <> null and i <= maxNames) {
                            insert line into names;
                            i++;
                          }
                          dlg.hide();
                        }
                    },
                    Button {
                      text: "Cancel"
                      defaultCancelButton: true
                      action:
                        function():Void {
                          dlg.hide();
                        }
                    }
                  ]
                };
              }
          },
          Group {
            transform: bind [
              Rotate.rotate(if (running) (a * rand) % 360
                            else chosenIdx.doubleValue() / sizeof names * 360.0, cX, cY)
            ]
            content: bind [
              for (name in names)
                Text {
                  transform: [
                    Rotate.rotate(((sizeof names - indexof name).doubleValue() /
                                    sizeof names * 360) % 360, cX, cY)
                  ]
                  font:
                    Font {
                      face: FontFace.SANSSERIF
                      size: 16
                      style: FontStyle.BOLD
                    }
                  fill: if ((indexof name % 3) == 1) Color.RED
                        else if ((indexof name % 3) == 2) Color.BLUE
                             else Color.GREEN
                  x: cX - rad + 5
                  y: cY
                  content: "{name}"
                }
            ]
          }
        ]
      };
  }
}

Here's a sample program that uses the WinnerWheelJFX component:

/*
*  WinnerWheelExample.fx - A JavaFX example of using a custom component
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*/

import javafx.ui.*;
import java.lang.System;

Frame {
  var winnerWheel =
    WinnerWheelJFX {
      names: [
        "Aaaaaaaa Aaaaaa",
        "Bbbbbbbb Bbbbbb",
        "Cccccccc Cccccc",
        "Dddddddd Dddddd",
        "Eeeeeeee Eeeeee",
        "Ffffffff Ffffff",
        "Gggggggg Gggggg",
        "Hhhhhhhh Hhhhhh",
        "Iiiiiiii Iiiiii",
        "Jjjjjjjj Jjjjjj",
        "Kkkkkkkk Kkkkkk",
        "Llllllll Llllll",
        "Mmmmmmmm Mmmmmmmmmmm"
      ]
    }
  title: "Winner Wheel JFX"
  height: 750
  width: 800
  resizable: false
  visible: true
  content:
    Canvas {
      background: Color.WHITE
      content: winnerWheel
    }
  onClose:
    function():Void {
      System.exit(0);
    }
}

Compiling and Running this Example

Use the normal commands to compile and run this example, but since there are two source files, to compile them you'll want to use a wildcard as shown below:

javafxc *.fx

To run the example, use the following command:

javafx WinnerWheelExample

Give it a whirl!

JavaFX Script Boot Camp Announcement

Javafx_bootcamp_indy_apr_911

As a heads-up, I will be offering a JavaFX Script 2.5 day "boot camp" on Wednesday, April 9 through Friday, April 11, 2008 (ending at noon) in Indianapolis, Indiana.  This course is designed to get you quickly up to speed in JavaFX Script application development.  A primary reference for this course is my JavaFX Script book, but the course has its own syllabus which includes material covered in the book as well as up to the minute developments in compiled JavaFX Script. Registration will open soon, and for this pilot class I am accepting 12 students.  The cost of this pilot class will be 900 USD per student. Additional students from the same organization will be 600 USD.  You'll need to bring your laptop computer with the latest versions of the JavaFX Script downloads (which I'll specify in more detail as the class date approaches).  The prerequisite for the class will be the completion of a JavaFX Script programming assignment that I'll post soon to this weblog.  I'm looking forward to teaching this class and hope that you can attend!

More details to follow,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/t/trackback/2709996/25469484

Listed below are links to weblogs that reference Spinning Wheel Got to Go 'Round - A Compiled JavaFX Script Example:

Comments

My bad, this is old code. Will try to get it ported to the latest SDK... unless you have a newer version. :D

Tried this with the JavaFX SDK and getting an error on

private attribute a:Integer on replace (olda) {
chosenIdx = (((a * rand) mod 360) / 360.0 * sizeof names).intValue();
chosenName = names[chosenIdx];

/Desktop/WinnerWheelExample/src/WinnerWheelJFX.fx:32: Sorry, I was trying to understand an optional identifier but I got confused when I saw '(' which is an operator.
private attribute a:Integer on replace (olda) {

Post a comment

If you have a TypeKey or TypePad account, please Sign In