JavaFX アプリケーションを素敵に着飾ってみる

この記事は [JavaFX Advent Calendar 2016](http://qiita.com/advent-calendar/2016/javafx) の2日目です。昨日は @khasunuma さんの「[JavaFX の Color クラスを探る](http://www.coppermine.jp/docs/promised/2016/12/javafx-color.html)」、明日は @skht777 さんです。

# 概要
JavaFX アプリケーションの見た目を変更する方法、主に CSS の書き方について説明します。

## :sun_with_face: JavaFX は CSS で見た目を変えられる
JavaFX は CSS により見た目を変更することが可能です。これにより、デザインと実装の分離が可能となっています。一般に浸透している CSS の記法を採用することで、 JavaFX の採用をより容易にする狙いがあるものと思われます(個人的な推測)。 なお、 CSS の記法は CSS2 準拠だそうです。

## JavaFX とは何か？
- Java で GUI アプリケーションを作るためのライブラリ
- JavaFX が正しい表記(スペースが入らない)
- Java 8 から標準の GUI ライブラリになった
- 今年の Duke's Choice Award を受賞した HeapStats でも JavaFX が使われている(重要)
- JavaFX 8 の前は2だった

## CSS とは何か？
私があえて語るまでもないと思いますが、念のため書きます。間違っていたら編集リクエストください:bow:。

- **C**ascading **S**tyle **S**heets の略
- 見た目の設定を実装から分離して分業をしやすくするための仕組み
- クラス単位、あるいは ID 単位で要素を指定し、適用したいスタイルを ```key: value;``` の形式で記述
- 一般的には HTML の装飾で使用

## 実行環境
今回の記事を書く際に試した環境は下記の通りです。

| Java SE | 1.8.0_102
|:---|:---
| OS | Windows 10
| Gradle | 3.0

----

# どうやって CSS の書き方を覚えるか？
JavaFX 界のすごい人たちが口を揃えて言うには「Modena を見ろ」とのことです。

## Modena とは？
JavaFX 8以降で標準となった CSS テーマです。白とグレーを中心とした、すっきりした見た目をしています。発音は「モディーナ」に近いようです。

### Modena の見た目
![modena.png](https://qiita-image-store.s3.amazonaws.com/0/102004/c0005bbc-a9b1-bfee-e01f-485393132e3e.png)

### Caspian の見た目
ちなみに、JavaFX 2までの標準だった Caspian も引き続き利用することは可能です。

![caspian.png](https://qiita-image-store.s3.amazonaws.com/0/102004/2ef191ff-3ff0-efc4-7cb6-28f6b90eb8c7.png)

このように黒っぽさと立体感を感じる見た目で、メニューバーが黒かったり、シャドウが効いていたりします。

### Modena の居場所
JDK8 ですと、 jfxrt.jar の中に入っているそうです。

```:Modenaの入っているJar
$JAVA_HOME/jre/lib/ext/jfxrt.jar
```

同じものを Gist に上げている方がいるようで、そちらを見てもよいでしょう。[modena.css from jdk1.8.0_91](https://gist.github.com/maxd/63691840fc372f22f470)

なお、空行含めて 3,427行あります。これを上から見て行ってもおそらく理解は難しいと思います。さすがに「Modenaを見ろ」は厳しすぎる、とはいえ、他の方が書かれた CSS を読む……というのも大して変わらないです。

一から書くのは面倒なので、ベースの色を3つ選択したら CSS を作るツールを以前作りました。
http://qiita.com/y_q1m/items/f56a8ba51bad6ed7f868

今回の記事ではこれを使って説明を致します。

----

# :pencil: CSS の書き方
CSS ドキュメントを見ながら、細部を変えていくことで少しずつ覚えるとよいでしょう。このドキュメントは今年日本語化されたようです。ありがとうございます。

- [JavaFX CSSリファレンス・ガイド](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html)
- [JavaFX CSS Reference Guide](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html)

以下、具体的な CSS の適用方法と書き方について軽く説明します。

## 文字のサイズを変える
スタイルの指定には -fx- という Prefix が必要です。

### コードから直接指定する
Node クラスのサブクラスにある ```setStyle``` メソッドを使えば、直接スタイルを指定することができます。例えば、フォントサイズを 40pt にしたい場合は下記の通りです。

```java:コードで直接指定
codeArea.setStyle("-fx-font-size: 40pt;");
```

## CSS ファイルにまとめて書いたのを反映する
ファイルに記述したスタイルをまとめて読み込ませるには、Scene オブジェクトが持っている StyleSheets にExternal form URL を渡すことで可能です。その際、CASPIAN か MODENA のどちらかを先に適用しておく必要があるようです([CON2476: Building JavaFX UI Controls](https://static.rainfocus.com/oracle/oow16/sess/1462484351438001p6a1/ppt/Custom%20Controls.pdf)参照)。

例えば、```user_defined.css```という CSS ファイルを読み込ませるには下記のようにします。

```java:CSS読み込み
final ObservableList<String> stylesheets = stage.getScene().getStylesheets();
if (stylesheets != null) {
    stylesheets.clear();
}

Application.setUserAgentStylesheet("MODENA");// CASPIAN か MODENA のどちらかを先に適用
stylesheets.addAll(Paths.get("user_defined.css").toUri().toString());
```

CSS ファイルに下記の通り記述し、読み込ませると

```css:
.text-area {
  -fx-font-size: 40pt;
}
```

下記のように TextArea ("Please input text"と表示されている箇所)の文字サイズが 40pt に拡大されます。

![ss4.png](https://qiita-image-store.s3.amazonaws.com/0/102004/f477ee05-1b54-4a20-c1dc-4d5d7624c9e0.png)

## 何か適当な画像を背景に設定する

### CSSファイルから指定
例として、Dドライブ直下の ```icon.jpg``` という画像を背景に指定したい場合は下記の通りです。

```css:画像をListCellの背景に指定
.list-cell {
  -fx-background-image: url('file:///D:/icon.png');
  -fx-background-position: center center;
  -fx-background-repeat: stretch;
}
```

#### icon.jpg
扇の画像です。

<img width="32" alt="Icon.png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/1a81da3a-a11e-19b8-4d8e-2ae83f2a3340.png">

#### スクリーンショット
![ss11.png](https://qiita-image-store.s3.amazonaws.com/0/102004/c078352b-93c4-7862-dec2-5cc62fbbe732.png)

ちょっと鬱陶しいですね……使い方は考えないといけません。

### Java コード上で指定
コード上から直接指定する場合は下記の通りです。

```java:コード上から背景画像を指定
final String image = Paths.get("path/to/images/splash_bg.jpg").toUri().toString();
controller.background.setStyle(
    "-fx-background-image: url('" + image + "'); " +
    "-fx-background-position: center center; " +
    "-fx-background-repeat: stretch;"
);
```

ちなみに、画像をjarに同梱する場合は、画像を src/main/resources に配置し、下記のように指定することも可能です。

```java
final String image = getClass().getResource("/splash_bg.jpg").toExternalForm();
```

### 参考
- [Setting background image by javafx code (not css)](http://stackoverflow.com/questions/9851200/setting-background-image-by-javafx-code-not-css)


## コメント
通常の CSS と同様、 ``/*  */```` で囲うことによりコメントを記述できます。

```css:コメント
.root {
  /*
  -fx-background: #EEEEFF;
   */
  -fx-background-image: url('file:///C:/Users/toastkidjp/Pictures/javaone1.jpg');
  -fx-background-position: center center;
  -fx-background-repeat: stretch;
}
```

## 色の指定

### 名前
色の名前で指定することができます。

```css:背景色を赤に指定
-fx-background-color: red;
```

CSS でサポートされている色は大体使えるようで、<font color="Crimson">Crimson</font>/<font color="LightGoldenrodYellow">LightGoldenrodYellow</font>/<font color="NavajoWhite">NavajoWhite</font>/<font color="MediumSpringGreen">MediumSpringGreen</font> といった色を使えることを確認しています。実際に使える色は「[CSSで使用可能な名前付きの色(JavaFX CSSリファレンスガイド)](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html#typecolor)」をご覧ください。

### HTMLカラーコード
16進数でRGBを指定する方法です。Android のような透明度の指定はできません。

```css:背景色を#FFFFFF(白)に指定
-fx-background-color: #FFFFFF;
```

### RGBA
透明度を含めた色の指定が可能です。

```css:背景色を白(透過50%)に指定
-fx-background-color: rgba(255.000000, 255.000000, 255.000000, 0.5);
```

## 擬似クラス
特定のイベント時に表示を変えることも可能です。JavaFX では disabled/focused/hover/pressed/show-mnemonic がすべての Control でサポートされています。また、ListView では even/odd を使うことができます。

ListView を例にとると、下記のように指定した場合

```css:ListViewの要素の色変更
.list-cell:even {
    -fx-text-fill: #FFFFFF;
    -fx-background-color: Navy
}

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

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

このようにできます。

![ss6.png](https://qiita-image-store.s3.amazonaws.com/0/102004/186bfa17-af4c-dfaa-edd3-2477e56790b1.png)

## class と id
HTML のように class や id での指定が可能です。class は Node クラスごとに固有のものが設定されていますのでそれを利用することができますし、
FXML やコード上から指定することも可能です。id も FXML やコードから指定します。

以下、FXML を使っての指定例を示します。

```xml
<TableView fx:id="table" prefHeight="130.0" prefWidth="259.0">
  <columns>
    <TableColumn styleClass="tbc" fx:id="personId" prefWidth="75.0" text="ID" />
    <TableColumn styleClass="tbc" fx:id="personName" prefWidth="75.0" text="Name" />
    <TableColumn styleClass="tbc" fx:id="isActive" prefWidth="75.0" text="Active" />
  </columns>
</TableView>
```

3つの TableColumn に同一の ```tbc``` というクラスを、 各列に左から ```personId```, ```personName```, ```isActive``` という id を指定しました。

### class での指定例

```css:classでの指定
.tbc {
    -fx-text-fill: crimson;
}

.tbc:even {
    -fx-text-fill: crimson;
}
```

#### スクリーンショット

![ss8.png](https://qiita-image-store.s3.amazonaws.com/0/102004/31f6db37-d6e7-5b77-f5a1-ba470e3e6f64.png)


### id での指定例

```css:idでの指定
#personId {
    -fx-background-color: crimson;
    -fx-text-fill: white;
}
```

#### スクリーンショット

![ss9.png](https://qiita-image-store.s3.amazonaws.com/0/102004/0d8117a3-fd8c-f68d-243f-28e767035f06.png)


## root
この class を使うと、すべての Node に対し Style を適用させられます。
手始めにこれだけを変えてみて、見た目の変化を楽しむところから始めてみてもよいでしょう。

例えば、下記のように指定すると

```css:root
.root {
    -fx-base: rgba(0.000000, 11.000000, 22.000000, 0.40);
}
```

このような表示となります。

![ss5.png](https://qiita-image-store.s3.amazonaws.com/0/102004/1237ff9a-985f-4c31-4d16-7128c58386fb.png)

# Tips

## 指定が重複した場合はどうなるか？
例えば下記の場合、下の指定が適用されて緑っぽくなります。

```css
.root {
    -fx-base: rgba(103.000001, 58.000000, 183.000004, 1.0);
}

.root {
    -fx-base: rgba(0, 58.000000, 0, 1.0);
}
```

### スクリーンショット
![ss13.png](https://qiita-image-store.s3.amazonaws.com/0/102004/4897d4da-87c5-5f9d-1755-2d9e3c4789c9.png)

## 表示したくない要素を display: none; で隠したい
どうやら CSS からではそういった操作ができないそうです。コード上から [Node](http://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/Node.html) クラスの ```setManaged(false)``` を使えば実現できます。この話については以前記事を書いたので、よろしければそちらをご覧ください。

→ [JavaFX アプリケーションに隠し Control を搭載する](http://qiita.com/y_q1m/items/9843da9e7b74fbebd60b)

### 表示

```java
@FXML
private void showNode() {
    node.setVisible(true);
    node.setManaged(true);
}
```

### 非表示

```java
@FXML
private void hideNode() {
    node.setVisible(false);
    node.setManaged(false);
}
```


# CSS の足りないところ(by Mr. @hansolo_ said)
私は本職の Web デザイナーではないので、JavaFX の CSS で困った点はそれほどないのですが、エキスパートからするといろいろ足りていないようです。Gerrit Grunwald (@hansolo_) 氏のセッション [CON1072: The Dark and Light Sides of JavaFX](https://static.rainfocus.com/oracle/oow16/sess/1460436066000001AmRv/ppt/DarkAndLightSideOfJavaFX.pdf) で、CSS Styling の暗黒面を語っていました。

- -fx- という prefix のせいで Web 標準の CSS と互換性がない
- 通常の CSS で使えるものが使えない
- パフォーマンス面の問題がある
- アニメーションを使えない
- visibility 操作ができない

ちなみに、関係ない話ですが Grunwald 氏は今年の JavaOne 報告会@東京で「[ハン・ソロ](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%B3%E3%83%BB%E3%82%BD%E3%83%AD)さんがいる」と軽く話題になっていましたので、ご記憶の方もいらっしゃるかもしれません。

# Others

## [Scenic View](http://fxexperience.com/scenic-view/)
JavaFX の Styling をするのには、すでに良いツールがありました。Scenic View は GUI で CSS を操作できるツールです。今年の [JavaOne 2016でBOFセッションにて紹介](https://static.rainfocus.com/oracle/oow16/sess/14624845615810011AzP/ppt/Scenic%20View.pdf)されていました。詳細はそちらをご覧ください。

## [Scene Builder](http://gluonhq.com/labs/scene-builder/#download)
GUI で部品を置きながら JavaFX アプリケーションの UI を作ることができるツールです。かつては公式で配布されていました。現在は Gluon 社のサイトで入手できます。このツールでも CSS の調整やデモが可能だと櫻庭さんから教えていただきました。

## [JFoenix](http://www.jfoenix.com/)
- JavaFX で Material design を実装するためのライブラリ
- 標準にはない Drawer/Toolbar/Snackbar が使える
- Apache 2.0 ライセンスで提供
- 既存のシーングラフ＆実装からのマイグレーションが簡単

## [RichTextFX](https://github.com/TomasMikula/RichTextFX)
- 高機能な複数行テキスト編集コンポーネントを提供
- 行番号表示やコードハイライトが可能

# まとめ
- Java で GUI アプリケーションを開発するなら JavaFX を使う
- JavaFX は CSS で色を変えられる
- CSS の書き方は使って覚える

デフォルトの Modena も見やすくてよいデザインですが、CSS やライブラリを駆使して、自分の理想とするイカしたデザインのアプリケーションを作ってみるのも面白いでしょう。

# 参考

## リファレンス・ガイド
- [JavaFX CSSリファレンス・ガイド](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html)
- [JavaFX CSS Reference Guide](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html)

## JavaOne 2016 関連
JavaFX のセッションが多数ありました。CSS と関連するセッションの資料へのリンクを記載いたします。

- [CON2476: Building JavaFX UI Controls](https://static.rainfocus.com/oracle/oow16/sess/1462484351438001p6a1/ppt/Custom%20Controls.pdf)
- [CON1072: The Dark and Light Sides of JavaFX](https://static.rainfocus.com/oracle/oow16/sess/1460436066000001AmRv/ppt/DarkAndLightSideOfJavaFX.pdf)
- [Code examples for the JavaOne 2016 hands on lab 1634 "Customize your JavaFX Controls"](https://github.com/HanSolo/hol1634)……[この CSS](https://github.com/HanSolo/hol1634/blob/ac3480ce06a6c54d292216b82c017ee3bb6117bf/src/main/resources/eu/hansolo/fx/hol1634/extending/extended.css)を適用すると、 CSS だけで Material design の TextField を実現できるようです。

## JJUG CCC 2016 Fall 関連
- [【発表資料】JavaFX アプリケーションを素敵に着飾ってみる](https://speakerdeck.com/toastkidjp/jjug-ccc-2016-fall-number-ccc-l5)

## Color
- [JavaFX の Color クラスを探る](http://www.coppermine.jp/docs/promised/2016/12/javafx-color.html)
- [CSSで使用可能な名前付きの色(JavaFX CSSリファレンスガイド)](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html#typecolor)

----
# いただいた質問について

## 自作の Control に独自のスタイルクラスは設定できるか？
自作のControlの場合、Nodeクラスが持っている styleClass に要素を 

```java
getStyleClass().setAll("custom-control");
``` 

で追加すれば、

```css
.custom-control {
  /**/
}
```

で指定できるようになります。

FXML で直に指定できる場合は styleClass を使うことで追加が可能です。

```xml:TableColumnにスタイルクラス"tbc"を設定
<TableColumn styleClass="tbc" ...
```

## Slider の目盛はどうやってデザインを変更するか？
.slider .axis の ```-fx-tick-label-font``` を使うと変更が可能です。デフォルトでは 8pt/システムフォントが設定されています。

```css:Sliderの目盛文字を20ptに変更
.slider .axis {
  -fx-tick-label-font: 20 system;
}
```

![編集例](https://pbs.twimg.com/media/CywpDR7UcAA49qh.jpg)

----

# おまけ

## 今回の記事を書く際に使用した JavaFX CSS お試し用アプリケーション
ビルド・実行には Java SE 1.8.0_40 以上の実行環境と Gradle が必要です。[GitHub リポジトリ](https://github.com/toastkidjp/javafx_css_generator) からコードをダウンロードし、```$ gradle run``` で実行できます。

```:実行例(gitの場合)
$ git clone git@github.com:toastkidjp/javafx_css_generator.git
$ cd javafx_css_generator
$ gradle run
```

![launch.png](https://qiita-image-store.s3.amazonaws.com/0/102004/6c041f01-b84e-0f7d-4784-9a8ae6b2b953.png)

起動するとこのように表示されます。

![launch2.png](https://qiita-image-store.s3.amazonaws.com/0/102004/e69efebd-afc9-ea94-e0e1-a11f5f1b450b.png)

Ctrl+E で CSS 編集用の TextArea が開きます。ここに CSS を記述し、試すことができます。

![launch3.png](https://qiita-image-store.s3.amazonaws.com/0/102004/f4a06d41-61dc-f50a-e0cb-7322f519cd76.png)

Ctrl+Enter で TextArea の内容を反映します。

## 利用 OSS
JavaFX は 3rd Party Library の開発が盛んで、標準では用意されていないリッチな Control を提供するライブラリが多数存在しています。このツールでは 前述の JFoenix(Material design コンポーネント集) と、RichTextFX(行番号表示やコードハイライトが可能な TextArea) を利用致しました。

| Library | Author | GitHub Repository | License
|:---|:---|:---|:---
| [JFoenix](http://www.jfoenix.com/) | jfoenix | https://github.com/jfoenixadmin/JFoenix | [Apache 2.0](https://github.com/jfoenixadmin/JFoenix/blob/master/LICENSE)
| RichTextFX | Tomas Mikula | https://github.com/TomasMikula/RichTextFX | [BSD 2 Clause](https://github.com/TomasMikula/RichTextFX/blob/master/LICENSE)