This e-card was written in JavaFX. The source code can be found here.
If you encounter problems seeing it, I am not surprised.Related Posts:
e海拾贝 - My public blog space on technical subjects & art.
This e-card was written in JavaFX. The source code can be found here.
If you encounter problems seeing it, I am not surprised.Related Posts:
2008 has been a disastrous year in many aspects all over the world - from cyclone Nargis to the earth quake in SiChuan China; from Mumbai Massacre to mobocracy in Thailand... and let's not forget the global economic crisis...
Now more to home, following the down-sizing trend a company that I know of has decided to wind down the operation in Sydney just 2 weeks before christmas. As a result, people in the Sydney office have been made redundant. Come to think of it, the holiday season is perfect to fire people. Why? During Christmas, New Year and Australia Day (January 26) people's productivity is not at its peak. Firing them during this period will save money for the company left and right - it is a win-win situation for the company! Being a profit-chasing business entity, it is perfectly logical to 'do the right thing' in the holiday season. After all, humans are nothing but resources to a company - hence the term HR.
In the story, Mr Scrooge came to his senses and became a better human being in the end. But in real life, the reverse is usually true...
My N95 battery could never last more than 16 hours even when sitting idle. I had to recharge it every night. This week when I was in Kathmandu, I was pleasantly surprised: my battery actually lasted over 36 hours during which I snapped more than 70 photos , played 20 minutes of music and had a couple of phone calls (totaling over 15 minutes of talk time maybe). I noticed that there was no 3G network in Nepal (or at least I was not roamed to one). I believe that is why my battery lasted so long. That fact that I live on the edge of the cell probably exacerbated the situation.
So I decided to disable 3G on my phone. Normally, this is a straight forward operation available from the Settings -> Phone menu. However, my phone was from 3 Australia (product code: 0547465) and it was branded - meaning that it was customised for 3 Australia: some 3 applications was installed, some menu items have been disabled... and the menu item to turn off 3G has been disabled. Super keen on saving the environment by extending my battery life I set out to debrand the phone and turn off 3G!
The process of debranding is as following:
On the negative side, there are some major drawbacks upgrading to the 30.x version of the firmware. TRK no longer works and I had to install alternative (but better) solution. Fontrouter does not seem to work any more in the new browser (because it does not have the character encoding that I need) and I got around the problem by installing Opera Mini instead.
The JavaFX 1.0 is shipped with 2D graphical capabilities and that's pretty much it. Even basic things such as 3D graphics, faster video codecs are labeled with 'coming soon...' on their demo site. Forget about building any business RIAs because there are only a handful of simple Swing widgets at your disposal. On the other hand, people have been using Flash to build business applications for years; Silverlight has got 3D capabilities since v1.0.
The JavaFX plugin for NetBeans is a joke - it's buggy, sluggish and poor in features. One mistyped word or bracket can result in red squiggly lines all over the screen; then it produces unhelpful error hints such as 'sorry I was expecting so and so but I saw such as such...' so that the user has to read through the whole sentence several times and still hard to figure out what the complaint is. What happens to KISS principle?
Sure, Sun had said that they intended the plugin to be used by designers also - hence the 'user friendly' error messages. But let's get serious, how can anyone assume graphical designers will be using computer programming language to define simple things such as Timelines? Sun doesn't have to look far to find a better way - Microsoft has Expression Web to be used by graphical designers, and VS.NET 2008 to be used by programmers when it comes to Silverlight design and development workflow. (The JavaFX plugin for Illustrator is only good for editing shapes).
To me there is not much point of deploying RIA as a thick client using Java Web Start. I am only interested in running RIA in a browser. With the new way of deploying applets in Java, JNLP is used, so that the HTML file points to a JNLP file and the JNLP file points to the JAR files and more JNLP files... It's a bit like my multi-hop flight from Sydney to Kathmandu (via Bristane then Bangkok) that took me over 20 hours and 6 of which was waiting at BKK for transit (that that is how I have the time to write this post) all because my company was too cheap to book the Qantas flight.
This is unnecessarily complicated because Sun doesn't want to have JavaFX plugin to the browser. On the other hand, Flash/Silverlight plugins are installed onto the browser so that all you need to specify is the object
or embed
tag in the HTML file without the hassle of running the myriad javascripts to detect/download the additional framework JAR files every time.
If you are using a browser that does not have the latest Java plugin (that supports JavaFX) installed (such as on my Nokia N95, or using the PC in the Thai Airways lounge at Kathmandu international airport), then you will be greeted with a message box forcing you to redirect to Sun's download website. There is no option to cancel!
Moreover, those javascripts do not report any errors if something goes wrong - it just hangs there with the Java logo spinning around and around forever...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:
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
- the CustomNode
simulating snow fall and snow flakes (using circle)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" } } }, ...
Stage
:
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:
... ...
SnowFall.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 * * 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 * * 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:
In the Telco business we often talk about the long tail products/services illustrated in the following diagram.
The idea is that for a service provider to stay competitive, it has to deploy and experiment with a large number of products and see how they are accepted by the market. There will be a small number of popular ones and the rest will be less popular as indicated in the long tail. So, over time, the service provider will kill the proven unpopular products and replace them with new/other products. You may wonder, why do they bother with the long tail since they are not that popular anyway? There are two major reasons:
How is this theory relevant to me? Well, being an IT professional, I am also paranoid about my knowledge and skills being stale. So I have to invest my time and effort on updating and expanding my knowledges constantly. With all these new technologies and frameworks popping up all the time, I feel like a headless chook all the time.
The best way to learn a new language/framework/library is to actually use it. 'Hello World' just doesn't cut it. Yet a full blown application such as Web Mail or Pet Shop may be too big or too long for the purpose of learning or proof of concept. That is why I have my own little pet project - Address (among others), which serves as the use cases for my learning and experimentation.
For the products that I like and feel useful in my work, I will keep them in my toolbelt and probably move them up to the head. For the ones that are irrelavent to me, I drop them from the Long Tail picture altogether. There are also products that have died (e.g. Orion Server) or dying and being replaced by others (e.g. GWT-Ext) in the game of evolution. But it does not mean that my time spent on them were wasted. I still learn and accumulate a great deal of experiences so that when I encounter a new product, I can always draw on these experiences and view it in a better light.
Before I show the SLOC comparison, I need to give a brief introduction of the little demo program that I wrote. The program is a GUI to show various capabilities of the Address package, which has a service tier and DAO implementations behind it. The Address package provides a meta-data driven way of managing address records (see here for the data model). The concerned application here is only the GUI portion. The GUI has the following screens (left=ZK, right=GWT):
This screen is used for Create, Retrieve, Update and Delete (CRUD) of the Address records from/to the database.
This screen is used to call the corresponding Address services to find addresses either by keywords or by location.
This screen is used to call the corresponding Address services to compare two given Address records. Note that the ZK implementation supports drag-and-drop - i.e. I can drag a row from the list of addresses and drop it onto the text box and the text box will be populated with the content of the address (as a string). On the other hand, the GWT implementation does not support DnD because at the time of writing it, GWT-Ext did not support this kind of DnD yet (and I am not about to rewrite it using new version of GWT-Ext now considering Sanjiv has left the project... but that's another story).
This screen is used to manage user preferences of the GUI application. Cookies are used as the storage.
In my SLOC count, I divided the source code into the following groups:
index.zul
and automatically forwarding to the main view after initialising the Address service); other versions do not have this screen.Note that in ZK, all the views are a result of drag-and-drop from the components palette with modifications by hand.
The physical and logical SLOC counts are shown below.
The results speak for themselves.
UPDATE [2008-12-08]: