KullaでJavaFXの操作を対話的にやってみる
Kullaネタ
JavaFXなどのGUIのプログラムをゴリゴリと書いている時に、ふとこのオブジェクトを少し動かしたらどうなるんだろうと気になることがあります。実際にやってみようとすると、コードを書いてアプリケーションを起動して実際の動きを見て、「ああ、なんか違うな」となって、コードを書きなおしてアプリケーションを起動してとなるかと思います。
まあ、FXMLなどで書かずにコードでGUIを作る場合は大抵そうなりますね。
そこで、次のようなKulla上で走るスクリプトを作ってみました。
import java.util.function.*; import javafx.application.Application; import javafx.application.Platform; import javafx.scene.*; import javafx.stage.Stage; // alias for Objects#requireNonNull <T> T nonNull(T o) { return Objects.requireNonNull(o); } // Runnable with Exception interface ExRunnable { void run() throws Exception; } // RuntimeException by Handler class HandlingException extends RuntimeException { private final Exception original; HandlingException(Exception e) { super(e); this.original = e; } public Exception getOriginal() { return original; } } // Handler for Exception interface Handler { void handle(Exception e); } // Handler Receiver interface OnError { void onError(Handler h); default void ignore() { onError(e -> {}); } default void rethrow() throws Exception { try { onError(e -> {throw new HandlingException(e);}); } catch (HandlingException he) { throw he.getOriginal(); } } } // Run some task on JavaFX Thread OnError run(ExRunnable er) { return h -> { nonNull(er); nonNull(h); Runnable r = () -> { try { er.run(); } catch(Exception e) { h.handle(e); } }; Platform.runLater(r); }; } class FxComponent<T> { private final T item; private FxComponent(T item){ this.item = nonNull(item); } public static <T> FxComponent<T> create(Supplier<? extends T> s) throws Exception { final BlockingQueue<T> q = new LinkedBlockingQueue<>(); run(() -> q.put(s.get())).rethrow(); return new FxComponent<>(q.take()); } public static <T> FxComponent<T> direct(T item) { return new FxComponent<>(item); } public T get() { return item; } public OnError compute(Consumer<? super T> task) { nonNull(task); final ExRunnable er = () -> task.accept(item); return run(er); } } class FxApp extends Application { private static final ExecutorService EXE = Executors.newFixedThreadPool(1); private static Size SIZE; private static FxApp APP; private static boolean initialized = false; public static FxApp get() throws IllegalStateException { if(initialized) return APP; throw new IllegalStateException("Application is not initialized"); } private FxComponent<Stage> stg; public FxComponent<Stage> getStage() { return stg; } private FxComponent<Scene> scn; public FxComponent<Scene> getScene() { return scn; } private FxComponent<Group> root; public FxComponent<Group> getRoot() { return root; } @Override public void start(Stage stage) { APP = this; // initialize group Group g = new Group(); this.root = FxComponent.direct(g); // initialize scene Scene s = new Scene(g, SIZE.width, SIZE.height); this.scn = FxComponent.direct(s); // initialize stage this.stg = FxComponent.direct(stage); stage.setScene(s); stage.setTitle("Kulla Fx Sample"); stage.show(); stage.setOnCloseRequest(we -> EXE.shutdown()); // initialization finished initialized = true; } public static ApplicationLauncher width(int w) { return h -> { FxApp.SIZE = new Size(w, h); EXE.submit(() -> Application.launch(FxApp.class)); }; } public static interface ApplicationLauncher { void height(int h); } private static class Size { final int width; final int height; Size(int width, int height) { this.width = width; this.height = height; } } }
上記のスクリプトをロードした後に、アプリケーションを始めるには次のように書いていきます。
// アプリケーション開始 FxApp.width(500).height(450) // アプリケーションのインスタンスを取得 FxApp app = FxApp.get() // ルートのGroupを取得 FxComponent<Group> root = app.getRoot() // ルートのSceneを取得 FxComponent<Scene> scene = app.getScene() // ルートのStageを取得 FxComponent<Stage> stage = app.getStage() // Boxオブジェクトを作る import javafx.scene.shape.Box FxComponent<Box> box = FxComponent.create(() -> new Box(10, 10, 10)) // BoxオブジェクトをルートのGroupに追加する root.compute(r -> r.getChildren().add(box.get())).ignore() // Sceneの背景を灰色にする import javafx.scene.paint.Color scene.compute(s -> s.setFill(Color.GRAY)).ignore()
実際にはこんな感じで対話的にJavaFXのオブジェクトをいじることができます。
【2015/07/08 9:28 補足】