JavaOne 2011 Rain or Shine

I’ve got a date tomorrow morning with a laptop,  JavaFX, and a mic:

Interface Layout with JavaFX2.0
Wednesday, 10:00 AM, Hotel Nikko – Nikko Ballroom II/III

I’ll be speaking about all the topics I’ve been meaning to blog about.

Posted in Uncategorized | Leave a comment

Circa 1996

Office drawers are like attics.  The other day I opened one and found a stack of pictures.   This was the Java Client team in late 1996.

Left to Right: Jim Graham, Rick Levenson, Tim Prinzing, Georges Saab, Jonni Kanerva, Jeannette Hung, Tom Ball, Jeff Dinkins, Amy Fowler.  
 

An email later and we all gathered for lunch at Hobees in Sunnyvale:

Left to Right: Jim Graham, Rick Levenson, Tim Prinzing, Georges Saab, Jonni Kanerva, Jeannette Hung, Tom Ball, Jeff Dinkins, Amy Fowler.  
 

A little more sun in the eyes and a few grey hairs.  The front row still works on the jdk!

A couple others who were there back in the day came along too.

Left to Right: Jim Graham, James Gosling, Rick Levenson, Tim Prinzing, Georges Saab, Jonni Kanerva, Jeannette Hung, Tom Ball, Brian Beck, Amy Fowler.
Posted in Uncategorized | 5 Comments

JavaFX2.0 Layout: A Class Tour

JavaFX2.0 Beta is out.    We’ve taken advantage of the language shift to extensively remodel the layout APIs based on a year’s worth of tire-kicking.   This article (the first in a series) will introduce you to the basics.

In marrying the GUI to a scene-graph, each graphical element is represented by a stateful object rather than drawn inside a clipped rectangle, and this makes layout more complicated.   Hence, many of the JavaFX1.3 concepts still apply, however we’ve worked hard to simplify and eliminate the seams.

An interface is created by assembling scene-graph nodes into a hierarchy within a Scene.  The three Parent types have different layout characteristics:

  • Group:  doesn’t perform any positioning of children and is not directly resizable (see next section) and just takes on the collective bounds of it’s visible children.  Groups are most suited when you need to statically assemble a collection of nodes in fixed positions and/or need to apply an effect or transform to that collection.
  • Region: base class for all general purpose layout panes;  is both resizable and stylable via CSS.   Supports dynamic layout by sizing and positioning children during a layout pass (if needed).  If a Region subclass (layout pane) supports the layout paradigm you need, then use it instead of hand-positioning with a Group.
  • Control: the base class for all skinnable controls.   Is resizable and subclasses are all stylable via CSS.   Controls delegate layout to their skins (which are Regions). Each layout Control subclass provides a unique API for adding content in the appropriate place within its skinyou do not add children to a control directly.

A silly example to give you the flavor if you’ve never programmed FX:

        // silly example
        Text text = new Text("JavaFX 2.0!");
        text.setFill(Color.RED);
        text.setFont(new Font(24));
        Line line = new Line(2, 8, 104, 8);
        line.setStroke(Color.BLUE);
        line.setStrokeWidth(4);
        Group group = new Group();
        group.setEffect(new DropShadow());
        group.getChildren().addAll(text, line);

        BorderPane root = new BorderPane();
        root.setTop(new Label("Introducing..."));
        root.setCenter(group);

        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setVisible(true);

To explore some core scene-graph layout concepts, let’s zoom in on the key classes:

Resizability

Each Node in the scene-graph has the potential to be “resizable”, which means it expresses it’s minimum, preferred, and maximum size constraints and allows its parent to resize it during layout (hopefully within that range, if the parent is honorable).

Each layout pane implements its own policy on how it resizes a resizable child, given the child’s size range.  For example, a StackPane will attempt to resize all children to fill its width/height (honoring their max limits) while a FlowPane always resizes children to their preferred sizes, which is what we mean by the term “autosize” (resize-to-preferred).

But here’s what surprises (and annoys) people: not all scene-graph node classes are resizable:

  • Resizable:  Region, Control, WebView
  • NOT Resizable: Group, Text, Shape, MediaView*, ImageView*

*will be made resizable in future release

If a Node class is not resizable, then isResizable() returns false, minWidth()/minHeight(), prefWidth()/prefHeight(), and maxWidth()/maxHeight() all return its current size, and resize() is a no-op.  The size of a non-resizable node is affected by its various properties (width/height for Rectangle, radius for Circle, etc) and it’s the application’s responsibility to set these properties (and remember that scaling is NOT the same as resizing, as a scale will also transform the stroke, which is not usually what you want for layout).   When non-resizable nodes are thrown into layout panes, they will be positioned, but not resized.

You inevitably want to ask why not make shapes resizable?  While it’s straight-forward to do the math on basic shapes (Rectangle, Circle, etc), it gets a bit muddier with Paths, Polygons, and 3D.  Also, keeping the core graphics shapes non-resizable allows us to make certain efficiencies, like ensuring a Group with non-resizable descendents pays no penalty for layout.  So for now anyway, that’s the way it is.

Tip: if you need a resizable rectangle, use a Region and style it with CSS.

Content Bias

For most node classes, width and height are independent of each other, however, there are a handful where height-depends-on-width or visa-versa.   In order to deal with these guys properly, the layout system needs a way to decipher it, hence Node’s getContentBias() method.  This returns null by default, which means there is no width/height dependency and layout code can pass -1 as the alternate dimension to the minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w), and maxWidth(h)/maxHeight(w) methods.

Labeled controls have a HORIZONTAL content-bias when wrapping is on.   FlowPane and TilePane support a content-bias that matches their orientation.  Note: turns out that HBox, VBox, and StackPane return a null content-bias, however they should really report a content-bias based on their childrens’ content-biases; this is a bug that will be fixed before the final release.

Our layout classes do the heavy-lifting in supporting content-bias in the system, so unless you are writing a custom layout class that handles arbitrary nodes, you needn’t deal directly with this API.  Just know layout supports content-bias automatically when you build your interfaces.

Overriding Min/Pref/Max Sizes

Most Control and layout pane classes are pretty good at computing how big they ought to be based on their content and property settings — we’ll call this their “intrinsic” min, pref, and max sizes.   For example, a Button will compute these sizes based on its text, font, padding, and so on;  additionally a Button’s intrinsic max size defaults to its preferred because you usually don’t want buttons expanding to fill space.  You shouldn’t have to explicitly set the size of controls, layout panes, or even the Scene, because the system will shrink-to-fit around your content.

But that’s a lie.   Or at least counter-intuitive to the nature of designers.   Thus, you can directly influence the size of Controls and Regions by setting their min, pref, and max size ‘override’ properties:

// make my button 100 pixels wide! 
button.setPrefWidth(100);

By default these properties have the sentinel value USE_COMPUTED_SIZE, which causes Node’s minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w), maxWidth(h)/maxHeight(w) methods to return their intrinsic values.   Setting them to a positive double value will override their intrinsic values.

Often you want to ensure that the min or max size tracks the preferred, so the min and max overrides may also be set to USE_PREF_SIZE, which does the obvious:

// clamp my stackpane's size
stackpane.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);

Beware of the subtle different between the Node methods and the size override properties:

button.getPrefWidth() returns the size override (might be a magic sentinel value) while button.prefWidth(-1) returns the actual preferred width of the button (returning the override if set).  Layout code should always use Node’s minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w), maxWidth(h)/maxHeight(w) methods for layout computations.

The Beauty of Beta

There’s more to the story of course, but I think that’s one blogful enough for now.

With the JavaFX2.0 public beta, you have the opportunity to influence the API based on cold, hard use cases, so don’t hold back (as if you would :-)).

Posted in Uncategorized | 70 Comments

A Heartfelt Ramble on Swing & JavaFX

At last night’s JavaOne JDK BOF, the question was posed, “What are the JDK plans for Swing?”      Mark Reinhold noticed me skulking in the audience and bounced the question: “Aim?”.  Caught a bit off guard, I answered a pat “it’s still there and it isn’t going anywhere”.   The poser of the question stormed out (I don’t blame him), as that wasn’t the answer he wanted to hear, so I’d like to elaborate.

In working on JavaFX the last couple years and occasionally posting about it, I’m accustomed to the hate mail from Swing fans.    In many ways I’m heartened by the public support for a technology on which Sun itself was so fickle.    Swing is a great toolkit.   It’s flexibility and extensibility enables developers to build  amazingly rich and complex applications.   It’s understandable that the Swing community would be incensed about the way Sun prematurely unveiled an infantile JavaFX as if it could replace 10+ years of steady enhancements and tuning of Swing & Java2D.   2007-2009 were excruciating years for all of us.

But Java and the JDK are under new stewardship now.    If you’re at JavaOne or reading the tweetesphere, then you know that Oracle is deadly serious about it’s plan to resurrect  Java and the JDK as the cross platform solution for the future.    The proof of course will be in execution over the next year or two, but this is a practical company who isn’t suffering from an identity crisis and knows how to make money from software.   A sleeper detail from Thomas Kurian’s keynote is that NetBeans will be the Java development IDE of choice going forward.   This is very good news for Swing, ensuring it’s support and upkeep for a long time to come.

But that’s still not what Swing developers want to hear. Which brings me to the actual point of this rambling.     User-interface technology is on a collision course with graphics.   For Java to be a viable client platform in the long haul, it must be inherently capable of the sort of visual delicacies that you see in iPhone/iPad applications.     As I’ve stated many times, it’s possible to achieve some of this using Java2D tricks in Swing (e.g. Kirill’s very nice Trident library), but it requires sophisticated gymnastics; it’s not a natural fit.     The crux of the problem is that Swing  is rooted in the antiquated AWT, rather than being integrated into the 2D coordinate system.   Fixing this would require some massively incompatible changes and once you march down that path, well, you start asking yourself what other incompatible improvements should be made ….pretty soon the remodel becomes reconstruction and you realize that a new foundation is really needed.

Enter Prism.  If you saw Jasper’s “Scott Pilgrim” demo at Kurian’s keynote then you’d understand what a hardware-accelerated graphics pipeline with a UI rooted in a 2D/3D scene-graph can do.    Sure, most UIs won’t need to kick hundreds of rotating media cubes around, but they will certainly need to translate, rotate, fade, and blend UI elements in interesting ways.   JavaFX (Prism + scene-graph + UI controls) is about making it very natural and easy to construct a modern interface and allow your graphics designers to make it pretty via CSS.

And now that we’re converting JavaFX to a proper Java library, you don’t have to learn a new language to use it.  Additionally, Oracle has committed to developing the UI controls under open source and our JavaFX 2.0 development plans are very clearly layed out.    Much of the JavaFX stack has already been ported off of script, so this is all quite real and the team is ecstatic to be doing Java again – it’s a far better language for toolkit development.   Moving to Java should also make it vastly easier for us to support embedding JavaFX in existing Swing applications, so continued investment in Swing applications is preserved.

And to those of you who are upset that we are killing JavaFX script, I say that I feel this pain also.   Like most developers who gave JavaFX script a chance, I too became an addict and will miss it as a venue for constructing applications (not libraries).    But restating Richard Bair’s excellent follow-up in words from the The Princess Bride: “It’s not dead.  It’s only mostly dead.”  And if you’ve seen the movie, then you know that ‘mostly dead’ can be resurrected with the right loving care;  I recommend attending Stephen’s Chin’s JFXtras session on Wednesday.

Many of you will undoubtedly say “it’s too late”, “developers have already moved on”.    That may be the case for some developers and projects, but unlike our beloved predecessor, Oracle sells a lot of applications, and those applications will need great UIs too.  It’s an ambitious plan.  I don’t expect words or slick demos to change your mind.  The burden of proof is now on us to deliver.

Posted in Uncategorized | 68 Comments

Back to our Roots for JavaOne

There is no place like San Francisco in September.     But aside from blue skies, I can say as someone who has been part of JavaOne since 1996 that I’m ecstatic that our newly acquired focus is returning to our roots — The JDK (7 & ~8).     Our plans for the technology will be revealed, uncut and without the cloud of uncertainty that hung over the last couple JavaOnes.    I think you may find it refreshing.

As evidence refuting the rumor that all smart people work at one Mt.View company, the session catalog has a wealth of interesting talks, but here’s a handful of sessions that pique my interest in the Java language and client technologies:

Monday (Dueling openers!):

  • 10am: JDK 7 and Java SE 7 (S314556)  Mark Reinhold/Oracle
  • 10am: JavaFX 2.0 (S319383) Richard Bair/Oracle, Jai Suri/Oracle, Jasper Potts/Oracle, Nicolas Lorain/Oracle (fortunately this one is being given again on Thursday@12:30 if you don’t want to miss Mark’s)

Tuesday

Wednesday

Thursday

This will likely be the most interesting JavaOne in years.     I’m already looking forward to lunch at Henry Hunan’s.

Posted in Uncategorized | 19 Comments

JavaFX 1.3: Growing, Shrinking and Filling

Establishing dynamic sizing behavior is one of the trickier aspects of GUI layout.   For JavaFX 1.3, we’ve introduced the grow/shrink/fill layout preferences on Resizable nodes to support more sensible default resizing behavior of controls and containers out of the box.   And while this feature is at first hard to get your head around, once it clicks, it pays.

We have to credit this one to Stephen Chin (JFXtras), who proposed this concept from his Grid API, which we then generalized across the containers.  (Thanks, Stephen!)

Filling

Let’s take filling first.  A Resizable node’s fill policy determines whether the node should be resized to fill its assigned layout area or kept to its preferred size and aligned within the area.

The screen shot below shows two stages which illustrate the difference between no-fill vs. fill:

No Fill Fill

In 1.2, the fill policy was dependent on which container was used; Stack and Tile implemented filling while HBox, VBox, and Flow did not.  So if you threw some buttons into a Tile container (the example above) you would get the fill result (on RIGHT) where all the buttons expand to fill each tile.  However, if the buttons were placed in an HBox, they would remain their preferred size, regardless of how much space the Hbox had.

Turns out that whether or not a node should fill is more naturally determined by the node type, rather than its parent container.  For example, a Button typically does not want to fill, whereas a ListView often does.  So in 1.3 we’ve added getHFill()/getVFill() to Resizable to allow container and control classes to return appropriate values which are honored by the container parents.

Growing & Shrinking

When a container is resized larger or smaller than its preferred size, the question is what to do with that surplus or deficit space.  In 1.2 we simply left that differential space hanging, but what an application really needs is a mechanism to identify which nodes should absorb it.  We considered a number of API approaches (weights, span percentages, etc) but ultimately chose a priority scheme based on Stephen’s Grid API.

A container uses the grow and shrink priorities of its children to determine how it should distribute a surplus or deficit of space in circumstances where the children are competing for that space. Child nodes can specify one of the following grow/shrink priorities:

  • NEVER: Child’s layout area will never grow (shrink) when there is an increase (decrease) in space available in the container.
  • SOMETIMES:  If no siblings have a grow (shrink) priority of ALWAYS or those layout areas didn’t absorb all of the increased (decreased) space, then will share the increase (decrease) in space with other sibling layout area’s of SOMETIMES.
  • ALWAYS: Child’s layout area will always grow (shrink), sharing the increase (decrease) in space with other layout areas that have a grow (shrink) of ALWAYS.
Note: There is a bug in the javafx1.3 online api documentation where the docs for javafx.scene.layout.Priority enum is mysteriously missing (JIRA RT-8740), so the doc text is copied above.

Below is an example an HBox using its childrens’ grow/shrink priorities to adjust layout as it is resized larger and smaller:

Note that grow priorities apply to the layout area assigned to the node.  Whether or not a resizable node is sized beyond its preferred to fill that extra space is determined by its fill preference.     In the example above, the toggle-group has grow=ALWAYS but fill=false, so only it’s layout area grows, whereas the search-hbox has grow=ALWAYS but a fill=true, so it resizes to fill the larger area.

Shrinking is not symmetrical, however.   If a resizable node’s shrink priority causes it to lose space, it will be resized down to fit within that space.

Similar to filling,  we’ve added getHGrow()/getVGrow()/getHShrink()/getVShrink() to Resizable to enable controls and containers to express appropriate preferences for their node type.   See the table of Control Fill/Grow/Shrink defaults at the end of this post.

Not All Containers Support Grow/Fill

It only makes sense for containers to support these preferences when nodes are in contention for space, so HBox supports it horizontally, VBox supports it vertically, and Grid supports it in both directions.    For Stack, since child nodes share the same layout area, childrens’ grow/shrink priorities do not apply. And since Tile and Flow redistribute space by wrapping child nodes rather than resizing them, child grow/shrink priorities are also ignored. 

Overriding Fill/Grow/Shrink Defaults

As with the other Resizable preferences,  LayoutInfo can be used to override a node’s default fill/grow/shrink defaults.   In the HBox example pictured above, most of the defaults took care of the desired behavior, however we had to override the defaults for MyToggleBox and the “?” Stack:

HBox {
    spacing: 8
    nodeVPos: VPos.BASELINE
    content: [
        Button {
            // no grow by default, so ok
            text: "Today"
        }
        MyToggleBox {
            // no grow by default, so turn on grow
            layoutInfo: LayoutInfo { hgrow: Priority.SOMETIMES hpos: HPos.CENTER }
            items: [ "Day", "Week", "Month" ]
        }
        HBox {
            // grows/fills by default, so ok
            nodeVPos: VPos.BASELINE
            content: [
                Label { text: "Search:" },
                TextBox {}
            ]
        }
        Stack {
             // grows/fills by default, so turn off grow
             layoutInfo: LayoutInfo { hgrow: Priority.NEVER }
             content: [ Circle {..}, Text {..}  ]
        }
    ]
}

Container/Control Defaults & Theoretical Propagation

The proper default grow/shrink/fill values for Stack, HBox, and VBox should be the greatest value of their children.  In other words, if Stack has any child with grow=ALWAYS or fill=true, then the stack should report its own preference as ALWAYS/true; if it only contains non-Resizables (shapes, etc) then it should report its own grow/shrink as NEVER and fill=false. This propagation of layout preferences has great power for the punch, enabling automatic resizing behavior on deeply nested hierarchies with little-to-no layoutInfo customization…..if…only….

…it weren’t for a bug in 1.3 (RT-8240) where the containers all report grow/shrink=SOMETIMES, fill=true, regardless of their content.      This means that in 1.3, you’ll often see containers growing when their content doesn’t really want them to.  The workaround is to set LayoutInfo as needed to adjust that behavior.  Double sigh.

Following is the current set of Control defaults (derived from their skins), which we’ll certainly tune as we put more mileage on this feature:

Class hgrow vgrow hshrink vshrink hfill vfill
Button NEVER NEVER NEVER NEVER false false
CheckBox NEVER NEVER NEVER NEVER false false
ChoiceBox NEVER NEVER NEVER NEVER false false
Hyperlink NEVER NEVER NEVER NEVER false false
Label NEVER NEVER NEVER NEVER false false
ListView SOMETIMES SOMETIMES SOMETIMES SOMETIMES true true
PasswordBox NEVER NEVER NEVER NEVER false false
ProgressBar NEVER NEVER NEVER NEVER false false
ProgressIndicator NEVER NEVER NEVER NEVER false false
RadioButton NEVER NEVER NEVER NEVER false false
ScrollBar NEVER NEVER NEVER NEVER true* true*
ScrollView SOMETIMES SOMETIMES SOMETIMES SOMETIMES true true
Separator NEVER NEVER NEVER NEVER true* true*
Slider NEVER NEVER NEVER NEVER false false
TextBox SOMETIMES SOMETIMES* SOMETIMES SOMETIMES* true true*
ToggleButton NEVER NEVER NEVER NEVER false false
Preview Controls:
CheckBoxMenuItem NEVER NEVER NEVER NEVER false false
Menu NEVER NEVER NEVER NEVER false false
MenuBar SOMETIMES NEVER SOMETIMES NEVER true false
MenuItem NEVER NEVER NEVER NEVER false false
MenuButton NEVER NEVER NEVER NEVER false false
PopupMenu NEVER NEVER NEVER NEVER false false
RadioMenuItem NEVER NEVER NEVER NEVER false false
SplitMenuButton NEVER NEVER NEVER NEVER false false
ToolBar SOMETIMES* SOMETIMES* SOMETIMES* SOMETIMES* true* true*
TreeView SOMETIMES SOMETIMES SOMETIMES SOMETIMES true true

* = value depends on orientation

Posted in Uncategorized | 10 Comments

JavaFX 1.3: Managed Gets Promoted

Sometimes a node just doesn’t want its parent messing with its size or position.  In 1.2 you could use LayoutInfo to mark a node as ‘unmanaged’ to tell its parent Container not to factor it into layout and this concept was limited to Container parents.   With 1.3’s universal auto-sizing, suddenly all parents (Group, Container, CustomNode) care about managing their children, so we’ve moved the variable from LayoutInfoBase up to Node:

Button {
    managed: false // better than "layoutInfo: LayoutInfo { managed: false }"
    layoutX: 10 layoutY: 10 // parent will not layout, so must position myself
    width: 80  height: 45 // parent will not autosize, so must size myself
}

So if a node is unmanaged, its parent will not lay it out.   If inside a Group, this simply means the node will not be auto-sized.  If inside a Container, it means the node will not be factored into the container’s preferred size calculations and it will not set the node’s position (layoutX/Y) or size (width/height).  So…

  • Rule: If you unmanage a node then you are solely responsible for its position and size (if its Resizable);  and don’t go there unless you know what you’re doing.

Layout Roots

Another 1.3 affect of unmanaging a Parent node (Container, Group, CustomNode, or Control) is that it automatically becomes a layout root.  This means that layout requests beneath it do not propagate above it and instead cause the layout root itself to be added to the scene’s dirty layout list, where it’s branch will be layed out on the next pulse.    Before this change it was impossible to layout discrete branches of the scenegraph without laying out everything above it, which was a huge inefficiency.     This change gave a nice boost to the performance of our controls where there are many cases that size changes within the skins do not affect the overall layout bounds of the control and thus shouldn’t trigger relayout.

Here’s a practical example to illustrate how you might utilize a layout root.  Say you want to show a little ‘info’ graphic about a particular node.  You want this graphic to share the coordinate space of the node so its position always remains relative to it, but you don’t want it factored into the node’s layout in any way — sort of like a popup layer.   By making it an unmanaged child of the node, it won’t affect the node’s layout, but also size/layout changes within the info graphic will not trigger any re-layouts of the node itself.

blue and purple branches layed out independently

The only catch is to remember the Rule above, which is that if the layout root happens to be a Resizable (Container or Control), then you must take responsibility to manage its size since its parent node will be ignoring it.    If you don’t set its size then it’s like to have a size of 0 x 0 and won’t be visible.   On the other hand, if the layout root happens to be a Group, then auto-sizing will ensure that it and its contents get resized automagically during layout.

Posted in Uncategorized | 6 Comments