30 Lines of JavaFX
steveonjava | September 29, 2009What can you do in 30 lines of JavaFX? JFXStudio is running a contest right now to find this out…
http://jfxstudio.wordpress.com/2009/08/31/jfxstudio-challenge-small-is-the-new-big/
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:
Please enable Javascript and Flash to view this Blip.tv video.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:[
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: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();
action();
}
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) {
LIGHTGRAY
} else if (indexof entry mod 3 == 1) {
DARKGRAY
} 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;
beep.play();
Timeline {
keyFrames: [
at(50ms) {bgColor => GREEN tween Interpolator.EASEBOTH}
at(150ms) {bgColor => BLACK tween Interpolator.EASEBOTH}
]
}.play();
}
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;
}
}
}.play();
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
}
}
}
}
}















There is an error in line 10? (unexpected type)
I can’t run the script
1000 chars difference all from white space n variable names! That’s 25% of the total char count
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!
Impressive stuff. Any chance you could post a formatted version of the source code?
[...] 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 [...]
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…
What can I say, I like descriptive variable names.
Thanks for the feedback. Please see my comment above about the copy/paste errors (it should work now).
It was a pain, but I reconstituted something close to the original. Enjoy!