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]







