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:
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:
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:
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
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
it was cool
Posted by: jinnapa | March 12, 2009 at 10:33 PM
My bad, this is old code. Will try to get it ported to the latest SDK... unless you have a newer version. :D
Posted by: Rom | August 08, 2008 at 05:09 AM
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) {
Posted by: Rom | August 08, 2008 at 04:57 AM