表示領域の制御 - clip

エントリーがパタリと止まってしまいましたが、JavaFXに飽きた訳ではありませんw
このところは地味な改修とかが多くてブログのネタもなかっただけです。というわけで、これからは本当にちょっとしたネタを書いていく予定です。

さて、タイトルのclipと表示領域の制御についてです。JavFXではHTMLのようにGUIコンポーネントは階層構造を持ちます(シーングラフ)。この考え方はIllustrator等のグラフィックツールにおけるグループ化やレイヤー分けの概念に近いものです。したがって、親コンポーネントの描画領域よりも子コンポーネントの描画領域が大きいというケースは十分に考えられます。また、次のようにある描画領域の内部にのみ他のノードを描画したいケースは多々あるでしょう。

 Stage {
     scene: Scene {
         width: 200
         height: 100
         content: [
             Group {
                 layoutX: 10, layoutY: 10
                 content: [
                     Rectangle { width:100, height: 40, fill:Color.LIGHTYELLOW }
                     Text { layoutY: 20, content: "Hello, Hello, Hello, Hello" }
                 ]
             }
         ]
     }
 }

このような場合、Nodeクラスに定義されているclipが有効です。

Node#clip

clipはそのノードに対して切り抜くシェイプを指定します。つまり、指定したシェイプで『型抜き』を行う訳です。
したがって上のサンプルを意図通りに型抜きしたい場合、次のようなコードをGroupに追加します。

             Group {
                 layoutX: 10, layoutY: 10
                 content: [
                     Rectangle { width:100, height: 40, fill:Color.LIGHTYELLOW }
                     Text { layoutY: 20, content: "Hello, Hello, Hello, Hello" }
                 ]
                 clip: Rectangle { width:100, height: 40}
             }

実行するとこうなります。

また、clipで指定するノードは矩形でなくても構いません。円形にクリッピングすることも星形にクリッピングする事も簡単に出来ます。

注意点

コードの重複を気にしていくと、次のように記述したくなるかもしれません。

 var rect: Rectangle = Rectangle { width:100, height: 40, fill:Color.LIGHTYELLOW };
// 略
             Group {
                 layoutX: 10, layoutY: 10
                 content: [
                     rect,
                     Text { layoutY: 20, content: "Hello, Hello, Hello, Hello" }
                 ]
                 clip: rect
             }

しかし、このコードは実行時エラーを発生させます。

Exception in trigger:
java.lang.IllegalArgumentException: illegal assignment of group.content: group=Group node=Rectangle

これは、JavaFXで配置するシーングラフの各ノードは複数回、Stage(Scene)に配置する事は出来ない事に起因します*1。ここで仕様しているclipは1つのノードとしてシーングラフ上に配置されている為、矩形の使い回しはできないのです。
次のようなコードで重複を回避するのが妥当でしょう。

 var rect: Rectangle;
// 略
             Group {
                 layoutX: 10, layoutY: 10
                 content: [
                     rect = Rectangle { width:100, height: 40, fill:Color.LIGHTYELLOW },
                     Text { layoutY: 20, content: "Hello, Hello, Hello, Hello" }
                 ]
                 clip: Rectangle { width: bind rect.width, height: bind rect.height }
             }

初期化のタイミングの関係でclipのRectangleが先に初期化される為、contentのrectの幅・高さを反映する為にbindを使っています。rectの大きさが不変であれば、postinitなどで遅延初期化するなどの方法でも構いません*2

ClipView

最後にClipViewを紹介します。javafx.scene.layout.ClipViewはclipの機能を拡張し、ビューポート機能を実現します。つまり、クリッピングするノードの形と、コンテナ内にあるシーングラフの表示位置を制御します。大きい画像やダイアグラムの中で一部だけを表示したい場合には有効なレイアウトなので、興味が有れば試してみてください。

*1:内部的には各ノードはIDで管理されているため、同じノードが別の箇所に配置できない、管理が難しいなどの理由があるのかと思います

*2:定数可するのも当然有りです