Tuesday, February 28, 2012

Viewing JavaFX 2 Standard Colors

The JavaFX 2 class javafx.scene.paint.Color includes several fields that are static Color members. I have taken advantage of the convenience of these publicly available static fields in many of my JavaFX 2 examples shown in this blog. There is a lengthy list of these predefined Color fields ranging (in alphabetical order) from Color.ALICEBLUE to Color.YELLOWGREEN. I have sometimes thought it would be nice to quickly see what some of the less obvious colors look like and the simple JavaFX 2 application featured in this post provides a sampling of those colors.

The sample JavaFX 2 application shown here uses simple Java reflection to introspect the JavaFX Color class for its public fields that themselves of type Color. The application then iterates over those public fields, providing information about each color such as the color's field name, a sample of the color, and the red/green/blue components of that color.

The final row allows the user to specify red/green/blue values to see how such a color is rendered. This is useful if the user sees a standard color that is close to what he or she wants and the user wants to try adjusting it slightly. To ensure meaningful values for displaying a color based on provided red/green/blue values, the application ensures that entered values are treated as doubles between 0.0 and 1.0 even if they are not numbers or are numbers outside of that range.

The simple JavaFX 2 application showing standard Color fields is shown in the next code listing.

JavaFxColorDemo.java
package dustin.examples;

import static java.lang.System.err;
import java.lang.reflect.Field;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.stage.Stage;

/**
 * Simple JavaFX 2 application that prints out values of standardly available
 * Color fields.
 * 
 * @author Dustin
 */
public class JavaFxColorDemo extends Application
{
   /** Width of label for colorn name. */
   private final static int COLOR_NAME_WIDTH = 150;
   /** Width of rectangle that displays color. */
   private final static int COLOR_RECT_WIDTH = 50;
   /** Height of rectangle that displays color. */
   private final static int COLOR_RECT_HEIGHT = 25;

   private final TextField redField = TextFieldBuilder.create()
      .text("Red Value").build();
   private final TextField greenField = TextFieldBuilder.create()
      .text("Green Value").build();
   private final TextField blueField = TextFieldBuilder.create()
      .text("Blue Value").build();
   private final Rectangle customColorRectangle = RectangleBuilder.create()
      .width(COLOR_RECT_WIDTH).height(COLOR_RECT_HEIGHT)
      .fill(Color.WHITE).stroke(Color.BLACK).build();

   /**
    * Build a pane containing details about the instance of Color provided.
    * 
    * @param color Instance of Color about which generated Pane should describe.
    * @return Pane representing information on provided Color instance.
    */
   private Pane buildColorBox(final Color color, final String colorName)
   {
      final HBox colorBox = new HBox();
      final Label colorNameLabel = new Label(colorName);
      colorNameLabel.setMinWidth(COLOR_NAME_WIDTH);
      colorBox.getChildren().add(colorNameLabel);
      final Rectangle colorRectangle = new Rectangle(COLOR_RECT_WIDTH, COLOR_RECT_HEIGHT);
      colorRectangle.setFill(color);
      colorRectangle.setStroke(Color.BLACK);
      colorBox.getChildren().add(colorRectangle);
      final String rgbString =
           String.valueOf(color.getRed())
         + " / " + String.valueOf(color.getGreen())
         + " / " + String.valueOf(color.getBlue())
         + " // " + String.valueOf(color.getOpacity());
      final Label rgbLabel = new Label(rgbString);
      rgbLabel.setTooltip(new Tooltip("Red / Green / Blue // Opacity"));
      colorBox.getChildren().add(rgbLabel);
      return colorBox;
   }

   /**
    * Extracts a double between 0.0 and 1.0 inclusive from the provided String.
    * 
    * @param colorString String from which a double is extracted.
    * @return Double between 0.0 and 1.0 inclusive based on provided String;
    *    will be 0.0 if provided String cannot be parsed.
    */
   private double extractValidColor(final String colorString)
   {
      double colorValue = 0.0;
      try
      {
         colorValue = Double.valueOf(colorString);
      }
      catch (Exception exception)
      {
         colorValue = 0.0;
         err.println("Treating '" + colorString + "' as " + colorValue);
      }
      finally
      {
         if (colorValue < 0)
         {
            colorValue = 0.0;
            err.println("Treating '" + colorString + "' as " + colorValue);
         }
         else if (colorValue > 1)
         {
            colorValue = 1.0;
            err.println("Treating '" + colorString + "' as " + colorValue);
         }
      }
      return colorValue;
   }

   /**
    * Build pane with ability to specify own RGB values and see color.
    * 
    * @return Pane with ability to specify colors.
    */
   private Pane buildCustomColorPane()
   {
      final HBox customBox = new HBox();
      final Button button = new Button("Display Color");
      button.setPrefWidth(COLOR_NAME_WIDTH);
      button.setOnMouseClicked(new EventHandler<MouseEvent>()
      {
         @Override
         public void handle(MouseEvent t)
         {
            final Color customColor =
               new Color(extractValidColor(redField.getText()),
                         extractValidColor(greenField.getText()),
                         extractValidColor(blueField.getText()),
                         1.0);
            customColorRectangle.setFill(customColor);
         }
      });
      customBox.getChildren().add(button);
      customBox.getChildren().add(this.customColorRectangle);
      customBox.getChildren().add(this.redField);
      customBox.getChildren().add(this.greenField);
      customBox.getChildren().add(this.blueField);
      return customBox;
   }

   /**
    * Build the main pane indicating JavaFX 2's pre-defined Color instances.
    * 
    * @return Pane containing JavaFX 2's pre-defined Color instances.
    */
   private Pane buildColorsPane()
   {
      final VBox colorsPane = new VBox();
      final Field[] fields = Color.class.getFields(); // only want public
      for (final Field field : fields)
      {
         if (field.getType() == Color.class)
         {
            try
            {
               final Color color = (Color) field.get(null);
               final String colorName = field.getName();
               colorsPane.getChildren().add(buildColorBox(color, colorName));
            }
            catch (IllegalAccessException illegalAccessEx)
            {
               err.println(
                  "Securty Manager does not allow access of field '"
                  + field.getName() + "'.");
            }
         }
      }
      colorsPane.getChildren().add(buildCustomColorPane());
      return colorsPane;
   }

   /**
    * Start method overridden from parent Application class.
    * 
    * @param stage Primary stage.
    * @throws Exception JavaFX application exception.
    */
   @Override
   public void start(final Stage stage) throws Exception
   {
      final Group rootGroup = new Group();
      final Scene scene = new Scene(rootGroup, 700, 725, Color.WHITE);
      final ScrollPane scrollPane = new ScrollPane();
      scrollPane.setPrefWidth(scene.getWidth());
      scrollPane.setPrefHeight(scene.getHeight());
      scrollPane.setContent(buildColorsPane());
      rootGroup.getChildren().add(scrollPane);
      stage.setScene(scene);
      stage.setTitle("JavaFX Standard Colors Demonstration");
      stage.show();
   }

   /**
    * Main function for running JavaFX application.
    * 
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      Application.launch(arguments);
   }
}

The snapshots shown next demonstrate this simple application. The first snapshot shows the application after loading. The second snapshot shows the application after scrolling down to the bottom and demonstrates use of the Tooltip and of the TextField.setPrompt(String) method. The third image shows the results of providing red/green/blue values and clicking on the button to see the corresponding color.

The simple JavaFX 2 application shown in this post makes it easy to get an idea of what colors are available as standard JavaFX 2 public static Color fields. It also allows one to enter red/green/blue values that might be provided to the Color constructor to obtain an instance of Color.

2 comments:

JewelseaFX said...

Nice idea on the reflection Dustin. For a forum answer I implemented a very simple color chooser for the standard colors, but manually determined and typed in the colors from the css reference guide. The typesafe reflection lookup method is better.

@DustinMarx said...

JewelseaFX,

Thanks for leaving the comment. I like your sample on the forum (Thread: ColorPicker or ColorChooser under JavaFX) because it illustrates several things related to JavaFX such as extending VBox, using Observables and Listeners, and dynamically dealing with layout issues. It is also useful to have options outlined in your earlier post on the same thread. As you point out, using reflection could save some lines in your sample and would stay synchronized with any additions to the standard Color choices in the future.

Thanks for sharing the example.

Dustin