Arvind@Sun
Graphically Challenged? JavaFX to the rescue!
Need nifty icons and buttons for your RIA but can't draw them yourself? If you're like me and
- want cool looking icons/buttons/graphics for your GUI
- are quite hopeless at using tools like Adobe® Photoshop®, GIMP
- waste hours (if not days!) googling for and unsuccessfully trying out the various Photoshop/GIMP tutorials that are on the Internet
- can't find somebody willing to do your dirty (graphics) work
- prefer programming to drawing
then JavaFX might just be what the doctor ordered!
After (quite) a few unsuccessful attempts, I finally succeeded at following the 8 steps in Jesse Norell's well written tutorial on creating 3D buttons using GIMP. When I tried to emulate the same steps using JavaFX, I was surprised by how easy it was to do. Here's what I did to create my first 3D button using JavaFX!
Table 1: Translating GIMP into JavaFX
| Step | GIMP | JavaFX | Result |
|
1. |
Draw a perfect circle |
Group {
var radius = 40
content: [
Circle {
centerX: 50
centerY: 50
radius: radius
stroke: Color.BLACK
strokeWidth: 1
fill: Color.WHITE
}
]
};
|
![]() |
|
2. |
Fade from white to black within the circle using either a linear or a radial fade. At the same time, remove the outline of the circle (strokeWidth: 0). The code to the right fills the circle with a linear gradient going from the south-west to the north-east. |
strokeWidth: 0
fill: LinearGradient {
startX: 0
startY: 1
endX: 1
endY: 0
stops: [
Stop {
offset: 0.0
color: Color.WHITE
},
Stop {
offset: 1.0
color: Color.BLACK
}
]
}
|
![]() |
|
3. |
Draw an inner circle and fill it with a linear fade in the opposite direction Instead of changing the start and end points of the gradient, I've just swapped the start and end colors. |
Circle {
centerX: 50
centerY: 50
radius: radius - 4
stroke: Color.BLACK
strokeWidth: 0
fill: LinearGradient {
startX: 0
startY: 1
endX: 1
endY: 0
stops: [
Stop {
offset: 0.0
color: Color.BLACK
},
Stop {
offset: 1.0
color: Color.WHITE
}
]
}
}
|
![]() |
|
4. |
Draw another smaller circle and fill it with any colour |
Circle {
centerX: 50
centerY: 50
radius: radius - 8
stroke: Color.BLACK
strokeWidth: 0
fill: Color.BLACK
}
|
![]() |
|
5. |
Smoothen out the sharp edges in the image |
effect: GaussianBlur {radius: 2}
|
![]() |
That's all there is to creating a spiffy looking graphic using a programming language that is intuitively easy!
Wouldn't it be cool if the 3D ring could be put around an arbitrary line of text or graphic such as an icon? To do so, we'd have to dynamically calculate the radius of the ring based on the size of the object that it "embeds". The rest of this blog describes the implementation of a custom node named SpiffyRing that draws a 3D ring around an user-specified node.
Here's the code for the SpiffyRing node:
package foobar.gui;
import java.lang.Math;
import javafx.scene.CustomNode;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
/**
* A class that draws a 3-dimensional ring around a specified node. You can
* customize the thickness of the ring, the colors used for the ring itself.
*/
public class SpiffyRing extends CustomNode {
/**
* The node around which to draw the ring
*/
public var content: Node;
/**
* Specifies the color used to fill the inside of the ring i.e. the space
* between the ring and the embedded content
*/
public var fill: Color = Color.BLACK;
/**
* Specifies the thickness of the ring in pixels.
*/
public var thickness:Integer = 8;
/**
* Specifies the gap (in pixels) between the ring and the embedded content
*/
public var gap:Integer = 2;
/**
* Specifies the two colors to use for coloring the ring itself with a
* 3D gradient
*/
public var color1:Color = Color.WHITE;
public var color2:Color = Color.BLACK;
// Calculate the radius of the ring using the dimensions of the embedded
// node as well as the thickness of the ring itself.
var radius = bind ((Math.hypot(content.boundsInLocal.width, content.boundsInLocal.height))/2 + thickness + gap);
public override function create(): Node {
var ring = Group {
// Center the ring with respect to the embedded node
translateX: bind content.boundsInLocal.width/2 - 1
translateY: bind content.boundsInLocal.height/2 - 1
content: [
// Draw the outer circle of the ring fading from color1-->color2
Circle {
centerX: 0
centerY: 0
radius: bind radius
stroke: Color.BLACK
strokeWidth: 0
fill: LinearGradient {
startX: 0
startY: 1
endX: 1
endY: 0
stops: [
Stop {
offset: 0.0
color: color1
},
Stop {
offset: 1.0
color: color2
}
]
}
},
// Draw the inner circle of the ring, fading in the opposite
// direction
Circle {
centerX: 0
centerY: 0
radius: bind (radius - thickness/2)
stroke: Color.BLACK
strokeWidth: 0
fill: LinearGradient {
startX: 0
startY: 1
endX: 1
endY: 0
stops: [
Stop {
offset: 0.0
color: color2
},
Stop {
offset: 1.0
color: color1
}
]
}
},
// Create the ring by filling the interior with the specified
// fill color
Circle {
centerX: 0
centerY: 0
radius: bind (radius - thickness)
stroke: bind fill
strokeWidth: 0
fill: bind fill
}
]
effect: GaussianBlur { // smoothen the sharp edges
radius: 2
}
};
// Put the ring and the embedded node together, adjusting their
// positions to account for the drawing radius of the ring itself
return Group {
translateX: bind (radius - content.boundsInLocal.width/2 + 2)
translateY: bind (radius - content.boundsInLocal.height/2 + 2)
content: [ ring, content ]
};
}
}
As you can see from the public attributes of SpiffyRing, there are several properties of the ring that you can customize including the thickness of the ring itself, the colors used to shade the ring and the node that you want to embed in the ring. Some aspects of the code are discussed below.
- The radius of the ring itself is calculated using the hypotenuse of the embedded content and the properties of the ring itself such as its thickness.
var radius = bind ((Math.hypot(content.boundsInLocal.width, content.boundsInLocal.height))/2 + thickness + gap);
- When drawing the circles that comprise the ring, their coordinates are adjusted so that the ring is drawn around the mid-point of the embedded node.
var ring = Group {
// Center the ring with respect to the embedded node
translateX: bind content.boundsInLocal.width/2 - 1
translateY: bind content.boundsInLocal.height/2 - 1
- Hardcoded values from the listing in Table 1 have now been replaced using the dynamic attributes of the SpiffyRing custom node.
- And finally, when putting the ring and embedded content together, their coordinates are adjusted to account for the fact that the ring needs to be positioned such that it appears to be drawn all around the content.
return Group {
translateX: bind (radius - content.boundsInLocal.width/2 + 2)
translateY: bind (radius - content.boundsInLocal.height/2 + 2)
content: [ ring, content ]
};
Let's see what SpiffyRing can do!
|
JavaFX |
Result |
SpiffyRing {
fill: Color.YELLOW
content: ImageView {
image: Image {
url: "https://duke.dev.java.net/images/iconSized/duke.gif"
}
}
};
|
![]() |
SpiffyRing {
fill: Color.CYAN
thickness: 4
content: Text {
textOrigin: TextOrigin.TOP
fill: Color.BLACK
content: "JavaFX"
font: Font { embolden: true }
}
};⁞
|
![]() |
SpiffyRing {
color1: Color.RED
color2: Color.WHITE
fill: Color.BLACK
gap: 0
content: Rectangle {
height: 20
width: 20
stroke: Color.RED
strokeWidth: 1
fill: LinearGradient {
startX: 0 startY: 0
endX: 0.5 endY: 0.5
stops: [
Stop { offset: 0.0 color: Color.WHITE },
Stop { offset: 0.5 color: Color.RED }
]
}
}
};
|
![]() |
This was my first try at dynamically sizing and positioning JavaFX nodes and I found it to be quite straightforward. I enjoy dabbling with JavaFX and hope that JavaFX has a great 2009!
Posted at 04:37AM Jan 02, 2009 by arvindsrinivasan in JavaFX | Comments[2]
Problems Using LinearGradients in Compiled JavaFX Script?
If you find that the javafx.ui.LinearGradient class in Compiled JavaFX Script is not working, then you are probably running into JFXC-531.
The tutorial demo that was part of the distribution for the interpreted version of the JavaFX language described a linear gradient as a color transition along a line given by two end points. The coordinates of the end points are specified as fractional values between 0 and 1. However, for the values of the startX, startY, endX and endY attributes of the javafx.ui.LinearGradient class (in Compiled JavaFX Script) you will need to specify geometric coordinates and not fractional values between 0 and 1.
The following examples illustrate how you can specify linear gradients in different directions:
| Description |
JavaFX |
Image |
|
A square that is simply filled with white colour.
The (x, y) coordinates (in the Group's coordinate system) of the four corners of the square (starting at the top-left corner and proceeding in a clockwise direction) are (20, 20), (120, 20), (120, 120) and (20, 120) |
import javafx.ui.*;
import javafx.ui.canvas.*;
Canvas {
content:
Group {
transform: Translate { x: 40 y: 20 }
content: [
Rect {
x: 20
y: 20
height: 100
width: 100
stroke: Color.BLACK
strokeWidth: 2
fill: Color.WHITE
}
]
}
};
|
![]() |
|
The same square filled with a horizontal (left-to-right) linear gradient that transitions from yellow to orange to red. The gradient starts at the top left corner (20, 20) of the square and ends at the top right corner (120, 20). |
fill: LinearGradient {
startX: 20
startY: 20
endX: 120
endY: 20
stops: [
Stop {
offset: 0.0
color: Color.YELLOW
},
Stop {
offset: 0.5
color: Color.ORANGE
},
Stop {
offset: 1.0
color: Color.RED
}
]
spreadMethod: SpreadMethod.PAD
}
|
![]() |
|
Horizontal (right-to-left) gradient fill (swap the start and end coordinates) |
fill: LinearGradient {
startX: 120
startY: 20
endX: 20
endY: 20
|
![]() |
|
Vertical (top to bottom) gradient fill |
fill: LinearGradient {
startX: 20
startY: 20
endX: 20
endY: 120
|
![]() |
|
Diagonal (south-west to north-east) gradient fill |
fill: LinearGradient {
startX: 20
startY: 120
endX: 120
endY: 20
|
![]() |
I plan on using gradients for drawing some cool JavaFX buttons/icons!
Posted at 09:09AM Jun 05, 2008 by arvindsrinivasan in JavaFX | Comments[1]
Timer Trouble
I wrote a simple GUI to test/demo my Timer class that I had blogged about previously. In doing so, I discovered a bug in my implementation.
![]() |
Figure 1a is a screenshot of the TimerDemo application that lets one configure the various attributes of the Timer class and test the timer by starting/stopping it. This TimerDemo application simply logs a couple of lines of text every time the timer action is invoked. The lines highlighted in grey show the timer action being invoked every 20 seconds.
![]() |
Figure 1b shows how the synchronized attribute of the Timer class affects when the first timer action is invoked. The highlighted portions in the above screenshot show that when a synchronized timer is used, the first invocation of the action occurs at the start of the next minute and thereafter when the configured interval expires.
The portion highlighted in yellow in Figure 1a shows a bug in my Timer implementation. It shows that the unsynchronized timer (incorrectly) invokes the action when the timer is started instead of after the expiry of the configured interval. The following diff fixes this bug:
--- Timer.fx.org 2008-05-31 23:52:40.000000000 +0530
+++ Timer.fx 2008-05-31 23:51:01.000000000 +0530
@@ -105,7 +105,6 @@
};
sync.start();
} else {
- alarm();
tick.start();
}
}
And now my Timer works better!
![]() |
Attachments:
-
TimerDemo.zip (consolidated source files)
Posted at 01:09AM Jun 02, 2008 by arvindsrinivasan in JavaFX | Comments[0]
A Timer written in JavaFX
I've been playing with JavaFX Script since the announcement at last year's JavaOne and I'm happy to report that my interest in dabbling with JavaFX hasn't waned! The declarative scripting syntax of JavaFX is very appealing to novice/weekend GUI programmers like myself.
For my JavaFX experiment, I needed a text-only display of the current time that would update itself every minute on the minute.
The first part was easy. I created TextClock.fx using my favourite JavaFX IDE - NetBeans. Here's a screenshot of TextClock.fx in action within NetBeans (using the Compiled JavaFX Script plugin).

The above program displays a timestamp but doesn't update it. I needed something that would update timestamp every minute. This is what I came up with.
foobar/util/Timer.fx:
package foobar.util;
import java.util.Calendar;
import javafx.lang.Duration;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
/**
* A Timer class that can be configured/extended to invoke an action after a
* specified number of seconds. It can optionally be configured to synchronize
* the timer to the system clock.
*/
public class Timer {
// Specifies the interval at which the timer should run
// (The default interval is 30 seconds)
public attribute interval: Number = 30;
// Specifies whether the timer should be synchronized to the system clock.
// This is useful when creating a timer that fires every minute on the
// minute, every hour on the hour etc.
public attribute synchronize: Boolean;
// Controls whether the timer is running or not. Setting it to true starts
// the timer, and setting this attribute to false terminates the timer.
public attribute active:Boolean
on replace {
if (initialized) {
if (active) {
start()
} else {
stop();
}
}
}
// User configurable action that is invoked when the specified
// interval expires. This can be used as an alternative to extending the
// Timer class to implement action2()
public attribute action: function():Void;
// Another user configurable action that is invoked when the specified
// interval expires. This operation can be implemented in the class that
// extends this Timer class
public function action2():Void {
// do nothing in this class
}
// Start the timer only after all attributes have been initialized
postinit {
initialized = true;
if (active) {
start();
}
}
// Prevents the timer from starting before all attributes are set
private attribute initialized = false;
// An internal counter used to synchronize this timer with the system clock
private attribute countdown: Duration;
// The "main" timer that invokes the user-specified action(s) every time the
// interval expires
private attribute tick: Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames:
[
KeyFrame {
time: Duration {
millis: bind (interval * 1000)
}
action: function() {
alarm();
}
}]
};
// A timer that helps synchronize 'tick' to the next minute
private attribute sync: Timeline = Timeline {
repeatCount: 1
keyFrames:
[KeyFrame {
time: bind countdown
action: function() {
sync.stop();
alarm();
tick.start();
}
}]
};
// Starts the timer
private function start():Void {
var seconds = 0;
if (synchronize) {
var d: Calendar = Calendar.getInstance();
seconds = d.get(Calendar.SECOND);
}
if (seconds > 0) {
countdown = Duration {
millis: ((60 - seconds) * 1000)
};
sync.start();
} else {
alarm();
tick.start();
}
}
// Stops the timer
private function stop():Void {
sync.stop();
tick.stop();
}
// Invokes the user configured action(s)
private function alarm():Void {
if (action <> null) {
(action)();
}
action2();
}
}
The new animation APIs javafx.animation.Timeline, javafx.animation.KeyFrame were much easier to understand and use than the dur operator that exists in the interpreted version of the language (Previously, I had implemented the Timer class using Interpreted JavaFX Script. That version can be found here).
Hooking up the timer to my clock program was straightforward. The lines in bold below show how I enhanced TextClock.fx to automatically update timestamp every minute.
package foobar.samples;
import java.util.Date;
import java.text.SimpleDateFormat;
import javafx.ui.*;
import foobar.util.Timer;
var formatter = new java.text.SimpleDateFormat("EEE MMM dd, hh:mm aa");
var now = new java.util.Date();
var timestamp = formatter.<<format>>(now);
var timer = Timer {
interval: 60 // fire every minute
synchronize: true
active: true
action: function():Void {
now = new java.util.Date();
timestamp = formatter.<<format>>(now);
}
};
Frame {
visible: true
title: "Text Clock"
width: 200
content: SimpleLabel { text: bind timestamp }
};
That brings me to the end of my first JavaFX blog! I hope you found it useful.
Attachments:
-
TextClock.zip (consolidated source files)
Posted at 06:37AM May 30, 2008 by arvindsrinivasan in JavaFX | Comments[0]















