JavaFX 1.0 was finally released last Friday. After over 2 years of brooding, I wonder what Sun has come up with - oh boy what a disappointment the JavaFX IDE (NB plug-in) is! But that is for another post.
The JavaFX 1.0 is no good for developing any RIA business applications for its lack of widgets. Instead it is OK for little toys such as those demos published on javafx.com. Immersed in the holiday season atmosphere, I implemented my Christmas card in JavaFX. A screenshot is shown below.
The Christmas card has the following features:
- Transformation - the red and yellow text fly into the screen using a combination of Translation, Rotation and Scale.
- Media playback - background music (mp3 file) from Michael Buble - Let it snow.
- Animation - snow fall simulation
- Drag out - when run as applet in a browser, the card can be dragged out of the browser.
The Project Structure
The project was created using NetBeans 6.5 with JavaFX plug-ins (installed from within NetBeans). I created a JavaFX project called MerryChristmas. The project structure is shown below:
The only files that I added are:
LetItSnow.mp3- the background music songtree.png- the background image: photo of Christmas tree shot at Darling Harbour on December 8, 2008.Main.fx- the main JavaFX file containing the scene and its contentsSnowFall.fx- theCustomNodesimulating snow fall and snow flakes (using circle)
The Main Contents
The contents of the main scene are two Texts, one background image, one background music and the snow fall (implemented as CustomNode).
The texts and their animation (actually transformation) are created as shown below. Notice the transforms which use binding data changed by the Timeline definition.
...
var x:Number;
var y: Number;
var scaleX : Number;
var scaleY:Number;
var angle:Number;
Timeline {
repeatCount: 1
keyFrames: [
at (0s) {x=>180; y => 250; scaleX => 0.0; scaleY => 0.0; angle => -180},
at (10s) {x=> 0; y => 0;
scaleX => 1.0; scaleY => 1.0;
angle => 0
tween Interpolator.EASEOUT},
]
}.play();
...
var lighting = Lighting{
light: DistantLight{
azimuth: 60,
elevation: 70
}
surfaceScale: 5
};
Stage {
...
scene: Scene {
content: [
...
Text {
content: "Merry Christmas & \nHappy New Year!"
font: Font.font("Arial Bold", FontWeight.BOLD, 40)
textOrigin: TextOrigin.TOP
textAlignment: TextAlignment.CENTER
x: 10
y: 20
transforms: bind[
Translate{
x:x
y:y}
Scale{
x:scaleX
y:scaleY}
Rotate{
angle: -angle
pivotX: 180
pivotY: 20 }
]
fill: Color.RED
effect: lighting
},
Text {
content: "From Romen"
font: Font.font("Times", FontWeight.BOLD, 40)
textOrigin: TextOrigin.TOP
x: 90
y: 470
transforms: bind[
Translate{
x:x
y:y}
Scale{
x:scaleX
y:scaleY}
Rotate{
angle: angle
pivotX: 180
pivotY: 450}
]
fill: Color.YELLOW
effect: lighting
},
...
Any suggestions on how to make unicode text work are welcome.
The background image and music are easy enough:
...
ImageView {
image: Image {
url: "{__DIR__}tree.png"
}
x:0,
y:0
},
...
MediaView {
mediaPlayer: MediaPlayer {
autoPlay: true
media: Media {
source: "{__DIR__}LetItSnow.mp3"
}
}
},
...
Drag Out
To enable drag out when run as an applet in browser, I added the following extension to theStage:
extensions: [
AppletStageExtension {
shouldDragStart: function(e): Boolean {
return e.primaryButtonDown;
}
useDefaultClose: false
}
]
Also I had to specify in the Project's properties dialog and check the Draggable checkbox so that it will generate the .html file with the draggable set to true. The generated .html file snippet:
... ...
Snow Fall
All snow related classes are in theSnowFall.fx file. I used white circle as the snow flake:
public class SnowFlake extends Circle {
init {
fill = Color.WHITE;
radius = 3 + Math.random() * 3;
opacity= Math.random() * 0.5 + 0.5;
}
}
The SnowFall class takes 3 attributes:
height, width- these specify the size of the canvas so that all the snow flakes can spread out in the canvas making it more realisticnumberOfFlakes- the number of snow flakes to be drawn and animated. The more flakes, the more threads JavaFX will have to create and manage under the hood.
KeyFrame {
...
values: [
x =>
x + (Math.random() - 0.5)
y =>
y + Math.random() tween Interpolator.LINEAR
]
...
When the snow flake hits either side of the boundary, it sort of bounces back a little; when it moves off the bottom of the canvas, it is wrapped around and re-appears at the top of the canvas and falls down again. This is achieved by using action of Keyframe which are executed at the beginning of each key frame.
...
action: function() {
if(flake.centerY+y >height) {
y=0; flake.centerY=0;
}
if(flake.centerX+x>width) {
x=0; flake.centerX=width;
}
if(flake.centerX+x<0) {
x=0; flake.centerX=0;
}
}
...
The SnowFall class generates all the snow flakes in a for loop. Each snow flake is accompanied by a Timeline controlling its animation. The number of frames in each Timeline is also randomised (between 60 and 120 seconds per cycle), to avoid the situation where all snow flakes suddenly reset at the same time.
for ( i in [1..numberOfFlakes]) {
...
def timer = 60+60*Math.random();
Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [ for (j in [0..timer]) {
KeyFrame {
...
Want to see the Christmas card in action? Well, you will have to wait till Christmas!Meanwhile, here are the complete source code.
SnowFall.fx
/*
* SnowFall.fx
*
* Created on 10/12/2008, 09:55:19
*/
package merrychristmas;
import java.lang.Math;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.animation.KeyFrame;
import java.lang.System;
/**
* @author ROMENL
*/
public class SnowFall extends CustomNode {
public var height:Number;
public var width:Number;
public var numberOfFlakes:Number;
override function create():Node {
return Group {
content: [
for ( i in [1..numberOfFlakes]) {
var x:Number;
var y:Number;
x=1;
y=1;
var flake = SnowFlake{
centerX: Math.random() * width
centerY: Math.random() * height
translateX: bind x;
translateY: bind y;
}
def timer = 60+60*Math.random();
Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [ for (j in [0..timer]) {
KeyFrame {
time: bind Duration.valueOf(j * 1000)
values: [
x =>
x + (Math.random() - 0.5)
y =>
y + Math.random() tween Interpolator.LINEAR
]
action: function() {
if(flake.centerY+y >height) {
y=0; flake.centerY=0;
}
if(flake.centerX+x>width) {
x=0; flake.centerX=width;
}
if(flake.centerX+x<0) {
x=0; flake.centerX=0;
}
}
}
}
]
}.play();
flake
}]
};
}
}
public class SnowFlake extends Circle {
init {
fill = Color.WHITE;
radius = 3 + Math.random() * 3;
opacity= Math.random() * 0.5 + 0.5;
}
}
Main.fx
/*
* Main.fx
*
* Created on 9/12/2008, 15:29:24
*/
package merrychristmas;
import javafx.animation.*;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.scene.image.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.text.*;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.AppletStageExtension;
import javafx.stage.Stage;
/**
* @author ROMENL
*/
var width=390;
var height=540;
var x:Number;
var y: Number;
var scaleX : Number;
var scaleY:Number;
var angle:Number;
Timeline {
repeatCount: 1//Timeline.INDEFINITE
keyFrames: [
at (0s) {x=>180; y => 250; scaleX => 0.0; scaleY => 0.0; angle => -180},
at (10s) {x=> 0; y => 0;
scaleX => 1.0; scaleY => 1.0;
angle => 0
tween Interpolator.EASEOUT},
]
}.play();
var lighting = Lighting{
light: DistantLight{
azimuth: 60,
elevation: 70
}
surfaceScale: 5
};
Stage {
title: "Merry Xmas JavaFX"
width: width
height: height
scene: Scene {
content: [
ImageView {
image: Image {
url: "{__DIR__}tree.png"
}
x:0,
y:0
},
Text {
content: "Merry Christmas & \nHappy New Year!"
font: Font.font("Arial Bold", FontWeight.BOLD, 40)
textOrigin: TextOrigin.TOP
textAlignment: TextAlignment.CENTER
x: 10
y: 20
transforms: bind[
Translate{
x:x
y:y}
Scale{
x:scaleX
y:scaleY}
Rotate{
angle: -angle
pivotX: 180
pivotY: 20 }
]
fill: Color.RED
effect: lighting
},
Text {
content: "From Romen"
font: Font.font("Times", FontWeight.BOLD, 40)
textOrigin: TextOrigin.TOP
x: 90
y: 470
transforms: bind[
Translate{
x:x
y:y}
Scale{
x:scaleX
y:scaleY}
Rotate{
angle: angle
pivotX: 180
pivotY: 450}
]
fill: Color.YELLOW
effect: lighting
},
MediaView {
mediaPlayer: MediaPlayer {
autoPlay: true
media: Media {
source: "{__DIR__}LetItSnow.mp3"
}
}
},
SnowFall {
height: height
width: width
numberOfFlakes: 30
}
]
}
extensions: [
AppletStageExtension {
shouldDragStart: function(e): Boolean {
return e.primaryButtonDown;
}
useDefaultClose: false
}
]
}
Related Posts:

No comments:
Post a Comment