Today, my email in-box contained a pleasant surprise. Some time ago, Edgar Merino volunteered some simplifications to the code in the Graphical Menu Example post. Today, a gracious reader named Jase Batchelor sent me code for the DeckNode example converted to JavaFX SDK 1.0, as well as for the Graphical Menu Example that it is dependent upon. These example are part of a series of posts, based upon an earlier version of JavaFX, that I created to show you how to create custom UI elements in JavaFX. Today's post is the beginning of a new series, located in the JFX Custom Nodes SDK 1.0 category of this blog, that contains the code from the former series converted to JavaFX SDK 1.0 syntax and APIs.
Today's example features a simple custom node, named DeckNode, that I developed as a way to store a set of Node instances and display one of these nodes at a time. It is being used to augment the graphical menu example by showing the Node that pertains to a given menu button. Note that the concept is very similar to the Java CardLayout, so I wouldn't be surprised to see a similar class appear in the javafx.scene.layout package at some point. There is, by the way, a class with similar functionality in the open source JFXtras project. Anyway, just for grins, I provided the ability to specify a fade-in duration for the node being displayed. Here's a screenshot of the example application, with one of the nodes in the "deck" being displayed as the splash page:
One of the goals of JavaFX is for graphics designers and developers to be able to work together effectively in creating great looking applications. To demonstrate this, a graphics designer at Malden Labs created some graphical mock-ups for the fictitious CD application shown above. Here's an example mocked-up page in the form of a graphic that is being displayed as a result of pressing the Play button:
If you would like to try today's example out, click the Java Web Start link below:
Here's the code for the DeckNode class, contained in a file named DeckNode.fx:
/*
* DeckNode.fx -
* A node that shows a deck of nodes one at a time.
*
* Developed 2008 by James L. Weaver (jim.weaver at javafxpert.com)
* to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.custom_node;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
/**
* A node that shows a deck of nodes one at a time. When the
* visibleNodeId attribute is assigned a value, the Node whose
* id is the same as the name becomes visible. Note that the
* the id attribute of each Node must be assigned a unique name.
* This class also has an attribute in which a fade-in duration
* may be specified.
*/
public class DeckNode extends CustomNode {
/**
* A sequence that contains the Node instances in this "deck"
*/
public var content:Node[];
/**
* An optional node that will appear in the background of all nodes
*/
public var backgroundNode:Node;
/**
* The id of the node that is to be visible
*/
public var visibleNodeId:String on replace {
var nodes = for (node in content where node.id == visibleNodeId) node;
visibleNodeRef = if (sizeof nodes > 0) nodes[0] else null;
deckTimeline.playFromStart();
}
/**
* The amount of time to fade-in the new Node
*/
public var fadeInDur:Duration = 100ms;
/**
* This attribute is interpolated by a Timeline, and the opacity
* attribute of this DeckNode class is bound to it. This helps
* enable the fade-in effect.
*/
var opa:Number;
/**
* Override the opacity attribute so that it can be bound to the
* opa attribute that is interpolated by a Timeline
*/
override var opacity = bind opa;
/**
* A Timeline to control the fade-in behavior
*/
public var deckTimeline =
Timeline {
keyFrames: [
KeyFrame {
time: bind fadeInDur
values: [
opa => 1.0 tween Interpolator.LINEAR,
]
}
]
};
/**
* A reference to the Node in the Node instances that is visible
*/
protected var visibleNodeRef:Node;
/**
* Create the Node
*/
public override function create():Node {
Group {
content: bind [
backgroundNode,
visibleNodeRef
]
};
}
}
Shown below is the main script for this program, in a file named DeckNodeExampleMain.fx:
/*
* DeckNodeExampleMain.fx -
* An example of using the DeckNode custom node. It also demonstrates
* the MenuNode and ButtonNode custom nodes
*
* Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
* to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.deck_node_example.ui;
import javafx.scene.Group;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import com.javafxpert.custom_node.*;
var deckRef: DeckNode;
var sceneRef: Scene;
var menuRef: MenuNode;
Stage {
title: 'DeckNode Example'
width: 500
height: 400
visible: true
scene: sceneRef = Scene {
fill: Color.BLACK
content: [
deckRef = DeckNode {
fadeInDur: 700ms
content: [
Group {
var vboxRef:VBox;
var splashFont = Font.font("Sans serif", FontWeight.BOLD, 12)
id: "Splash"
content: [
ImageView {
image:
Image {
url: "{__DIR__}images/splashpage.png"
}
},
vboxRef = VBox {
translateX: bind sceneRef.width - vboxRef.boundsInLocal.width - 20
translateY: 215
spacing: 1
content: [
Text {
content: "A Fictitious Audio Application that Demonstrates"
fill: Color.WHITE
font: splashFont
},
Text {
content: "Creating JavaFX Custom Nodes"
fill: Color.WHITE
font: splashFont
},
Text {
content: "Application Developer: Jim Weaver"
fill: Color.WHITE
font: splashFont
},
Text {
content: "Graphics Design: Malden Labs"
fill: Color.WHITE
font: splashFont
},
]
}
]
},
Group {
id: "Play"
content: [
ImageView {
image:
Image {
url: "{__DIR__}images/playlist.png"
}
}
]
},
Group {
id: "Burn"
content: [
ImageView {
image:
Image {
url: "{__DIR__}images/burning.png"
}
}
]
},
Group {
id: "Config"
content: [
ImageView {
image:
Image {
url: "{__DIR__}images/config.png"
}
}
]
},
Group {
id: "Help"
content: [
ImageView {
image:
Image {
url: "{__DIR__}images/help.png"
}
}
]
}
]
},
menuRef = MenuNode {
translateX: bind sceneRef.width / 2 - menuRef.boundsInLocal.width / 2
translateY: bind sceneRef.height - (menuRef.boundsInLocal.height)
buttons: [
ButtonNode {
title: "Play"
imageURL: "{__DIR__}icons/play.png"
action:
function():Void {
deckRef.visibleNodeId = "Play";
}
},
ButtonNode {
title: "Burn"
imageURL: "{__DIR__}icons/burn.png"
action:
function():Void {
deckRef.visibleNodeId = "Burn";
}
},
ButtonNode {
title: "Config"
imageURL: "{__DIR__}icons/config.png"
action:
function():Void {
deckRef.visibleNodeId = "Config";
}
},
ButtonNode {
title: "Help"
imageURL: "{__DIR__}icons/help.png"
action:
function():Void {
deckRef.visibleNodeId = "Help";
}
},
]
}
]
}
}
deckRef.visibleNodeId = "Splash";
Please notice the last line of the listing above is what causes the node that we named "Splash" to be displayed when the application starts.
The code for the other custom nodes used in this program from the Graphical Menu Example post was converted by the gracious reader as well. Here's ButtonNode.fx:
/*
* ButtonNode.fx -
* A node that functions as an image button
*
* Developed 2008 by James L. Weaver (jim.weaver at javafxpert.com)
* to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.custom_node;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.effect.Glow;
import javafx.scene.shape.Rectangle;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
public class ButtonNode extends CustomNode {
/**
* The title for this button
*/
public var title:String;
/**
* The Image for this button
*/
var btnImage:Image;
/**
* The URL of the image on the button
*/
public var imageURL:String on replace {
btnImage =
Image {
url: imageURL
};
}
/**
* The percent of the original image size to show when mouse isn't
* rolling over it.
* Note: The image will be its original size when it's being
* rolled over.
*/
public var scale:Number = 0.9;
/**
* The opacity of the button when not in a rollover state
*/
public var opacityValue:Number = 0.8;
/**
* The opacity of the text when not in a rollover state
*/
public var textOpacityValue:Number = 0.0;
/**
* A Timeline to control fading behavior when mouse enters or exits a button
*/
public var fadeTimeline =
Timeline {
keyFrames: [
KeyFrame {
time: 500ms
values: [
scale => 1.0 tween Interpolator.LINEAR,
opacityValue => 1.0 tween Interpolator.LINEAR,
textOpacityValue => 1.0 tween Interpolator.LINEAR
]
}
]
};
/**
* This attribute is interpolated by a Timeline, and various
* attributes are bound to it for fade-in behaviors
*/
var fade:Number = 1.0;
/**
* This attribute represents the state of whether the mouse is inside
* or outside the button, and is used to help compute opacity values
* for fade-in and fade-out behavior.
*/
var mouseInside:Boolean;
/**
* The action function attribute that is executed when the
* the button is pressed
*/
public var action:function():Void;
/**
* Create the Node
*/
public override function create():Node {
Group {
var textRef:Text;
content: [
Rectangle {
width: bind btnImage.width
height: bind btnImage.height
opacity: 0.0
},
ImageView {
image: btnImage
opacity: bind opacityValue;
scaleX: bind scale;
scaleY: bind scale;
translateX: bind btnImage.width / 2 - btnImage.width * scale / 2
translateY: bind btnImage.height - btnImage.height * scale
onMouseEntered:
function(me:MouseEvent):Void {
mouseInside = true;
fadeTimeline.rate = 1.0;
fadeTimeline.play();
}
onMouseExited:
function(me:MouseEvent):Void {
mouseInside = false;
fadeTimeline.rate = -1.0;
fadeTimeline.play();
me.node.effect = null
}
onMousePressed:
function(me:MouseEvent):Void {
me.node.effect = Glow {
level: 0.9
};
}
onMouseReleased:
function(me:MouseEvent):Void {
me.node.effect = null;
}
onMouseClicked:
function(me:MouseEvent):Void {
action();
}
},
textRef = Text {
translateX: bind btnImage.width / 2 - textRef.boundsInLocal.width / 2
translateY: bind btnImage.height - textRef.boundsInLocal.height
textOrigin: TextOrigin.TOP
content: title
fill: Color.WHITE
opacity: bind textOpacityValue
font: Font.font("Sans serif", FontWeight.BOLD, 16)
},
]
};
}
}
And here is the MenuNode.fx listing:
/*
* MenuNode.fx -
* A custom node that functions as a menu
*
* Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
* to demonstrate how to create custom nodes in JavaFX
*/
package com.javafxpert.custom_node;
import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.effect.Reflection;
import javafx.scene.layout.HBox;
public class MenuNode extends CustomNode {
/*
* A sequence containing the ButtonNode instances
*/
public var buttons:ButtonNode[];
/**
* Create the Node
*/
public override function create():Node {
HBox {
spacing: 10
content: buttons
effect:
Reflection {
fraction: 0.50
topOpacity: 0.8
}
}
}
}
As always, please leave a comment if you have any questions.
Regards,
Jim Weaver
JavaFXpert.com
Recent Comments