Skip to main content

Layout Primer for JavaFX1.0

Posted by aim on January 9, 2009 at 11:21 PM PST

Don't believe anyone who tells you that you don't need layout management for rich internet applications. While it's true that this emerging class of interfaces are more graphical, fluid, organic, and animating than the traditional rectangular GUIs of the last 20 years, they, by their nature of being "applications" and not merely glitzy ads, must deal with dynamic content and user interaction.



Perhaps some of the bad rap on layout management is due to our own coddling of AWT "Layout Managers", a mostly programmatic approach for achieving layout in Java clients. Anyone who's ever had to design and construct a significant user interface knows that the more natural process is to draw the thing, or at least lay it out visually. But even with the most exceptional tools, the initial layout is just the beginning. The sticky wicket is how to then integrate the dynamic layout behavior -- what should happen if the container is resized or the content changes. Tools like Interface Builder, Dreamweaver, and NetBeans/Mattisse have strived to support this with varying success. The fact is that it's a really hard problem and the graphic sophistication of RIAs only increases the complexity. Dynamic layout now has to also work with timeline based animation and visual effects never anticipated in the aforementioned tool set.



The JavaFX api was designed with these needs in mind. Although the FX designer tool is not yet available to fully reveal the larger picture, developers dipping their toes into FX should understand the basic principles of scene graph layout, which are laid out (pun intended) in the following article.

Ironic sidenote: you might be questioning how I could extoll the virtues of using tools for layout and then follow that with a detailed article on programmatic layout interfaces. The answer is that I'm a geek who likes to write for like-minded geeks. And fortunately the material translates to understanding 2D scene graph principles, whether programmatic or tool driven.


Note: The APIs covered in the article have been slightly modified in JavaFX1.2, so this article should be read with caution until I have a chance (very soon!) to replace it with the 1.2 version.



Layout Primer for JavaFX1.0



JavaFX1.0 provides a full-featured 2D scene graph API for creating dynamic and visually rich interfaces, however it does not yet include concrete layout container classes which automate the process of creating dynamic layout in a scene. A future version of JavaFX will more fully address automated layout, however this document will explain how to best achieve dynamic layout (including use with animated transitions) using the 1.0 API.

This article assumes you have knowledge of basic 2D scene graph concepts and are somewhat familiar with the JavaFX scripting language. For more information on these prerequisites, see:


What Is Dynamic Layout?



Dynamic layout is the ability to dynamically position and size elements in the scene graph in response to internal and external changes to the scene which are often triggered by user interaction and involve unpredictable content. For example, the user resizes the stage (desktop profile) and expects the content to adjust to fill it, or on a mobile device the user loads a set of thumbnail images and expects them to flow horizontally and wrap at screen boundaries. Additionally critical is the ability to support animated transitions (zooming out, pulsing, sliding, etc) without disturbing the layout of other nodes in the scene.



In JavaFX1.0, there are hooks in the geometry APIs designed for this purpose. Dynamic layout can be achieved either by using binding on individual nodes to respond to layout changes in the scene or by creating Container classes which have the ability to algorithmically layout their children. This article will cover the related apis in detail.



Understanding Node Bounds Calculations



To layout a scene effectively, one must be able to query the rectangular bounds of nodes in the graph. The question of what is the current bounds of a node becomes complicated when considering the many variables which contribute to its bounds, such as local shape geometry (startX/startY, width, radius, etc), transformations (scale, rotate, etc), effects (shadows, glow, etc), and clipping. It turns out that each of these variables is applied to a node in a specific sequence, and that being able to query the bounds of the node at specific points in this transformation sequence is crucial to achieving desired layouts in the scene graph.



Below is a diagram which shows the transformation sequence for a javafx.scene.Node; transformations are applied left to right (effect first, translateX/Y last):



NodeChain.png



Rectangular bounds are represented by the javafx.geometry.Rectangle2D class which provides minX, minY, maxX, maxY, width, height variables. Since the bounding box can be anywhere in the 2D coordinate space, the X/Y values may often be negative, as we'll see in further examples.


To query the bounds of a node at particular points in the transformation sequence, the JavaFX api supports the following variables on the Node class:

boundsInLocal (read-only): The rectangular bounds in the node's untransformed coordinate space, including any space required for a non-zero stroke that may fall outside the shape's position/size geometry, the effect (if set) and the clip (if set).



layoutBounds: By default, layoutBounds is defined as boundsInLocal plus transforms set in the transforms[] chain variable. Unlike the other read-only bounds variables, layoutBounds may be set or bound to other values. Its purpose is to define the bounding box for layout code to use when performing layout calculations and it can be wired to something other than the default.



boundsInParent (read-only): The bounds of the node after ALL transforms have been applied to the boundInLocal, including those set by transforms[], scaleX/scaleY, rotate, translateX/translateY.




This is easier to visualize graphically, as depicted in the diagram below:



NodeChainBounds.png



Stepping Through a Node Bounds Example


Example 1: Let's look further at these bounds variables using a concrete code example which creates a rectangle.


rectangle1.png



Note that x and y are variables specific to javafx.scene.shape.Rectangle and that they position the rectangle within its own coordinate space rather than moving the entire coordinate space of the node. I throw this out as the first example because it is often the first thing that trips up developers coming from traditional toolkits such as Swing, where changing x,y effectively performs a translation of the component's coordinate space.
All of the javafx.scene.shape classes have variables for specifying appropriate shape geometry within their local coordinate space (e.g. Rectangle has x, y, width, height, Circle has centerX, centerY, radius, etc) and such position variables should not be confused with a translation on the coordinate space, as we'll look at in our next example.




Example 2: To translate the rectangle along with its coordinate space (rather than move the rectangle within it), we instead set translateX/translateY, which are variables on Node.


rectangle2.png




Now boundsInParent has changed to reflect the translated rectangle, however boundsInLocal and layoutBounds remain unchanged because they are relative to the rectangle's coordinate space, which was what was shifted.

inside design note: we debated endlessly on whether to rename translateX,translateY to "x","y" (as it's less typing and more familiar to traditional toolkit programming), however we decided that "translate" was more descriptive in the 2D sense and keeping it avoided renaming the x,y position variables in some shape classes.




Example 3: And now let's scale the same rectangle by 150% by placing a Scale object in the the transforms sequence variable.


rectangle3.png




Note that the default pivot point for javafx.scene.transform.Scale is 0,0, therefore the rectangle (and its coordinate space) grows down and to the right from its origin.



Example 4: And finally, if we instead scale the rectangle by using the scaleX/scaleY variables instead of a scale inside the transforms[] sequence, we'll see different results:

rectangle4.png




The most obvious difference is that the scaleX/scaleY variables on Node always scale about the node's center, which is different from the previous example which scaled from 0,0. Next, the layoutBounds does not account for the scale since it wasn't set in the transforms sequence, which means that code which lays out this rectangle will use its unscaled bounds (I'll explain why that's useful later). This distinction also applies to the rotation and translation transforms; if set in the transforms sequence, layoutBounds will account for them, however if set from Node's rotate, translateX/translateY variables, it will not.



Transformation Order Matters



Before moving on to layout, there is one more point to make about the order transformations are applied. This is easiest to explain if we rotate my Node chain diagram above vertically and show it in the context of a scene graph branch where a sequence of transforms has been set:



NodeTree.png



Transformations are applied bottom to top (again, effect first, translateX/translateY last) and by that I mean that a given node in the chain takes the one below it as input. However, "order" can be a misleading term. When it comes to understanding a series of transformations, it's often easier to visualize them by processing them top-down, which happens to be the order they are listed in the transforms sequence. For example, the transforms specified in the above diagram would be coded like this:

    Rectangle {
        width: 50 height: 50
        fill: Color.GREEN
        transforms: [
            Translate { x: 50 }
            Scale { x: 1.5 y: 1.5 }
            Rotate { angle: 45 }
       ]



And the picture below shows how each transform effects the coordinate space and node:



transforms1.png



Now, if we invert the order of the transforms so the Scale is listed first:
    Rectangle {
        width: 50 height: 50
        fill: Color.GREEN
        transforms: [
            Rotate { angle: 45 }
            Scale { x: 1.5 y: 1.5 }
            Translate { x: 50 }
       ]

We'll see that the result is very different:



transforms2.png



In fact, the reason the transforms[] sequence exists on the Node class is to give complete flexibility in both the transformations and the order in which they are applied.



This is all basic 2D graphics (recall your matrix math from college), but I illustrate it here because it can confuse those who haven't made a career in it.




Laying Out A Node



When laying out a node in a scene, it's often necessary to determine its current size so it can be aligned within a certain space (e.g. centered) and you must know it's current location so you can perform the appropriate translation to adjust it to where you want. This is why translateX and translateY are NOT included in the layoutBounds. layoutBounds is used to query the node's current position and size and THEN translateX and translateY are set to perform the adjustment for layout (if translateX and translateY were included in layoutBounds, you wouldn't be able to use them for positioning since it would cause a circular definition -- every time you updated translateX it would cause layoutBounds to change, which would recompute translateX!).



For example, if a rectangle is created:
     var rect = Rectangle {
         x:10 y:10 width:100 height:100
     }

And later some code needs to layout that rectangle at an x,y location:
    var x = 20;
    var y = 50;
    rect.translateX = x - rect.layoutBounds.minX;
    rect.translateY = y - rect.layoutBounds.minY;

Note that translateX and translateY are the deltas required to adjust the current position of the node (as defined by layoutBounds.minX, layoutBounds.minY) to get it to the desired location. translateX/translateY are NOT the final destination values.

another insider design note: we realize this is cumbersome and are considering providing variables where the final position could be set directly without this extra math.



If node transformations need to be incorporated into layout calculations, then those should be set using the transforms[] variable. An example would be if you scaled a node larger and you wanted nodes laid out relative to it to adjust their positions to make room for the larger node.



On the flip side, if a transform is to be applied without affecting its layout or those laid out relative to it, then it should be set using the scaleX, scaleY, and rotate variables, which will NOT be incorporated into layoutBounds. This is particularly useful for implementing animated transitions where you want to animate a node without affecting the layout of other nodes in the scene (e.g. a pulsating glow or zoom-fade). The transition classes in javafx.scene.transition are already built to work this way.



For example, the following code shows how you could add a pulse effect to a circle using javafx.animation.transition.ScaleTransition without disturbing the layout of the circle or nodes relative to it:




pulsecircle2.png





Using Binding to Layout Nodes



For cases where you need to configure dynamic layout for individual nodes, then you can use the powerful binding feature of JavaFX. For details on the language binding feature, see the current JavaFX Language specification.



For example, if you want to create a background color on a group of nodes, you would create a Rectangle node and use binding to ensure its bounds track the bounds of the group:
var group = Group {
    content: [
         Rectangle {
             fill:Color.BLUE
             translateX: bind group.layoutBounds.minX
             translateY: bind group.layoutBounds.minY
             width: bind group.layoutBounds.width
             height: bind group.layoutBounds.height
         }
         other nodes...
    ]
};



Another common case where binding is useful for layout is in centering a node within an area.
Here is an example of how to create a circle that remains centered in the scene even it the stage
is resized:
Stage = Stage {
  var scene:Scene;
  var circle:Circle;

  width: 200 height: 200
  scene: scene = Scene {
      content: circle = Circle {
          fill:Color.RED radius:50
          translateX: bind (scene.width - circle.layoutBounds.width)/2 - circle.layoutBounds.minX
          translateY: bind (scene.height - circle.layoutBounds.height)/2 - circle.layoutBounds.minY
      };
  };
}



A Future for Containers



Where the binding approach becomes tedious is when you have a multitude of nodes that need to be placed relative to each other in some normalized scheme, such as a horizontal flow or a grid. In such cases it would be more convenient to use a Container class whose purpose is to layout its children using a particular algorithm.



The scene graph api includes a javafx.scene.Group class, and although it acts as a parent to its child nodes, it is not a "container" in the layout sense. The Group class merely allows nodes to be treated as a collection for scene graph operations (e.g. fade this group of nodes by 50% or shift this group of nodes to the
right by 100, etc) . It is not designed to constrain its children to fit into a particular geometric area (it has no settable width/height variables) and it merely takes on the collective bounds of its child nodes.



For example, if a Circle (whose default centerX and centerY is at its origin) is placed inside a Group, the Group will take on the same bounds and be centered on its origin:




group.png


The 1.0 api provides two simple Group subclasses that will lay out nodes in either a row or a column, javafx.scene.layout.HBox and javafx.scene.layout.VBox.


The javafx.scene.layout.Container is the base class for creating nodes which are capable of both laying out children and constraining those children to fit within a particular area. The layout algorithm is performed in a layout function (defined by the concrete Container subclass) which is called by the scene graph automatically when it detects a change that requires layout. Currently the hooks for creating Containers include private api (public variables and functions prefixed with "impl") that will change in the future, therefore be aware that Containers written for 1.0 will need to be modified in future FX versions.



Ideally, for a container to perform an algorithm that positions and resizes nodes, it must be able to query the minimum, preferred, and maximum dimensions of its child nodes, as well as be able to set width and height of the nodes. Node subclasses that wish to be resized by containers need to extend the javafx.scene.layout.Resizable class so that Containers can properly size them. Note that Node and the concrete shape classes do NOT currently extend Resizable, therefore when placed in containers, they will be positioned, but not resized (a deficiency we'll address in a future release).



Layout Beyond 1.0



Even the most graphical 3D games often need to present information to users in standard layouts and we've definitely found in our own FX experience that more support for layout management is crucial. And as a design tool emerges to enable visual-driven layout, the need for the scene graph to support the right apis is a top priority for our team (and me personally). In more concrete terms, we are planning on providing Container classes for common layout idoms -- flow and grid in particular, as well as defining a mechanism for animating changes in layout. And a primary goal of this effort will be to stablize the Container apis for external use. So watch this space and send feedback, ideas, and bugs my way.

Related Topics >>

Comments

Layout Primer for JavaFX1.0

Background Rectangle snippet
Bit detailed stuff to comprehend easily. Probably laying it out more step by step would be easier ! but nice work overall. I believe the order of components in scene matter so the snippet showing layering to draw background rectangle should be such that //other nodes... should be placed above rectangle and then later when rectangle is laid out it will have group size to work with and also would need to set opacity of rectangle.

buglet in pulsing circles

Very nice article. Great mix of geekiness and readability. There seems to be a minor bug in the pulsing circles app: once an individual circle has been started and paused once, it cannot be started again. I fixed the problem by checking for the paused state (which is different from the running state on a ScaleTransition): if (pulser.running and not pulser.paused) then pulser.pause() else pulser.play(); Now it works better. (If the circles overlap, it seems possible to click two of them at once, which a pedant might argue is not a bug, since it obeys the stated interface rules, but which seems likely to confuse users. How does one specify that a mouse click belongs to only the first (presumably "top") of several candidates? John

buglet in pulsing circles

.

Hi Steven, Your point is well taken. We shouldn't expect the masses to become experts in 2D in order to put together UIs. As we roll out higher level functionality for fx (containers, controls, etc) in the coming year, I think you'll see that putting together old-school type business apps will be quite straight-forward. Point being that you won't need to understand all those diagrams I spent so much time in Illustrator to create :-) Also, I have high hopes that there will be a MIG Layout for FX in the short term; turns out Mikael was very smart in his MIG architecture, in that his layout logic was written in Java to be toolkit-independent, which should make it pretty easy to create an fx version. Aim

Amy, Excellent article, as we have come to expect from you. I do have a strategic roadmap question, however: Although I can appreciate the complexities involved in creating layout managers that must deal with transformations, I wonder if one should not consider an incremental approach that ignores transformations, so as to get adoption of JavaFX even at this stage, perhaps for old-school type business apps? There are awesome examples upon which to base such simpler approaches: MIG Layout, JGoodies, and such. Steven

Great resource. JavaFX's programming model is much different from what folks were used in Swing.

And to follow up on other comments: - As part of revising the Container apis, we'll be looking at how to add support for specifying a common set of constraints (as well as container-specific ones) to nodes to control precisely how they are to be positioned/sized within their layout "space". - be cautious about using anything prefixed with "impl" and be doubly cautious about using impl_layoutX/impl_layoutY as they will definitely be going away.

After receiving some excellent public and private comments, I have updated the article with the following changes: 1) Fixed the "Using Binding" background example to bind the rectangle's translateX/translateY variables to the group's minX/minY in order to ensure the background tracks the location of the group. 2) Added section "Transformation Order Matters" to clarify a subtlty in how the transforms sequence is processed by the scene graph. Thanks in particular to Stephen Chin and Kim Topley for their detailed feedback.

Very nice article !

Very informative. I ran into a problem by using the snipped where the dimensions of a Rectangle a set to the size of the layoutBounds of the surrounding Group. See my code at http://forums.sun.com/thread.jspa?threadID=5360251&tstart=0.

Great article. So a layout manager for JavaFX must use node.translateX / node.translateY to set the position of a Node? What about node.impl_layoutX / node.impl_layoutY ?

thank you for that great article. i was wondering: since the zindex is not a node property but more an artifact of its index in its parent children list, doesn't that make the group a visual container, not in the xy space but in the z dimension ? rémy

Thanks for the very informative article. I would certainly like to see more advanced layout manages/Containers in JavaFX. In Swing layout was an interface that defined some basic behavior. It supported parameters per component. Between your article and Sergey Malenkov's Blog [http://weblogs.java.net/blog/malenkov/archive/2008/12/how_to_lay_out.html] I can see how to use Container/Group to layout nodes, but passing parameters per component dose not look possible. I would think this would be necessary to tell the layout about resizing rules and stuff like that. Am I missing something?

Please don't abandon Java for UI like you've done with Swing. At least make it possible in Java 8 with proper properties and binding, when JavaFX will be widely adopted (or forgotten), but tell us now where Java-the-language-for-UI is going. Olivier Allouch

I think Sun and Chris Oliver forgot on the way why they created this language. In January 2007 he was saying on his blog: "The F3 project was originally called GBTDS which stood for "GUI Builder That Doesn't Suck". I just wanted an easy way of creating GUI's. " and 2 years later with stable version you are saying: "FX designer tool is not yet available" and "it does not yet include concrete layout container classes which automate the process of creating dynamic layout in a scene." and also we cannot use it to build swing desktop applications. Somehow instead of making better swing you did better SVG. SVG was not very successful and people still use HTML, but obviously not because of XML syntax. Your examples with rectangle and animation look to me like better way of making SVG graphics. Please show us how to use table, tree, text-area, buttons etc.

"...it's a really hard problem..." - That was largely solved DECADES ago by NeXT in the Interface Builder still used on the Mac platform today (and it includes the equivalent of binding properties, not just solving layout issues). But that gripe is more for Swing apps... :-) Too bad we can't officially mix Swing and JavaFX...

Glad to you see you blog again Amy :) Very interesting article too.

Thanks for the excellent clarification, as layout management is currently an obscure, incomplete part of Java FX. I hope a good set of more sophisticated containers come soon, as they are essential for "traditional" forms-based GUIs. The impl_* stuff is interesting, it seems you took yet another page of Smalltalk tradition -- exposing implementation that should be private and document their instability or planned obsolescence. But I hope this need disappears in a more mature release (perhaps 1.5, certainly 2.0?) when Java FX doesn't have any major feature gap anymore - I'm much more comfortable with the Java tradition of libraries that once released, will support all their public APIs in future releases for eternity.

Very nice article. That is the best explanation I have seen of the difference between boundsInLocal, layoutBounds, and boundsInParent (and the circle graphic is invaluable!) It also gave me ideas for some additional boundary tests for my JFXtras Grid (http://code.google.com/p/jfxtras/), which will help improve the quality of that library.