Tray component in JavaFX

March 17, 2009

To aid my exploration of JavaFX I created the following tray-type component (as seen in OS X to display Dashboard widgets). When the disclosure button is pressed, the main content area slides up to reveal a tray area where controls or other information can be placed.

The code is straight-forward, containing a DisclosureButton class (the black cross button) and the main script, which composes the tray, main content area and button. I had to inline quite a bit, as many of the components need to reference other components for visual calculations – I’m still learning what the right balance of declarative syntax is.

I’m not particularly happy with the animation performance on OS X (you’ll notice it’s a bit choppy). Commenting out the main content areas drop shadow eliminates this issue – maybe future Java updates on the Mac will address this problem.

Here are the two states of the window:
tray
tray_exposed

You can web start it as well:
launch

And here’s the code (note that I’m not crazy about the way NetBeans formats the code, but I’ve given up fighting with it):

Main.fx

import javafx.scene.effect.DropShadow;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.Scene;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import tray.DisclosureButton;

def trayHeight = 75;

def trayGradient = LinearGradient {
    endX: 0.0
    endY: 1.0
    stops: [
        Stop {
            offset: 0.0
            color: Color.web("#2b2b2b")
        },
        Stop {
            offset: 1.0
            color: Color.web("#1b1b1b")
        } ]
}

def contentGradient = LinearGradient {
    endX: 0.0
    endY: 1.0
    stops: [
        Stop {
            offset: 0.0
            color: Color.web("#7c8697")
        },
        Stop {
            offset: 1.0
            color: Color.web("#596171")
    } ]
}

Stage {
    var scene: Scene
    var rectangle: Rectangle
    var button: DisclosureButton
    var mainContentText: Text

    title: "Tray"
    width: 400
    height: 300
    scene: scene = Scene {
            fill: Color.BLACK
            content: [
                Rectangle {
                    y: bind scene.height - trayHeight
                    width: bind scene.width
                    height: trayHeight
                    fill: trayGradient
                }
                Group {
                    translateY: bind button.buttonPercentRotated * - trayHeight
                    content: [
                        rectangle = Rectangle {
                            width: bind scene.width
                            height: bind scene.height
                            fill: contentGradient
                            effect: DropShadow {
                                color: Color.color(0, 0, 0, 0.35)
                                radius: 10
                                offsetY: 10
                            }
                        },
                        button = DisclosureButton {
                            translateX: 30;
                            translateY: bind scene.height - 30
                        },
                        mainContentText = Text {
                            fill: Color.WHITESMOKE
                            translateX: bind scene.width / 2 - mainContentText.boundsInLocal.width / 2
                            translateY: bind scene.height / 2 - mainContentText.boundsInLocal.height / 2
                            font: Font { size: 20
                            }
                            content: "Your main content here."
                        }
                    ]
                }
            ]
        }
}

DisclosureButton.fx

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;

public class DisclosureButton extends CustomNode {

    def startButtonAngle = 0.0;
    def endButtonAngle = -135.0;
    def strokeWidth = 2;
    def buttonRadius = 18;
    def lineRadius = 12;

    public var buttonPercentRotated: Double = 0;

    var buttonRotation: Double = 0 on replace oldValue {
        buttonPercentRotated = buttonRotation / endButtonAngle;
    };


    var buttonPressed = function(Void): Void {
        rotateTimeline.rate = rotateTimeline.rate * - 1;
        rotateTimeline.play();
    }

    def firstKeyFrame = KeyFrame {
        time: 0ms
        values: buttonRotation => startButtonAngle
    }

    def lastKeyFrame = KeyFrame {
        time: 650ms
        values: buttonRotation => endButtonAngle
    }

    def rotateTimeline = Timeline {
        rate: -1;
        keyFrames: [ firstKeyFrame, lastKeyFrame ]
    }

    def buttonBackground = Circle {
        radius: buttonRadius;
        fill: Color.BLACK;
        stroke: Color.WHITE;
        strokeWidth: strokeWidth
        onMouseClicked: buttonPressed
    }

    def lineOne = Line {
        startX: -lineRadius
        endX: lineRadius
        stroke: Color.WHITE
        strokeWidth: strokeWidth;
    }

    def lineTwo = Line {
        startX: -lineRadius
        endX: lineRadius
        rotate: 90
        stroke: Color.WHITE
        strokeWidth: strokeWidth;
    }

    def cross = Group {
        content: [lineOne, lineTwo]
    }

    def button = Group {
        content: [ buttonBackground, cross];
        rotate: bind buttonRotation;

    }

    override public function create(): Node {
        return button;
    }

}

9 Responses to “Tray component in JavaFX”

  1. Eric Burke Says:

    Is this supposed to work on Leopard? I get a Java Web Start exception and it won’t run.

  2. Igor Says:

    when the window is maximized it’s getting slow

  3. Tbee Says:

    Animation in JavaFX (1.1) is not on Flash’s level yet. I wrote a copy of a screen I did in flash before, and JFX definitely is not as smooth. Apparently they’re working on that.

    But I’m pleased to see people start building JFX components. And aside from all the Swing-vs-JavaFX discussion; that is not much code for such a nice thingamading.

  4. Ken Says:

    Hi Eric,

    It should work on Leopard. I’ve run it on both my home and work Leopard machine without issue.

    -Ken

  5. Ken Says:

    Hi Tbee,

    I too am assuming that Sun is working on performance issues – JavaFX is still a fresh and immature product.

    In general I’ve found that writing graphics code in JavaFX is very straight forward and certainly easier and more natural than using Java 2D. The one area where there is still some pain, though, is in laying things out. That is, getting components to fill their parent and centering them, etc. is harder than it should be – but I hear they’re working on this.

    -Ken

  6. Eric Burke Says:

    OK, works today here at work on Vista. Nice effect! The Snap Shots popups on this blog are extremely irritating, though… :-(

  7. Ken Says:

    Hi Eric,

    Yeah, that popup thing is annoying (it’s a WordPress “feature”). You can disable it, though.

    -Ken


  8. Hi,

    I’m very pleased to know that you are learning javaFX since your a great swing developer and I’m also learning and working on JavaFX.

    The animation is also choppy on my windows vista. I don’t know if this is the same case but also most of the demos on javafx site that use interpolation get choppy animation. This also happened when I worked with java3D (also has interpolation) but I don’t know what the problem is.

    Pedro


  9. […] Orr , never failing to impress, has created a simple JavaFX tray component , which oozes […]


Leave a comment