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
- theCustomNode
simulating 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! [Update NYe2008]Now that it is NYE 2008, click here to run the e-card.
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