Foojay Today

Wordish with JavaFX – Part 2

May 24, 2022

Building Games and Having Fun with Java and JavaFX

Welcome to Part 2 of this five part series!

In Part 1, we introduced the Wordish game with JavaFX and discussed the main UI layout.

In this second part, we'll discuss some important look and feel enhancements.

We'll show you specialized Label and Button controls and using pseudo-classes for advanced CSS styling.

We'll also cover incorporating third-party font libraries and customizing Scene Builder to leverage these features.

Here's an example screenshot of Wordish.

Wordish Game in progress
Figure 1. Wordish Main View Layout

You can access the code on github here: https://github.com/gailasgteach/Wordish.

Part 2: Look and Feel Enhancements

Wordish uses a specialized Label control that reflects the user’s guess compared to the target word. Similarly, the virtual keyboard Button control must also reflect the matching status of its specific key.

Let’s examine how to provide specialized Label and Button controls that hold meaningful state in our application. We’ll extend the standard controls and add JavaFX properties.

By leveraging these properties with CSS pseudo-classes, you’ll see how to keep the UI synchronized with the model when updating the controls’ properties.

Special icons add flair to an application, so we picked a few appropriate icons from the iKonli Font Library. We show you how to incorporate such third-party icons in JavaFX applications.

It’s straightforward to add our specialized controls as well as third-party libraries to Scene Builder. This means you can continue to use Scene Builder visualizations, even when your controls and fonts are not part of the standard JavaFX library.

Finally, we show you some of the CSS customizations we use to help keep our UI looking Wordishly.

Specialized Label Control

For the Labels in TilePane and the Buttons in FlowPane, we add state to these controls to reflect the matching status of its letter as the user plays Wordish. 

For the Label, here’s class LetterLabel with two added JavaFX properties:

public class LetterLabel extends Label {
    private final ObjectProperty<DisplayType> matchResult = new 
         SimpleObjectProperty<>(DISPLAYING);
    private final ObjectProperty<DisplayType> letterDisplay = new
         SimpleObjectProperty<>(PLAIN);
    . . .
}

Property matchResult holds the status of that letter’s match against the target word: exact match, partial match, or no-match. Since we don’t reveal match results until after animating the tiles, a second property, letterDisplay, determines the CSS styling of the LetterLabel. DisplayType is an enum that reflects one of the five possible states of a LetterLabel.

public class LetterStyle {
     . . .
    public enum DisplayType {
            PLAIN, DISPLAYING, NOMATCH, PARTIALMATCH, MATCHING;
    }  
}

Figure 2 illustrates all five DisplayType enum states: The three lower rows of LetterLabel in the TilePane are styled with DisplayType PLAIN. The word “BLEAK” is styled with DisplayType DISPLAYING. This state applies when the user is entering the word before submitting it. The words “STONE” and “FLAIR” illustrate the other three DisplayTypes applied after the matching process is complete: the L in FLAIR is MATCHING, the E in STONE and the A in FLAIR is PARTIALMATCH, and the remaining LetterLabels are DisplayType NOMATCH.

Wordish UI shoing customized LetterLabel and KeyButton controls
Figure 2. Customized LetterLabel and KeyButton controls

Specialized Button Control

Similar to LetterLabel, the virtual keyboard is made up of enhanced Button controls called KeyButton. Here, we add a single JavaFX property to reflect the status of that keyboard letter in the current game.

public class KeyButton extends Button {
    private final ObjectProperty<DisplayType> letterDisplay = new
         SimpleObjectProperty<DisplayType>(PLAIN);
    . . .
}

As Figure 2 illustrates, key L is MATCHING, keys E and A are PARTIALMATCH, and keys R, T, I, O, S, F, and N are NOMATCH. The remaining keys are PLAIN.

When you submit a word, the controller code updates the LetterLabel and KeyButton states. In the following code snippet, we invoke the setter for property matchResult, which specifies that the letter in the LetterLabel is matching.

// ll is LetterLabel, **pseudo code here**
// Do we have a match?
if (isMatching(ll.getText())) {
   ll.setMatchResult(MATCHING);
}

After processing the submitted word, we update the LetterLabel’s letterDisplay property with the value from its matchResult property.

// update the LetterLabel’s letterDisplay property
ll.setLetterDisplay(ll.getMatchResult());

Next, we synchronize the associated key in KeyButton by updating its letterDisplay property. Here, list is the row of LetterLabel controls for this play and keyLetters is a Map. Note that we use the Map’s key to identify the KeyButton control for LetterLabel’s letter.

list.stream().forEach(ll -> keyLetters.get(ll.getText())
            .setLetterDisplay(ll.getMatchResult()));

Importantly, by calling the JavaFX property setter setLetterDisplay() for both LetterLabel and KeyButton, the UI styles automatically update to reflect the matching state. To see how this works, let’s discuss CSS pseudo-classes with the well-known Button control.

CSS Pseudo-Classes

Many JavaFX controls, including Button, have a JavaFX property called disableProperty. To disable button myButton, for example, use

myButton.setDisable(true);    // disable myButton

When this code executes, the UI reflects the disabled state by reducing the opacity of the control. Note that we don’t update the styling ourselves. JavaFX implements this styling behavior with CSS pseudo-classes, where a change in a property triggers a change in the node’s CSS styling.

We want this same behavior for both LetterLabel and KeyButton, where updates to the letterDisplay property automatically update the UI.

For this to work, we have to build CSS scaffolding in our classes. Let’s return to our LetterStyle class where we define the DisplayType enum. Here, we associate each DisplayType enum with a pseudo-class that names a CSS state.

import javafx.css.PseudoClass;

public class LetterStyle {
    public static final PseudoClass PLAIN_PSEUDO_CLASS = 
          PseudoClass.getPseudoClass("plain");
    public static final PseudoClass DISPLAYING_PSEUDO_CLASS = 
          PseudoClass.getPseudoClass("displaying");
    public static final PseudoClass NOMATCH_PSEUDO_CLASS = 
          PseudoClass.getPseudoClass("nomatch");
    public static final PseudoClass PARTIALMATCH_PSEUDO_CLASS = 
          PseudoClass.getPseudoClass("partialmatch");
    public static final PseudoClass MATCHING_PSEUDO_CLASS = 
          PseudoClass.getPseudoClass("matching");

    public enum DisplayType {
            PLAIN, DISPLAYING, NOMATCH, PARTIALMATCH, MATCHING;
    }   
}

Let’s examine the pseudo-class returned by PseudoClass.getPseudoClass("matching"). Here, the styling is identified in our CSS file and depends on the node. For LetterLabel control in a TilePane, for instance, style class .matching-letter in state matching has the following style.

.tile > .matching-letter:matching {
    -fx-outer-border: -fx-match-color;
    -fx-body-color: -fx-match-color;
    -fx-text-fill: -fx-text-fill-alt-color;
}

And for a KeyButton control in a FlowPane, style class .matching-letter in state matching is defined as follows.

.flow > .matching-letter:matching {
    -fx-button-base-color: -fx-match-color;
    -fx-text-fill: -fx-text-fill-alt-color;
}

Note: Colors -fx-match-color and -fx-text-fill-alt-color are defined elsewhere in the CSS file.

There are similar style classes defined for states plain, displaying, partialmatch, and nomatch for controls in TilePane. We also have classes defined for states plain, partialmatch, and nomatch with controls in FlowPane. We assign these style classes to our customized controls in the LetterLabel and KeyButton constructors with the following (for example).

getStyleClass().add("matching-letter");

Reacting to Property Updates

The final piece of scaffolding code is to react when the KeyButton and LetterLabel letterDisplay property updates. Since JavaFX properties are observable, we add this code to the property’s invalidated() method. This method is invoked each time you call the property setter.

Here is the letterDisplay property for LetterLabel. We leverage the Java 14 switch syntax, which makes this code more streamlined than the pre-Java 14 switch statement. The KeyButton letterDisplay property is similar, but with style “displaying-letter” not needed.

public class LetterLabel extends Label {
    public LetterLabel() {
          super();
        // assign these style classes to our customized controls
          getStyleClass().add("displaying-letter");
          getStyleClass().add("matching-letter");
          getStyleClass().add("nomatch-letter");
          getStyleClass().add("partialmatch-letter");
          getStyleClass().add("plain-letter");
    }
    
    private final ObjectProperty<DisplayType> letterDisplay = new 
          SimpleObjectProperty<>(PLAIN) {
        @Override
        public void invalidated() {
            // Turn off all custom PseudoClasses in the LetterLabel,
            // then uses a switch statement to set a
            // specific PseudoClass that depends on the value
            // in the letterDisplay property
            pseudoClassStateChanged(PLAIN_PSEUDO_CLASS, false);
            pseudoClassStateChanged(DISPLAYING_PSEUDO_CLASS, false);
            pseudoClassStateChanged(NOMATCH_PSEUDO_CLASS, false);
            pseudoClassStateChanged(PARTIALMATCH_PSEUDO_CLASS, false);
            pseudoClassStateChanged(MATCHING_PSEUDO_CLASS, false);
            switch (get()) {
                case PLAIN -> 
                    pseudoClassStateChanged(PLAIN_PSEUDO_CLASS, true);
                case DISPLAYING -> 
                    pseudoClassStateChanged(DISPLAYING_PSEUDO_CLASS, true);
                case NOMATCH -> 
                    pseudoClassStateChanged(NOMATCH_PSEUDO_CLASS, true);
                case PARTIALMATCH -> 
                    pseudoClassStateChanged(PARTIALMATCH_PSEUDO_CLASS, 
                    true);
                case MATCHING -> 
                    pseudoClassStateChanged(MATCHING_PSEUDO_CLASS, true);
            }
        }
    };
. . .
}

The invalidated() method for property letterDisplay must ensure that a pseudo-class state is mutually exclusive. That is, no letterDisplay pseudo-class state can match more than one state. Therefore, before turning on the matching pseudo-class state, we make all pseudo-class states false, as shown above.

Note: See LetterStyle.java, LetterLabel.java, and KeyButton.java for the above discussion.

Note: See styles.css for the CSS styles used in Wordish.

iKonli Font Library

It’s easy to add flair to your applications with third party icon packs. In Wordish, we use the iKonli Font Library for the Delete Key as well as the Replay, Information, and Stats (Bar Chart) buttons. In addition, the Close icon lets you return to the main view from either the Information or Stats view. These icons are based on Material Design. Using familiar icons and fonts help make an application intuitive when you match icons with common, expected behavior.

To access the iKonli Font library in Wordish, we add appropriate modules to the module-info.java file, as follows.

module wordish {
    . . .
    requires org.kordamp.ikonli.core;
    requires org.kordamp.ikonli.javafx;
    requires org.kordamp.ikonli.materialdesign2;
      . . .
}

And add these dependencies to the pom.xml file.

<dependencies>
     <dependency>
            <groupId>org.kordamp.ikonli</groupId>
            <artifactId>ikonli-javafx</artifactId>
            <version>12.3.1</version>
     </dependency>
     <dependency>
            <groupId>org.kordamp.ikonli</groupId>
            <artifactId>ikonli-materialdesign2-pack</artifactId>
            <version>12.3.1</version>
     </dependency>
      . . . 
</dependencies>

Here's how to add an icon graphic to either a Button or Label in FXML. For example, we add the Delete icon named "mdi2b-backspace-outline" to our virtual keyboard KeyButton control. Note that you can specify its pixel width with attribute iconSize.

<KeyButton fx:id="deleteButton" onAction="#processDelete" 
         prefHeight="57.0" 
         prefWidth="57.0" 
         textAlignment="CENTER">
    <graphic>
        <FontIcon iconLiteral="mdi2b-backspace-outline" iconSize="24" />
    </graphic>
</KeyButton>

The Material Design2 cheat sheet provides a handy list of other iconLiteral codes that you can incorporate into your UI.

Adding Custom Controls to Scene Builder

To use customized controls with Scene Builder, supply a JAR file with these compiled classes. After you import them into Scene Builder, you can use Scene Builder’s visual features with your custom controls. Similarly, to incorporate third party icons within Scene Builder, import the required JAR file or repository. 

From the Scene Builder Library menu, select the Settings icon (at the top next to Library) and the JAR/FXML Manager, as shown in Figure 3.

Figure 3. Use the JAR/FXML Manager to customize Scene Builder

Scene Builder will then display the Library Manager. To add the KeyButton and LetterLabel classes, select Add Library/FXML from file system, as shown in Figure 4. Supply the jar file containing the compiled class files for LetterLabel, KeyButton, and LetterStyle (here, file wordish.jar). 

Figure 4. The Scene Builder Library Manager

Once you add them, the classes appear in the Custom section under Library. You can then select a LetterLabel, for example, place it in your visual view, and even configure its properties, including the custom properties letterDisplay and matchResult that we showed earlier.

For the iKonli Font Library, select Search repositories and provide the Group or Artifact ID. You can then select the libraries you need.

Customizing CSS

Besides providing style classes to support the customized pseudo-classes, we also added custom styles to support the look you see in Figure 2. We styled the application using root values for certain colors.

By assigning names to these colors, such as -fx-match-color, we can reuse them throughout the style sheet.  We can even change the color theme of the entire application just by changing the colors assigned to our color names.

For our application, each FXML file uses the same style sheet. By identifying the CSS style sheet in the FXML file, Scene Builder can accurately reflect the styles you’re using. Furthermore, the views share many of the same style settings.

We use black and white for the letter stroke color. Wordish has four main colors: a basic button background color and three match result colors: goldish, greenish, and dark gray. We define these in the style sheet root style as follows.

.root {
    -fx-button-base-color: #d3d6d9;
    -fx-text-fill-base-color: black;
    -fx-text-fill-alt-color: white;
    -fx-partialmatch-color: #c5b565;
    -fx-nomatch-color: #797c7e;
    -fx-match-color: #78a86b;
}

These colors are then reused when we define our pseudo-class states. 

Normally, when a JavaFX button is disabled, its opacity is reduced to .4. To mimic the Wordle UI, we override this behavior by leaving disabled buttons in our virtual keyboard fully opaque, as shown.

.flow > .button:disabled {
    -fx-opacity: 1.0 ;
}

However, we commented out the above style during development and testing, since it was helpful to notice when we disabled a button.

Note: See styles.css for the CSS styles used in Wordish.

Next: In Part 3 we'll examine the controller code. The controller code maintains game state and responds to user input with appropriate updates to the UI.

Topics:

Related Articles

View All

Author(s)

  • Gail Anderson

    Gail is a Java Champion and Oracle Groundbreaker Ambassador. She is Director of Research and founding member of the Anderson Software Group, a leading provider of training courses in Java, ... Learn more

Comments (0)

Your email address will not be published.

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard