By: Dean Iverson
Today I want to write about the Group
class. The Group
class
is a type of Node
that holds a reference to a sequence of child nodes.
When nodes are grouped together like this they can be transformed (translated, rotated,
scaled) all at once. It is very common to see the Group
class in any
JavaFX program. It is a workhorse in the field of GUIs: faithful, reliable, utilitarian.
Boring? Not on your life! Lurking under that plain exterior is a facilitator of
sexy GUIs just waiting to be unleashed.
Blend, Baby
A quick look at the documentation for the
Group
node will reveal two variables in the class (not counting the ones inherited from
Node
, of course). The first is the aforementioned sequence
of nodes held by the contents
variable. But the other is a variable called
blendMode
. It is of type
BlendMode,
which is a Java enumeration. There are 19 different blend modes that range from the
obvious to the obscure. It may not look like much, but there is a lot of special effects
potential contained in those 19 enums.
If you take a black rectangle and blend it with a circle that contains a radial gradient... Presto! Instant spotlight. If you then take this spotlight and blend it with other nodes... Well, cool things start to happen.
A First Experiment
For our first spotlight experiment, let's use these cute puppies. Aren't they adorable? Don't worry, I promise that no harm will come to them during these experiments. All we're going to do is shine a spotlight on them. Well, about 19 different spotlights to be exact. I wrote a little program that demonstrates what affect each of the 19 different blend modes has on our spotlight. Here is the source code:
var blendModes = BlendMode.values();
var blendModeIndex = 5;
var spotlightX = 0.0;
var spotlightY = 0.0;
var puppyImage = Image {
url: "{__DIR__}puppies.jpg"
}
Stage {
title: "Spotlighting Group Blend Modes"
scene: Scene {
content: [
Group {
blendMode: bind blendModes[blendModeIndex]
content: [
ImageView {
image: puppyImage
},
Group {
id: "spotlight"
blendMode: BlendMode.DIFFERENCE
content: [
Rectangle {
width: bind puppyImage.width
height: bind puppyImage.height
fill: Color.BLACK;
onMouseClicked: function( me:MouseEvent ) {
if (blendModeIndex == (sizeof blendModes) - 1) {
blendModeIndex = 0;
} else {
++blendModeIndex;
}
}
},
Circle {
translateX: bind spotlightX
translateY: bind spotlightY
radius: 100
fill: RadialGradient {
centerX: 0.5
centerY: 0.5
stops: [
Stop { offset: 0.1, color: Color.WHITE },
Stop { offset: 0.5, color: Color.BLACK },
]
}
}
]
}
]
},
Text {
x: 10
y: 30
fill: Color.LIGHTGRAY
font: Font { size: 24 }
content: bind blendModes[blendModeIndex].name()
},
]
},
}
Timeline {
keyFrames: [
at(0.0s) { spotlightX => 30; spotlightY => 110 },
at(0.5s) { spotlightX => 30; spotlightY => 110 },
at(1.0s) { spotlightX => 125; spotlightY => 84 },
at(1.5s) { spotlightX => 125; spotlightY => 84 },
at(2.0s) { spotlightX => 210; spotlightY => 110 },
at(2.5s) { spotlightX => 210; spotlightY => 110 },
at(3.0s) { spotlightX => 280; spotlightY => 115 },
at(3.5s) { spotlightX => 280; spotlightY => 115 },
]
repeatCount: Timeline.INDEFINITE
autoReverse: true
}.play()
The first thing to notice is that BlendMode
is a regular Java enum.
Since JavaFX integrates so well with Java, we can call all of the normal enum
methods like BlendMode.values()
to get a sequence containing all of
the blend modes. We can also make use of all the usual methods on the individual
members of an enum like BlendMode.SRC_OVER.name()
to get the name
string of the enum.
Next, check out the group with the id of "spotlight" that is declared within the
scene. It consists of a black rectangle that is the same size as the puppy image
and a circle that has a radial gradient fill. The circle is translated using
the spotlightX
and spotlightY
variables. These two
shapes are blended with the DIFFERENCE
blend mode. This works
well to create a hole through which to view our puppies.
Finally, the spotlight group is placed in another group with an ImageView
that displays the puppy image. The blend mode of this new group is bound to the
current index into the blendModes
sequence. This index is incremented
every time you click on the spotlight group. This way you can just click to cycle
through each blend mode to see its effect. Here are some of my favorites.
You can download the source code for this project here, or just run the project by clicking the following Java Web Start launch icon:
Text In The Spotlight
The cool thing about this functionality is that you can use it to blend any nodes that are inserted into a group. In honor of the M3DD Conference I was inspired to use this technique to recreate the famous "slide to unlock" effect of the iPhone. All it takes is some text, a gradient-filled circle, and some animation. This is no problem in JavaFX; the code for the effect is simple.
var spotlightX:Number = 0;
var scene:Scene;
var textNode:Text;
Stage {
title: "Spotlight Text"
scene: scene = Scene {
width: 200
height: 100
fill: Color.BLACK
content: Group {
blendMode: BlendMode.SRC_ATOP
content: [
textNode = Text {
content: "slide to unlock"
translateX: bind (scene.width - textNode.layoutBounds.width) / 2
translateY: bind (scene.height - textNode.layoutBounds.height) / 2
textOrigin: TextOrigin.TOP
fill: Color.GRAY
font: Font { size: 24 }
},
Circle {
radius: 15
translateX: bind spotlightX
translateY: bind scene.height / 2
fill: RadialGradient {
centerX: 0.5
centerY: 0.5
stops: [
Stop { offset: 0.0, color: Color.WHITE },
Stop { offset: 0.1, color: Color.WHITESMOKE },
Stop { offset: 0.5, color: Color.GRAY },
]
}
}
]
}
}
}
Timeline {
keyFrames: [
at(0s) { spotlightX => textNode.boundsInParent.minX - 30 },
at(2s) { spotlightX => textNode.boundsInParent.maxX + 30 }
]
repeatCount: Timeline.INDEFINITE
}.play()
The heart of the effect is a group that blends the text node with the circle. Here
we use the SRC_ATOP
blend mode which effectively clips the spotlight
to the shape of the text. The result looks like this:
It loses something without being able to see the animation, but once again you can download the source code for this project here, or just run the project by clicking the following Java Web Start launch icon:
Being able to blend nodes together inside a Group
is a powerful feature
that we've only scratched the surface of here. I encourage you to play around with
the different blend modes and see what cool things you can discover. You may just
find that implementing the group and blend technique is the key to making short work
of that difficult effect you've been struggling with.
Dean Iverson
JavaFXpert.com
Hello James,
Thank you for the lecture today, and thank you for your reply to my question about JavaFX on mobile phones.
Hard to get a question in at the right time, would like to hear more about the work on +MobileFx+ or +JavaFX for Mobile+.
now to my subject *s*
Probably the only Icelander in your audience today.
So I just have to comment on Leif - or on Leifur.
Leif Eriksson.
His name is Leifur Eiriksson ( often called Leifur Heppni, or "Leifur the Lucky one" ) born around the year 980 in Iceland. His father was Eiríkur rauði Þorvaldsson, or short Eiríkur rauði ( Eirikur the Red ) born in Norway.
Eiríkur was the first European to settle on Greenland, he took his family with him to the Viking settlement in Greenland.
Leifurs mother, Þjóðhildar, and his two brothers came with him.
Leifur is said to have sailed around the year 1000 to North America and the rest is history.
The source to his travel is found in the " Grænlendingasögu " or the Greenland-sagas.
The sagas can ofcourse be bought on Amazon ( Graenlendinga Saga )
Bestu kveðjur, Ing
Posted by: Ingimar | January 27, 2009 at 05:01 PM
Adam,
Considering that JavaFX is partially targeted at mobile and embedded applications, I'm sure the guys at Sun are hard at work on optimization.
Even so, your numbers seem extremely anomalous. On my Mac and on Windows XP running in VMWare on my Mac, I get far more than .5 frames per second. The effect is nice and smooth in both cases. The Java Console tells me that the app is taking about 24MB of memory.
I think there might be something else going wrong on your computer other than a 1.0 version of JavaFX.
Dean
Posted by: Dean Iverson | January 25, 2009 at 12:24 PM
I want to love JavaFX. I really really do. Maybe it is just version 1.0 hiccups, but your top example (with the adorable puppies) runs at what looks like maybe a half a frame/sec and uses 25% of my CPU. (Mind you I have a Core 2 WinXP machine with 4gigs of memory)
It looks mostly like nasty GC pauses. Perhaps moving the Xms in the jnlp file will help. But, looking at the process size, it is already taking up 50m. This is the kind of stuff that gives me heartburn over the future of a rich client java. A neat, but exceedingly simple effect, requires hand tuning and more memory than any other application except my web browser.
Sigh..
Posted by: Adam Malter | January 25, 2009 at 11:57 AM