« JavaFX Skins Game, and Happy New Year! | Main | Create Widgets in JavaFX with Newly-Released WidgetFX 1.0 »

January 04, 2009

DeckNode Example Converted to JavaFX SDK 1.0

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 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: 

Decknodeexample_5  

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:

Decknodeexample_play

If you would like to try today's example out, click the Java Web Start link below:

Webstartsmall2

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

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00e54f133d698834010536ab2eb5970c

Listed below are links to weblogs that reference DeckNode Example Converted to JavaFX SDK 1.0:

Comments

Thanks James for this post,

I have a problem when i try to run your example
with a MigLayout component from the jfxtras project, i have the following error "java.lang.stackoverflowerror..."
do you have any idea why the deck node generate this error;

thanks a lot for your help.

sorry James, forget my last post :(

Thanks James for your great work !
Could you explain us the role of DeckNode::backgroundNode ?
I do not catch it and it is not used. In its definition, I don't see anything that ensure it will be placed and shown as the "background" ?

Thanks,

tibO.

"Thanks James for your concern and helpfull blog !
Short question : what is the DeckNode::backgroundNode purpose ? it is used anywhere :(
What was its forst goal ?
Tahnks,
tibO"

The purpose of the DeckNode::backgroundNode variable is to provide a background for the deck node that is always visible no matter what node is also visible. It can be assigned when the DeckNode is created.

Thanks,
Jim Weaver

Thanks James for your concern and helpfull blog !
Short question : what is the DeckNode::backgroundNode purpose ? it is used anywhere :(
What was its forst goal ?
Tahnks,
tibO

"please let me know how it goes!"

Thanks, Jim. That worked.
The key is "content: bind [ activeScr ]" construction. Where "activeScr" is a reference to different instances of my classes which extends from Group and created in runtime depending on application state.

"Is there a way to change content of the scene in runtime? What I mean is what if you want to create multiple document interface in javaFX. So you would have a bunch of complicated GUI groups created in runtime, and you want to switch between them on your main scene. How do you do that?"

Mike,
Great idea! I haven't tried this, but here's a suggestion. Please let me know if it works or not: Create some instances of Scene that have the desired contents. Bind the scene to the stage in a similar manner to which this DeckNode example binds the visibleNodeRef to the content. For modularity, you may want to have multiple subclasses of Scene. That would have the advantage of being able to introduce a variable, perhaps named "id", that can identify each Scene (in the same manner that I'm using the id variable of Node). Please let me know if you'd like me to clarify, and again, please let me know how it goes!

Thanks,
Jim Weaver

Great blog, Jim!
Question:
Is there a way to change content of the scene in runtime? What I mean is what if you want to create multiple document interface in javaFX. So you would have a bunch of complicated GUI groups created in runtime, and you want to switch between them on your main scene. How do you do that?

Thanks,
Mike

"My goodness, Jim! You are really are rocking on your blog now. I mean you have the Java Champions logo integrated in the header. Now I see the standard for 2009. Congratulations and well done."

Thanks Peter! I do look forward to meeting up with you at a conference again soon. You'll be at J1? How about Jfokus?

Rock on! :-)
Jim Weaver

My goodness, Jim! You are really are rocking on your blog now. I mean you have the Java Champions logo integrated in the header. Now I see the standard for 2009. Congratulations and well done.

"One general question: Does it make more sense to bind node's variables (e.g. width) to our scene now instead to the stage's variables?"

riepi,

You are correct, and thanks for the suggestion. The stage height and width include the frame around it (when running in a frame, of course). The width and height of the scene are the dimensions of the interior of the frame. I have modified the code in this post.

Thanks again,
Jim Weaver

very nice, thank you both.
One general question: Does it make more sense to bind node's variables (e.g. width) to our scene now instead to the stage's variables?

regards, riepi

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been posted. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment

My Photo

Upcoming Speaking Engagements:


  • Stephen Chin and Jim Weaver speaking about JavaFX Platform

  • Speaking on JavaFX and Java at Øredev in Malmö, Sweden on 2-6 November, 2009

Upcoming JavaFX Training:


  • Developing Secure, Rich Internet Applications Hosted on a Variety of Clients Using JavaFX Technology

Enter your email address:

Delivered by FeedBurner

Available now as early access eBook


  • Click book image above to obtain eBook

Twitter Updates

    follow me on Twitter

    Affiliations:

    DZone Links:


    July 2009

    Sun Mon Tue Wed Thu Fri Sat
          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 31  

    Disclaimer:

    • By reading this site, you are agreeing that under no circumstances will Veriana Networks, Inc. or its affiliates be responsible for (1) any information contained on or omitted from the site, (2) any person's reliance on any such information, whether or not the information is correct, current or complete, (3) the consequences of any action you or any other person takes or fails to take, whether or not based on information provided by or as a result of the use of the sites.