WebEngineでJRubyとHTML間で連携をしてみる

JRuby(JRubyFX)とWebView上のHTMLの連携を試してみる。

JRuby側のボタンを押したらHTML上のspanにメッセージを表示して、
HTML上のボタンを押したらJRuby側のラベルにメッセージを表示する

プログラムは、RubyコードとHTMLコードの2つを用意します。

require 'jrubyfx'

class App < JRubyFX::Application

  def start(stage)
    with(stage, width: 800, height: 600, title: "sample") do
      layout_scene do
        vbox do
          hbox do
            button(id:"fx-btn", text:"click", min_width: 100)
            label(id:"fx-msg", text:"=== ===")
          end
          web_view(id:"browser") do |v| 
            v.engine.load "file:///path/to/sample.html"
            v.engine.set_java_script_enabled true
          end
        end
      end 
      show
    end 

    stage['#fx-btn'].set_on_action do
      doc = stage['#browser'].engine.get_document
      doc.get_element_by_id("web-msg").set_text_content "clicked javafx button."
    end 

    stage['#browser'].engine.get_load_worker.state_property.add_change_listener do |ov,os,new_state|
      if new_state == Worker::State::SUCCEEDED
        el = Proc.new do
          stage['#fx-msg'].text = " clicked browser button !!!"
        end
        doc = stage['#browser'].engine.get_document
        doc.get_element_by_id('web-btn').add_event_listener( "click", el, false)
      end 
    end 

  end 
end

App.launch
  • sample.html
<html>
<head>
  <title>sample</title>
</head>
<body>
  <p>hello sample.</p>
  <input type="button" id="web-btn" value="button" /><br/>
  message: <span id="web-msg">hogehoge</span><br/>
</body>
</html>

実行すると、こんな感じになります。


普通にGUIでいいじゃんって話なんですが、どうしてもJavascriptGUIRubyから扱いたくて、現在試行錯誤中です。
これであとはRubyからJavascriptにアクセスできればやりたいことができそうな気がします。

重い処理をバックグラウンドスレッド実行させてみる

重い処理をTimeline上に書いてしまうと、処理中はGUIが止まってしまうことになります。
それを回避するためにServiceとTaskを用いる方法があります。
重い処理をTaskに書いて、バックグラウンド実行し、GUIで定期的にモニタリングするようなプログラムを作ってみました。

Taskのcall関数内がバックグラウンドで実行するプログラムです。
sleepを入れているが、入れなくてもよい。バックグラウンド実行中もGUI操作を阻害しません。

なかなかよいです。

require 'jrubyfx'

@@count = 0

class XTask < Java::javafx::concurrent::Task
  def call
    loop do
      sleep(0.03)
      puts "background #{@@count}"
      @@count += 1
    end
  end
end

class XService < Java::javafx::concurrent::Service
  def createTask
      puts "CreateTask"
      XTask.new
  end
end

class Hello < JRubyFX::Application

  def initialize
    puts "intialise"
    @service = XService.new
    @pos_x = 100
  end

  def start(stage)
    @stage = stage
    with(stage,title: "sample",width: 100,height: 50) do
      layout_scene do
        group do
          label("hello", id: "v")
        end
      end
      show
    end
    @service.start
    play
  end

  def play
    handler = EventHandler.impl do |n, e|
      @stage['#v'].text = "hello #{@@count}"
      puts "handler"
    end
    time = Timeline.new
    time.cycle_count = :indefinite
    time.keyFrames << KeyFrame.new(1000.ms, handler)
    time.play
  end
end

Hello.launch


はじめ、Rubyのスレッドを使ってみたのですが、うまくいかず、今回の方法にしました。

なかなかJRubyFXの情報がなくて苦戦中。
今回はJRubyFXでServiceとTaskがていぎされていなかったので、JavaFXから直接読んでしまっています。
JRubyFXは素晴らしいと思いますが完成度がまだまだでちょっと残念。。

円を左右に動かすアニメーション

JRubyFXでアニメーションをやりたくて、まずは簡単なところから始めてみた。
はじめに、サークルを描画して、大きさを150、色をgreen、位置を縦方向に200の場所においた。
そのあと、play関数を呼んで、
そこで、idが"c"となっているサークルのX方向をtimelineを使って動かす。
0秒(0.sec)の状態で横方向200の位置、1秒(1.sec)の状態で横方向600の位置、自動的にリバースさせる
という設定になっている。

timelineの設定はnodeごとにできるっぽい。

require 'jrubyfx'

class Hello < JRubyFX::Application
  def start(stage)
    @stage = stage
    with(stage,title: "sample",width: 800,height: 400) do
      layout_scene do
        group do
          c = circle(150, Color.web("green"), id: "c")
          c.translate_y = 200 
        end
      end 
      show
    end 
    play
  end 

  def play
    translate_x = @stage['#c'].translateXProperty
    timeline(cycle_count: :indefinite, auto_reverse: true) do
      animate translate_x, 0.sec => 1.sec, 200 => 600 
    end.play
  end
end

Hello.launch

なるほど、なんとなくアニメーションがわかってきたぞ。

Timelineを用いて1秒ごとに時刻を表示するプログラム

JRubyFXによるアニメーションを調べていて、まずは簡単なところから始めてみようと思う。
テキスト(ラベル)を1秒ごとに更新するプログラムを作成してみた。
Timelineに登録されたハンドラを1000msごとに呼ぶという単純なプログラムになっている。

require 'jrubyfx'

class Hello < JRubyFX::Application
  def start(stage)
    @stage = stage
    with(stage,title: "sample",width: 200,height: 100) do
      tt = nil
      layout_scene do
        stack_pane do
          label("", id: 'view' )
        end
      end
      show
    end
    play
  end

  def refresh_time
    t = Time.now
    #puts t
    @stage['#view'].text = t.to_s
  end

  def play
    refresh_time
    handler = EventHandler.impl {|n,e| refresh_time}
    time = Timeline.new
    time.cycle_count = :indefinite
    time.keyFrames << KeyFrame.new(1000.ms, handler)
    time.play
  end
end

Hello.launch

実行するとこんな感じ。

[JRuby][JRubyFX] Ubuntu16.04でJRubyFXを動かす

手順としては次の通り。

  1. Java8を入れる
  2. rbenvを入れる
  3. jruby-1.7.26を入れる
  4. jrubyfxを入れる
  5. サンプルを動かす

まず、Javaを入れる。

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer

次に、rbenvを入れる。

$ sudo apt-get install git build-essential libssl-dev
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

~/.profile の最後行あたりに次を記述。

export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

つぎに、rbenvによりJRubyをインストールする。
ここでは、jruby-1.7.26を入れる。jruby-9kでもよいがアプリを動かしたときwarningが出るので、1.7系にしておく。

$ rbenv install --list
$ rbenv install jruby-1.7.26
$ rbenv global jruby-1.7.26


次に、jrubyfxを入れる。

$ jruby -S gem install jrubyfx


これで、準備が完了。
サンプルプログラムを動かしてみる。

require 'jrubyfx'

class Hello < JRubyFX::Application
  def start(stage)
    with(stage,title: "sample",width: 200,height: 100) do
      layout_scene do
        vbox do
          label("hello", id:"view")
        end
      end
      show
    end 
  end 
end

Hello.launch
$ jruby hello.rb

シグモイド関数をグラフ表示

Ubuntu 16.04 で試した

 % sudo apt-get install gnuplot
 % gem install gnuplot
require 'gnuplot'

def sigmod( x ) 
  1.0 / (1.0 + Math.exp( -x ) ) 
end

Gnuplot.open do |gp|
    Gnuplot::Plot.new(gp) do |plot|
      plot.title 'sample'
      plot.ylabel 'ylabel'
      plot.xlabel 'xlabel'

      x = (-100..100).collect{|v| v.to_f*0.1 }
      y = x.map {|f| sigmod(f) }

      plot.data << Gnuplot::DataSet.new( [x,y] ) do |ds|
        ds.with = "lines"
        ds.notitle
      end
    end 
end


UDPで送受信を行う

単純に2つのポートを開いて
Server->Client ... port:10000
Client->Server ... port:10001
として双方向の通信を行ってみます。

UDPのため送信されたデータが届くことは保証されないですが、TCPより手続きが簡単かと思います。

  • サーバ側のプログラム
require 'socket'

u1 = UDPSocket.new()
u1.bind('localhost', 10001)

u2 = UDPSocket.new()
u2.connect('localhost', 10000)


loop do
  sleep 1
  begin
    p u1.recvfrom_nonblock(65536)
  rescue
    puts "[Server] Error, recvfrom"
  end

  begin
    puts "[Server] >>"
    u2.send('[Server] Hello world', 0)
  rescue
    puts "[Server] Error, send"
  end
end
  • クライアント側のプログラム
require 'socket'

u1 = UDPSocket.new()
u1.bind('localhost', 10000)

u2 = UDPSocket.new()
u2.connect('localhost', 10001)


loop do
  sleep 1
  begin
    p u1.recvfrom_nonblock(65536)
  rescue
    puts "[Client] Error, recvfrom"
  end

  begin
    puts "[Client] >>"
    u2.send('[Client] Hello world', 0)
  rescue
    puts "[Client] Error, send"
  end
end