JavaFX の CSS ジェネレータを作る

# 概要
Java の標準 GUI ライブラリである JavaFX は CSS を使ってデザインを変更することが可能です。自分で CSS をすべて記述するのは結構な手間がかかりますので、使いたい2色を選択するだけで標準的な CSS を自動的に作成できるアプリケーションを作ってみました。

# スクリーンショット
<img width="459" alt="cssgen1.png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/17cbbcae-19f0-202a-31fc-c783ad0e1678.png">

起動直後です。上側は CSS の変更反映を確認するためのダミー Control 集で、下部に実際の操作機能を提供する Control を配置しています。

<img width="465" alt="スクリーンショット (60).png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/2873c0e3-e96f-ecb9-0614-ba0efa9552cb.png">

カラーコードを表示しているラベルが ColorPicker です。これをクリックすると色一覧が表示され、使いたい色を選択できます。この一覧にない色を使いたい場合は 「Custom Color」をクリックしてください。

<img width="499" alt="スクリーンショット (61).png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/c9f2116b-88bf-5918-4068-733798a299c2.png">

より多彩な色を選択することが可能です。

| <img width="459" alt="cssgen2.png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/6b0e8975-fd95-8482-0aa5-8be9f2d502a9.png"> | <img width="458" alt="cssgen4.png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/35d6f2a7-908a-7eae-da5d-cec915aa79c4.png"> |
|:---|:---|



色を選択するたびに変更が反映されます。

<img width="460" alt="cssgen3.png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/632c1fab-9910-19f2-08e5-3fe367693259.png">

下部の TextField に名前を入力し、 Save ボタンを押すと、その名前で CSS ファイルを保存します。

下記の CSS が生成されました。少し長いですが全文を掲載します。

```css
.root {
    -fx-base: rgba(165.000005, 214.000002, 167.000005, 1.0);
}

.root:forcused {
    -fx-base: rgba(255.000000, 160.000006, 0.000000, 1.0);
}

.color-picker {
    -fx-base: transparent;
}

.text {
    -fx-font-family: 'Roboto';
}

.menu {
    -fx-text-fill: #000000;
}

.menu-item:focused {
    -fx-background-color: #FFA000;
}

.menu-item:focused .label {
    -fx-text-fill: #FFFFFF;
}

.progress-bar {
    -fx-background-color: #FFA000;
}

.progress-indicator {
    -fx-base: rgba(165.000005, 214.000002, 167.000005, 1.0);
    -fx-progress-color: #FFA000;
}

.text-field {
    -fx-background: #FFCDD2;
    -fx-prompt-text-fill: #FFFFFF;
    -fx-text-fill: #FFFFFF;
}

.text-field:focused {
    -fx-text-fill: #739574;
}

.text-area {
    -fx-background: #FFCDD2;
}

.text-area:focused {
    -fx-base: gray;
}

.tree-view:focused {
     -fx-base: rgba(255.000000, 160.000006, 0.000000, 0.1);
}


.tree-cell:even {
    -fx-text-fill: #000000;
    -fx-background-color: rgba(165.000005, 214.000002, 167.000005, 1.0);
}

.tree-cell:hover {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: #739574;
}

.tree-cell:selected {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: #739574;
}

.table-cell:even {
    -fx-text-fill: #000000;
    -fx-background-color: rgba(165.000005, 214.000002, 167.000005, 1.0);
}

.table-cell:hover {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: #739574;
}

.table-cell:selected {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: #739574;
}

.tab-pane {
    -fx-base: rgba(255.000000, 160.000006, 0.000000, 0.6);
    -fx-background-color: #FFFFFF;
}

.list-view {
    -fx-background-color: rgba(255.000000, 160.000006, 0.000000, 0.1);
}

.list-cell {
    -fx-text-fill: #000000;
    -fx-background-color: rgba(255.000000, 160.000006, 0.000000, 1.0);
}

.list-cell:even {
    -fx-text-fill: #000000;
    -fx-background-color: rgba(165.000005, 214.000002, 167.000005, 1.0);
}

.list-cell:hover {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: #739574;
}

.list-cell:selected {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: #739574;
}

.button {
    -fx-text-fill: #000000;
}

.combo-box .list-cell {
    -fx-text-fill: #000000;
}

.tab-label {
    -fx-text-fill: #000000;
}
```

# 実装
全体の実装は [GitHub Repository](https://github.com/toastkidjp/javafx_css_generator/tree/4c85191e5ef066f48813ad37a36106c4cca0203e) をご覧ください。以下はポイントを紹介します。

## カラーコード
JavaFX の Color クラスで取得できる RGB 値は 0-1 の double 値であるため、 よく見られる #FF00BB の形にするには String クラスの format メソッドを使うと便利です。

```java:カラーコード生成
public static String toRgbCode(final Color color) {
    return String.format( "#%02X%02X%02X",
            (int)( color.getRed()   * 255 ),
            (int)( color.getGreen() * 255 ),
            (int)( color.getBlue()  * 255 ) );
}
```

## シャットダウンフック
JavaFX でスタイルシートを適用するには、いったん何らかの形でファイルに出力する必要があるようです。今回は固定名の一時ファイルを生成する方法を採りました。生成した一時ファイルはプログラムの終了時に自動的に消してほしいので、シャットダウンフックを登録します。

シャットダウンフックは JVM プロセスが終了する際に、登録された処理を必ず実行する仕組みです。Runtime.addShutdownHook に処理を定義した Thread オブジェクトを渡すことで登録できます。このメソッドを呼び出すところはどこでもよいようです。なるべく initialize 系のメソッドか main メソッドで登録した方が見逃しが少なくなってよいと思います。

```java:シャットダウンフックの登録
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        try {
            Files.delete(Paths.get(TEMP_FILE_PATH));
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }));
```

今回はこの通り、ファイルを削除するだけの簡単なものを登録しました。

## FXML 側からの Java メソッド呼び出し
ColorPicker を操作するたびに Controller クラスのメソッドを呼び出すには、 ColorPicker の onAction でメソッドを指定してやれば実装できます。下記の例ですと、この mainColor という ColorPicker で何らかのアクションが発生するたびに change メソッドを呼び出します。

```xml
<JFXColorPicker fx:id="mainColor" onAction="#change" prefWidth="160.0" />
```

なお、 FXML 側から呼び出しの指定ができるのは、 Controller に指定したクラスの FXML アノテーションが付与されているメソッドに限られます。可視性は public である必要はありません。

```
@FXML
private void change() {
```

## JFoenix でのマテリアルデザイン適用
以前の私の記事 [JFoenix で Material design を JavaFX に適用する](http://qiita.com/y_q1m/items/15f316a8adf02dc0da0b) で紹介いたしました JFoenix を画面下部の Control に使いました。JFoenix の Control は元の Control クラスを継承する形で作られているため、Java ソースコードを修正することなく、 FXML での指定を変えるだけで導入可能です。

```xml
    <VBox prefHeight="200.0" prefWidth="600.0">
      <children>
        <Label text="JavaFX CSS Generator">
          <font><Font size="48.0" /></font>
        </Label>
        <HBox prefHeight="45.0" prefWidth="600.0">
         <children>
            <JFXColorPicker fx:id="mainColor" onAction="#change" prefWidth="160.0" />
            <JFXColorPicker fx:id="subColor" onAction="#change" prefWidth="160.0" />
            <JFXColorPicker fx:id="inputColor" onAction="#change" prefWidth="160.0" />
         </children>
       </HBox>
       <HBox prefHeight="45.0" prefWidth="600.0">
         <children>
            <Label text="Opacity: " />
            <JFXSlider fx:id="opacity" max="1.0" min="0.0" prefWidth="400.0" value="1.0" />
            <Label fx:id="opacityValue" />
         </children>
       </HBox>
       <HBox prefHeight="45.0" prefWidth="600.0">
         <children>
           <Label text="Save as: " />
           <JFXTextField fx:id="fileName" prefWidth="200.0" onAction="#saveAs" promptText="Please input css file name." />
           <JFXButton prefWidth="100.0" onAction="#saveAs" text="Save" />
           <JFXButton prefWidth="120.0" onAction="#backToDefault" text="Default" />
         </children>
       </HBox>
     </children>
   </VBox>

```

# 適用
生成した CSS を実際の JavaFX アプリケーションに適用するには下記の通りにします。

```java:CSS適用
private void setStyle(final String cssName) {
    final Path path = Paths.get(cssName);
    if (!Files.exists(path)) {
        return;
    }

    // すでに設定されている CSS をクリア
    final ObservableList<String> stylesheets = stage.getScene().getStylesheets();
    if (stylesheets != null) {
        stylesheets.clear();
    }

    // 設定
    Application.setUserAgentStylesheet("MODENA");
    stylesheets.add(path.toUri().toString());
}
```

# まとめ
色を選択するだけで簡単な CSS を自動生成するアプリケーションを開発しました。今回作成したアプリケーションで生成される CSS はごく基本的なものですので、実際に使う場合はもっと手を入れる必要が出てくると思います。最初から自分で書くよりは手間を省くことができるでしょう。この記事をお読みになった方がこれを機に JavaFX でのアプリケーション開発、あるいは CSS の導入を始めてくださいますと幸いです。

# 参考
1. [JavaFX CSSリファレンス・ガイド](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html)……JavaFX の CSS でどんな属性を指定できるのかを調べるには、まずこのドキュメントを読むとよいでしょう。最近日本語化されたようです。
2. [JavaFX - Wrapping FXML to Java Class Controller](http://stackoverflow.com/questions/15439894/javafx-wrapping-fxml-to-java-class-controller)
3. [How to get hex web String from JavaFX ColorPicker color?](http://stackoverflow.com/questions/17925318/how-to-get-hex-web-string-from-javafx-colorpicker-color)
4. [How to populate a TableView that is defined in an fxml file that is designed in JavaFx Scene Builder](http://stackoverflow.com/questions/11180884/how-to-populate-a-tableview-that-is-defined-in-an-fxml-file-that-is-designed-in)