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
        }
    ]
};
RingButton - Step 1

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
                    }
                ]
            }
RingButton - Step 2

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
                    }
                ]
            }
        }
RingButton - Step 3

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
        }
RingButton - Step 4

5.

Smoothen out the sharp edges in the image

        effect: GaussianBlur {radius: 2}
RingButton - Step 5

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 around Duke
SpiffyRing {
    fill: Color.CYAN
    thickness: 4
    content: Text {
        textOrigin: TextOrigin.TOP
        fill: Color.BLACK
        content: "JavaFX"
        font: Font { embolden: true }
    }
};⁞
SpiffyRing around some text
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 }
            ]
        }
    }
};
SpiffyRing around a shape

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!

Comments:

This is cool stuff, I was in the same position as you I hated to use drawing tools, I like more programming and now with your article I see that with JavaFX is plain simple, This awesome, thanks for sharing this ideas.

Posted by Otengi Miloskov on January 02, 2009 at 04:45 PM PST #

very nice ;-)

Posted by jos koenen on February 10, 2009 at 10:08 PM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

arvindsrinivasan

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today