30 Lines of JavaFX

29 09 2009

What can you do in 30 lines of JavaFX?  JFXStudio is running a contest right now to find this out…


I strongly encourage you to give it a try.  So much so that I took a quick stab at putting together a sample myself this evening.  Welcome to the Time Wheel:

[blip.tv ?posts_id=2677636&dest=-1]

Everything is rendered using JavaFX Shape and Text primitives with clever PerspectiveTransform effects applied.  I also made use of an overlay gradient Flooded and Blended on an outer group in order to create the top and bottom shadow giving it a rounded 3d appearance.

Below is the full code for the application (clearly I was going for the 3000 character limit).  The formatted version was about 200 lines long and over 4000 characters, but by shortening variable names, removing white space, and merging lines, I was able to bring it down substantially.  This is definitely not a recommended coding practice, but met the contest constraints:

import javafx.scene.*;import javafx.scene.effect.*;import javafx.scene.layout.*;import javafx.scene.paint.*;import javafx.scene.paint.Color.*;
import javafx.scene.shape.*;import javafx.scene.text.*;import javafx.stage.*;import javafx.util.Math;import javafx.animation.*;import javafx.scene.media.*;
var ez=Interpolator.EASEBOTH;class Slot{var n:Node[];var v:Number on replace{
Timeline{keyFrames:at(.9s){c => v tween ez}}.play();a();}
var a:function();var c:Number;var e:String[];var s:Number;var f:Number;
init{var entryHeight=bind Math.sin(Math.PI/sizeof e)*h;n=for(entry in e)Stack{
var textLine:Text;content:[Group{var w1=bind Math.max(w,textLine.boundsInLocal.width);
content:[Rectangle{width:bind w1,height:bind h/12,fill:bind if(indexof entry mod 3==0){LIGHTGRAY} else if(indexof entry mod 3==1){DARKGRAY}
else{hsb(360*Math.abs((indexof entry*2-sizeof e as Number)/sizeof e),.4,.7)}}]}
textLine=Text{content:bind entry font:bind Font.font(null,h/12*.75)}]
visible:bind Math.abs(indexof entry-(c mod sizeof e))<=(sizeof e as Number)/4 or Math.abs(indexof entry-(c mod sizeof e))>=(sizeof e as Number)*3/4
effect:PerspectiveTransform{var sw=bind 360.0/sizeof e;var a=bind(indexof entry-c mod sizeof e)/sizeof e*360 + 90-sw/2;def sr=bind Math.toRadians(a + 90);
def er=bind Math.toRadians(a + 90 + sw);def r=bind h/2;var uy=bind r + Math.sin(er)*r;uly:bind uy ury:bind uy var ly=bind r + Math.sin(sr)*r;
lly:bind ly lry:bind ly ulx:bind Math.cos(er)*r*s;llx:bind Math.cos(sr)*r*s;urx:bind w-Math.cos(er)*r*f;lrx:bind w-Math.cos(sr)*r*f;}}}}
var x=45;var y=10;var w=24;var h=200;var ss=for(i in [1..9])Slot{e:for(j in [0..9])"{j}"s:.3-indexof i*.07 f:-.25 + indexof i*.07}
var so=MediaPlayer{media:Media{source:"http://jfxtras.org/sounds/beep.wav";}}
var bg:Color;ss[5].a=function(){so.stop();so.currentTime=0s;so.play();
Timeline{keyFrames:[at(50ms){bg => GREEN tween ez}at(150ms){bg => BLACK tween ez}]}.play();}
var d:java.util.Date;
Timeline{repeatCount:Timeline.INDEFINITE keyFrames:KeyFrame{time:1ms canSkip:true action:function(){d=new java.util.Date();
ss[8].c=java.lang.System.currentTimeMillis()mod 10;ss[7].c=(java.lang.System.currentTimeMillis()mod 100 as Number)/10;
ss[6].c=(java.lang.System.currentTimeMillis()mod 1000 as Number)/100;ss[5].v=d.getSeconds()mod 10;ss[4].v=d.getSeconds()/10;
ss[3].v=d.getMinutes()mod 10;ss[2].v=d.getMinutes()/10;ss[1].v=d.getHours()mod 10;ss[0].v=d.getHours()/10;}}}.play();
Stage{width:400 height:300 title:"Time Wheel"scene:Scene{fill:bind bg content:Group{content:[for(s in ss)Panel{
translateX:x + indexof s*30 +(Math.min(indexof s,7)/2)*10 translateY:y content:s.n height:h width:w}Text{content:bind "The time is now: {d}",fill:WHITE,x:58,y:242}]
effect:Blend{mode:BlendMode.SRC_ATOP topInput:Flood{paint:LinearGradient{endX:0 stops:[
y:y width:400 height:h}}}}}

There is still time to get your own submission in for the contest. The official deadline is Wednesday at midnight, so with a little hard work and determination you can easily crank out 30 lines of JavaFX goodness!

Update:  By popular demand, here is a cleanly formatted version of the same code:

import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.paint.Color.*;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import javafx.stage.*;
import javafx.util.Math;
import javafx.animation.*;
import javafx.scene.media.*;

class Slot {
    var node:Node[];
    var value:Number on replace {
            Timeline {keyFrames: at(.9s) {center => value tween Interpolator.EASEBOTH}}.play();
    var action:function();
    var center:Number;
    var entries:String[];
    var start:Number;
    var finish:Number;

    init {
        var entryHeight = bind Math.sin(Math.PI / sizeof entries) * height;
        node = for (entry in entries) Stack {
            var textLine: Text;
            content: [
                Group {
                    var w1 = bind Math.max(width,textLine.boundsInLocal.width);
                    content: [
                        Rectangle {
                            width: bind w1
                            height: bind height/12
                            fill: bind if (indexof entry mod 3 == 0) {
                            } else if (indexof entry mod 3 == 1) {
                            } else {
                                hsb(360 * Math.abs((indexof entry*2-sizeof entries as Number) / sizeof entries), .4, .7)
                textLine = Text {
                    content: bind entry
                    font: bind Font.font(null, height / 12 * .75)
            visible: bind Math.abs(indexof entry - (center mod sizeof entries)) <= (sizeof entries as Number) / 4 or Math.abs(indexof entry - (center mod sizeof entries)) >= (sizeof entries as Number) * 3/4
            effect: PerspectiveTransform {
                var sweep = bind 360.0 / sizeof entries;
                var action = bind(indexof entry - center mod sizeof entries) / sizeof entries * 360 + 90 - sweep/2;
                def startRadians = bind Math.toRadians(action + 90);
                def endRadians = bind Math.toRadians(action + 90 + sweep);
                def radius = bind height/2;
                var uy = bind radius + Math.sin(endRadians) * radius;
                uly: bind uy
                ury: bind uy
                var ly = bind radius + Math.sin(startRadians) * radius;
                lly: bind ly
                lry: bind ly
                ulx: bind Math.cos(endRadians) * radius * start
                llx: bind Math.cos(startRadians) * radius * start
                urx: bind width-Math.cos(endRadians)*radius*finish
                lrx: bind width-Math.cos(startRadians)*radius*finish
var x = 45;
var y = 10;
var width = 24;
var height = 200;
var slots = for(i in [1..9]) Slot {
    entries: for(j in [0..9]) " {j}"
    start: .3 - indexof i * .07
    finish: -.25 + indexof i * .07
var beep = MediaPlayer {
    media: Media {
        source: "http://jfxtras.org/sounds/beep.wav";
var bgColor: Color;
slots[5].action = function() {beep.stop();
    beep.currentTime = 0s;
    Timeline {
        keyFrames: [
            at(50ms) {bgColor => GREEN tween Interpolator.EASEBOTH}
            at(150ms) {bgColor => BLACK tween Interpolator.EASEBOTH}
var d: java.util.Date;

Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames: KeyFrame {
        time: 1ms
        canSkip: true
        action: function() {
            d = new java.util.Date();
            slots[8].center = java.lang.System.currentTimeMillis() mod 10;
            slots[7].center = (java.lang.System.currentTimeMillis() mod 100 as Number) / 10;
            slots[6].center = (java.lang.System.currentTimeMillis() mod 1000 as Number) / 100;
            slots[5].value = d.getSeconds() mod 10;
            slots[4].value = d.getSeconds() / 10;
            slots[3].value = d.getMinutes() mod 10;
            slots[2].value = d.getMinutes() / 10;
            slots[1].value = d.getHours() mod 10;
            slots[0].value = d.getHours() / 10;

Stage {
    width: 400
    height: 300
    title: "Time Wheel"
    scene: Scene {
        fill: bind bgColor
        content: Group {
            content: [
                for(start in slots) Panel {
                    translateX: x + indexof start*30 +(Math.min(indexof start,7)/2)*10
                    translateY: y content: start.node height: height width: width
                Text {
                    content: bind "The time is now: {d}"
                    fill: WHITE
                    x: 58
                    y: 242
            effect: Blend {
                mode: BlendMode.SRC_ATOP
                topInput: Flood {
                    paint: LinearGradient {
                        endX: 0
                        stops: [
                            Stop {offset: 0, color: color(0,0,0,.9)}
                            Stop {offset: .3, color: TRANSPARENT}
                            Stop {offset: .7, color: TRANSPARENT}
                            Stop {offset: 1, color: color(0,0,0,.9)}
                    y: y
                    width: 400
                    height: height



9 responses

29 09 2009

There is an error in line 10? (unexpected type)

I can’t run the script 😦

29 09 2009

The dog ate my homework!!! (there were some signs that got nuked in the conversion) The sample code is fixed and tested, give it another try…

29 09 2009

1000 chars difference all from white space n variable names! That’s 25% of the total char count

29 09 2009

What can I say, I like descriptive variable names. 🙂

29 09 2009

Copy/paste in a file, I get a compilation error at line 11 (which is 10 in your curious code area starting to number lines at 0!):
Test.fx:11: unexpected type
required: variable
found : value
visible:bind Math.abs(indexof entry-(c mod sizeof e))=(sizeof e as Number)*3/4
Note: Test.fx uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
1 error

Is that JavaFX 1.1 or before?

Oh, and the Click To Play link goes to a .flv file!

Beside, it looks nice, well done!

29 09 2009

Thanks for the feedback. Please see my comment above about the copy/paste errors (it should work now).

29 09 2009

Impressive stuff. Any chance you could post a formatted version of the source code?

30 09 2009

It was a pain, but I reconstituted something close to the original. Enjoy!

4 10 2009
Java desktop links of the week, October 5 | Jonathan Giles

[…] winner has been announced.. A number of people have posted their code for the challenge, including Stephen Chin, Carl Dea, Muhammad Hakim, Sergey Surikov, Vinu and Philippe […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: