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]

JFX Days 2019

Once again we are organizing a three-day conference completely focused on JavaFX. This year we call it “JFX Days”. The conference website including venue information and last year’s presentations can be found at jfx-days.com. Each day will offer ample opportunity to talk, discuss, and enjoy fine food.

Day 1: we will have a workshop given by Anton Epple where attendees will take a deep dive into performance related aspects of JavaFX coding.

Day 2: sessions from dusk till dawn. You find the planned sessions below.

Day 3: a second workshop will focus on testing JavaFX applications. This workshop will be given by Sergey Grinev who used to work at Oracle inside the JavaFX team.

 

Planned Sessions

 

Keynote / Real World Applications / Show & Tell

Dirk Lemmermann, DLSC,

JavaFX applications are being used in many different domains by many small and large companies. However, most people never get to see these applications as they are being developed and used behind closed company doors. In the keynote we will present several “real world” applications. The “show and tell” section will give attendees the opportunity to also show the cool stuff that they have been working on.

 

The JavaFX Roadmap

Johan Vos, Gluon, Wolfgang Weigend, Oracle

About a year ago, JavaFX 11 was released. This was the first release after JavaFX has been decoupled from the core JDK. Since the decoupling was announced, developers from Oracle, Gluon and other companies worked together to ensure that the JavaFX platform was modularized, and that JavaFX developers could use the JavaFX API’s in the same way they use other API’s. As part of the decoupling, a mirror on github has been created for making the development of OpenJFX easier and more transparent to developers that are used to modern development systems. After that work was done, focus turned again to new functionalities and improvements. The development of JavaFX is mainly driven by its contributors and their customers.

In this session:

  • we look at the most important new feature in JavaFX 13: native rendering support

  • we look into the new features that are being developed as part of JavaFX 14 and beyond, including support for Metal

  • we explain the new packaging mechanisms, including jpackage and GraalVM support

  • we focus on the WORA aspects of JavaFX. Increasingly, more platforms are supported, including desktop, mobile and embedded.

 

From GitHub Source to GitHub Release: Free CI/CD Pipelines for JavaFX Apps

Bruno Borges, Microsoft

Streamline the building, testing, packaging, and release of your desktop JavaFX applications for all major platforms with simple to use CI/CD Pipelines and GitHub. This session will cover the details of combining GitHub for hosting source code and binaries for Mac OS, Windows and Linux of your application, and how to take advantage of Azure Pipelines plan for Open Source projects. We will learn about using a Maven archetype and a Gradle starter project for JavaFX apps, both ready for CI/CD and how they are configured. Join this talk and get ready to streamline your desktop apps just like your microservices.

 

NativeFX

Michael Hoffer, https://mihosoft.eu

NativeFX brings the power of JavaFX to the next level. It provides access to any native visualization library or app that can render to a shared memory buffer. This enables the integration of WebGL/OpenGL, Vulkan, Qt and more to the scene graph. We will showcase WebGL and Qt in this presentation. Since NativeFX uses multiple processes, native code can’t crash the JVM. These are essential features for many industrial and scientific applications that rely on a specific visualization technology that can’t be easily ported to JavaFX. You get the reliability of the Java platform and the power of native rendering technologies. In this presentation you will learn how to use NativeFX for your own projects. Additionally, we will discuss how you can create your own native renderer.

 

JavaFX Testing

Sergey Grinev, Azul Systems, Inc.

Once you’ve built a beautiful and reliable JavaFX application you’ll want to keep it this way. To address that in this session we’ll talk about automated testing of JavaFX UI application. We’ll identify main challenges and look at best practices of the JavaFX UI testing and review frameworks which can help with test automation of JavaFX apps: JemmyFX used by Oracle to test JavaFX platform itself, another OpenSource one TestFX, and commercial framework TestComplete.

 

JavaFX on Mobile – A Big Update!

Johan Vos, Gluon

Since we launched the JavaFXPorts initiative, it has been possible for JavaFX developers to deploy their applications on Android and iOS devices. While there are great apps out there created this way, the limits imposed by Apple and Google have been challenging. As a consequence, the mobile ports always were a few versions behind the desktop releases, and not all of the latest Java functionality was supported. With the new GraalVM, and especially the native-image functionality that is part of it, you can now create JavaFX apps with the very latest JDK release (13) and the very latest JavaFX release (13). Another benefit of GraalVM is the excellent AOT compiler, resulting in much faster startup. In this session, we’ll show how GraalVM is changing the Java landscape in general, and we focus on the consequences for mobile/embedded.​​

 

JavaFX in the Browser: JPRO

Florian Kirmaier (Sandec, JPro One)

This session will show how to develop and deploy with JPro, which enables Java programs to run in standard web browsers without a plugin.  By taking a deeper look into some real-world applications, the audience will learn how Java can be used for cross platform development, to write applications for not only desktops, but also for mobile devices and web browsers.  The audience will learn how a typical web page can be created with pure Java.  And the code for the web page runs not only in browsers, but also as native apps (desktops, iOS or Android).  A new portal, a web-based JavaFX ensemble, will be announced and presented, which already consolidates a number of prominent JavaFX libraries, such as ControlsFX and JFoenix and can hopefully serve as a common API Portal for many more libraries in time to come.  Attendees will also learn how to let Java code interoperate with currently popular web technologies such as Angular and React.

 

OpenWebStart / AdoptOpenJDK

Hendrik Ebbers, Karakun

The fine folks over at Karakun are working on a replacement for Java Webstart and Hendrik will update us on the status of this project and how you can use it to deploy your Java application. Another important initiative is AdoptOpenJDK where developers now have the liberty to pick from several Java distributions, some of which bundle JavaFX, some do not.

JavaFX / OpenJFX 11 Interview Series

I was recently interviewed by jaxenter.com regarding JavaFX 11 / OpenJFX. The interview was done together with Johan Vos, Jonathan Giles, and Donald Smith. The interview was published in three parts. They can be found here:

JavaFX Days Zurich – Sessions

The holidays are over, time to get serious, time to fill the session catalogue for the JavaFX Days Zurich (Website). We managed to convince some of the best JavaFX experts to come to Switzerland and to show us the things they work on or work with. So without further ado here is a list of the currently scheduled sessions:

 

JavaFX State of the Union

Wolfgang Weigend, Oracle Corp.

The current state of JavaFX UI development will be explained from the perspective of Oracle, with an inventory of existing development resources and the continuation of JavaFX in a free ecosystem. With the release of the OpenJFX and the decoupling of the Oracle JDK, the Java module system has created new possibilities for the integration of JavaFX modules into OpenJDK. The companies involved in the JavaFX ecosystem are creating additional functionalities and shaping the transition from active Oracle engineering to the year 2022, thereby securing the long-term technological viability of JavaFX. The organizational developer participation takes place via the OpenJDK and from there the open source software could be redistributed. Depending on the expertise of the respective development projects, the involved development companies offer independent support and thus close the gap to new innovative JavaFX features and customer requirements that go beyond the current state of development. JavaFX support is available with the commercial Java SE subscription for JDK 8 (LTS) until March 2025 and could be extended as desired.

Building Mobile Apps with Gluon

Johan Vos, Gluon

Building cross-platform mobile applications for iOS and Android with Java is fairly simple with the Gluon open source and commercial tools. Development teams can quickly build beautiful apps leveraging their Java skills, without extra budget or external teams. This session revisits the state of the latest developments (JDK, Gluon VM, JavaFX). It also demonstrates how you can build applications with one cross-platform Java API and deploy to mobile platforms with compelling UI, native services integration, and seamless connection with the cloud and enterprise back end. You will profit from improved security and common mobile features such as push notifications, authentication, and data synchronization or persistency, among others.

 

The JavaFX Ecosystem

Andres Almiray, Trivadis AG

For the past 7 years we have seen open source libraries and JavaFX projects popping up slowly, however the pace at which new projects appear has increased. We’ll cover a wide range of libraries that will ensure your next JavaFX project becomes a success. Make the most of layouts with MigPane. Spice up your control repertoire with JideFX, Medusa and ControlsFX. Change the looks of your application with the flick of a CSS switch, thanks to JFoenix and BootstrapFX. Decorate your screens with a wide variety of icons from Ikonli. And these are but a few of the libraries we’ll cover.

Developing Business Applications on top of e(fx)clipse

Tom Schindl, BestSolution

In this talk we’ll introduce you to e(fx)clipse which is a JavaFX application framework built on top of OSGi and the UI-agnostic parts of Eclipse 4. We’ll show you applications we built with customers around the world, ranging from 3d modeling tools to sophisticated form applications and PDF-Viewers.

The TilesFX and Medusa Frameworks

Gerrit Grunwald, Karakun, Blog: Harmonic Code

Gerrit presents his two open source frameworks TilesFX and Medusa. TilesFX is used for creating professional and sophisticated dashboards. Medusa delivers a huge set of custom controls that implement gauges, ideal for monitoring applications. Gerrit will share many tricks on how to accomplish eye candy effects.

Extreme GUI Makeover

Dirk Lemmermann, Hendrik Ebbers, Karakun, Blog: GuiGarage

Come and see Dirk and Hendrik take you step by step through the process of turning a dull movie database application into a sexy app filled with eye candy.

JPro in Production

Hans-Henry Sandbaek, Florian Kirmaier, Sandec, JPro One

This session will show how to develop and deploy with JPro, which enables Java programs to run in standard web browsers without a plugin.  By taking a deeper look into some real-world applications, the audience will learn how Java can be used for cross platform development, to write applications for not only desktops, but also for mobile devices and web browsers.  The audience will learn how a typical web page can be created with pure Java.  And the code for the web page runs not only in browsers, but also as native apps (desktops, iOS or Android).  A new portal, a web-based JavaFX ensemble, will be announced and presented, which already consolidates a number of prominent JavaFX libraries, such as ControlsFXand JFoenixand can hopefully serve as a common API Portal for many more libraries in time to come.  Attendees will also learn how to let Java code interoperate with currently popular web technologies such as Angular and React.

JavaFX Days Zurich

Today is the day: the website for the “JavaFX Days Zurich” has gone live and I hope that it will find a lot of interest in the Java developer community. The idea behind this conference is quite simple: to provide an event that is highly focused on JavaFX technology and to bring together the leaders in this area with interested parties. Over the last couple of years I came to realize that even though JavaFX is a pretty cool piece of technology it is not getting the attention it deserves because of the overwhelming concentration on anything that runs inside the browser. Hopefully this conference can make a difference.

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(contentWidth 2 * fadingSize), snapSpace(contentHeight));
resizeRelocate(rightClip, snapPosition(contentX + contentWidth fadingSize), 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();
}
}

 

CalendarFX goes Open Source

I am happy to announce that CalendarFX has found a new home on GitHub. As of today it is no longer a commercial product but an open source project. Over the last two years I have noticed that the developer community has resisted to use CalendarFX for their projects as the source code was not freely available. So now that CalendarFX has been released into the wild I am hoping that it will live a long and happy live.