Thursday Feb 13, 2014

Dynamical layouts in FX

Since I took over JavaFX layout code, I had several bug reports that were related to dynamical layouts and noticed that many people struggle with the implementation. By dynamical layout I mean a layout Pane that not only resizes and positions it's children, but also manipulates with it's child list depending on the acquired size.

In a standard layout Pane, the typical workflow is this:

  1. Parent calls prefWidth/prefHeight; the preferred size is computed from the current set of children and Pane properties/constraints
  2. Parent assigns the actual width/height
  3. The layout is computed from the width/height and the same set of children used in step #1

A dynamical Pane would work like this:

  1. The preferred size is either fixed or depends just on Pane properties or some items (based on which the children are created)
  2. Parent assigns the actual width/height
  3. The layout is computed from the width/height and children are created accordingly

A simple dynamical Pane with fixed-size Rectangles may be implemented like this:


      private class GrowingPane extends Pane {

        private static final int PREF_NUMBER = 2;
        private static final int RECTANGLE_SIZE = 50;
        private static final int PADDING = 10;

        @Override
        protected double computePrefWidth(double height) {
            return PREF_NUMBER * RECTANGLE_SIZE + (2 + PREF_NUMBER - 1) * PADDING;
        }

        @Override
        protected double computePrefHeight(double width) {
            return RECTANGLE_SIZE + 2 * PADDING;
        }


        @Override
        protected void layoutChildren() {
            final double w = getWidth();

            getChildren().clear();
            // count the number of Rectangles that will fit
            final int num = (int) (w - PADDING) / (RECTANGLE_SIZE + PADDING);

            int curX = PADDING;
            // Do the layout
            for (int i = 0; i < num; ++i) {
                Rectangle rec = new Rectangle(RECTANGLE_SIZE, RECTANGLE_SIZE, Color.LIGHTGREEN);
                getChildren().add(rec);
                rec.relocate(curX, PADDING);
                curX += RECTANGLE_SIZE + PADDING;
            }

        }
    }
     

Now we look at a more sophisticated version with Buttons that are not-fixed size. For our sample, we generate the Button text - each subsequent button text is double the size. Notice the difference in computePrefWidth/computePrefHeight:


      private class GrowingPane2 extends Pane {

        private static final int PREF_NUMBER = 2;
        private static final String BASE_STRING = "A";
        private double prefWidth = -1;
        private double prefHeight = -1;
        private static final int PADDING = 10;

        @Override
        protected double computePrefWidth(double height) {
            if (prefWidth == -1) {
                computePrefSize();
            }
            return prefWidth;
        }

        private void computePrefSize() {
            String str = BASE_STRING;

            prefHeight = snapSpace(2 * PADDING + computeHeight());

            prefWidth = PADDING;
            for (int i = 0; i < PREF_NUMBER; ++i) {
                prefWidth += computeWidthForString(str) + PADDING;
                str += str;
            }
            prefWidth = snapSpace(prefWidth);

        }

        private double computeHeight() {
            Button b = new Button(BASE_STRING);
            getChildren().add(b);
            b.applyCss();
            double result = b.prefHeight(-1);
            getChildren().remove(b);
            return result;
        }

        private double computeWidthForString(String str) {
            Button b = new Button(str);
            getChildren().add(b);
            b.applyCss();
            double result = b.prefWidth(-1);
            getChildren().remove(b);
            return result;
        }

        @Override
        protected double computePrefHeight(double width) {
            if (prefHeight == -1) {
                computePrefSize();
            }
            return prefHeight;
        }


        @Override
        protected void layoutChildren() {
            final double w = getWidth();

            getChildren().clear();

            double widthLeft = w - PADDING;
            double curX = PADDING;
            String s = BASE_STRING;

            while(widthLeft >= PADDING) {
                double bw = computeWidthForString(s);
                if (bw + PADDING > widthLeft) {
                    break;
                }
                Button b = new Button(s);
                getChildren().add(b);
                b.applyCss();
                s += s;

                b.autosize();
                b.relocate(curX, PADDING);
                curX += bw + PADDING;
                widthLeft -= bw + PADDING;
            }

        }
    }
      

In real-life example, where you generate Buttons (or whatnot) based on some item list or other model, you'd invalidate the computed size on this model change.

Lets look at the computation. You may notice that we added and removed the child and also called applyCss() before computing it's preferred size. This is because the CSS pass is done after the layout pass, so modifying the children in layout pass require explicit execution of CSS pass for every new Node. The CSS depends on context (position in the scenegraph), so we need to add the child first. Of course, to be absolutely accurate, we should add the child to it's correct position and clear the list at the end of the computation. 

Tuesday Jan 21, 2014

Johan Vos blogged about Java FX and Android

If you're interested in latest development in Java FX for Android read Johan's blog entry here. I feel also that some tooling support would help in this stage of the project. So far the gradle script shipped in a binary worked well but who wants to stick with a script forever, right? Looking at the current IDEs that support Android ( Eclipse ADT, Netbeans NBAndroid and Google's Android Studio ) the natural choice would be a new gradle build system. This is a build system that Google has chosen for the future. Unfortunately it is still in a quite rough state and doesn't allow fine grained setup which is essential for us. To be more specific a dex task doesn't have capability that old good ant task had and we can't fix an issue with "too many methods" error. Positive is that there's an issue filed on Android gradle build system so there's a chance that Google will fix that. Until that happens using ant seems to be the only solution. Thanks to Radim Kubacki former NetBeans guy who's the father of NBAndroid we might have a chance to extend NBAndroid plugin for Netbeans. Add a wizard for creating Java FX projects, properly bootstrap it with FX libraries and use all great tools that NBAndroid and NetBeans and SceneBuilder have for Java FX developers. I would really glad to see that happen in near future.
About

JavaFX is a Java GUI toolkit, partially developed from Prague, Czech Republic. The Prague team uses this blog to post articles, code samples and insights about the range of topics the team members specialize in. This includes JavaFX Scenegraph (javafx.scene.*), JavaFX Core libraries & animations, iOS port & Android port.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today