JavaFX Tip 32: Need Icons? Use Ikonli!

Motivation

I have been coding JavaFX applications and libraries since 2013 and one thing they all had in common was that I needed to find good icons / graphics that I could use for them. As a former Swing developer I started by using image files, GIFs or PNGs. Normally I would license a library like the “O-Collection” from IconExperience (www.incors.com). But soon it became apparent to me that using image files is too painful.

Just imagine for a moment that you want to support different pseudo states of your nodes (e.g. “hover”, “pressed”, “focused”). You end up with a different version of the same icon for each state. Even more files are needed if you want to support different sizes (small, medium, large) or screen resolutions (e.g. “Retina Display” on Mac, 2x Icons). Ultimately you end up in image file hell.

At first I didn’t care that much, because I started with framework development. Projects like CalendarFX or FlexGanttFX required only a very small number of icons. So using PNG files for these libraries was not an issue. But once I started working on bigger projects the need to support hundreds of icons became clear.

Icon Fonts

Luckily for all of us the web has already come up with a solution for this problem, and the solution is called “Icon Fonts”. The big advantage of an icon font is the fact that all icons are contained within a single file. That makes managing them in your workspace very easy. The other advantage is that font icons can be styled via CSS. This way a single icon can be displayed in many different colors or sizes.

The most popular icon font, at least in the beginning, was FontAwesome and there is a JavaFX implementation for it called FontAwesomeFX by Jens Deters. I used this library for all of my projects for a very long time and never thought I needed anything else. That was until I stumbled over Andres Almiray’s excellent library called “Ikonli”. You can find it on GitHub. After that I used it for all of my JavaFX-related work. For my large applications but also for my libraries.

What I like about Ikonli is that it integrates so seamlessly with the existing JavaFX API. An icon is simply an extension of the “Text” node (duh!) and it comes with styleable properties. There are properties for the icon itself, the icon “code”, for its color and for its size. The names of these properties in CSS files also follows convention. There they are called -fx-icon-code, -fx-icon-color, and -fx-icon-size.

Integration

Ikonli does not just ship with FontAwesome but with a total of 31 (!) different fonts. Among them material design icons, weather icons, payment icons (credit cards, etc…). Each one of these comes in their own module / artefact and can be imported individually, e.g. via Maven dependencies. The following dependencies need to be added to your Maven project’s POM file if you want to use the Material Design icon font.

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

<dependencies>
    <dependency>
        <groupId>org.kordamp.ikonli</groupId>
        <artifactId>ikonli-javafx</artifactId>
        <version>11.3.5</version>
    </dependency>
</dependencies>

<dependency>
    <groupId>org.kordamp.ikonli</groupId>
    <artifactId>ikonli-materialdesign-pack</artifactId>
    <version>11.3.5</version>
</dependency>

Cheat Sheets

Icon fonts often come with a lot of icons in them. So finding the right one is hard. Ikonli makes this easy by providing a “cheat sheet” for each font. The one for “Material Design” icons can be seen below.

Coding

Once you have created a FontIcon node / instance you can use it anywhere in the JavaFX scenegraph. Below you see an example for setting it on a button via code.

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

    Button button = new Button("User Account");
    button.setGraphic(new FontIcon());
    button.setId("account-button");

To style the icon you add the following to your CSS file:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

#account-button .ikonli-font-icon {
    -fx-icon-code: "mdi-account";
    -fx-icon-color: blue;
    -fx-icon-size: 1.2em;
}

To use an icon inside an FXML file you can write this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

<Button text="User Account" id="account-button">
  <graphic>
     <FontIcon iconLiteral="mdi-account"/>
  </graphic>
<Button>

Custom Fonts

You can also create your own custom icon font for JavaFX based on Ikonli. Obviously the first thing you will need is the font file. There are several online services available that allow you to create such a font. They let you choose icons from various already existing fonts but they also let you upload your own SVG files. The one I used is called Fontello. It has three main areas: icon selection and / or upload, customize names, customize codes.

Once you have configured your icons properly online you can then download the result as a ZIP file. Inside the ZIP file you will find the icon font in various formats.

To verify that all of your icons are actually inside the font you can open the file via “Font Book” (on Mac). It should look something like this:

Icon Enum

To make this font available inside your JavaFX application you have to first implement an enumeration with a value for each icon. It will look similar to this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

package com.acme.font;

import org.kordamp.ikonli.Ikon;

public enum MyIcon implements Ikon {
    HOUSE("my-icon-house", '\ue815'),
    CAR("my-icon-car", '\ue816'),
    DOG("my-icon-dog", '\ue817'),
    CAT("my-icon-cat", '\ue818'),
    KID("my-icon-kid", '\ue819');

    private String description;
    private char icon;

    MyIcon(String description, char icon) {
        this.description = description;
        this.icon = icon;
    }

    public String getDescription() {
        return description;
    }

    public char getCode() {
        return icon;
    }

    public static MyIcon findByDescription(String description) {
        for (MyIcon icon : values()) {
            if (icon.description.equals(description)) {
                return icon;
            }
        }
        throw new IllegalArgumentException("Icon not supported: " + description);
    }
}

Ikon Handler

The next thing you have to implement is an extension of AbstractIkonHandler. For this example where all icon literals are prefixed with “my-icon” the “supports” method returns exactly that prefix.

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

package com.acme.font;
import org.kordamp.ikonli.AbstractIkonHandler;
import org.kordamp.ikonli.Ikon;

public class MyIkonliHandler extends AbstractIkonHandler {

    public boolean supports(String description) {
        return description != null && description.startsWith("my-icon-");
    }

    public Ikon resolve(String description) {
        return MyIcon.findByDescription(description);
    }

    public String getFontResourcePath() {
        return "com/acme/fonts/my-icons.ttf";
    }

    public String getFontFamily() {
        return "my-icons";
    }
}

Service Lookup

Now all that is left to do is to make the font available to the world. This is done via a service lookup, which means that you have to create a file called “org.kordamp.ikonli.IkonHandler” inside the folder META-INF/services. Inside this file you need to add the full class name of your handler class (com.acme.font.MyIkonliHandler).

Example

The following screenshot shows the font module we use in our Maven project. Your project should look similar to that.

That’s it! Happy coding everyone!

 [/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

Advertisement

JavaFX Tip 31: Masking / Clipping / Alpha Channel

Selection Strip

I recently had to implement a custom control that lets the user select a single item out of a list of items. This “SelectionStrip” control had to lay out the items horizontally and in case of too many items allow the user to scroll horizontally left and right. The control was to be used in a space-constrained area, so the buttons for scrolling should only appear when needed. They also should not waste any additional space when showing. So I decided to place them on top of the control on the left and right sides. All of this was easily achieved, except that it was now difficult to distinguish the scroll buttons from the items. This can be seen in the three images below.



Alpha Channel?

So I thought it would be nice to somehow fade out the items when they are close to the left or right edge. This kind of behavior can normally be accomplished by using the alpha channel. It could decrease the opacity of pixels as their distance to the edges decreases. OK ….. but how is this done in JavaFX? For quite some time I was looking at the various “blend modes” that can be used to define how two overlapping nodes are drawn on top of each other. However, this was the wrong direction to look. As it turned out I already could have known how to do it because I once wrote a blog article talking about clipping and the difference between a filled and a non-filled clip. But I guess this was too long ago and I did not make the connection between “filled” and “filled with opacity less than 1”.

Complex Clip!

So far most of the clips I used for custom controls were simple rectangles. They usually made sure that children nodes that reached outside the layout bounds of their parent control were not or only partially visible. But this clip was different, it was more complex. It had to define three different zones. A “fade-in” area on the left-hand side, a “full opacity” area in the center, and a “fade-out” area on the right-hand side. For this to work I defined a “Group” that consists of three filled “Rectangle” nodes. While the fill color of the center rectangle is a solid black, the fill colors of the other two rectangles are linear gradients going from transparent to black and vice versa. The image below illustrates this.

With this setup we can now add any node as a child to the stack pane and it will be drawn with fade-in and fade-out effects on its sides.

Result

When applied to the “SelectionStrip” control from the beginning the scroll arrows / buttons are now always nicely visible and the overall user experience has become a little bit more pleasing. It is these little details that make the difference between a UI that is considered a “student project” or a “commercial application”. So sometimes it is really worth investing time in them.



Source Code

I put the masking logic into a custom control called “MaskedView”. At the bottom of this post you will see the Gist (or the link to the Gist) that contains the source code of this control. Think of it as a wrapper around a given content node.


import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
public class MaskedView extends Control {
public MaskedView(Node content) {
setContent(content);
}
@Override
protected Skin<?> createDefaultSkin() {
return new MaskedViewSkin(this);
}
private final SimpleObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
public final Node getContent() {
return content.get();
}
public final SimpleObjectProperty<Node> contentProperty() {
return content;
}
public final void setContent(Node content) {
this.content.set(content);
}
private final DoubleProperty fadingSize = new SimpleDoubleProperty(this, "fadingSize", 120);
public final double getFadingSize() {
return fadingSize.get();
}
public final DoubleProperty fadingSizeProperty() {
return fadingSize;
}
public final void setFadingSize(double fadingSize) {
this.fadingSize.set(fadingSize);
}
}

view raw

MaskedView.java

hosted with ❤ by GitHub


import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
public class MaskedViewSkin extends SkinBase<MaskedView> {
private final Rectangle leftClip;
private final Rectangle rightClip;
private final Rectangle centerClip;
private final Group group;
private final StackPane stackPane;
public MaskedViewSkin(MaskedView view) {
super(view);
leftClip = new Rectangle();
rightClip = new Rectangle();
centerClip = new Rectangle();
centerClip.setFill(Color.BLACK);
leftClip.setManaged(false);
centerClip.setManaged(false);
rightClip.setManaged(false);
group = new Group(leftClip, centerClip, rightClip);
stackPane = new StackPane();
stackPane.setManaged(false);
stackPane.setClip(group);
getChildren().add(stackPane);
view.contentProperty().addListener((observable, oldContent, newContent) -> buildView(oldContent, newContent));
buildView(null, view.getContent());
view.widthProperty().addListener(it -> updateClip());
view.fadingSizeProperty().addListener(it -> updateClip());
}
private final InvalidationListener translateXListener = it -> updateClip();
private final WeakInvalidationListener weakTranslateXListener = new WeakInvalidationListener(translateXListener);
private void buildView(Node oldContent, Node newContent) {
if (oldContent != null) {
stackPane.getChildren().clear();
oldContent.translateXProperty().removeListener(weakTranslateXListener);
}
if (newContent != null) {
stackPane.getChildren().setAll(newContent);
newContent.translateXProperty().addListener(weakTranslateXListener);
}
updateClip();
}
private void updateClip() {
final MaskedView view = getSkinnable();
Node content = view.getContent();
if (content != null) {
final double fadingSize = view.getFadingSize();
if (content.getTranslateX() < 0) {
leftClip.setFill(new LinearGradient(0, 0, fadingSize, 0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.TRANSPARENT), new Stop(1, Color.BLACK)));
} else {
leftClip.setFill(Color.BLACK);
}
if (content.getTranslateX() + content.prefWidth(-1) > view.getWidth()) {
rightClip.setFill(new LinearGradient(0, 0, fadingSize, 0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.BLACK), new Stop(1, Color.TRANSPARENT)));
} else {
rightClip.setFill(Color.BLACK);
}
}
view.requestLayout();
}
@Override
protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
final double fadingSize = Math.min(contentWidth / 2, getSkinnable().getFadingSize());
stackPane.resizeRelocate(snapPosition(contentX), snapPosition(contentY), snapSpace(contentWidth), snapSpace(contentHeight));
resizeRelocate(leftClip, snapPosition(contentX), snapPosition(contentY), snapSpace(fadingSize), snapSpace(contentHeight));
resizeRelocate(centerClip, snapPosition(contentX + fadingSize), snapPosition(contentY), snapSpace(contentWidth2 * fadingSize), snapSpace(contentHeight));
resizeRelocate(rightClip, snapPosition(contentX + contentWidthfadingSize), snapPosition(contentY), snapSpace(fadingSize), snapSpace(contentHeight));
}
private void resizeRelocate(Rectangle rect, double x, double y, double w, double h) {
rect.setLayoutX(x);
rect.setLayoutY(y);
rect.setWidth(w);
rect.setHeight(h);
}
}

I hope you will find a good use case for this control.

Happy coding everyone!

JavaFX Tip 30: ScrollPane with DropShadow

In one of my projects I recently noticed that it was hard for the user to see whether the content of a ScrollPane instance was currently scrolled or not. One way of making this more clear is to add a drop shadow to the top of the scroll pane. This is also something suggested by Google’s Material Design. So I gave it a try. In my solution I simply added a region to the ScrollPane and when laying it out I am moving it out of the viewport bounds of the ScrollPane so that only the shadow effect applied to the region still reaches into it. To really make sure that the region is not visible I also had to set a clip on the ScrollPane. This works quite well although I must admit I am not 100% sure this is the best way to do it. So if anybody has any suggestions / alternativ approaches then please leave a comment.

Below you see before and after scrolling screenshots of one of the screens of our application.

Before Scrolling

After Scrolling

BTW: I implemented this in such a way that the drop shadow doesn’t just appear all of a sudden, but it moves into the viewport step by step, depending on how far the user has scrolled. To see this you need to scroll down very slowly.

The code for the ShadowScrollPane can be found in this gist on GitHub:


package uk.co.senapt.desktop.shell;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
/**
* Created by lemmi on 23.08.17.
*/
public class ShadowScrollPane extends ScrollPane {
private Region shadow = new Region();
public ShadowScrollPane() {
super();
init();
}
public ShadowScrollPane(Node content) {
super(content);
init();
}
private void init() {
skinProperty().addListener(it -> {
getChildren().addAll(shadow);
});
setFitToWidth(true);
setVbarPolicy(ScrollBarPolicy.NEVER);
setHbarPolicy(ScrollBarPolicy.NEVER);
shadow.setManaged(false);
shadow.setStyle("-fx-pref-height: 10;" +
"-fx-background-color: black;" +
"-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, .75), 20, 0.19, 0, 6);");
shadow.getStyleClass().add("shadow");
shadow.visibleProperty().bind(showShadowProperty());
shadow.setMouseTransparent(true);
shadow.visibleProperty().bind(vvalueProperty().greaterThan(0));
Rectangle clip = new Rectangle();
clip.widthProperty().bind(widthProperty());
clip.heightProperty().bind(heightProperty());
setClip(clip);
vvalueProperty().addListener(it -> {
if (lastOffset != computeOffset()) {
requestLayout();
}
});
showShadowProperty().addListener(it -> requestLayout());
}
private final BooleanProperty showShadow = new SimpleBooleanProperty(this, "showShadow", true);
public final BooleanProperty showShadowProperty() {
return showShadow;
}
public final boolean isShowShadow() {
return showShadow.get();
}
public final void setShowShadow(boolean show) {
showShadow.set(show);
}
private final int SHADOW_HEIGHT = 30;
@Override
protected void layoutChildren() {
super.layoutChildren();
if (isShowShadow()) {
Insets insets = getInsets();
double w = getWidth();
double offset = computeOffset();
shadow.resizeRelocate(-10, insets.getTop() – shadow.prefHeight(-1) – SHADOW_HEIGHT + offset, w + 20, shadow.prefHeight(-1) – 1);
lastOffset = offset;
}
}
private double lastOffset = 0;
private double computeOffset() {
if (getContent() != null) {
return Math.min(getVvalue() * getContent().prefHeight(-1), SHADOW_HEIGHT);
}
return 0;
}
}

JavaFX Tip 29: Make Layouts Ignore Invisible Nodes

Back in the days when I was still implementing UIs in Swing I used to be a big fan of MigLayout (“one layout manager to rule them all”, right Mikael?). One of the features I really liked was the possibility to define different behaviors when a component became invisible. MigLayout allowed me to either preserve the space that the now invisible component occupied or to make it available for the still visible components. So how can I do this in JavaFX?

Even though the answer is quite simple it is not obvious by looking at the API. JavaFX uses layout panes such as VBox, HBox, BorderPane, FlowPane, or GridPane, to lay out managed children nodes. The keyword here is “managed”. The layout panes only consider those nodes inside their layout algorithms that are flagged as managed (default is true). The same is true for the the code that computes the pref, min, max widths of a pane. This code also only considers managed nodes.

Let’s look at an example. We create an HBox with four labels. Initially it looks like this.

We now set the visibility of label 2 to false and we receive this layout.

To reuse the space that used to be occupied by the label we now set the “managed” property of label 2 to false. As you can see below the remaining three labels are now filling the entire width of the HBox and the width of the HBox was adjusted properly.

That’s it for today. May the code be with you!

P.S.: the demo code is below


package uk.co.senapt.desktop;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* A little demo showing how the "visible" and "managed" property can be used
* to make a node disappear in such a way that the layout pane / container can
* reclaim the space previously occupied by the now invisible node.
*/
public class VisibleManagedDemo extends Application {
@Override
public void start(Stage primaryStage) {
Label label1 = createLabel("Label 1");
Label label2 = createLabel("Label 2");
Label label3 = createLabel("Label 3");
Label label4 = createLabel("Label 4");
CheckBox visibleBox = new CheckBox("Visible");
CheckBox managedBox = new CheckBox("Managed");
visibleBox.setSelected(true);
managedBox.setSelected(true);
label2.visibleProperty().bind(visibleBox.selectedProperty());
label2.managedProperty().bind(managedBox.selectedProperty());
HBox hBox1 = new HBox(10, new Label("Label 2 settings:"), visibleBox, managedBox);
HBox hBox2 = new HBox(10, label1, label2, label3, label4);
hBox2.setStyle("-fx-background-color: lightgray; -fx-padding: 20");
VBox vBox = new VBox(20, hBox1, hBox2);
vBox.setFillWidth(false);
vBox.setPadding(new Insets(20));
primaryStage.setTitle("Visible / Managed Demo");
primaryStage.setScene(new Scene(vBox));
primaryStage.sizeToScene();
primaryStage.centerOnScreen();
primaryStage.show();
}
private Label createLabel(String text) {
Label label = new Label(text);
label.setStyle("-fx-background-color: orange; -fx-background-radius: 4; -fx-padding: 20;");
label.setPrefSize(200, 200);
return label;
}
public static void main(String[] args) {
launch();
}
}

 

JavaFX Tip 28: Pretty List View

When I look at the list views on my mobile phone I always notice that they display their scrollbar (normally a vertical one) very differently than JavaFX does. The same is true for applications running on MacOS X. Below you can see a snapshot of Apple’s calendar app. You will notice that the scrollbar is actually on top of the content.

In JavaFX the scrollbar would be placed to the right of the view. However on mobile devices in order to save space the scrollbar shows up on top of the content.

So obviously I wanted to see how I could achieve the same thing for my JavaFX apps. The trick to do this is to first “style away” the original scrollbars. Then you add your own scrollbar and lay it out in such a way that it will be on top of the content (the ListView). Next you style your own scrollbar to look nice and lightweight, and finally you bind your scrollbar to the original scrollbar.

In the end the result will look like this:

Below you can see the class PrettyListView, which extends a standard ListView.

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;

import java.util.Set;

public class PrettyListView<T> extends ListView<T> {

    private ScrollBar vBar = new ScrollBar();
    private ScrollBar hBar = new ScrollBar();

    public PrettyListView() {
        super();

        skinProperty().addListener(it -> {
            // first bind, then add new scrollbars, otherwise the new bars will be found
            bindScrollBars();
            getChildren().addAll(vBar, hBar);
        });

        getStyleClass().add("pretty-list-view");

        vBar.setManaged(false);
        vBar.setOrientation(Orientation.VERTICAL);
        vBar.getStyleClass().add("pretty-scroll-bar");
        vBar.visibleProperty().bind(vBar.visibleAmountProperty().isNotEqualTo(0));

        hBar.setManaged(false);
        hBar.setOrientation(Orientation.HORIZONTAL);
        hBar.getStyleClass().add("pretty-scroll-bar");
        hBar.visibleProperty().bind(hBar.visibleAmountProperty().isNotEqualTo(0));
    }

    private void bindScrollBars() {
        final Set<Node> nodes = lookupAll("VirtualScrollBar");
        for (Node node : nodes) {
            if (node instanceof ScrollBar) {
                ScrollBar bar = (ScrollBar) node;
                if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                    bindScrollBars(vBar, bar);
                } else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
                    bindScrollBars(hBar, bar);
                }
            }
        }
    }

    private void bindScrollBars(ScrollBar scrollBarA, ScrollBar scrollBarB) {
        scrollBarA.valueProperty().bindBidirectional(scrollBarB.valueProperty());
        scrollBarA.minProperty().bindBidirectional(scrollBarB.minProperty());
        scrollBarA.maxProperty().bindBidirectional(scrollBarB.maxProperty());
        scrollBarA.visibleAmountProperty().bindBidirectional(scrollBarB.visibleAmountProperty());
        scrollBarA.unitIncrementProperty().bindBidirectional(scrollBarB.unitIncrementProperty());
        scrollBarA.blockIncrementProperty().bindBidirectional(scrollBarB.blockIncrementProperty());
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        Insets insets = getInsets();
        double w = getWidth();
        double h = getHeight();
        final double prefWidth = vBar.prefWidth(-1);
        vBar.resizeRelocate(w - prefWidth - insets.getRight(), insets.getTop(), prefWidth, h - insets.getTop() - insets.getBottom());

        final double prefHeight = hBar.prefHeight(-1);
        hBar.resizeRelocate(insets.getLeft(), h - prefHeight - insets.getBottom(), w - insets.getLeft() - insets.getRight(), prefHeight);
    }
}

The CSS looks like this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

.pretty-list-view > .virtual-flow > .scroll-bar,
.pretty-list-view > .virtual-flow > .scroll-bar .decrement-arrow,
.pretty-list-view > .virtual-flow > .scroll-bar .increment-arrow,
.pretty-list-view > .virtual-flow > .scroll-bar .decrement-button,
.pretty-list-view > .virtual-flow > .scroll-bar .increment-button {
    -fx-pref-width: 0;
    -fx-pref-height: 0;
}

.pretty-list-view .pretty-scroll-bar:vertical .thumb {
    -fx-background-insets: 0 2 0 0;
}

.pretty-list-view .pretty-scroll-bar:horizontal .thumb {
    -fx-background-insets: 0 0 2 0;
}

.pretty-list-view .pretty-scroll-bar .decrement-arrow,
.pretty-list-view .pretty-scroll-bar .increment-arrow {
    -fx-pref-width: 0;
    -fx-pref-height: 0;
}

.pretty-list-view .pretty-scroll-bar {
    -fx-background-color: transparent;
    -fx-pref-width: 12;
    -fx-pref-height: 12;
    -fx-padding: 2;
}

.pretty-list-view .pretty-scroll-bar .thumb {
    -fx-background-color: rgba(0, 0, 0, .2);
    -fx-background-radius: 1000;
    -fx-pref-width: 12;
    -fx-pref-height: 12;
}

[/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

JavaFX Tip 27: HiRes / Retina Icons

I recently had a lot of icons sent to me by a graphics / UX designer. It was my job to add them to a JavaFX application. Each icon was shipped in three sizes (16×16, 32×32, 64×64). The naming convention for them was like this:

  • icon-name.png
  • icon-name@2x.png
  • icon-name@3x.png

At first I thought that this was some kind of standard naming used by graphic designers for their files but in the end it turns out to be a convention that was initially introduced by Apple for their Retina displays and adapted by the JavaFX team. The idea is that JavaFX will automatically load the appropriate image file for the current screen resolution. So when the application wants to load a 16×16 icon and JavaFX determines that the screen is a Retina / HiRes display it will then load the @2x file. The result of this are crisp graphics that take full advantage of these beautiful screens.

To enable this automatic image loading one can use CSS or code. In CSS you can define a style like this (assuming image file is in the same directory as CSS file):

    .my-icon {
        -fx-graphic: url("icon-name.png");
    }

Then you can apply the style to your button and it will show the @2x icon if applicable:

    button.getStyleClass().add("my-icon");

When you want to load images in code you have to make sure not to use input streams but a URL. Example:

    Image image = new Image(MyApp.class.getResource("icon-name.png").toExternalForm());

Using hires icons is really worth your time as your UI will look even more beautiful on HiRes displays.

JavaFX Tip 26: Go Dark – The “Darcula” Theme

I am a big fan of IntelliJ IDEA and use it every day for the various projects I work on. One thing I like a lot is the dark theme called “Darcula”. I like it because it looks cool and because of the low contrast it is better for my eyes. When you stare at something for at least eight hours a day for decades (yes, I am that old) then you better make sure it goes easy on your eyes. This said I obviously also like to use dark themes for my own creations. The following snapshot shows an application called “Presentation Builder”. It will be used by sports coaches to assemble presentation videos for their teams to discuss previous matches or to study the next opponent.

Another project I worked on is called “AnimakerFX” (working title). This is a side-project I like to work on when I have time (which unfortunately currently is not the case). Here I also used the dark theme. In general I think the dark theme goes well with applications that are used to produce visually rich content. The dark theme makes sure that the user’s focus remains on the content.

So how can we do this in JavaFX? By simply setting a new value in CSS for the “-fx-base” variable. This variable is part of the default modena.css stylesheet and it is the “base” for various other color constants. Below you can see how -fx-base is defined in Modena and how other colors are derived from it:

.root {
    /***************************************************************************
     *                                                                         *
     * The main color palette from which the rest of the colors are derived.   *
     *                                                                         *
     **************************************************************************/

    /* A light grey that is the base color for objects.  Instead of using
     * -fx-base directly, the sections in this file will typically use -fx-color.
     */
    -fx-base: #ececec;

    /* A very light grey used for the background of windows.  See also
     * -fx-text-background-color, which should be used as the -fx-text-fill
     * value for text painted on top of backgrounds colored with -fx-background.
     */
    -fx-background: derive(-fx-base,26.4%);

    /* Used for the inside of text boxes, password boxes, lists, trees, and
     * tables.  See also -fx-text-inner-color, which should be used as the
     * -fx-text-fill value for text painted on top of backgrounds colored
     * with -fx-control-inner-background.
     */
    -fx-control-inner-background: derive(-fx-base,80%);
    /* Version of -fx-control-inner-background for alternative rows */
    -fx-control-inner-background-alt: derive(-fx-control-inner-background,-2%);
    ....
}

As you can see in the snippet above the -fx-base color is the base for the background color and the “inner” background color. All in all -fx-base can be found around 60 times inside the modena stylesheet.

In the stylesheets of our applications all we need to do now is to override -fx-base with a dark color like this:

.root {
    -fx-base: rgba(60, 63, 65, 255); // dark
}

The cool thing is that the text color is also dependent on this base color and Modena will pick a text color that has good contrast to the background color. As a result the application will end up using white for text. So we get this for free.

Obviously more fine-tuning will be needed to reach the final appearance but -fx-base will take us to our goal very quickly.

Happy coding everyone!

JavaFX Tip 25: Use FXSampler!

The Problem

As a framework developer it is essential to have an easy way to individually test the appearance of and the interaction with each custom control. I really hate it when I first have to open five different screens before I finally get to my new control so that I can test it. Not only is it annoying but it also costs too much time. When doing UI work you constantly make changes to your controls and look at the impact of those changes. If a single change roundtrip (code, launch, test) takes 5 minutes then you can only squeeze 12 change iterations into an hour, making slow progress. If it takes 1 minute then you are looking at 60 iterations and fast progress. As simple as that.

The Solution

Luckily the ControlsFX project contains a very nice subproject called FXSampler, which is a generic sampler application for any JavaFX framework. I am currently using it for FlexGanttFX and CalendarFX. The screens below show the individual CalendarFX samples in a hierarchical tree structure on the left-hand side. The selected sample shows up in the center pane. In this case it is showing the view used for visualizing a calendar entry on a weekday. The property sheet on the right-hand side allows the user to edit the properties of the selected control. It should be noted that the right-hand side can show any kind of control for interacting with the sample, it doesn’t have to be a property sheet. However, very often the property sheet is the fastest way of coming up with a good way of manipulating the control.

The center pane has several tabs. The first one shows the actual sample. The second one displays the javadocs / API of the control. Ideally each sample shows exactly the API of the featured control and not just the index page of the entire framework API. Which page gets loaded into this tab can be configured individually for each sample.

The next tab is used for showing source code. This can be any code that you find relevant for the sample but in my case I always show the code of the sample itself.

Tab number four contains CSS styling information that is relevant for the selected sample. If your control overrides the getUserAgentStylesheet() method then the tab should display the stylesheet that is returned by this method. In CalendarFX all controls inherit from CalendarFXControl which does override this method and always returns “calendar.css”. So in this case there is only a single large CSS file and that is what is being displayed here.

The Setup

For my frameworks and projects I usually work with a multi-module Maven setup and add a separate module just for the samples. The pom.xml file of this module has to contain a dependency to FXSampler like this:

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

<dependency>
   <groupId>org.controlsfx</groupId>
   <artifactId>fxsampler</artifactId>
   <version>1.0.9</version>
</dependency>

The next thing needed is a class that represents the sampler project. The class has to implement the fxsampler.FXSamplerProject interface. It is used to specify a project name, a base package, and a welcome page. The code below shows the implementation of this interface as it was done for CalendarFX.

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

package com.calendarfx.demo;

import fxsampler.FXSamplerProject;
import fxsampler.model.WelcomePage;

public class CalendarFXSamplerProject implements FXSamplerProject {

   @Override
   public String getProjectName() {
      return "CalendarFX";
   }

   @Override
   public String getSampleBasePackage() {
      return "com.calendarfx.demo";
   }

   @Override
   public WelcomePage getWelcomePage() {
      return new CalendarFXSamplerWelcome();
   }
}

Welcome Page

The welcome page is optional and allows you to specify a title for the sampler and to show a UI that will be presented to the user when the sampler application starts up. The CalendarFX sampler shows a simple “about message”.

Base Package

FXSampler will use the “base package” to find all classes that are samples. FXSampler will place the samples found in the base package at the top of the navigation tree on the left-hand side of its window. Samples found in sub packages will end up in a tree node. The image below shows the package structure of the CalendarFX sampler project.

The base package for CalendarFX is com.calendarfx.demo and because the “day entry view” sample was found inside the entries package it means that the sample will be displayed in a tree node called “Entries”. The next image shows how the navigation tree reflects the package structure.

You will notice that the samples are all prefixed with “Hello…”. This is simply a convention I inherited from the ControlsFX project. There is no technical reason for it.

The Samples

A class becomes a sample if it implements the fxsampler.Sample interface. But it is even better if the class extends from fxsampler.SampleBase. The clever thing about this class is that it extends from Application, which means that each sample can also be run standalone.

A sample defines / contains the following things:

  • The name of the sample (in our example “Day Entry View”).
  • A short description of the sample / the control (what does it do? what is it used for?).
  • The name of the project that is belongs to.
  • The project version (e.g. 8.4.0).
  • A node that will be placed in the center pane and that displays the custom control.
  • A control panel for manipulating / interacting with the sample (right-hand side, often a property sheet).
  • The position of the control panel divider handle (based on the width requirements of the control panel).
  • The URL to the JavaDocs (I always point to the docs on my server).
  • The URL to the stylesheet that is relevant for the sample.
  • The URL to the source code of the sample.
  • A flag to indicate if the sample is currently visible (good for hiding samples that are still work-in-progress).

The Launcher

To run the sampler project we need a class with a main() method that extends the JavaFX Application class. Luckily FXSampler ships a class called fxsampler.FXSampler. For CalendarFX it looks like this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

package com.calendarfx.demo;

import fxsampler.FXSampler;
import javafx.stage.Stage;

public class CalendarFXSampler extends FXSampler {

   @Override
   public void start(Stage primaryStage) throws Exception {
      super.start(primaryStage);
   }

   public static void main(String[] args) {
      FXSampler.main(args);
   }
}

You will notice that the launcher does not reference / instantiate any specific sampler project. You would normally expect to see the  launcher create an instance of CalendarFXSamplerProject but its’ main() method simply delegates to the main() method of FXSampler. So how does it find anything?

The Service Provider

The answer is simple but not something that every Java developer has encountered in their day to day coding routine. FXSampler finds the sampler project because it will be registered as a “service provider”. This is done by adding a file to the directory META-INF/services. The file name has to be fxsampler.FXSamplerProject. Inside the file we add the full class name of the sampler project. FXSampler can now lookup all service providers that implement the FXSamplerProject and instantiate them. This allows FXSampler to show the samples found in several JARs at the same time. If I wanted to I could create a single application showcasing the samples found in FlexGanttFX, CalendarFX, and ControlsFX at the same time. The image below shows the location of the service provider file and its content inside the CalendarFXSampler module.

The Executable

When you download CalendarFX or FlexGanttFX you will see that the distributions include the sampler projects (inside “demos” folder). They were added as executable / runnable JAR files. The required Maven configuration for building them looks like this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

<build>
   <plugins>
      <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <version>2.4</version>
         <configuration>
            <finalName>sampler-demo</finalName>
            <appendAssemblyId>false</appendAssemblyId>
            <descriptorRefs>
               <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
            <archive>
               <manifest>
                  <mainClass>com.calendarfx.demo.CalendarFXSampler</mainClass>
               </manifest>
            </archive>
         </configuration>
         <executions>
            <execution>
               <id>make-samples</id>
               <phase>package</phase>
               <goals>
                  <goal>single</goal>
               </goals>
            </execution>
         </executions>
      </plugin>
   </plugins>
</build>

Conclusion

I highly recommend using FXSampler and making it a habbit to add a sample for every custom control you write, no matter if you are a working on a framework or an application. The benefits of being able to quickly test a control in a controlled environment greatly outweighs the overhead of creating and maintaining the sample.

Happy coding everyone![/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

JavaFX Tip 24: Custom Layouts for Performance and Flexibility

I just finished a two month sprint on advancing CalendarFX and getting it ready for release 8.4.0. One focus of this sprint was on performance. There are many things that can influence performance but when it comes to JavaFX the number of nodes in your scenegraph and CSS styling are top candidates for optimisation. After reviewing the custom controls that ship with CalendarFX I realized that many of them used a lot of nested panes (BorderPane, VBox, HBox, GridPane) in order to achieve a specific layout. Nested panes result in a high node count and complex CSS styling instructions.

One simple example are the views that CalendarFX creates to visualize calendar entries in the DayView control. This control shows the 24 hours of a day vertically and places DayEntryView instances on them. These entry views look like this:

day-entry-view

In the previous releases of CalendarFX the skin of the entry view used a VBox instance to lay out the two labels that show the title of the entry and its start time. That was the only purpose of the VBox. Doesn’t sound like a big issue but when your application starts to show hundreds of those entries then you end up creating hundreds of VBox instances, too.

The way to avoid this is to mange the positioning of these labels yourself and to implement / override the layoutChildren() method of the skin. Not only do we save one node but it also gives us more flexibility. We can now decide to hide or show the labels based on the available space. In the case of the DayEntryView we can decide to hide the start time label if the available height of the view is not big enough to show both labels. This kind of, may I dare to say it, “responsiveness” is not possible when using the out-of-the-box layout panes that are shipping with JavaFX.

The code of the layoutChildren() method of DayEntryViewSkin can be seen below.

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

    @Override
    protected void layoutChildren(
                               double contentX, 
                               double contentY, 
                               double contentWidth, 
                               double contentHeight) {

        // Title label.
        double titleHeight = titleLabel.prefHeight(contentWidth);

        // It is guaranteed that we have enough height to display 
        // the title (because "computeMinHeight" returns the min
        // height of the title label).

        titleLabel.resizeRelocate(
                snapPosition(contentX), 
                snapPosition(contentY), 
                snapSize(contentWidth), 
                snapSize(titleHeight));

        // Start time label (only show it when there is enough space).

        double timeLabelHeight = 
                           startTimeLabel.prefHeight(contentWidth);

        if (contentHeight - titleHeight > timeLabelHeight) {

            // make sure to set visibility to true again
            startTimeLabel.setVisible(true);

            startTimeLabel.resizeRelocate(
                    snapPosition(contentX), 
                    snapPosition(contentY + titleHeight), 
                    snapSize(contentWidth), 
                    snapSize(timeLabelHeight));

        } else {
            // Not enough space, hide the start time label.
            startTimeLabel.setVisible(false);
        }
    }

The nice thing about the layoutChildren() method is that it tells you exactly which space it is that your children nodes can use. The space is given by the four parameters contentX, contentY, contentWidth, and contentHeight. So there is no need to first lookup things like insets or padding. The “content” rectangle is the space that is available to you.

The second nice thing when writing your own layoutChildren() method is that you can utilize the snapXYZ() methods provided by the SkinBase superclass. These methods ensure that the child nodes will be sized and placed in such a way that they will appear crisp and not blurry. Why is this needed? Because JavaFX uses double precision coordinates to place nodes and unfortunately “int” coordinates are actually located between pixels on your display. So the snapPosition() method might take your x / y coordinate of 100 / 100 and change it to 100.5 / 100.5.

As mentioned before, the DayEntryView is a simple example. Obviously you can also create very complex layouts by overriding the layoutChildren() method. Did I always do this in the current release of CalendarFX? No, I did not. Very often I exchanged all those nested panes with instances of type GridPane, which does give you a lot of layout options but unfortunately no support for responsiveness.

Hope this has been helpful for some of you.

Happy coding![/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

Wanted: JavaFX Tips and Tricks

At this year’s JavaOne conference in San Francisco I will be presenting a session called “JavaFX Tips and Tricks”. This talk will be based on the posts in my blog. However, I have a feeling that there are many more nice tips and tricks out there that should be mentioned in this presentation. So I am asking everybody to mail me their cool tricks and tips for inclusion. Let everybody benefit from your experiences! Anybody who sends input will of course be mentioned by name and their twitter handle and / or website will be listed as part of the slides. Use dlemmermann@gmail.com to send me your input.