Developing multi-touch application in JavaFX

This blog entry is the first entry in Japan JavaFX Advent Calendar 2012.

I made a presentation about multi-touch support in JavaFX at 8th Japan JavaFX workshop. I write additional information for the presentation in this entry.

Presentation material is below.

Developing multi-touch application in JavaFX from Takashi Aoe

And source code of sample application is below.
https://gist.github.com/4143183

Additional information for multi-touch API in JavaFX

JavaFX2.2 introduces API related multi-touch. I was very surprised when Developer Preview released because the description of the API has been added to JavaDoc casually without notification in release notes.

Multi-touch support is described as follows in the entry entitled "What's new in JavaFX 2.2" in The JavaFX Blog.

Multi-touch support for touch-enabled devices. As of today this is mostly relevant for desktop-class touch screen displays and touch pads, this will enable the adoption of sophisticated UIs on embedded devices running Java SE Embedded on ARM-based chipsets, such as kiosks, telemetry systems, healthcare devices, multi-function printers, monitoring systems, etc. This is a segment of the Java application market that is usually overseen by most application developers, but that is thriving.

I think main target of multi-touch support is embedded devices. It's my opinion, Oracle is probably going to be taking the initiative in the development of embedded devices user interface before Android move into the same domain.
Of course, I think it is intended for the development of iOS/Android/WindowsPhone/WindowsRT application.

As I explained in the workshop, TouchEvent handling the basic touch operation and GestureEvent handling more abstract events are added to javafx.scene.input package.
And properties to register a handler for these events are added to Scene and Node.

The following is a list of items I didn't speak for a time limit in workshop.

  • About TouchEvent
    • If you touch multiple points, eventSetId is allocated to individual Event.
    • Ctrl, Alt, Meta and Shift key combination can be detected.
    • It implements copyFor() method that copy event to other nodes.
    • In order to get the number of touch points, use getTouchCount() method.
    • TouchPoint class stores the information of touch point. It implements following methods.
      • getX()/getY() method to get the position of the touch point relative to the origin of the event source.
      • getSceneX()/getSceneY() method to get the position of the touch point relative to the origin of the Scene.
      • getScreenX()/getScreenY() method to get the absolute position of the touch point on screen.
      • There is a grabbing API to modify event target and you can switch by using grab() method ... I guess. (Sorry, I have not investigated enough.)
  • About RotateEvent
    • If the trackpad supports multi-touch, you can fire this event on the trackpad. When the event delivered on the trackpad, the mouse cursor location is used as the gesture coordinates.
  • About ScrollEvent
    • You can fire this event by mouse wheel. But only SCROLL event is delivered.
    • Use the deltaX/deltaY property to obtain the amount of scroll usually, but use the textDeltaX/textDeltaY property when the text-based component. The textDeltaXUnits()/textDeltaYUnits() method determine how to interpret the textDeltaX and textDeltaY values.
    • In order to get the number of touch points, use getTouchCount() method. You can implement multi-finger scrolling by using this.

Additonal information for sample application

I showed three samples in workshop. There are videos that had been recorded in preparation to the failure of the demonstration.

  • TouchEvent sample. Draw a circle on touch point. The circle can be moved with a touch and can be erased with a double tap.

  • Demonstration of Scroll/Rotate/ZoomEvent. I demonstrated you can move, transform the Rectangle object easily by using the values obtained from these events.

  • Demonstration of scrolling support for JavaFX control. I used ListView control and Pagination control introduced in JavaFX2.2. These controls supports scrolling operation without any coding.

I wrote compressed souece code in the presentation material because of space limitation. So I hope you check the source code uploaded to gist.
I describe below additional explanation.

  • Node class has properties scaleX/Y, rotate, translateX/Y for translation or transformation. Movement amount obtained from GestureEvent can be applied directly to these properties.
  • I set a list of font families supported by the system obtained from javafx.scene.text.Font.getFamilies() method to Pagination and ListView control.
  • The contents of the page displayed in Pagination control are Labels in VBox control as follows. Pagination control is my favorite because its API is performance-friendly.
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        fonts = Font.getFamilies();
        pagination.setPageCount(fonts.size() / FONTS_PER_PAGE);
        pagination.setPageFactory(new Callback<Integer, Node>() {
            @Override
            public Node call(Integer idx) {
                return createPage(idx);
            }
        });
        lvFonts.setItems(FXCollections.observableArrayList(fonts));
    }

    private VBox createPage(int idx) {
        VBox box = new VBox(5.0);
        int page = idx * FONTS_PER_PAGE;
        for (int i = page; i < page + FONTS_PER_PAGE; i++) {
            Label lblFont = new Label(fonts.get(i));
            box.getChildren().add(lblFont);
        }
        return box;
    }

As you see, API that enables to develop multi-touch application easily have been added in JavaFX2.2. Unfortunately, fully available environment is Windows8 devices only (furthermore, only in desktop mode), Let's use everyone!

JavaFX Advent Calendar 2012 1日目 JavaFXでのマルチタッチアプリケーション開発

JavaFX Advent Calendarの1日目を担当することになってしまった@ / id:aoe-tkです。

実は前日の第8回JavaFX勉強会にて、JavaFXでのマルチタッチプログラミングについての発表を行いました。このエントリではその内容のフォローアップをしたいと思います。
当日の発表資料は以下です。

JavaFXでマルチタッチプログラミング from Takashi Aoe

また、デモに使ったサンプルアプリケーションのコードもgistにアップしています。
https://gist.github.com/4143183

JavaFX2.2で追加されたマルチタッチ関連のAPIについての補足

発表でもお話ししたように、JavaFX2.2になってタッチ関連のAPIが追加されました。Developer Previewの時に、特にリリースノートでの通知がなく、さりげなくJavaDocに追記されていたので驚いたのを覚えています。
JavaFX2.2新機能紹介ページ (日本OracleのエンベデッドJavaチームによる日本語訳です) では次のように紹介されています。

タッチ操作可能な機器のためのマルチタッチサポート。これは現段階では主にデスクトップクラスのタッチスクリーンやタッチパッド向けですが、将来的にはARMベースのチップ上にJava SE Embeddedが搭載されているさまざまな組み込み機器に洗練されたUIを導入することを可能にします。例えばキオスク端末、テレメトリシステム、ヘルスケア機器、多機能プリンタ、監視システムなどです。これらは、多くのアプリケーション開発者にはまだあまり注目されていないJavaアプリケーションの市場セグメントですが、近年活況を呈しつつあります。

当日の発表でも少し触れましたが、主に組み込み機器をメインターゲットにしているように見受けられます。こういったセグメントにもこれからAndroidが進出してくるでしょうから、その前にしっかり押さえておこうとOracleは考えているのでしょう。
もちろんiOSWindows8/RT/Phone、Android向けの開発も視野には入れているとは思いますが。

さて、追加されたAPIの内容についてですが、発表でも説明したように、javafx.scene.input パッケージに基本的なタッチ操作を扱う TouchEvent と、タッチ操作を組み合わせたより抽象度の高い GestureEvent というイベントが追加されています。
そして、Scene 及び Node にこれらイベントに対するハンドラを登録するためのプロパティが追加されています。

以下に時間の関係で当日の発表で話せなかったことを列挙します。

  • TouchEvent について
    • 複数点タッチされた場合、個々のEventには eventSetId が割り振られます。
    • control、alt、meta、shiftキーとのコンビネーションも検出可能です。(isAltDown() とか isShiftDown() といったメソッドがある)
    • 他のノードにイベントをコピーする copyFor() というメソッドがあります。
    • 何点同時にタッチされているかは getTouchCount() メソッドで取得できます。
    • タッチポイントの情報は TouchPoint クラスに格納されますが、TouchPoint には次のようなAPIが用意されています。
      • getX()/getY() はタッチの対象となる Node オブジェクト上の座標を、getSceneX()/getSceneY() は Scene 上の座標を、getScreenX()/getScreenY() は画面上の絶対座標を取得でします。
      • タッチポイントのイベントターゲットとなる Node オブジェクトを切り替えることのできる grabbing API というものがあり、grab() メソッドで切り替えることができる...らしいです。(ごめんなさい、まだ調べ切れていません)
  • RotateEvent について
    • マルチタッチをサポートしているトラックパッドならば、トラックパッドでもこのイベントを起こすことができます。トラックパッドで起こした場合はマウスカーソルの位置が基準になります。
  • ScrollEvent について
    • じつはマウスホイールによるスクロールでもこのイベントは発生します。ただし、マウスホイールの場合は SCROLL タイプのイベントのみ発生します。
    • 移動量については基本的に deltaX/Y で見ますが、テキスト系コンポーネントの場合は textDeltaX/Y でも取得可能なようです。単位は行単位とページ単位があるようで、getTextDeltaUnits() でどちらの単位なのかを取得できます。
    • getTouchCount() でタッチ数を取得できます。これを使うことで2本指スクロールや3本指スクロールが実現できます。

デモアプリケーションについての補足

デモでは次の3つのサンプルをお見せしました。デモが失敗したときに備えて録画しておいた動画がありますので、それも一緒にアップしておきます。

  • TouchEvent を使い、タッチされたポイントに対してサークルを描画するデモ。描画したサークルはタッチで移動でき、ダブルタップで消去可能。

  • GestureEvent のうち、Scroll/Rotate/ZoomEvent を利用したデモ。描画した Rectangle に対して、これらイベントから取得した値を使って、簡単に移動、変形が可能であることを示しました。

  • JavaFXのコントロールのスクロール対応のデモ。ListView コントロールとJavaFX2.2になって追加された Pagination コントロールを使いました。これらは何もしなくてもスクロール操作に対応しています。

スライドではスペースの都合上、コードをかなり圧縮して載せていました。上のgistにアップしたコードの方を良く確認してもらえたらと思います。
以下にちょっと補足を。

  • Node には移動や変形を行うための scaleX/YrotatetranslateX/Y といったプロパティがあります。GestureEvent で取得できる移動量はこれらプロパティにそのまま適用できるようになっています。
  • Pagination コントロールや ListView コントロールには、javafx.scene.text.Font.getFamilies() メソッドを使って取得した、システムがサポートしているフォント名の一覧を渡しています。
  • Pagination コントロールに表示するページの内容は次のように VBox を作って、その中にラベルを並べています。Pagination コントロールはパフォーマンスに考慮した作り方ができるようなAPIになっていて、個人的に気に入っています。
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        fonts = Font.getFamilies();
        pagination.setPageCount(fonts.size() / FONTS_PER_PAGE);
        pagination.setPageFactory(new Callback<Integer, Node>() {
            @Override
            public Node call(Integer idx) {
                return createPage(idx);
            }
        });
        lvFonts.setItems(FXCollections.observableArrayList(fonts));
    }

    private VBox createPage(int idx) {
        VBox box = new VBox(5.0);
        int page = idx * FONTS_PER_PAGE;
        for (int i = page; i < page + FONTS_PER_PAGE; i++) {
            Label lblFont = new Label(fonts.get(i));
            box.getChildren().add(lblFont);
        }
        return box;
    }

このようにJavaFX2.2ではマルチタッチを容易に開発可能なAPIが追加されています。今のところまともに使える環境はWindows8くらい (しかもデスクトップモード) なのがちょっと悲しいですが、みなさんもどんどん使ってみてください。

第7回JavaFX勉強会でLTしてきました

ちょっと遅くなりましたが先日行われた第7回JavaFX勉強会に参加し、LTもしてきましたので、こちらで軽く報告します。

自分の発表資料は以下の通りです。
http://www.slideshare.net/takashiaoe/macjavafx-at-7javafx-13518044

発表内容はMacでのJavaFXの開発や配備についてのお話しです。ちょっとMacユーザーならではの自虐ネタも交えてw
内容は結構絞ったつもりですが、それでもLTの5分間となると駆け足でしゃべるしかないですね。もう少し時間があればJARバンドラーのデモをやりたいと思っていました。
JARバンドラーについては後日このブログでサポートするエントリでも書こうかな。
発表でも最後に話しましたが、MacでもJavaFXの開発環境はほぼ整いました。と言うことでMacユーザーの皆様もJavaFXでの開発を楽しんでくださいね。
JDK7がLionしかサポートしていないのはつらいですが、Macの世界はいつもこんな感じですからねえ...。iCloudとかもSnow Leopard以前はガン無視ですし。

では他の方々の発表についてもそれぞれ軽くコメントを。

  • 片貝さんのNetBeansについての発表 (発表資料)
    • NetBeans7.2ではSceneBuilderとの連携強化や、CSSの補完が効くようになったり (個人的にはこれは嬉しい!) 、とJavaFXの開発環境としての強化が順調に図られているようです。あとはFXMLの補完が効くようになってくれれば...。
    • 地味にSwingベースのJavaFXアプリケーションプロジェクトが作成できるようになっているのは嬉しいですね。今後JavaFXが最も良く使われるのがSwingのリプレースだと思いますが、印刷などまだまだAWT/Swingをカバーできていないところが多いので、これの重要性は高いと思っています。
    • みんなここぞとばかりにNetBeansへのリクエストを出してましたw 自分も懇親会でいくつかリクエストしたような。
  • 櫻庭さんのSceneBuilderについての発表 (発表資料)
    • SceneBuilderについて、デモを中心にした説明でした。相変わらずJavaFXで作った資料のクオリティが高い! 驚いていた参加者も多かったように見えます。
    • NetBeansとSceneBuilderの分担が少し中途半端とのことですが、これはMaciOSの開発でのXCodeとInterfaceBuilderの分担を真似たからこうなっているのかなと個人的には思っています。
    • 質問が沢山飛んでいました。そのやりとりの中でカスタムコンポーネントのパレットへの登録ができない、と言うの話が出てきましたが、これは確かに早く実装して欲しいですね。
      • 質問の中に、コンポーネントの整列はないか? と言うのがありましたが、コンポーネントを選択してGridPaneを適用することはできるので、これで代替できそうだと後で気付きました。
      • それからSceneBuilderは結構すいすい動きますよー。機能を純粋にFXMLの編集だけに絞っているというシンプルな作りだからというのもあるでしょうが。
  • mike_neckさんのFx-JS-JUnitについてのLT (発表資料)
    • ニャル子さんネタ全開の怒濤のスライド! これはやられましたw トークも軽妙で場の雰囲気持って行っちゃいましたね。
    • でも中身は普通に数十分掛けてやるようなとても濃い内容です。JavaFXのWebEngineを使ってJavaScriptのテストに活用するという発想は面白いと思います。期待しています。
  • taiz77さんの画面遷移ライブラリのLT
    • 大学院でJavaFXを研究の題材にされているそうです!
    • JavaFXのアプリケーションフレームワークを考えておられるようで、現時点ではJavaFXに対するフレームワークというのは発表でも紹介されていたJFX Flowくらいしか見当たらないので。こういう取り組みは貴重だと思っています。
    • ただ、タイムアップでそのフレームワーク自体の紹介までたどり着かなかったのが残念でした。
  • kimukou2628さんのGriffonについてのLT (発表資料)
    • デスクトップGUI向けGrailsのような位置付けを目指しているフレームワークであるGriffonについての紹介でした。
    • 先にJavaFXではまだフレームワークらしきものはない、と書きましたが、このGriffonも有望だなーと思いました。FXMLとの連携も考えているのがいいですね。
  • 寺田さんのJavaFXエンタープライズアプリケーションについてのLT (発表資料)
    • 何とApplication Client Containerを使ってJavaFXとサーバーのEJBを連携させる方法についての発表でした。ある意味今後JavaFXが最も使われそうな用途のようにも思えます。
    • 実は自分は過去のSI案件でSwingアプリケーションとサーバーのEJBを直接RMIで接続する形態の開発に関わったことがあります。ですがそれは古のJ2EE1.3の時代の話。JavaEE6でのEJBへのリモート接続についての情報はとても貴重だと思いました。
      • リモートEJBのインジェクション対象のフィールドはstaticである必要があったんですねえ...。

このような感じで他の方々の発表も内容が多岐にわたり、充実していました。発表資料へのリンクを張っておいたので、是非見て頂ければと。

回を重ねるごとに参加人数が増えてきているのに驚きです。Swingをやっていた方はもちろん、Flexのような他のRIAの世界から来た感じの方とかもいましたねえ。
懇親会も参加者が多くて、色んな方と交流ができて楽しかったです。どんどん盛り上がってきて嬉しい限りです。

JDK7u6に入ってくるJavaFXのネイティブパッケージ機能を早速試してみました

先日、OracleJava開発に関するblogに "Native packaging for JavaFX" なるエントリが載っていたことを知りました (こちらに江草ロジ子さんによる日本語訳もあります) 。


JavaFX 2.2 adds new packaging option for JavaFX applications, allowing you to package your application as a "native bundle". This gives your users a way to install and run your application without any external dependencies on a system JRE or FX SDK.
なんとJavaFX2.2にはOSネイティブなパッケージを生成する機能が付いてくるということです!
具体的にはJavaFXアプリケーションをWindowsなら.exeや.msiMacなら.appや.dmg、そしてLinuxならrpmの形でアプリケーションパッケージやインストーラーを出力できるようになるということです。
パッケージにはJREJavaFXのランタイムも含めてしまい、配布先にJavaFXのランタイムはおろか、JREが入っていなくてもインストール、実行可能にすることができるということです。
これは今までのJavaの歴史を考えると結構衝撃的な話だと思っています。

と言うわけで早速試してみました。

この機能はJDK7u6の最新版のDeveloper Previewに入っているので、まずは以下の場所からダウンロードします。
http://jdk7.java.net/download.html

自分はMac環境で試しました。ダウンロードしたインストーラーを使ってインストールするだけです。
既にJDK7のGA版をインストールしている場合は上書きしてしまうので注意してください。

$ java -version
java version "1.7.0_06-ea"
Java(TM) SE Runtime Environment (build 1.7.0_06-ea-b14)
Java HotSpot(TM) 64-Bit Server VM (build 23.2-b05, mixed mode)

$ /usr/libexec/java_home 
/Library/Java/JavaVirtualMachines/jdk1.7.0_06.jdk/Contents/Home

自分の環境だと、NetBeansJavaプラットフォーム設定の変更も必要でした。以下のスクリーンショットのように、デフォルトJavaプラットフォームの設定で、JDK7u6のパスを指定する必要があります。

では早速試してみます。NetBeansのビルドでネイティブパッケージを生成したい場合、NetBeansJavaFXアプリケーションプロジェクトの直下にあるbuild.xmlに対して、次のように -post-jfx-deploy タスクをオーバーライドします。fx 名前空間の記述の追加が必要なことに注意してください ( タグの記述を参照) 。

<project name="MouseInfoSample" default="default" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">

(中略)

    <target name="-post-jfx-deploy">
        <fx:deploy width="${javafx.run.width}" height="${javafx.run.height}" nativeBundles="all"
            outdir="${basedir}/${dist.dir}" outfile="${application.title}">
             <fx:info title="${application.title}" vendor="${application.vendor}"/>
             <fx:application name="${application.title}" mainClass="${javafx.main.class}">
             </fx:application>
             <fx:resources>
                 <fx:fileset dir="${basedir}/${dist.dir}" includes="${application.title}.jar"/>
             </fx:resources>
        </fx:deploy>
    </target>
</project>

ポイントは タグの属性に入っている nativeBundles="all" という記述です。これで実行しているOSに応じたネイティブパッケージを生成するようになります。

これでNetBeansの構築を実行したところ、appの生成には成功しましたが、dmgの生成には失敗しちゃいました。

Launching <fx:deploy> task from /Library/Java/JavaVirtualMachines/jdk1.7.0_06.jdk/Contents/Home/lib/ant-javafx.jar
Using base JDK at: /Library/Java/JavaVirtualMachines/jdk1.7.0_06.jdk
Creating app bundle: /Users/aoetakashi/NetBeansProjects/MouseInfoSample/dist/bundles/MouseInfoSample.app
Building DMG package for MouseInfoSample
java.io.IOException: Exec of failed with code 1 command [[osascript, /var/folders/m5/vk8q5qx16yd2scc1jt0mlv100000gn/T/build3045467055592762416.fxbundler/macosx/MouseInfoSample-dmg-setup.scpt] in unspecified directory
	at com.sun.javafx.tools.packager.bundlers.IOUtils.exec(IOUtils.java:130)
	at com.sun.javafx.tools.packager.bundlers.IOUtils.exec(IOUtils.java:108)
	at com.sun.javafx.tools.packager.bundlers.MacDMGBundler.buildDMG(MacDMGBundler.java:262)
	at com.sun.javafx.tools.packager.bundlers.MacDMGBundler.bundle(MacDMGBundler.java:71)
	at com.sun.javafx.tools.packager.PackagerLib.generateNativeBundles(PackagerLib.java:453)
	at com.sun.javafx.tools.packager.PackagerLib.generateDeploymentPackages(PackagerLib.java:434)
...

でも、この例外メッセージが出る前に、何故かそのdmgが以下のスクリーンショットのように「マウント」はされていたんですよね。うーむ不思議だw

いくつかのJavaFXプロジェクトで試しましたが、同じ結果でした。まあ、今回はここについてはまだ深くは調べていません。とりあえずappが出来たので自分的にはOKですw
で、できあがったappはちゃんとダブルクリックで普通に起動します。デフォルトでも結構かっこいいアイコンです。
でも、ファイルサイズを見てみると...

166.9MBだとぉ!?
ちなみにアプリ本体のJARのサイズは20KBです。いったい何が入っているんだと思って中身を見てみると...。

はい、JDK丸ごと入っていました。いくら何でもワイルドすぎやろこれwww

とまあ、こんな感じでした。色々と問題はありますが、まだ実験段階ですし、これから少しずつ洗練されていくでしょう。
アプリケーションの配布形態としてネイティブパッケージという選択肢が出てきたことは素直に嬉しいことだと思っています。
ランタイムごと配布するので、配布先の環境のことを考慮しなくてもいいというのは色んな場面で効いてくるでしょう。例えばなかなかソフトウェア環境を更新してくれない企業向けとか...。*1
もっとも、これはデスクトップアプリ向けというよりはiOSWindows Metroの方をにらんで用意した機能だと見ています。

と言うわけで皆さんも是非試してみてください。
今度はWindowsでも試してみよう。

*1:以前、電子政府で要求JREのバージョンがばらばら、バージョンアップへの追従も遅いという問題が報じられていたことがあったように記憶していますが、そういうのにも向いていそうですね。

JavaFXで画像を使ったボタンを作る方法

ちょっとしたメモです。

RIAを開発するとき、デザイナさんと連携して作ることが多いと思います。
ボタンのようなコントロールについても画像として素材を作ってもらい、それを組み込んで使うというシチュエーションも結構あることでしょう。
というわけでJavaFXで画像を使ってボタンを作る方法です。

JavaFXCSSを使って装飾することができます。HTMLのCSS2.1やCSS3をベースにしており、多くの場合同じプロパティを使うことができます。ただし、ベンダープレフィックス "-fx-" を付ける必要がありますが。

画像をボタンの背景として使いたい場合、CSS3のborder-image属性を使うことができます。次のようにCSSを作成します。

.img-button {
    -fx-border-image-source: url('img/btn.png');
    -fx-border-image-slice: 4 5 4 5 fill;
    -fx-border-image-width: 4 5 4 5;
    -fx-border-image-repeat: stretch;
    -fx-border-color: null;
    -fx-background-color: null;
    -fx-text-fill: white;
}
.img-button:hover {
    -fx-border-image-source: url('img/btn_hover.png');
}
.img-button:pressed {
    -fx-border-image-source: url('img/btn_pressed.png');
}
.img-button:disabled {
    -fx-border-image-source: url('img/btn_disabled.png');
}

border-image-slice プロパティを使うことで、画像を枠の部分と中央部分に分割し、枠部分の拡大率を保持したまま中央部分のみを拡大縮小する、いわゆる「9スライス」も指定可能です。fill も指定して、真ん中も塗りつぶすようにします。

border-image-width プロパティでボーダーの幅を指定します。border-image-repeat で stretch を指定すると、中央は引き伸ばされるようになります。
ベンダープレフィックスを取り除けば、HTMLのCSSと同じになることが分かります。

文字の色については -fx-text-fill プロパティで指定します。これはHTMLとは異なるので注意が必要です。
後は、border-color、background-color を null にして、従来の描画を消せばOKです。
hover や pressed といった疑似クラスもHTMLの場合と同じように指定可能です。

Java側では次のような感じでCSSを設定します。

        final Button imgBtn = new Button("これは画像を使ったボタンです");
        imgBtn.setPrefHeight(40);
        imgBtn.getStyleClass().add("img-button");

実行するとこんな感じで画像を背景にしたボタンが表示されます。わざと縦方向も伸ばしていますがちゃんと四隅を崩さずに伸ばしているのが分かりますね。

スクリーンショットじゃ分かりませんが、マウスオーバーやマウスクリックでも用意した画像に切り替わります。

とまあこんな感じで割とHTMLでのCSSと同じように書けそうです。HTMLデザインの技術を活かすことができるというのは結構なアドバンテージではないかと思っています。*1

*1:FlexでもCSSは使えますが、プロパティはHTMLで使うCSSとはかなり異なります。

JavaFXでxeyes作ってみました

今の自分の仕事場は環境的に結構恵まれています。広い机にエルゴヒューマンの椅子。
そしてディスクプレイが27インチのデュアルです! (iMac+Thunderbolt Display)
広いディスプレイはやっぱり快適です。あまりに快適なので家でも27インチのUltra Cinema Displayを買っちゃったくらいです。

でも広いディスプレイだと1つ困ることがあります。それはマウスカーソルを見失いやすいということです。
そこで古き良きxeyesを使うことにしました。(確かxeyeが元々作られた理由ってマウスカーソルの位置を把握するためでしたよね?)
ところがMacにはxeyesが確かに付属しているのですが、これはX Window上で動くアプリケーションで、Xのアプリケーション上にマウスカーソルがあるときしか反応してくれません...。

というわけでJavaFXを使って自分で作ってみることにしました。
が、まず一番最初でつまずきます。今のJavaFXAPIではマウスのグローバルな座標を取る手段が見当たりません。
仕方が無いのでAWTの MouseInfo を使って取得しようと思ったのですが、JavaFXのアプリケーション上で使うと、HeadlessExceptionが飛びます。
JavaFXはAWTを全く使わず一から作り直しているので、AWT的にはヘッドレスで動いていることになるのです。(システムプロパティ java.awt.headless に true がセットされています)

(2011/12/26 修正)
櫻庭さんからご指摘があり、SwingのEDT上で実行すれば、Swingを土台とせずともHeadlessExceptionは飛ばないとのことでした。
簡単なサンプルで確認したところ、Windows環境では問題なく動作しました。ただ、Mac環境ではやはりHeadlessExceptionが送出され、どうもMac環境固有の問題だったようです。
ただ、WinとMacで微妙に実行環境が異なるところがあったので (前者はJDK7update1 + JavaFX2.0.2で後者はJDK6update29 + JavaFX2.0.1beta) 、後ほど時間があるときにもう少し詳しく再現条件について調べてみます。
あと、改めてJavaFXとして作ってみます。
櫻庭さん、ご指摘ありがとうございました!

(2012/02/21 追記)
どうもJIRAで報告されているRT-13739が近い感じです。一応修正ターゲットは2.1になってますが、これを書いた時点ではまだアサインがないようです。

結局Swingを土台にしてその上にJavaFXで作ることにしました。全ソースコードはgistに貼り付けてあります。
https://gist.github.com/1519410

ちょっとしたアプリケーションですが、結構学ぶことがありました。
まず、Swingアプリケーション上にJavaFXアプリケーションを構築する方法です。

public class SwingFXEyes extends JFrame {
    private JFXPanel jfxPanel;
    private Eye leftEye;
    private Eye rightEye;
    private Timer timer;

    public SwingFXEyes() throws HeadlessException {
        initComponents();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new SwingFXEyes().setVisible(true);
            }
        });
    }

    private void initComponents() {
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        jfxPanel = new JFXPanel();
        add(jfxPanel);

        Platform.runLater(new Runnable() {
            public void run() {
                initJFXComponents();
            }
        });
        pack();
    }

    private void initJFXComponents() {
        Group root = new Group();
        Scene scene = new Scene(root);

        leftEye = new Eye();
        rightEye = new Eye();
        rightEye.setLayoutX(leftEye.prefWidth(-1) + 5);
        root.getChildren().addAll(leftEye, rightEye);

        jfxPanel.setScene(scene);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                startTimer();
            }
        });
    }

Swing上にJavaFXコンポーネントを載せるには JFXPanel を貼り (initComponents) 、その上にJavaFXのシーングラフを構築 (initJFXComponents) します。目玉部分は独自コンポーネント Eye クラスとして作りました。
注意点としてはSwingとJavaFXは実行スレッドが異なるので、互いのコンポーネントを触るときは SwingUtilities.invokeLaterPlatform.runLater を経由して触る必要があります。これがちょっとめんどくさいです。

マウス位置の取得はSwingのタイマーを使って行いました。

    /**
     * マウス位置取得処理を行うタイマーを起動する。
     */
    private void startTimer() {
        timer = new Timer(50, new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                updateMousePosition();
            }
        });
        timer.start();
    }

    /**
     * グローバルのマウス位置を取得し、目のマウス位置を更新する。
     */
    private void updateMousePosition() {
        final Point mouseLocation = MouseInfo.getPointerInfo().getLocation();
        Platform.runLater(new Runnable() {
            public void run() {
                updateEye(mouseLocation.x, mouseLocation.y);
            }
        });
    }

    /**
     * 左右のEyeにコンポーネントからの相対的なマウス位置を渡す。
     */
    private void updateEye(double mouseX, double mouseY) {
        Point panelLocaiton = jfxPanel.getLocationOnScreen();
        leftEye.updateMousePosition(mouseX - panelLocaiton.x, mouseY - panelLocaiton.y);
        rightEye.updateMousePosition(mouseX - panelLocaiton.x - leftEye.prefWidth(-1) - 5, mouseY - panelLocaiton.y);
    }

AWTの MouseInfo を使って、マウス位置を取得し、目玉コンポーネントにはコンポーネントからの相対的な座標を渡すようにしています。
JFXPanel は javax.swing.JComponent を継承しているので、getLocationOnScreen メソッドを使って画面上での位置を取得可能であることを利用しました。

目玉は独立したコンポーネントとして作ってみました。JavaFXでは子を持つコンポーネントParent クラスを継承する必要があります。

public class Eye extends Parent {
    private static final double CENTER_X = 42.5;
    private static final double CENTER_Y = 62.5;
    private static final double ELLIPSE_RADIUS_X = 40d;
    private static final double ELLIPSE_RADIUS_Y = 60d;

    private Circle eye;

    public Eye() {
        super();
        createChildren();
    }

    private void createChildren() {
        final Ellipse ellipse = new Ellipse(CENTER_X, CENTER_Y, ELLIPSE_RADIUS_X, ELLIPSE_RADIUS_Y);
        ellipse.setStrokeWidth(5.0);
        ellipse.setStroke(Color.BLACK);
        ellipse.setFill(null);

        eye = new Circle(CENTER_X, CENTER_Y, 10d, Color.BLACK);

        this.getChildren().addAll(ellipse, eye);
    }

    /**
     * 自分の位置からの相対的なマウス位置を受け取り、自身のステータス (つまり目玉の位置) を更新する。
     * @param mouseX マウスのx座標
     * @param mouseY マウスのy座標
     */
    public void updateMousePosition(double mouseX, double mouseY) {
        // マウスの自コンポーネントの中心からの相対座標を算出
        double localMouseX = mouseX - CENTER_X;
        double localMouseY = mouseY - CENTER_Y;
        computeEyePosition(localMouseX, localMouseY);
    }

    private void computeEyePosition(double mouseX, double mouseY) {
        double parameter = Math.atan2(mouseY, mouseX);
        double eyeX = (ELLIPSE_RADIUS_X - 7.5) * Math.cos(parameter);
        if (Math.abs(mouseX) < Math.abs(eyeX)) {
            eyeX = mouseX;
        }
        double eyeY = (ELLIPSE_RADIUS_Y - 7.5) * Math.sin(parameter);
        if (Math.abs(mouseY) < Math.abs(eyeY)) {
            eyeY = mouseY;
        }
        eye.setCenterX(eyeX + CENTER_X);
        eye.setCenterY(eyeY + CENTER_Y);
    }
}

目玉の縁を Ellipse で、目玉を Circle で書いて自分の子供に追加しています。
外からマウスの相対的な位置をもらって目玉の位置を決めるようにしました。 Math.atan2 を使って極座標に変換しています。

ということでこんな感じでできました!

JavaFXユーザグループ第6回勉強会に行って来ました

前回に引き続き第6回JavaFX勉強会に参加してきましたので、その時のメモを貼っておきます。色々あって公開が遅くなっちゃいました。
今回は参加者が随分増えていて驚きました。やはりJavaOneOracleJavaFXを全面に押し出したことや、GroovyやScalaの話題があったからでしょうか。

何が変わった JavaFX 2.0 by 櫻庭さん (@skrb)

JavaOneでの話題や、正式版となったJavaFX2.0についての説明が中心でした。

JavaOneでのお話し
  • 今回のJavaOneではJavaSE、EE、MEそれぞれで大きな動きがあった。
  • Java8ではJavaFX3.0がSwing/AWTに代わる立場に。
    • その前に多分JavaFX2.1が来る。
    • 現時点ではまだJavaSEに入る水準に達していない。
  • JavaMEの変化がサブライズ。
    • CDCとSE Embeddedが一緒になった。
      • JavaMEは10年前の規格で、その時に比べると携帯端末の性能は大幅に上がっているし。
    • NandiniさんがiPadでデモ! (JavaFXが動いた!)
      • もちろん実際に載せられるようになるかは政治的な話もあるので...
JavaFX2.0で変わったこと

前回のご説明とかぶっている内容も結構あったので、差分点を中心にメモっています。

  • シーングラフをJavaで書くのは辛いので、XMLでも書けるようにした。それがFXML。
  • Production Suiteなくなっちゃった!
    • Adobe製品からの取り込みができなくなった。
    • SVG経由で取り込むようなライブラリを自作した。(後日公開するとのことです)
  • JSONのParserがなくなっちゃった。JavaEE7にも入るので、まとめようとしているのかな?
デモ

テキストエリアに入れたコードをその場で解釈して実装するアプリを使ってデモしていました。

  • WebView
    • HTML文字列の解釈もOK。
    • この上でHTML5で作っても大抵のアプリは提供できちゃいますね。(身も蓋もないこと言いますねw)
    • 純粋な意味でのHTML5CSSJavaScriptは大体OK。WebSocketなど幾つか使えない仕様もある。
  • アニメーション
    • 移動とか回転だったら始点と終点を指定するだけで後は自動補完する。
    • 移動、回転、拡大縮小、パスアニメーションとかができる。
  • Effect
    • 基本 Node に対して setEffect する。
    • 徐々に効果を掛ける時はタイムラインを作って少しずつ変えていく。
  • CSS
    • Scene に対して add する。つまり複数指定がOK。
    • 接頭辞 "-fx" が付く。
  • Bind
    • オブジェクト間のプロパティの結びつけ。
    • MVCGUIを構築する場合、MからVに対してはオブザーバー同期を行うが、これは実装がめんどくさい。それを解決するのがBind。
    • 高レベルAPI、低レベルAPIがあり、パフォーマンスを出したかったら低レベルAPIを使う。
まとめとか
  • OpenJDKに入ることに。OpenJFXという昔聞いたような名前でw
  • ツールとしてはNetBeansに加えて、Scene Builderが発表された。
    • ただしこれはFlashで言えばFlash Builderのようなもので、デザイナ向きではない。
  • 次のバージョンは多分2.1。もしかしたら3かも。その前にJavaSE7対応ということで2.01が出そう。
質疑応答

Q. まだJavaSEに組み込める水準ではないとは?
A. まだ意図した動きがしない所があるとか、Prismが対応しているGPUがまだ少ない所とか。
Q. CSSの属性名に何で接頭辞が入っている?
A. 何ででしょう、困りますよねえ (爆
Q. FXMLには日本語が使えるか?
A. XMLなのでたぶん大丈夫なはず。
(これは私がコメント) 自分が試してみたところ、属性では普通に使えましたよー。
※こんな感じで試してました。

<VBox id="boxPane" prefHeight="200" prefWidth="320" spacing="10"
    xmlns:fx="http://javafx.com/fxml" fx:controller="samplefxmlapplication.Sample">
    <children>
        <Button id="button" prefWidth="80" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label
            id="label" prefHeight="16" prefWidth="200"
            text="クリックしたら表示が変わるよ" fx:id="label">
            <alignment>
                <Pos fx:value="CENTER"/>
            </alignment>
        </Label>
    </children>
    <alignment>
        <Pos fx:value="CENTER"/>
    </alignment>
</VBox>

Q. コンパイルはどのように行うのか?FXMLは?
A. 普通にJavaアプリケーションとしてビルドする。FXMLはJavaプログラム中でロード処理を書く。(デコンパイルしたら中でStAXを使ってた)
Q. bindを入力チェックとかに応用できるか?
A. 低レベルAPIを使えば割と好きに書けるのでいけます。
Q. CSSのリファレンスが見当たらなかったのですが。(<- 質問者私です)
A. OracleのJavaFXのページからリンクがありますよ。

すみません、めっちゃ正面玄関にありましたね!失礼しましたー。
Q. Swingとの混在はやりやすい?
A. スレッドが別になるのでやりにくい。また、Swing上でJavaFXを動かすとJava2D上で動くことになるので遅くなる。

感想

JavaOneではJavaFXが全面に押し出されていて、自分も結構驚きました。ただ、現時点ではJavaSE本体に入るレベルじゃないというのは分かります。AWT/Swingと決別して一から作り直しているので、AWT/Swingが長い時間を掛けて蓄積してきた資産がそのままでは使えないですからねえ。自分でいじっている時でもグローバルなマウス位置が取れなくて面食らったりとか*1、まだまだ不足しているところはあるという印象です。
FXMLは結構面白いと思っています。XML側とJavaコード側を結びつける仕組みがMXML等の類似技術よりも良く考えられているという印象です。
MXMLコンパイルするとActionScriptクラスに変換されますが、FXMLはあくまでXMLドキュメントのまま読み込む形になっているのもちょっと変わっていますね。この方法が本当にいいのかは分からないですが...。

GroovyFX – Groovy な JavaFX 2.0 by 関谷さん (@kazuchika)

「プログラミングGROOVY」の共著者でもある関谷さんから、GroovyFXについて、デモアプリケーションと共に紹介して頂きました。デモアプリケーションが素晴らしかったです。

Groovyとは?
  • 書いたことある人?
    • (会場の半分くらいでした。すみません、実は自分もこれまでGroovyと接する機会がありませんでした...)
  • JVM上で動くスクリプト言語の中でも、特にJava共生するという姿勢がある言語。
    • 文法的に近いし、ツールや文化も近い。
GroovyFX
  • JavaFXをより簡単に書けるようにラップしている。
  • まだアルファ段階で、SVNから落として自分でビルドする必要がある。
  • Groovyのビルダーを活用したDSLを提供する。宣言的にシーングラフやタイムラインを構築できる。
  • ビルダーとは?
    • Groovyの柔軟な言語仕様をフル活用して、オブジェクトを構築する仕組み。
    • HTMLとかSwingやAnt、CLIに対応したビルダーがある。
  • 様々なシンタックスシュガーを提供。
    • Duarationを100.msとか5.sと書ける。(Numberにモンキーパッチでメソッドを増やしている)
    • 擬似定数の提供。
    • 属性値の自動型変換でFontやPoint3Dの値をリテラルで書くことができる。
デモアプリケーション
  • Google+のプロフィールを取ってくるアプリ。
    • クロージャの中で構造だけでなく、effectの設定とかも書き下せる。
    • ParallelTransitionがすごく見通しよく書ける。
  • WebViewのデモ。
    • HTML5Canvasを使ってグラフィック生成。JavaScriptコードも連動している!
    • Twitter4Jを使って、ハッシュタグ付きのつぶやきを拾って、Canvasにサウンドと共に表示するデモが見事!
    • 非同期処理も綺麗に書ける印象。
その他
  • バインディングは現在見直しが入っているとのこと。
  • Griffonフレームワークと連携する計画が。MVCベースのフレームワークになる模様。
  • プレゼンテーションツールPreziのGroovyFX版である、Greziというものも開発されているとのこと。
  • 12/1 に JavaDeveloper Workshop を開催します!
    • Nandiniさんが来るとのこと!
感想

Groovy、意外とこれまで触る機会がなかったのですが、いいですねえ。Javaを補佐する便利な小道具という感じで。
GroovyFXでUI構造を記述すると、見通しはFXMLよりもずっといいですね。あとは見直し中というバインディングの部分がどうなるのか、注目しています。

JavaFX 2.0 + Scala → ScalaFX by 深井さん (@fukai_yas)

こちらはScalaFXの紹介です。深井さんはScalaは業務ではなく趣味で触っているとのことでした。
ScalaFXはまだまだ始まったばかりの状態のようですが、こちらはこちらでとても興味深いものがありました。

ScalaFXの概要
  • JavaFXの各クラスにラッパーを提供する。
  • Scalaの柔軟な文法をフルに活用している。
  • まだ現時点では未実装なものが多く、これからといったところ。
  • そのままではAppletやWebStartアプリケーションを作ることができない。
シーングラフの構築
  • ぱっと見はGroovyFXと同様、JavaFXScriptに似せた書き方になっている。
    • プロパティを組み立てている所が少し違う。"foo_=" というsetterメソッドが "foo = " と呼び出せるというScalaシンタックスシュガーを利用している。
  • JFXAppがmainメソッド起動前提なのでAppletがNG。
    • Appletで使いたかったらJavaFXのApplicationを直接使う。
  • バインドは次のように書く。複雑な処理も書ける。
        new Rectangle {
          width <== stage.widthProperty / 2
          height <== stage.heightProperty / 2
          fill <== when (checkbox.selected) then Color.RED otherwise Color.BLUE
        }

何これ、めっちゃクールなんですけど!
※同じ事をJavaで書くと次のようになります。分かりやすさが全然違いますよね!

        Rectangle rect = new Rectangle();
        rect.widthProperty().bind(stage.widthProperty().divide(2));
        rect.heightProperty().bind(stage.heightProperty().divide(2));
        rect.fillProperty().bind(new ObjectBinding<Color>() {
            {
                super.bind(checkbox.selectedProperty());
            }
            @Override
            protected Color computeValue() {
                return checkbox.selectedProperty().getValue() ? Color.RED : Color.BLUE;
            }
        });
  • GroovyFXと同様、Duarationとかを書きやすくするようにしている。
まとめ
  • Scalaの特徴をフルに活かしたDSL
    • Scalaの勉強にもなるので是非!
  • まだまだ始まったばかりのプロダクト。
感想

こちらはこちらでScalaの機能をフルに活かして、見通しのいいDSLを提供しているな、と思いました。バインドの記述が実にいいです!
個人的にはScalaは勉強を始めた所という段階なのですが、そうか、これをネタに勉強すればいいのか。

その他

  • 次回は来年1月くらいに。ツールをメインテーマに!

全体を通しての感想

冒頭にも書きましたが参加者が増えて驚きました。懇親会でも様々なバックグラウンドを持つ人が集まってきているという印象でした。JavaEE系の人とかAndroidの人とか自分みたいにHadoop触っている人とかw
少しずつ盛り上がり始めているのは確かだと思っています。別のエントリでも書きましたが、Flexがあんなことになったので、実は今JavaFXは結構いい立ち位置にあるのかも知れません。

*1:これについては別途エントリを書こうと思っています。