Get started with javafx.lang, javafx.util, and javafx.animation The JavaFX APIs cover a lot of distance, and so will the third and fourth installments in the Jump into JavaFX series. Jeff eases you into the JavaFX APIs with a tour of the javafx.lang and javafx.util packages. He then pops open javafx.animation to demonstrate JavaFX’s support for keyframe animation. Finally, you’ll use the JavaFX API classes found in javafx.application and javafx.scene to create an application window, paint its stage, and populate the stage with text, shapes, and images. Examples in this article are based on JavaFX Preview SDK.When working in JavaFX, you have access to both the standard Java APIs and the JavaFX APIs, which were created specifically for rich Internet application development. The JavaFX APIs are organized into the following packages:javafx.animationjavafx.applicationjavafx.ext.swingjavafx.inputjavafx.langjavafx.scenejavafx.scene.effectjavafx.scene.effect.lightjavafx.scene.geometryjavafx.scene.imagejavafx.scene.layoutjavafx.scene.mediajavafx.scene.paintjavafx.scene.textjavafx.scene.transformjavafx.utilIn this third article in the Jump into JavaFX series we’ll begin to explore many the API classes located in these packages. I’ll first look at some of the basic API classes found in javafx.lang and javafx.util. I’ll then introduce you to JavaFX’s support for keyframe animation, via classes in the javafx.animation package, and to the essential classes found in the javafx.application package. We’ll use these to create a sample JavaFX application’s decorated top-level window and specify its staging area. Finally, we’ll paint the application’s stage and specify its scene using node-based text, geometric shapes, and images. Don’t worry if some of the concepts discussed here (stage and scene, for instance) are unfamiliar; they won’t be after you’ve done the exercises! JavaFX Preview SDKThe discussion in this article is based on the JavaFX APIs found in the JavaFX Preview SDK, which will differ somewhat from the APIs found in JavaFX SDK 1.0. What you learn here will serve as background for Jeff’s first look at JavaFX SDK 1.0, in the final two articles of this series.Basic APIsJavaFX provides several basic APIs via the javafx.lang and javafx.util packages. The javafx.lang package specifies the language-oriented DeferredTask, Duration, and Sequences classes. The javafx.util package specifies the single StringLocalizer class. Except for DeferredTask, we’ll explore all of these classes in this section.In order to explore these classes, you’ll need to start up your NetBeans IDE and use its New Project wizard to introduce a new APIDemo1 project with apidemo1.BasicAPIs as the main file. As we explore the basic APIs, we’ll insert a series of scripts into BasicAPIs.fx, replacing the previous script with a new one.DurationThe first class we’ll explore is Duration. To begin, replace the skeletal BasicAPIs.fx‘s // place your code here line with the following script: Listing 1. Introducing the Duration classimport java.lang.System; import javafx.lang.Duration; var time1: Duration; System.out.println (time1); // Output: 0.0ms var time2 = 10m; System.out.println (time2) // Output: 600000.0ms This script accesses javafx.lang‘s Duration class, which represents a time interval. When a Duration variable is declared, it is by default initialized to 0.0 milliseconds. Alternately, you may specify an initializer based on a time literal, which is an integer suffixed with m (minutes), s (seconds), h (hours), or ms (milliseconds).Client-side Java’s evolutionary leapWhere does JavaFX fit in the big picture of Sun’s client-side offerings? Find out what client-side Java’s leaders and innovators have to say about it: “Client-side Java’s evolutionary leap” features insight from Richard Bair, Tim Boudreau, Stephen Chin, Danny Coward, Joseph Darcy, Mikael Grev, Kirill Grouchnikov, Cay Horstmann, and Jim Weaver.Just as Java does for the String class, JavaFX Script provides syntactic sugar for Duration. Along with time literals, which represent Duration instances, this sugar includes addition (+) and subtraction (-) operators for adding/subtracting duration values, and multiplication (*) and division (/) operators for multiplying/dividing a duration value by an arbitrary number. You can see these operators in Listing 2.Listing 2. Add, subtract, multiply, and divide time literals via the addition, subtraction, multiplication, and division operatorsjava.lang.System.out.println (10m+20s); // Output: 620000.0ms java.lang.System.out.println (10m-20s); // Output: 580000.0ms java.lang.System.out.println (1h/4); // Output: 900000.0ms java.lang.System.out.println (1h*2.5) // Output: 9000000.0ms Additionally, you can compare time literals for equality (30s == 30s), inequality (30s != 40s), and more (10m < 20m, 10m <= 10m, 10m > 20m, 10m >= 10m). You can also negate time literals (-2h), and invoke various conversion functions (3h.toMinutes() and 23m.toSeconds ()). When the JavaFX compiler encounters an expression consisting of time literals and operators, it converts this syntactic sugar to equivalent Duration instances and function calls. For example, var startTime = 10m is equivalent to var startTime = Duration { millis: 600000 }, and startTime+20s is equivalent to startTime.add (Duration { millis: 20000 }).A bug to watch out forThe NetBeans implementation of JavaFX Script is somewhat buggy. For example, var startTime = 10m; startTime += 10s; compiles correctly, but results in a thrown java.lang.ClassCastException at runtime. The workaround to this bug involves replacing += with +, which results in startTime = startTime+10s;. (This bug has been reported. See the Resources section to learn about reporting bugs in JavaFX.)SequencesThe javafx.lang package’s Sequences class is useful for manipulating sequences via its various utility functions. For example, you can sort a sequence via the Sequences public static sort(seq: java.lang.Object[], c: java.util.Comparator): <any>[] function, which is demonstrated by the following script:Listing 3. Sorting a sequencevar ages = [32, 23, 45, 19, 67, 98, 52]; java.lang.System.out.println (javafx.lang.Sequences.sort (ages, null)) // Output: [ 19, 23, 32, 45, 52, 67, 98 ] Additional interesting functions that you’ll find in Sequences include: public static binarySearch(seq: java.lang.Comparable[], key: java.lang.Comparable): Integer — search the specified sequence for the specified object using the binary search algorithm.public static reverse(seq: java.lang.Object[]): <any>[] — reverse a sequence.public static shuffle(seq: java.lang.Object[]): <any>[] — randomly permute a sequence (as in shuffling a deck of cards) using a default source of randomness.StringLocalizerTo localize string literals in JavaFX Script, you could take advantage of Java’s localization support. However, this support doesn’t let you express localization in a declarative manner. Fortunately, you can avoid working with Java’s localization support by taking advantage of the javafx.util package’s StringLocalizer class.StringLocalizer provides four attributes, starting with propertiesName, which identifies the package and base names of a properties-based resource bundle file. The base name has the form basename_xx.fxproperties, where xx is a two-letter locale designation (en for English); the file’s entries have the form "key"="value" (the double quotes are necessary).The key attribute identifies one of the keyed entries, and is assigned a string that doesn’t contain embedded double quotes. The locale attribute identifies the java.util.Locale object that’s used to supply the xx portion of the base name. Finally, the defaultString attribute identifies a default string to return if no appropriate localized string is found in the resource bundle. Localization is performed by invoking the public bound localizedString(): java.lang.String function. (This is just one of five functions located in StringLocalizer.) The following script demonstrates localizedString(), along with the propertiesName, key, and locale attributes:Listing 4. Declarative localization in JavaFXvar localizer = javafx.util.StringLocalizer { key: "Welcome" propertiesName: "apidemo1.foo.bar.MyResources" } java.lang.System.out.println (localizer.localizedString ()); localizer.locale = java.util.Locale.FRENCH; java.lang.System.out.println (localizer.localizedString ()) This script assumes that APIDemo1 contains a buildclassesapidemo1foobar directory hierarchy, and that bar contains MyResources_en.fxproperties (my default locale is English) and MyResources_fr.fxproperties properties files. foobar and suitable same-named properties files are located in this article’s code archive.Now that you’re familiar with the basic APIs, let’s explore JavaFX’s API support for animation. Keyframe animationBecause animation is crucial to the success of rich internet applications, JavaFX supports keyframe animation, a declarative animation model based on timelines (time sequences on which animations play out) and key frames (snapshots of animation state at points in time relative to their containing timelines). This support mostly consists of classes in the javafx.animation package.A timeline is expressed as a Timeline instance, which provides descriptive attributes and functions that control the timeline’s playback. Attributes include autoReverse, which declares that each animation cycle plays in reverse to the previous cycle, repeatCount, which declares a number of animation cycles, and so on. Functions are start(), stop(), pause(), and resume().A key frame is expressed as a KeyFrame instance. This class provides a list of key values (as a sequence of KeyValue objects). Each key value describes an end-state of a property at the time of the key frame, together with a function used to calculate “in-between” values relative to the previous key frame for the property. The KeyFrame class also provides a list of sub-timelines (as a sequence of Timelines) and a trigger. Each sub-timeline is evaluated with its starting point relative to the key frame’s time attribute value. A trigger is a void function that’s executed at the time instant specified by the key frame.JavaFX Script supports two kinds of keyframe animation: discrete and interpolated. I’ll discuss both in the next sections.Discrete keyframe animationWith discrete keyframe animation, the value of a given property instantaneously transitions to the value given in the key frame at the time instant of the key frame. In effect, this would be like replacing the previous image with the next one in a sequence of images. To demonstrate discrete keyframe animation for yourself, start NetBeans and use its New Project wizard to introduce a new APIDemo2 project with apidemo2.Animation as the main file. Replace the skeletal Animation.fx‘s // place your code here line with the following script:Listing 5. Discrete keyframe animation demovar timeline = javafx.animation.Timeline { autoReverse: true // reverse direction on each cycle repeatCount: javafx.animation.Timeline.INDEFINITE // never stop keyFrames: for (i in [30..40]) { javafx.animation.KeyFrame { time: 100ms*indexof i // each frame is 100ms apart action: function () { java.lang.System.out.println (i) } } } } timeline.start () The script in Listing 5 creates a Timeline instance and starts the animation via this instance. This animation continues indefinitely; each cycle increases property i from 30 through 40, or decreases this property from 40 through 30, in an increase/decrease pattern. We can understand this animation in terms of its various attributes:autoReverse (of type Boolean) reverses the direction on each animation cycle when set to true.repeatCount (of type Number) sets the number of animation cycles; INDEFINITE means until Timeline‘s stop() function is called.keyFrames (of type KeyFrame[]) describes 10 animation key frames via a KeyFrame sequence: time (of type Duration) defines the reference elapsed time offset within a single cycle of the Timeline instance at which the associated properties will be set, and at which the trigger will be executed. The first time offset is 0 milliseconds, and each subsequent offset is 100 milliseconds later than its predecessor.action (of type function():Void) identifies a trigger block (expressed as a function) that’s called when the elapsed time on a cycle passes the specified time of this KeyFrame. The function is called if the elapsed time passes the indicated value, even if it was never exactly equal to the time value.Interpolated keyframe animationUnlike discrete keyframe animation, interpolated keyframe animation calculates key frames that lie between a few chosen key frames, which are specified via at-based blocks. Each block informs JavaFX Script on how to calculate intermediate key frames via => and tween sugar, and interpolators defined by the Interpolator class. Try this for yourself by replacing the previous script in your NetBeans editor with the following one: Listing 6. Interpolated keyframe animation demovar timeline = javafx.animation.Timeline { autoReverse: true // reverse direction on each cycle repeatCount: javafx.animation.Timeline.INDEFINITE // never stop var i on replace { java.lang.System.out.println (i) } var begin = at (0s) { i => 30 } var end = at (10s) { i => 40 tween javafx.animation.Interpolator.LINEAR } keyFrames: [begin, end] } timeline.start () The first chosen key frame, which is assigned to begin, has its time attribute set to 0 seconds. At this moment, property i is initialized to 30 via =>. The second chosen key frame, which is assigned to end, has its time attribute set to 10 seconds. At this moment, property i is initialized to 40.The at block for the second key frame also tells JavaFX Script to use a linear interpolator to create intermediate key frames for i values ranging from 31 through 39. The linear interpolator ensures that each key frame’s time attribute reflects the same amount of time passing between any two key frames, and that no more than 10 seconds pass from the first to the last key frame of an animation cycle.More about keyframe animationIf you want to learn more about keyframe animation, and especially interpolation, I recommend reading “Time Again” by Chet Haase. I also recommend Chris Oliver’s blog, where he explains how JavaFX Script implements keyframe animation. Remember that at, =>, and tween are nothing more than syntactic sugar for a more verbose syntax.In addition to LINEAR interpolation, the Interpolator class supports DISCRETE, EASEIN, EASEOUT, and EASEBOTH forms of interpolation. With DISCRETE interpolation, you end up with discrete keyframe animation because intermediate key frames aren’t calculated. The EASEIN, EASEOUT, and EASEBOTH forms are similar to LINEAR, except for slowly easing into, out of, or both into and out of the animation. There’s a great deal more to learn about animation in JavaFX, but we’ll move on now to some of the javafx.application classes, where you’ll begin to experience JavaFX’s distinctive approach to rich Internet application development.Make a Window: Frame and StageThe javafx.application package’s Frame and Stage classes are used to specify a decorated top-level application window and its content area for a JavaFX application. Frame (a subclass of javafx.application.Window) provides access to a variety of attributes, including the six described below:closeAction (of type function():Void) identifies the function to execute whenever the user closes the window.height (of type Integer) specifies the window’s height (in pixels).stage (of type Stage) identifies the window’s content area, or stage.title (of type String) specifies the window’s title.visible (of type Boolean) causes the window to appear when true is assigned to this attribute.width (of type Integer) specifies the window’s width (in pixels).Similarly, Stage provides access to four attributes: content (of type javafx.scene.Node[]) specifies the stage’s content as a sequence of nodes — which I’ll discuss later.fill (of type javafx.scene.paint.Paint) describes the stage’s background paint.height and width (both of type Integer) are read-only attributes that provide the current dimensions of the content area.You might recall that I illustrated the Frame and Stage classes in “Jump into JavaFX, Part 1,” via my Hello, JavaFX! (Main.fx) script. After declaring a Model class to record the script’s application state in Model‘s text, opacity, and rotAngle attributes (whenever these attributes change, user interface attributes bound to them also change), the script uses a pair of object literals to instantiate Model, followed by Frame.The first attribute initializer in the Frame object literal, title: bind model.text, binds Frame‘s title attribute to the script’s model’s text attribute. Whenever the model’s text attribute changes, the new title appears on the frame window’s title bar. It also appears centered within the window’s content area (the stage).Remaining attribute initializers size the window by assigning values to width and height; create the content area via a Stage literal and assign this literal to the stage attribute; display the window by assigning true to visible; and assign an anonymous function to closeAction. This function executes when the window closes. ModelsWhile exploring JavaFX scripts, you’ll often encounter model classes and user interface attributes bound to model attributes. Separating an application’s model from its user interface is recommended practice for all JavaFX scripts.The stage presents the content that’s stored in Stage‘s content sequence attribute. Before we look at several interesting kinds of content that can be assigned to content, let’s learn how to paint the stage’s background.Paint the stage: Paint, Color, and GradientsStage‘s fill attribute lets you determine how the content area’s background is painted. Because this attribute is of type Paint, and because the javafx.scene.paint.Color, javafx.scene.paint.LinearGradient, and javafx.scene.paint.RadialGradiant classes subclass this abstract class, you can paint the background using either a solid color or a gradient pattern.The Color class lets you specify a solid color in various ways. For example, you can create an object literal, assigning the color’s red, green, and blue components to the literal’s red, green, and blue Number attributes. Alternatively, you can work with functions such as those listed below:public static color(red: Number, green: Number, blue: Number, opacity: Number): Color returns a solid color in the sRGB color space with the specified red, green, blue, and opacity values in the range 0.0-1.0.public static fromAWTColor(c: java.awt.Color): Color returns a solid color derived from the provided java.awt.Color instance.public static hsb(hue: Number, saturation: Number, brightness: Number, opacity: Number): Color returns a solid color based on the specified values for the HSB color model. The saturation, brightness, and opacity components should be floating-point values ranging from 0.0 through 1.0. The hue component can be any floating-point number. The floor of this number is subtracted from it to create a fraction between 0 and 1. This fractional number is then multiplied by 360 to produce the hue angle in the HSB color model.public static rgb(red: Integer, green: Integer, blue: Integer, opacity: Number): Color returns a solid color in the sRGB color space with the specified red, green, and blue values in the range 0-255, and the opacity value in the range 0.0-1.0.public static web(color: java.lang.String, opacity: Number): Color returns a solid color specified in hexadecimal notation, and having the specified opacity.If you refer back to the Hello, JavaFX! script from “Jump into JavaFX, Part 1,” you’ll discover that I assigned an instance of LinearGradient (via the object literal that I’ve reproduced below) to fill, to establish a gradient pattern with black at the top and blue-violet at the bottom for the content area’s background: Listing 7. Creating a linear gradient background patternfill: LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.BLACK }, Stop { offset: 1.0 color: Color.BLUEVIOLET } ] } LinearGradientPaint provides startX, startY, endX, and endY attributes that identify the start and end points of a linear gradient, which fills a shape with interpolated colors. By leaving the X values at 0.0, and by having Y range from 0.0 through 1.0, this gradient paint will paint its gradient in a vertical direction.The stops attribute identifies a sequence of javafx.scene.paint.Stop instances that describe how colors are distributed along the gradient. Each Stop instance is like a key frame — its offset attribute identifies a solid color position along a 0.0 through 1.0 range, and its color attribute (a Color instance) identifies this solid color.Now that we know how to paint a stage’s background, we’re almost ready to specify the stage’s node-based content. Before we can do this, however, we need to learn about the Node class.Understand the Node classStage‘s content attribute lets you describe a graphical scene via a sequence of nodes, instances of classes that subclass the abstract javafx.scene.Node class. This sequence of nodes may include hierarchical groups of nodes, much like Swing GUIs consist of component/container hierarchies. The resulting nodes hierarchy is known as a scene graph.More about scene graphsFor a good introduction to scene graphs, check out Wikipedia’s Scene graph entry. Also see Java.net’s Project Scene Graph, which provides the foundation for JavaFX’s scene-graph support.When it comes to creating scenes in JavaFX, you’ll need to familiarize yourself with Node and its subclasses. For starters, Node defines a local coordinate system with (0, 0) representing a node’s upper-left corner, x increasing to the right, and y increasing downwards. This class also provides a variety of attributes and functions, including those listed below, which all subclasses share:anchorX (of type Number) specifies the X coordinate of this node’s anchor point (the point around which node content rotates) — 0.0 is the default value.anchorY (of type Number) specifies the Y coordinate of this node’s anchor point — 0.0 is the default value.cursor (of type javafx.scene.Cursor) identifies this node’s mouse cursor.effect (of type javafx.scene.effect.Effect) identifies this node’s visual effect (such as dropshadow, glow, and motion blur).onKeyPressed (of type function(:KeyEvent):Void) identifies a function that’s invoked when this node has input focus and a key is pressed.onKeyReleased (of type function(:KeyEvent):Void) identifies a function that’s invoked when this node has input focus and a key is released.onKeyTyped (of type function(:KeyEvent):Void) identifies a function that’s invoked when this node has input focus and a key is pressed and released.onMouseClicked (of type function(:MouseEvent):Void) identifies a function that’s invoked when a mouse button is pressed and released while the mouse cursor is over this node.onMouseDragged (of type function(:MouseEvent):Void) identifies a function that’s invoked when a mouse button is pressed while the mouse cursor is over this node and the mouse cursor is moved while the mouse button remains pressed.onMouseEntered (of type function(:MouseEvent):Void) identifies a function that’s invoked when the mouse cursor enters this node.onMouseExited (of type function(:MouseEvent):Void) identifies a function that’s invoked when the mouse cursor exits this node.onMouseMoved (of type function(:MouseEvent):Void) identifies a function that’s invoked when the mouse is moved over this node.onMousePressed (of type function(:MouseEvent):Void) identifies a function that’s invoked when a mouse button is pressed while the mouse cursor is over this node.onMouseReleased (of type function(:MouseEvent):Void) identifies a function that’s invoked when a mouse button is released while the mouse cursor is over this node.opacity (of type Number) identifies this node’s opacity, from 0.0 (invisible) to 1.0 (fully visible).rotate (of type Number) specifies the angle (in degrees) by which this node is rotated (the default value is 0.0).scaleX (of type Number) specifies this node’s scaling factor in the X direction (the default value is 1.0).scaleY (of type Number) specifies this node’s scaling factor in the Y direction (the default value is 1.0).shearX (of type Number) specifies the multiplier by which this node’s coordinates are shifted in the direction of the positive X axis as a factor of their Y coordinate.shearY (of type Number) specifies the multiplier by which this node’s coordinates are shifted in the direction of the positive Y axis as a factor of their X coordinate.translateX (of type Number) specifies this node’s translation factor in the X direction (the default value is 0.0).translateY (of type Number) specifies this node’s translation factor in the Y direction (the default value is 0.0).visible (of type Boolean) specifies this node’s visibility (the default value is true).public contains(x: Number, y: Number): Boolean returns true if the (x, y) point (specified in this node’s coordinate system) is located within this node’s visual bounds (whether or not this node is visible).public bound getBoundsHeight(): Number returns this node’s current height, taking into account any specified transformations.public bound getBoundsWidth(): Number returns this node’s current width, taking into account any specified transformations.public bound getBoundsX(): Number returns the X coordinate of this node’s origin, taking into account any specified transformations.public bound getBoundsY(): Number returns the Y coordinate of this node’s origin, taking into account any specified transformations.public bound getHeight(): Number returns this node’s current height without accounting for transformations.public bound getWidth(): Number returns this node’s current width without accounting for transformations.public bound getX(): Number returns the X coordinate of this node’s origin without accounting for transformations.public bound getY(): Number returns the Y coordinate of this node’s origin without accounting for transformations.The transform attributeNode provides a transform attribute as an alternative to rotate, scaleX, scaleY, shearX, shearY, translateX, and translateY. This attribute, of type javafx.scene.transform.Transform[], lets you chain together a sequence of transformations that are applied to the node. Although you can populate this sequence with objects created directly from javafx.scene.transform.Rotate, javafx.scene.transform.Scale, and other javafx.scene.transform subclasses of the abstract Transform class, the convention is to obtain these objects from Transform functions such as public static rotate(angle: Number, x: Number, y: Number): Rotate.Responding to input eventsAs you saw in the above listing, Node offers onMouseClicked, onMouseDragged, onMouseEntered, onMouseExited, onMouseMoved, onMousePressed, and onMouseReleased attributes for responding to mouse events. Each function assigned to one of these attributes requires a javafx.input.MouseEvent argument. This class provides the functions below for ascertaining various kinds of information about the event:public getButton(): Integer returns an indication of which (if any) buttons have changed state.public getClickCount(): Integer returns the number of mouse clicks associated with this event.public getDragX(): Number returns the X offset of the event relative to the most recent press event if the MouseEvent is part of a press-drag-release gesture, otherwise returns 0.public getDragY(): Number returns the Y offset of the event relative to the most recent press event if the MouseEvent is part of a press-drag-release gesture, otherwise returns 0.public getScreenX(): Number returns the mouse cursor’s horizontal X position relative to the upper-left corner of the screen.public getScreenY(): Number returns the mouse cursor’s horizontal Y position relative to the upper-left corner of the screen.public getStageX(): Number returns the mouse cursor’s horizontal X position relative to the stage’s origin.public getStageY(): Number returns the mouse cursor’s horizontal Y position relative to the stage’s origin.public getWheelRotation(): Number returns the number of “clicks” the mouse wheel has rotated.public getX(): Number returns the mouse cursor’s horizontal X position relative to this node’s origin.public getY(): Number returns the mouse cursor’s horizontal Y position relative to this node’s origin.public isAltDown(): Boolean returns true if the Alt modifier key is down.public isControlDown(): Boolean returns true if the Control modifier key is down.public isMetaDown(): Boolean returns true if the Meta modifier key is down.public isPopupTrigger(): Boolean returns true if this mouse event is the popup menu trigger event for the platform. Because popup menus are triggered differently on different systems, isPopupTrigger() should be invoked in functions assigned to both of a node’s onMousePressed and onMouseReleased attributes to ensure proper cross-platform behavior.public isShiftDown(): Boolean returns true if the Shift modifier key is down.Node also provides onKeyPressed, onKeyReleased, and onKeyTyped attributes for responding to key events. Each function assigned to one of these attributes requires a javafx.input.KeyEvent argument. This class provides the following functions for obtaining different kinds of event information:public getKeyChar(): java.lang.String returns the character associated with this key event — “A” returns when Shift and A are simultaneously pressed, for example.public getKeyCode(): Integer returns the integer keycode associated with this key event.public getKeyText(): java.lang.String returns a String describing the keycode (such as "HOME", "F1" or "A") for key-pressed and key-released events — null returns for key-typed events.public isAltDown(): Boolean returns true if the Alt modifier key is down.public isControlDown(): Boolean returns true if the Control modifier key is down.public isMetaDown(): Boolean returns true if the Meta modifier key is down.public isShiftDown(): Boolean returns true if the Shift modifier key is down.Keystrokes and input focusA node cannot respond to keystrokes without having the input focus. You can provide that focus by invoking Node‘s requestFocus() function. When invoked, requestFocus() asks the runtime to give input focus to the invoking node, and to make the node’s top-level ancestor the focused window.Adding keyboard supportOne of the samples bundled with NetBeans IDE 6.1 with JavaFX is Transparency, whose GUI I revealed in the first article in this series. The Transparency sample demonstrates how to set an image’s opacity so that background pixels are partly visible. This sample responds to mouse-moved events by moving the image horizontally in the direction of the mouse.Let’s extend this sample script to also support keyboard input. For example, we can modify the code to respond to key-pressed events. Whenever a left-arrow key is pressed, we’ll move the image to the left. Similarly, when a right-arrow key is pressed, we’ll move the image to the right. Listing 8 presents the relevant part of Transparency.fx, with the new code highlighted. (Note that I’ve removed the imports and copyright preamble from the revised Transparency.fx script below.)Listing 8. Transparency.fx with keyboard supportvar x : Number = 40; var img : Image = Image { url : "{__DIR__}/../resources/overlay.png" }; Frame { stage : Stage { content : [ ImageView { image : Image { url : "{__DIR__}/../resources/background.png" } onMouseMoved : function( e : MouseEvent ):Void { x = e.getX() - 100 * 0.5; } <b><i>onKeyPressed: function(e: javafx.input.KeyEvent):Void { if (e.getKeyCode () == javafx.input.KeyCode.VK_LEFT) x-- else if (e.getKeyCode () == javafx.input.KeyCode.VK_RIGHT) x++; }</i></b> }, ImageView { image : bind img transform : [ Translate { x : bind x, y : 100 - 32 }, Scale { x : 0.5, y : 0.5 } ] opacity : 0.5 } ] } visible : true title : "Transparency" width : 200 height : 232 closeAction : function() { java.lang.System.exit( 0 ); } } The new (bolded and italicized) code assigns an anonymous function to the onKeyPressed attribute of the ImageView node (I discuss ImageView later). When a key is pressed, this function extracts the key’s code via its KeyEvent argument’s getkeyCode() function, and compares it with KeyCode virtual key codes for the left- and right-arrow keys. The image’s location is updated when there’s a match.Given our understanding of Node, we can now begin to appreciate this class’s children, starting with the Text subclass. We’ll also learn how to work with the related Font class.Adding Text and FontThe javafx.scene.text.Text class defines a node that presents a single line of text — I used this class in the Hello, JavaFX! script from “Jump into JavaFX, Part 1” to describe the text that was displayed on the stage and rotated around its center point. In addition to inheriting Node‘s attributes and functions, Text provides the following attributes:content (of type String) identifies the line of text to be displayed.font (of type javafx.scene.Font) identifies the font used to present the text.textOrigin (of type javafx.scene.text.TextOrigin) identifies the origin of the text in local coordinates.x (of type Number) identifies the X coordinate of the text’s origin.y (of type Number) identifies the Y coordinate of the text’s origin.The TextOrigin class presents BASELINE, BOTTOM, and TOP attributes that respectively identify the text’s baseline, the bottom of the text’s maximum descent, and the top of the text’s maximum ascent. I chose to assign the TOP attribute to textOrigin when specifying the Text literal in the “Hello, JavaFX!” script.Closely associated with Text and TextOrigin are the Font and javafx.scene.FontStyle classes. Font specifies the font, in terms of the following attributes, used to map the text’s sequence of characters to an equivalent sequence of glyphs, and to render these glyphs onto the screen:name (of type String) identifies the font’s name.size (of type Integer) identifies the font’s point size.style (of type FontStyle) identifies the font’s style.FontStyle presents BOLD, BOLD_ITALIC, ITALIC, and PLAIN attributes that identify the font’s style. In the “Hello, JavaFX!” script, I chose to keep the default plain style, while specifying a 30-point size and Arial as the font’s name in the Font literal that I assigned to the Text literal’s font attribute.To illustrate Text, TextOrigin, and Font, I’ve excerpted “Hello, JavaFX!”‘s Stage object literal from its containing Frame object literal (without repeating the content of the LinearGradient object literal, which I excerpted earlier in this article). Check out the code fragment shown in Listing 9.Listing 9. “Hello, JavaFX!”‘s minimized Stage object literalvar stageRef: Stage stage: stageRef = Stage { fill: LinearGradient { /**/ } var textRef: Text content: [ textRef = Text { content: bind model.text x: bind (stageRef.width-textRef.getWidth ())/2 y: bind (stageRef.height-textRef.getHeight ())/2 textOrigin: TextOrigin.TOP rotate: bind model.rotAngle anchorX: bind textRef.x+textRef.getWidth ()/2 anchorY: bind textRef.y+textRef.getHeight ()/2 font: Font { name: "Arial" size: 30 } fill: Color.YELLOW stroke: Color.ORANGE opacity: bind model.opacity } ] } You’ll notice that it’s possible to introduce local variables into object literals. These variables are handy for referencing containing attributes and a class’s functions. By convention, Ref is appended to each local variable name. Also, the bind keyword is required to access the stage’s current dimensions, and to access Text‘s calculated x and y values.Because Text subclasses Node, it inherits that class’s opacity and rotation-oriented attributes that I previously listed when discussing the Node class. But where do the fill and stroke Paint attributes, which let you determine how the text’s interior (fill) and outline (stroke) are painted, come from? Let’s find out.Playing with ShapesThe javafx.scene.geometry package includes an abstract Shape class that serves as the base class for various geometric shape subclasses (such as Circle, Line, and Path). Because this class subclasses Node, it inherits Node‘s attributes and functions, while introducing the following attributes of its own:fill (of type Paint) specifies this shape’s background paint — the default value is null.smooth (of type Boolean) specifies whether or not antialiasing hints are used for this shape — the default value is true (use antialiasing hints).stroke (of type Paint) specifies this shape’s outline paint — the default value is null.strokeDashArray (of type Number[]) specifies a sequence representing the lengths of the dash segments. Alternate sequence entries represent the user space lengths of the opaque and transparent segments of the dashes. As the pen moves along the outline of this shape, the user space distance that the pen travels is accumulated. The distance value is used to index into the dash sequence. The pen is opaque when its current cumulative distance maps to an even element of the dash sequence, and transparent otherwise. The default value is [1.0] (a one-element sequence containing 1.0).strokeDashOffset (of type Number) specifies a distance in user coordinates that represents an offset into the dashing pattern — the point in the dashing pattern that will correspond to the beginning of the stroke. The default value is 0.0.strokeLineCap (of type javafx.scene.geometry.StrokeLineCap) specifies the end cap style of this shape as one of StrokeLineCap.BUTT (end unclosed subpaths and dash segments with no added decoration), StrokeLineCap.ROUND (end unclosed subpaths and dash segments with a round decoration that has a radius equal to half of the width of the pen), and StrokeLineCap.SQUARE (end unclosed subpaths and dash segments with a square projection that extends beyond the end of the segment to a distance equal to half of the line width). The default value is StrokeLineCap.SQUARE.strokeLineJoin (of type javafx.scene.geometry.StrokeLineJoin) specifies the decoration applied where path segments meet as one of StrokeLineJoin.BEVEL (join path segments by connecting the outer corners of their wide outlines with a straight segment), StrokeLineJoin.MITER (join path segments by extending their outside edges until they meet), and StrokeLineJoin.ROUND (join path segments by rounding off the corner at a radius of half the line width). The default value is StrokeLineJoin.MITER.strokeMiterLimit (of type Number) specifies the limit for the StrokeLineJoin.MITER line join style — the default value is 10.0.strokeWidth (of type Number) specifies a square pen line width — the default value is 1.0.Along with Circle, Line, and Path, and additional shape classes such as Arc, CubicCurve, Polygon, and Rectangle, javafx.scene.geometry provides PathElement. This abstract class is the base class for LineTo, MoveTo, and other classes that describe a Path‘s elements.Let’s play with some of these shape classes. Start NetBeans and use the New Project wizard to introduce a new APIDemo3 project with apidemo3.Shapes as the project’s main file. Then replace the skeletal Shapes.fx‘s // place your code here line with the script in Listing 10.Listing 10. Shapes.fx/* * Shapes.fx * */ package apidemo3; /** * @author Jeff Friesen */ import java.lang.Math; import java.lang.System; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Frame; import javafx.application.Stage; import javafx.scene.geometry.Arc; import javafx.scene.geometry.ArcType; import javafx.scene.geometry.Circle; import javafx.scene.geometry.CubicCurve; import javafx.scene.geometry.Ellipse; import javafx.scene.geometry.Line; import javafx.scene.geometry.QuadCurve; import javafx.scene.geometry.Rectangle; import javafx.scene.geometry.Shape; import javafx.scene.paint.Color; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Stop; class Model { attribute width: Integer; attribute height: Integer; attribute shape: Shape; private attribute opacity: Number; private function rnd (limit: Integer): Integer { Math.random ()*limit as Integer } private function arc (): Shape { Arc { centerX: rnd (width) centerY: rnd (height) radiusX: rnd (Math.min (width/2, height/2)) radiusY: rnd (Math.min (width/2, height/2)) startAngle: rnd (180) length: rnd (180) type: ArcType.ROUND fill: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } private function circle (): Shape { Circle { centerX: rnd (width) centerY: rnd (height) radius: rnd (Math.min (width/2, height/2)) fill: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } private function cubicCurve (): Shape { CubicCurve { startX: rnd (width) startY: rnd (height) controlX1: rnd (width) controlY1: rnd (width) controlX2: rnd (width) controlY2: rnd (width) endX: rnd (width/2) endY: rnd (height/2) fill: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } private function ellipse (): Shape { Ellipse { centerX: rnd (width) centerY: rnd (height) radiusX: rnd (Math.min (width/2, height/2)) radiusY: rnd (Math.min (width/2, height/2)) fill: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } private function line (): Shape { Line { startX: rnd (width) startY: rnd (height) endX: rnd (width) endY: rnd (height) stroke: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } private function quadCurve (): Shape { QuadCurve { startX: rnd (width) startY: rnd (height) endX: rnd (width) endY: rnd (height) controlX: rnd (width) controlY: rnd (height) fill: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } function rectangle (): Shape { Rectangle { x: rnd (width) y: rnd (height) width: rnd (width) height: rnd (height) arcWidth: rnd (10) arcHeight: rnd (10) fill: Color.rgb (rnd (256), rnd (256), rnd (256)) opacity: bind opacity } } private attribute shapes = [arc, circle, cubicCurve, ellipse, line, quadCurve, rectangle]; private attribute timeline = Timeline { repeatCount: Timeline.INDEFINITE keyFrames: for (i in [1..sizeof shapes]) { KeyFrame { time: 3s*indexof i action: function () { shape = shapes [rnd (sizeof shapes)] () } timelines: [ Timeline { autoReverse: true repeatCount: 2 var begin = at (0s) { opacity => 0.0 } var end = at (1s) { opacity => 1.0 tween Interpolator.LINEAR } keyFrames: [begin, end] } ] } } } attribute animate: Boolean on replace o=n { if (n == true) timeline.start () else timeline.stop () } } Frame { var model = Model { width: 300 height: 300 animate: true } title: "Shapes Tour" width: bind model.width with inverse height: bind model.height with inverse stage: Stage { fill: LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.YELLOW }, Stop { offset: 1.0 color: Color.CYAN } ] } content: bind [model.shape] } closeAction: function () { model.animate = false; System.exit (0) } visible: true } Unlike the Hello, JavaFX! script listing, Listing 10 encapsulates its keyframe animation code in the Model class, and instantiates this class within the Frame literal. These encapsulations lead to a script consisting only of classes, which seems to be preferred (judging from various scripts that you can find on the Internet) over mixing classes and extraneous global code.The Model class presents a public interface consisting of attributes width, height, shape, and animate. The Frame literal binds its width and height attributes to the first two attributes, specifying with inverse to inform the model whenever the user changes the window’s dimensions.Frame also binds to Model‘s shape attribute, to be informed when a new Shape subclass instance is assigned to this attribute — a new instance is assigned every three seconds — so that the shape can be displayed. To start the animation that’s responsible for generating shapes, assign true to animate; assign false instead to terminate the animation.Model‘s private implementation demonstrates the useful function pointers language feature. The private attribute shapes = [arc, circle, cubicCurve, ellipse, line, quadCurve, rectangle]; declaration stores pointers to shape-creation functions, which are later invoked via the shape = shapes [rnd (sizeof shapes)] () expression.The private implementation also demonstrates nested timelines. After the action() function assigns a randomly chosen and created shape to shape, the sub-timeline stored in Timeline‘s timelines sequence attribute starts to animate opacity from invisible to fully visible, and then in reverse. A shape is faded in, and then faded out before fading in another shape, as Figure 1 reveals.Along with the previous classes, javafx.scene.geometry provides the SVGPath class, which was revealed in the shapes.fx, shapes.fxd, and shapesUI.fx listings from Jump into JavaFX, Part 1. This node and shape class describes a Scalable Vector Graphics path via an encoded string that corresponds to the specification at https://www.w3.org/TR/SVG/paths.html.By now, it should be obvious that Text is a Shape subclass (in addition to being a Node subclass — JavaFX Script supports multiple inheritance).ImagesIn addition to text and shapes, JavaFX supports images, via the javafx.scene.image package and its Image and ImageView classes. You’ll use the former class to load an image, and the latter class to view the image. Because ImageView subclasses Node, anything you can generally do to nodes also applies to images. Listing 11 demonstrates these classes.Listing 11. ScaledImage.fx/* * ScaledImage.fx * */ package apidemo4; /** * @author Jeff Friesen */ import javafx.animation.Interpolator; import javafx.animation.Timeline; import javafx.application.Frame; import javafx.application.Stage; import javafx.input.MouseEvent; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; class Model { attribute url: String; attribute scale: Number = 1.0; private attribute timelineShrinkEnlarge = Timeline { toggle: true var begin = at (0s) { scale => 1.0 } var end = at (1s) { scale => 0.1 tween Interpolator.LINEAR } keyFrames: [begin, end] } function animate () { timelineShrinkEnlarge.start () } } Frame { var model = Model { url: "{__DIR__}3frogs.jpg" } width: 400 height: 400 var stageRef: Stage stage: stageRef = Stage { fill: Color.BLACK content: ImageView { var imageRef: Image image: bind imageRef = Image { url: model.url } x: bind (stageRef.width-imageRef.width)/2 y: bind (stageRef.height-imageRef.height)/2 scaleX: bind model.scale scaleY: bind model.scale onMouseEntered: function (me: MouseEvent) { model.animate () } } } visible: true } This script declares a Model class whose url and scale attributes respectively identify the location and name of an image to load, and a scaling percentage for shrinking/enlarging the image — scale is initialized to 1.0 so that the loaded image is initially displayed at normal size. The animate() function performs an animation cycle each time it’s invoked.The url attribute’s initializer relies on a special precreated variable named __DIR__ (of type java.net.URL) to return the absolute path of the directory containing the script’s equivalent classfile, and appends 3frogs.jpg to this location. For example, on my Windows XP platform this image’s complete URL (which I’ve split across two lines for readability) isfile:/C:/Documents%20and%20Settings/Jeff%20Friesen/My%20Documents/NetBeansProjects/ APIDemo4/build/classes/apidemo4/3frogs.jpg Moving on, the script creates the user interface window, and creates and stores a single ImageView component in this window’s stage’s content sequence attribute. (The [] delimiter characters don’t need to appear around one-element sequences.)ImageView‘s image attribute binds to an Image instance that identifies the image to be displayed. Whenever Image‘s String-based url attribute changes, such as when Model‘s url attribute is first initialized, a new Image instance is created and loads the model-specified image, and the resulting image appears.The anonymous function assigned to the onMouseEntered attribute is invoked each time the mouse cursor enters the image view node. This function starts an animation that, because true is assigned to Timeline‘s toggle attribute, alternates between shrinking the loaded image to 10% of its initial size, and enlarging the image back to its initial size.Create an APIDemo4 project, specifying apidemo4 as the package name, and ScaledImage as the filename. After replacing the skeletal script with the contents of Listing 11, copy the 3frogs.jpg file from this article’s code archive into the directory identified by __DIR__. Compile and run ScaledImage.fx. Figure 2 reveals the unshrunken image.Images are loaded in the background. Although this process is quite fast for disk-based images, it tends to be slower when loading over a network. To help with slow image loading, Image provides backgroundLoading and placeholder attributes (for displaying an alternate image until the image has fully loaded), and a progress attribute (for determining how much is left to load).In conclusionJavaFX provides a good assortment of APIs for creating scenes that are visually compelling. Now that you’re acquainted with some of these APIs, you should find it easier to understand what’s going on with the Hello, World! script from “Jump into JavaFX, Part 1.”There still are more APIs to explore, so be sure to look for the fourth installment in this series, where I’ll introduce the JavaFX APIs for dealing with effects, layouts, media, Swing components, and more.Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out his javajeff.mb.ca website to discover all of his published Java articles and more. His book Beginning Java SE 6 Platform: From Novice to Professional (Apress, October 2007) explores most of the new features introduced in Java SE 6. JavaSoftware Development