Nashorn から JavaFX を呼び出す

はじめに

Nashorn から JavaFX を呼び出す方法が決まった様です。サンプルコードとともにご覧下さい。

おことわり

以下は jlaskey による Nashorn Blog への投稿の翻訳です。原文は https://blogs.oracle.com/nashorn/entry/jjs_fx でご覧頂けます。翻訳文の URL は https://blogs.oracle.com/nashorn_ja/entry/jjs_fx です。

訳文

JavaFX の開発者達と話し合った結果、JavaFX と Nashorn シェルの起動スクリプトの連携方法について良い案が見つかりました。この案では jjs コマンドに -fx フラグを付けるとjavafx.application.Application を使って起動します。その後は Nashorn から JavaFX の呼び出しはとても簡単です。

基本的なコマンドラインは jjs -fx fxscript.js の様な形になります。そこに -scripting オプションや -- オプションを付けて jjs -fx -scripting fxscript.js -- my script args の様に実行することも出来ます。

以下のサンプルコードの処理内容は過去にこの Blog に載せたサンプルから取っています。jjs に渡すスクリプトに JavaFX 用の init, start, stop 関数を含める事も可能です。以前と異なるのは、それら JavaFX 用の関数を定義せずに、いきなりスクリプトを書き始めることが出来ることです。元々の Hello World プログラムはこうでした。

var Button    = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene     = javafx.scene.Scene;

function start(stage) {
    stage.title = "Hello World!";
    var button = new Button();
    button.text = "Say 'Hello World'";
    button.onAction = function() print("Hello World!");
    var root = new StackPane();
    root.children.add(button);
    stage.scene = new Scene(root, 300, 250);
    stage.show();
}

これを次の様に start 関数を定義しない形で書く事が出来ます。

var Button    = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene     = javafx.scene.Scene;

$STAGE.title = "Hello World!";
var button = new Button();
button.text = "Say 'Hello World'";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
$STAGE.scene = new Scene(root, 300, 250);
$STAGE.show();

stage 変数は $STAGE グローバル変数に代わり、start() 関数の引数として渡す必要はなくなりました。

また、利便性を上げるために JavaFX のクラスをまとめてロードするための仕組みも用意しました。推奨されるのは(オブジェクトの生成と静的なフィールドアクセスに)必要なクラスだけをロードすることですが、プロトタイピングなどで手早く実装を進めたい場合には、まとめてロード出来るととても便利です。

その仕組みを利用すると、先ほどの Hello World プログラムは次の様に書き換えられます。

load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

$STAGE.title = "Hello World!";
var button = new Button();
button.text = "Say 'Hello World'";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
$STAGE.scene = new Scene(root, 300, 250);
$STAGE.show();

この方法で読み込めるクラスは以下の通りです。

fx:base.js
    javafx.stage.Stage
    javafx.scene.Scene
    javafx.scene.Group
    javafx/beans
    javafx/collections
    javafx/events
    javafx/util

fx:graphics.js
    javafx/animation
    javafx/application
    javafx/concurrent
    javafx/css
    javafx/geometry
    javafx/print
    javafx/scene
    javafx/stage

fx:controls.js
    javafx/scene/chart
    javafx/scene/control

fx:fxml.js
    javafx/fxml

fx:web.js
    javafx/scene/web

fx:media.js
    javafx/scene/media

fx:swing.js
    javafx/embed/swing

fx:swt.js
    javafx/embed/swt

もう少しサンプルコードをご紹介します。

// fx3d.js

load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

var material = new PhongMaterial();
material.diffuseColor = Color.LIGHTGREEN;
material.specularColor = Color.rgb(30, 30, 30);

var meshView = Java.toJavaArray([
    new Box(200, 200, 200),
    new Sphere(100),
    new Cylinder(100, 200)
], "javafx.scene.shape.Shape3D");

for (var i = 0; i != 3; i++) {
    meshView[i].material = material;
    meshView[i].translateX = (i + 1) * 220;
    meshView[i].translateY = 500;
    meshView[i].translateZ = 20;
    meshView[i].drawMode = DrawMode.FILL;
    meshView[i].cullFace = CullFace.BACK;
};

var pointLight = new PointLight(Color.WHITE);
pointLight.translateX = 800;
pointLight.translateY = -200;
pointLight.translateZ = -1000;

var root = new Group(meshView);
root.children.add(pointLight);

var scene = new Scene(root, 800, 800, true);
scene.fill = Color.rgb(127, 127, 127);
scene.camera = new PerspectiveCamera(false);
$STAGE.scene = scene;
$STAGE.show();

// ColorfulCircles.js

load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

var WIDTH = 500;
var HEIGHT = 600;
var animation;

function setup(primaryStage) {
    var root = new Group();
    primaryStage.resizable = false;
    var scene = new Scene(root, WIDTH, HEIGHT);
    scene.title = "Colourful Circles";
    primaryStage.scene = scene;

    // 1 つ目の Circle のリスト
    var layer1 = new Group();
    for(var i = 0; i < 15; i++) {
        var circle = new Circle(200, Color.web("white", 0.05));
        circle.strokeType = StrokeType.OUTSIDE;
        circle.stroke = Color.web("white", 0.2);
        circle.strokeWidth = 4;
        layer1.children.add(circle);
    }

    // 2 つ目の Circle のリスト
    var layer2 = new Group();
    for(var i = 0; i < 20; i++) {
        var circle = new Circle(70, Color.web("white", 0.05));
        circle.strokeType = StrokeType.OUTSIDE;
        circle.stroke = Color.web("white", 0.1);
        circle.strokeWidth = 2;
        layer2.children.add(circle);
    }

    // 3 つ目の Circle のリスト
    var layer3 = new Group();
    for(var i = 0; i < 10; i++) {
        var circle = new Circle(150, Color.web("white", 0.05));
        circle.strokeType = StrokeType.OUTSIDE;
        circle.stroke = Color.web("white", 0.16);
        circle.strokeWidth = 4;
        layer3.children.add(circle);
    }

    // それぞれのレイヤーに blur エフェクトを掛ける
    layer1.effect = new BoxBlur(30, 30, 3);
    layer2.effect = new BoxBlur(2, 2, 2);
    layer3.effect = new BoxBlur(10, 10, 3);

    // ウィンドウと同じ大きさの矩形を作成し、カラーグラデーションを設定する
    var colors = new Rectangle(WIDTH, HEIGHT,
            new LinearGradient(0, 1, 1, 0, true, CycleMethod.NO_CYCLE,
                               new Stop(0,    Color.web("#f8bd55")),
                               new Stop(0.14, Color.web("#c0fe56")),
                               new Stop(0.28, Color.web("#5dfbc1")),
                               new Stop(0.43, Color.web("#64c2f8")),
                               new Stop(0.57, Color.web("#be4af7")),
                               new Stop(0.71, Color.web("#ed5fc2")),
                               new Stop(0.85, Color.web("#ef504c")),
                               new Stop(1,    Color.web("#f2660f"))));
    colors.blendMode = BlendMode.OVERLAY;

    // メインのコンテンツを作成
    var group = new Group(new Rectangle(WIDTH, HEIGHT, Color.BLACK),
                          layer1, 
                          layer2,
                          layer3,
                          colors);
    var clip = new Rectangle(WIDTH, HEIGHT);
    clip.smooth = false;
    group.clip = clip;
    root.children.add(group);

    // 全ての円を含むリストを作成
    var allCircles = new java.util.ArrayList();
    allCircles.addAll(layer1.children);
    allCircles.addAll(layer2.children);
    allCircles.addAll(layer3.children);

    // allCircles に入っている円がランダムに移動するアニメーションを作成
    animation = new Timeline();
    for each (var circle in allCircles) {
        animation.getKeyFrames().addAll(
              new KeyFrame(Duration.ZERO, // 開始時間を 0 秒に設定
                           new KeyValue(circle.translateXProperty(), Math.random() * WIDTH),
                           new KeyValue(circle.translateYProperty(), Math.random() * HEIGHT)),
              new KeyFrame(new Duration(20000), // 終了時間を 20 秒に設定
                           new KeyValue(circle.translateXProperty(), Math.random() * WIDTH),
                           new KeyValue(circle.translateYProperty(), Math.random() * HEIGHT))
              );
    }
    animation.autoReverse = true;
    animation.cycleCount = Animation.INDEFINITE;
}

function stop() {
    animation.stop();
}

function play() {
    animation.play();
}

function start(primaryStage) {
    setup(primaryStage);
    primaryStage.show();
    play();
}

追記 : この仕組みは CCC に承認され確定しました。

Comments:

Post a Comment:
Comments are closed for this entry.
About

JavaVM 用 JavaScript エンジンの Nashorn について情報発信しているブログです。Nashorn の読み方はナズホーンです。

Search

Archives
« 4月 2014
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today