jrubyfx でメモ帳のようなものをつくった


丁度良いエントリがあったので、練習がてら全部 `jrubyfx` で書いてた。

* [Try! JavaFX! : JavaFX 2.0によるGUI開発入門 (第一回) - 　naokirin tech news](http://naokirintechnews.hatenablog.com/entry/2012/01/19/134715 "Try! JavaFX! : JavaFX 2.0によるGUI開発入門 (第一回) - 　naokirin tech news")
* [Try! JavaFX! : JavaFX 2.0によるGUI開発入門 (第二回) - 　naokirin tech news](http://naokirintechnews.hatenablog.com/entry/2012/02/05/060159 "Try! JavaFX! : JavaFX 2.0によるGUI開発入門 (第二回) - 　naokirin tech news")
* [Try! JavaFX! : JavaFX 2.0によるGUI開発入門 (第三回) - 　naokirin tech news](http://naokirintechnews.hatenablog.com/entry/2012/04/15/124103 "Try! JavaFX! : JavaFX 2.0によるGUI開発入門 (第三回) - 　naokirin tech news")

[Getting Started · jruby/jrubyfx Wiki](https://github.com/jruby/jrubyfx/wiki/Getting-Started "Getting Started · jruby/jrubyfx Wiki") ここらへんを読んでどんな形で DSL を提供してるかを把握して [jrubyfx/samples/javafx at master · jruby/jrubyfx](https://github.com/jruby/jrubyfx/tree/master/samples/javafx "jrubyfx/samples/javafx at master · jruby/jrubyfx") サンプルから似たような事をやっていそうな所を見つけてくる作業。

## 第一回のサンプルをJRubyで書く

```rb:hello_world.rb
require 'jrubyfx'

class JRubyHelloWorld < JRubyFX::Application
  def start(stage)
    with(stage, title: 'Hello') do
      layout_scene do
        hbox do
          label 'Hello World!'
        end
      end

      show
    end
  end
end

JRubyHelloWorld.launch

```

とても  Ruby らしく書けて素晴らしい！:)

JavaFX 固有の API は大体ブロックを渡すと良い感じに処理してくれるっぽい。
具体的には、ブロック内のメソッド呼び出しのレシーバが、ブロックを渡したメソッドのレシーバになるっぽい。
実装見てないけど多分 `instance_eval{ yield }` みたいな感じ。

### ポイント
* stage オブジェクトがウィンドウの枠担当
* scene オブジェクトが描画されるオブジェクトのルートとなる
    * scene はレンダリングの為にシーングラフというものを内部に持っている模様
* scene 内に様々なオブジェクトを配置してシーングラフをつくって、それを stage#show で描画するのが一連の流れなのかな
* hbox は子要素を水平方向に描画する

## 第二回の最終的なコード

メモ帳っぽい体裁になった。
ウィンドウサイズの変化に合わせて、中身のオブジェクトの大きさも変化させる。

```rb
require 'jrubyfx'

class JRubyFXMemo < JRubyFX::Application
  def start(stage)
    with(stage, title: self.class.name, height: 700, width: 1024) do
      edit_area = nil
      command_area = nil
      command_field = nil
      command_button = nil

      scene = layout_scene do
        vbox do
          edit_area = text_area

          command_area = hbox do
            command_field = text_field
            command_button = button('Command')
          end
        end
      end

      edit_area.min_height_property.bind(scene.height_property.subtract(command_area.height_property))
      command_field.min_width_property.bind(scene.width_property.subtract(command_button.width_property))

      show
    end
  end
end

JRubyFXMemo.launch
```

### ポイント
* 描画オブジェクトのインスタンス変数には `*_property` が必ず存在し、このメソッドでインスタンス変数へのプロパティオブジェクトが取得できる
* プロパティオブジェクトの `bind` メソッドは、渡したオブジェクトが変更されたときに、レシーバのプロパティに対応するインスタンス変数を変化させる
    * `hoge.min_height_property.bind(foo)` としたら、`foo` の値が変わると `hoge` の `min_height` の値も一緒に変わる(同じ値になる)
* 渡せるオブジェクトは、Property もしくは Binding のオブジェクト（のよう）である
* 今回のように計算後の値が必要な場合は、プロパティに定義されている四則演算のメソッドを使って、Binding オブジェクトを生成して渡してやるとよいらしい

### もやもや
* バインドの為だけにインスタンス変数が増えたのが・・・悔しい
* ここら辺は非常に Java っぽいのが更に悔しい。まる。
* もっと良いやり方がある気がするけど、とりあえずこれで良しとしよう

## 第三回の宿題までをやってみる

イベントの実装。
終了とファイル保存を実装。

```rb
# -*- coding: utf-8 -*-
require 'jrubyfx'

class JRubyFXMemo < JRubyFX::Application
  def start(stage)
    with(stage, title: self.class.name, height: 700, width: 1024) do
      edit_area = nil
      command_area = nil
      command_field = nil
      command_button = nil

      scene = layout_scene do
        vbox do
          edit_area = text_area

          command_area = hbox do
            command_field = text_field
            command_button = button('Command') do
              set_on_action do
                case command_field.text
                when 'quit', 'q', 'exit'
                  Platform.exit
                when /^save:(.*)/
                  File.write($1, edit_area.text)
                else
                  edit_area.text = 'コマンドが間違っています'
                end
              end
            end
          end
        end
      end

      edit_area.min_height_property.bind(scene.height_property.subtract(command_area.height_property))
      command_field.min_width_property.bind(scene.width_property.subtract(command_button.width_property))

      show
    end
  end

  def stop
    puts 'アプリケーションを終了します...'
  end
end

JRubyFXMemo.launch
```

終了コマンドを、元記事より増やしてみた。
名前を付けて保存、上書き保存は、実装が面倒そうだから、正規表現でパパっと。スクリプト言語で書けると、この辺がすごく楽で良い。

### ポイント
* 描画オブジェクトには `set_on_*` メソッドが存在していて、ブロックを使ってイベントを登録できる
* Ruby の力を持ってすれば、文字列のパースがとても楽
* 描画経路

> 1. コンストラクタ
2. init()メソッド
3. start()メソッド
4. ウィンドウが全て閉じられたとき、もしくはPlatform.exit()が呼び出されたとき、アプリケーション終了を待機
5. stop()メソッド

## 感想

* JavaFX さえ分かってしまえば、簡単に GUI アプリが作れる
* jrubyfx はとても Ruby ぽくコードを書ける
* でも API レベルで Java 臭い箇所はあきらめるしかないかも
* スクリプト言語で GUI アプリを書けるのはとても良い
* 何より GUI アプリつくるのは楽しい:)
* 次は簡単な twitter クライアントでもつくりたいなぁ…