javafxclojureleiningen

Clojure JavaFX Live Manipulation


I've created a simple "Hello, World!" app in Clojure with JavaFX.

There are many like it, but this one is mine, as the Rifleman's Creed goes. Also, this one works with recent JavaFXs, and doesn't use the Swing JFX Panel:

(ns ezclj.core
  (:gen-class :extends javafx.application.Application)
  (:import (javafx.application Application)
           (javafx.fxml FXMLLoader)
           (javafx.scene Scene Group)))

(def _app (atom nil))

(defn -start [app stage]
  (reset! _app app)
  (.setTitle stage "Hello From Clojure!")
  (let [f (Scene. (FXMLLoader/load (.getResource (.getClass app) "sample.fxml")) 800 600)
        _ (.setScene stage f)])
  (.show stage)
  (println "Running but doesn't return to REPL"))


(defn -main
  [& args]
  (Application/launch ezclj.core (into-array String [])))

If I start this in a REPL and launch it with (-main) the window comes up. My first question is WHY does this work? Is firing up Leiningen compiling the ezclj.core class? And if not, how is the Application/launch able to find said class?

Now, this displays whatever's in the sample.fxml and continues on its merry way...which is good...but hangs up the REPL...which is bad. If I create a thread, however:

(def t (Thread. (fn[] (-main))))
(.start t)

The REPL is available and I can access _app directly, which brings me to my second question: Can I now safely build my UI by altering the JFX graph through _app? Or am I overlooking something, like potential collisions on the JFX thread?


Solution

  • I haven't played with JavaFX, but it is clear from your question that Application/launch is blocking the main thread. So, starting it in another thread is the answer, as you have discovered. A slightly easier way would be to use:

    (defn -main
      [& args]
      (future 
        (Application/launch ezclj.core (into-array String []))))
    

    since future takes a body of expressions and invokes them in another thread.

    Please look at this list of documentation, especially the Clojure CheatSheet.

    Enjoy!


    P.S.

    Once you get the basics working, you may find the "auto-reload" workflow of the lein-test-refresh plugin useful.

    If you follow the pattern in this template project the lein-test-refresh plugin will reload & recompile your code after each file save. You can invoke -main from a unit test so that it will run after every file save.