clojurefx

0.0.13-SNAPSHOT


Helper functions and probably a wrapper to simplify usage of JavaFX in Clojure.

This is meant to be used with Java 8. If you add JavaFX 2.2 to your classpath it might still work, but that isn't tested.

This Project On GitHub

Installation: [clojurefx "0.0.11"]

Navigation

dependencies

org.clojure/clojure
1.5.1



(this space intentionally left almost blank)
 
(ns clojurefx.core
  (:require [clojure.string :as str]
            [clojure.data :as cljdata]
            [clojure.walk :refer :all]
            [clojure.reflect :refer :all]))
(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))

Threading helpers

Simple wrapper for Platform/runLater. You should use run-later.

(defn run-later* [f]
(javafx.application.Platform/runLater f))
(defmacro run-later [& body]
  `(run-later* (fn [] ~@body)))

A modification of run-later waiting for the running method to return. You should use run-now.

(defn run-now* [f]
(if (javafx.application.Platform/isFxApplicationThread)
  (apply f [])
  (let [result (promise)]
    (run-later
     (deliver result (try (f) (catch Throwable e e))))
    @result)))

Runs the code on the FX application thread and waits until the return value is delivered.

(defmacro run-now  [& body]
  `(run-now* (fn [] ~@body)))

Helper functions

(defmacro keyword->enum [enum kw]
  (let [upper (-> kw name str/upper-case)]
    `(~(symbol (str (name enum) "/" "valueOf")) ~upper)))
(defn enum->keyword [enum]
  (-> (.toString enum) str/lower-case))

Fetches all public methods of a class.

(def method-fetcher 
  (memoize
   (fn [class]
     (let [cl (reflect class)
           current-methods (filter #(contains? (:flags %) :public) (filter :return-type (:members cl)))
           static-methods (filter #(contains? (:flags %) :static) current-methods)
           instance-methods (remove #(contains? (:flags %) :static) current-methods)
           methods {:instance (map :name instance-methods)
                    :static (map :name static-methods)}]
       (if (nil? cl)
         methods
         (reduce (fn [a b]
                   (let [b (method-fetcher (resolve b))]
                     (-> a
                        (update-in [:instance] #(flatten (conj % (:instance b))))
                        (update-in [:static] #(flatten (conj % (:static b))))))) methods (:bases cl)))))))

TODO inefficient.

(defn exec-method [inst method & args]
  (let [clazz# (symbol (.getName (class inst)))
        clazz# (if (= clazz# 'java.lang.Class) inst clazz#)
        methods# (method-fetcher inst)]
    (cond
     ((comp not empty?) (filter #(= method %) (:instance methods#)))
     (clojure.lang.Reflector/invokeInstanceMethod inst (name method) (to-array args))
     ((comp not empty?) (filter #(= method %) (:static methods#)))
     (clojure.lang.Reflector/invokeStaticMethod (name clazz#) (name method) (to-array args))
     :else (throw (new Exception (str "Method not existing: " method))))))
(defn camel [in & [method?]]
  (let [in (name in)
        in (str/split in #"-")
        in (map #(if (= (str (first %)) (str/upper-case (first %)))
                   % (str (str/upper-case (subs % 0 1)) (subs % 1))) in)
        in (apply str (into [] in))]
    (if method?
      (str (str/lower-case (subs in 0 1)) (subs in 1))
      in)))

TODO this is an idiotic place for this function.

(defn prepend-and-camel [prep s]
  (let [c (camel (str prep "-" s))]
    (str (str/lower-case (subs c 0 1)) (subs c 1))))

fetches a property from a node.

TODO inefficient.

(defn getfx  [obj prop & args]
  (let [method (if (= "?" (subs (name prop) (dec (count (name prop)))))
                 (symbol (str (prepend-and-camel "is" (subs (name prop) 0 (dec (count (name prop)))))))
                 (symbol (str (prepend-and-camel "get" (name prop)))))]
    (apply exec-method obj method args)))

creates and applies a setter.

TODO inefficient.

(defn setfx  [obj prop & args]
  (let [method (if (= "?" (subs (name prop) (dec (count (name prop)))))
                 (symbol (str (prepend-and-camel "set" (subs (name prop) 0 (dec (count (name prop)))))))
                 (symbol (str (prepend-and-camel "set" (name prop)))))]
    (apply exec-method obj method args)))

Collection helpers

This probably isn't the ideal approach for mutable collections. Check back for better ones.

(defn seq->observable [s]
  (javafx.collections.FXCollections/unmodifiableObservableList s))
(defn map->observable [m]
  (javafx.collections.FXCollections/unmodifiableObservableMap m))
(defn set->observable [s]
  (javafx.collections.FXCollections/unmodifiableObservableSet s))

Autoboxing-like behaviour for arguments for ClojureFX nodes.

Argument wrapping

(defmulti wrap-arg  (fn [arg class] arg))
(defmethod wrap-arg :default [arg class] arg)
(defmethod wrap-arg :accelerator [arg _]
  (if (string? arg)
    (javafx.scene.input.KeyCombination/keyCombination arg)
    arg))

Data binding

(defmulti bidirectional-bind-property! (fn [type obj prop & args] type))
(defmulti bind-property!* (fn [obj prop target] (type target)))
(defmethod bind-property!* clojure.lang.Atom [obj prop at]
  (let [listeners (atom [])
        inv-listeners (atom [])
        prop (str (camel prop true) "Property")
        property (run-now (clojure.lang.Reflector/invokeInstanceMethod obj prop (to-array [])))
        observable (reify javafx.beans.value.ObservableValue
                     (^void addListener [this ^javafx.beans.value.ChangeListener l] (swap! listeners conj l))
                     (^void addListener [this ^javafx.beans.InvalidationListener l] (swap! inv-listeners conj l))
                     (^void removeListener [this ^javafx.beans.InvalidationListener l] (swap! inv-listeners #(remove #{l} %)))
                     (^void removeListener [this ^javafx.beans.value.ChangeListener l] (swap! listeners #(remove #{l} %)))
                     (getValue [this] @at))]
    (add-watch at (keyword (name prop))
               (fn [_ r oldS newS]
                 (run-now (doseq [listener @inv-listeners] (.invalidated listener observable))
                          (doseq [listener @listeners] (.changed listener observable oldS newS)))))
    (run-now (.bind property observable))
    (run-now (doseq [listener @inv-listeners] (.invalidated listener observable)))
    obj))

Binds properties to atoms. Other STM objects might be supported in the future. Whenever the content of the atom changes, this change is propagated to the property.

args is a named-argument-list, where the key is the property name (e.g. :text) and the value the atom.

(defmacro bind-property!  [obj & args]
  (let [m# (apply hash-map args)]
    `(do ~@(for [entry# m#]
             `(bind-property!* ~obj ~(key entry#) ~(val entry#))))))

Events

(defn- prep-key-code [k]
  {:keycode k
   :name (-> (.getName k) str/lower-case keyword)
   :value (-> (.valueOf k) str/lower-case keyword)
   :arrow? (.isArrowKey k)
   :digit? (.isDigitKey k)
   :function? (.isFunctionKey k)
   :keypad? (.isKeypadKey k)
   :media? (.isMediaKey k)
   :modifier? (.isModifierKey k)
   :navigation? (.isNavigationKey k)
   :whitespace? (.isWhitespaceKey k)})
(defn- prep-pickresult [p]
  {:pickresult p
   :distance (.getIntersectedDistance p)
   :face (.getIntersectedFace p)
   :node (.getIntersectedNode p)
   :point (.getIntersectedPoint p)
   :tex-coord (.getIntersectedTexCoord p)})
(defn- prep-event-map [e & {:as m}]
  (let [prep {:event e
              :source (.getSource e)
              :type (.getEventType e)
              :target (.getTarget e)
              :consume #(.consume e)
              :string (.toString e)}]
    (if (nil? m) prep (merge prep m))))
(defn- add-modifiers [m e]
  (merge m
         {:alt-down? (.isAltDown e)
          :control-down? (.isControlDown e)
          :meta-down? (.isMetaDown e)
          :shift-down? (.isShiftDown e)
          :shortcut-down? (.isShortcutDown e)}))
(defn- add-coords [m e]
  (merge m
         {:screen-coords {:x (.getScreenX e) :y (.getScreenY e)}
          :scene-coords {:x (.getSceneX e) :y (.getSceneY e)}
          :coords {:x (.getX e) :y (.getY e) :z (.getZ e)}}))
(defmulti preprocess-event (fn [e] (type e)))
(defmethod preprocess-event :default [e]
  (prep-event-map e))

ContextMenuEvent

(defmethod preprocess-event javafx.scene.input.ContextMenuEvent [e]
  (-> (prep-event-map e
                     :pickresult (prep-pickresult (.getPickResult e))
                     :keyboard-trigger? (.isKeyboardTrigger e))
     (add-coords e)))

InputMethodEvent

(defmethod preprocess-event javafx.scene.input.InputMethodEvent [e]
  (prep-event-map e
                  :caret-position (.getCaretPosition e)
                  :committed (.getCommitted e)
                  :composed (.getComposed e)))

KeyEvent

(defmethod preprocess-event javafx.scene.input.KeyEvent [e]
  (-> (prep-event-map e
                     :character (.getCharacter e)
                     :code (prep-key-code (.getCode e))
                     :text (.getText e))
     (add-modifiers e)))

MouseEvent

(defmethod preprocess-event javafx.scene.input.MouseEvent [e]
  (-> (prep-event-map e
                     :button (-> (.getButton e) .valueOf str/lower-case keyword)
                     :click-count (.getClickCount e)
                     :pickresult (prep-pickresult (.getPickResult e))
                     :drag-detected? (.isDragDetect e)
                     :primary-button? (.isPrimaryButtonDown e)
                     :secondary-button? (.isSecondaryButtonDown e)
                     :middle-button? (.isMiddleButtonDown e)
                     :popup-trigger? (.isPopupTrigger e)
                     :sill-since-press? (.isStillSincePress e)
                     :synthesized? (.isSynthesized e))
     (add-modifiers e)
     (add-coords e)))

TouchEvent

(defmethod preprocess-event javafx.scene.input.TouchEvent [e]
  (prep-event-map e
                  :set-id (.getEventSetId e)
                  :touch-count (.getTouchCount e)
                  :touch-point (.getTouchPoint e) ;; TODO Wrapper for TouchPoint
                  :touch-points (.getTouchPoints e)
                  :alt-down? (.isAltDown e)
                  :control-down? (.isControlDown e)
                  :meta-down? (.isMetaDown e)
                  :shift-down? (.isShiftDown e)))

API

(defn set-listener!* [obj event fun]
  (run-now
   (clojure.lang.Reflector/invokeInstanceMethod obj (prepend-and-camel "set" (name event))
                                                (to-array [(reify javafx.event.EventHandler
                                                             (handle [this t]
                                                               (fun (preprocess-event t))))]))))

Adds a listener to a node event. The listener gets a preprocessed event map as shown above.

(defmacro set-listener!  [obj event args & body]
`(set-listener!* ~obj ~event (fn ~args ~@body)))

Content modification

Easy modification of child elements.

(defmulti swap-content!* (fn [obj fun] (class obj)))
(defmacro def-simple-swapper [clazz getter setter]
  `(defmethod swap-content!* ~clazz [obj# fun#]
     (let [bunch# (~getter obj#)]
       (run-now (~setter bunch# (fun# (into [] bunch#)))))))

Usage: (swap-content! <object> <modification-function>). The return value of modification-function becomes the new value, analogous to how swap! works.

Types of implementations

There are two kinds of swap-content!* implementations. First there are the simple ones for types like Menu, Accordion or the basic Pane. These simply pass the incoming function the list of children and expect to get such a list back. That kind is usually defined using the def-simple-swapper-macro.

The others are of containers that have multiple content types. e.g. SplitPane: It has items and dividers. Therefore it hands the function a map with the keys :items and :dividers, each containing a list-like collection with all the items.

Metadata

Metadata can be added to the functions handed to swap-content!*. The following metadata must get respected by the implementations with multiple content types: * :modify Allowed values: :primary. This means that the given function only accepts the list of the primary child components (usually retrieved using getChildren) as an input.

(defmacro swap-content!
  ([obj fun] `(do (swap-content!* ~obj ~fun) ~obj))
  ([obj f arg & args]
     `(do (swap-content!* ~obj (fn [x#] (~f x# ~arg ~@args))) ~obj)))

Conjoins the given element to the end of the list of the primary child elements of obj or the child elements of type k.

(defmacro fx-conj! 
  ([obj elem]
     `(swap-content! ~obj conj ~elem))
  ([obj k elem]
     `(swap-content! ~obj (fn [x#] (update-in x# [~k] conj ~elem)))))

Removes the given element from either the primary child elements of obj, or from the child element type designated by the keyword k. See the specific swap-content!*-implementations to see what types are available.

(defmacro fx-remove! 
  ([obj elem]
     `(swap-content! ~obj (with-meta (fn [x#] (remove #(= % ~elem) x#)) {:modify :primary})))
  ([obj k elem]
     `(swap-content! ~obj (fn [x#] (update-in x# [~k] (fn [coll#] (remove #(= % ~elem) coll#)))))))

Analogous to fx-remove!, this removes all child elements from either the primary or the designated child element type.

(defmacro fx-remove-all! 
  ([obj]
     `(do (swap-content!* ~obj (with-meta (fn [x#] []) {:modify :primary})) ~obj))
  ([obj k]
     `(do (swap-content!* ~obj (fn [x#] (update-in x# [~k] (fn [x#] [])))) ~obj)))
(def-simple-swapper javafx.scene.layout.Pane .getChildren .setAll)
(def-simple-swapper javafx.scene.Group .getChildren .setAll)
(def-simple-swapper javafx.scene.control.Accordion .getPanes .setAll)
(def-simple-swapper javafx.scene.control.ChoiceBox .getItems .setAll)
(def-simple-swapper javafx.scene.control.ColorPicker .getCustomColors .setAll)
(def-simple-swapper javafx.scene.control.ComboBox .getItems .setAll)
(def-simple-swapper javafx.scene.control.ContextMenu .getItems .setAll)
(def-simple-swapper javafx.scene.control.ListView .getItems .setAll)
(def-simple-swapper javafx.scene.control.Menu .getItems .setAll)
(def-simple-swapper javafx.scene.control.MenuBar .getMenus .setAll)
(def-simple-swapper javafx.scene.control.TableColumn .getColumns .setAll)
(def-simple-swapper javafx.scene.control.TabPane .getTabs .setAll)
(def-simple-swapper javafx.scene.control.ToggleGroup .getToggles .setAll)
(def-simple-swapper javafx.scene.control.ToolBar .getItems .setAll)
(def-simple-swapper javafx.scene.control.TreeItem .getChildren .setAll)
(def-simple-swapper javafx.scene.control.TreeTableColumn .getColumns .setAll)
(def-simple-swapper javafx.scene.shape.Path .getElements .setAll)
(defmethod swap-content!* javafx.scene.control.SplitPane [obj fun]
  (let [data {:items (into [] (.getItems obj))
              :dividers (into [] (.getDividers obj))}
        res (fun data)]
    (.setAll (.getItems obj) (:items res))
    (.setAll (.getDividers obj) (:dividers res))))

TODO TreeTableView

(defmethod swap-content!* javafx.scene.control.ScrollPane [obj fun]
  (.setContent obj (fun (.getContent obj))))
(defmethod swap-content!* javafx.scene.control.TitledPane [obj fun]
  (.setContent obj (fun (.getContent obj))))
(defmethod swap-content!* javafx.scene.control.Tab [obj fun]
  (.setContent obj (fun (.getContent obj))))

Builder parsing

(def pkgs (atom {"javafx.scene.control" '[accordion button cell check-box check-box-tree-item check-menu-item choice-box
                                          color-picker combo-box context-menu custom-menu-item date-picker date-cell hyperlink
                                          indexed-cell index-range label list-cell list-view menu-bar menu menu-button menu-item
                                          pagination password-field popup-control progress-bar progress-indicator
                                          radio-button radio-menu-item scroll-bar scroll-pane separator
                                          separator-menu-item slider split-menu-button split-pane tab table-cell table-column
                                          table-position table-row table-view tab-pane text-area text-field tree-cell
                                          titled-pane toggle-button toggle-group tool-bar tooltip tree-item tree-view
                                          tree-table-cell tree-table-column tree-table-row tree-table-view]
                 "javafx.scene.control.cell" '[check-box-list-cell check-box-table-cell check-box-tree-cell
                                               choice-box-list-cell choice-box-table-cell choice-box-tree-cell
                                               combo-box-list-cell combo-box-table-cell combo-box-tree-cell
                                               text-field-list-cell text-field-table-cell text-field-tree-cell
                                               property-value-factory]
                 "javafx.scene.layout" '[anchor-pane border-pane column-constraints flow-pane grid-pane h-box pane region
                                         row-constraints stack-pane tile-pane v-box]
                 "javafx.scene.text" '[text font text-flow]
                 "javafx.scene.shape" '[arc arc-to circle close-path cubic-curve cubic-curve-to ellipse
                                        h-line-to line line-to move-to path polygon polyline quad-curve quad-curve-to
                                        rectangle SVG-path box cylinder mesh-view sphere]
                 "javafx.scene.canvas" '[canvas]
                 "javafx.scene.image" '[image-view]
                 "javafx.scene.input" '[clipboard-content key-character-combination key-code-combination mnemonic]
                 "javafx.scene.effect" '[blend bloom box-blur color-adjust color-input displacement-map drop-shadow float-map
                                         gaussian-blur glow image-input inner-shadow lighting motion-blur perspective-transform
                                         reflection sepia-tone shadow]
                 "javafx.scene.paint" '[color image-pattern linear-gradient radial-gradient stop]
                 "javafx.scene.chart" '[chart area-chart bar-chart line-chart bubble-chart pie-chart scatter-chart
                                        stacked-area-chart stacked-bar-chart x-y-chart
                                        axis category-axis number-axis value-axis]
                 "javafx.scene.media" '[audio-clip media media-player media-view]
                 "javafx.scene.transform" '[affine rotate scale shear translate]
                 "javafx.scene.web" '[HTML-editor prompt-data web-engine web-view]
                 "javafx.scene" '[scene group image-cursor perspective-camera snapshot-parameters
                                  ambient-light parallel-camera perspective-camera point-light]
                 "javafx.animation" '[fade-transition fill-transition parallel-transition path-transition pause-transition
                                      rotate-transition scale-transition sequential-transition stroke-transition timeline
                                      translate-transition]
                 "javafx.stage" '[stage directory-chooser file-chooser popup]
                 "javafx.geometry" '[bounding-box dimension-2D insets point-2D point-3D rectangle-2D]
                 "javafx.embed.swing" '[JFX-panel]
                 ;; Controlsfx
                 "org.controlsfx.control" '[ButtonBar CheckComboBox CheckListView CheckTreeView
                                            GridCell GridView HyperlinkLabel NotificationPane
                                            PopOver PropertySheet RangeSlider Rating SegmentedButton]
                 "org.controlsfx.dialog" '[Dialog]}))

An exhaustive list of every visual JavaFX component. To add entries, modify the pkgs atom.
Don't use this yourself; See the macros "fx" and "deffx" below.

(def get-qualified  (memoize (fn [builder]
             (let [builder (symbol builder)]
               (first (filter (comp not nil?) (for [k (keys @pkgs)]
                                              (if (not (empty? (filter #(= % builder) (get @pkgs k))))
                                                (symbol (str k "." (camel (name builder))))))))))))
(defn- uncamelcaseize [sym]
  (let [s (-> sym str seq)]
    (loop [s s
           out (list)]
      (if (empty? s)
        (subs (->> out reverse (apply str)) 1)
        (recur (rest s)
               (if (Character/isUpperCase (first s))
                 (->> out (cons \-) (cons (Character/toLowerCase (first s))))
                 (cons (first s) out)))))))
(def get-method-calls (memoize (fn [ctrl]
                                 (let [full (resolve (get-qualified ctrl))
                                       fnm (eval `(method-fetcher ~full))
                                       fns (flatten [(:static fnm) (:instance fnm)])
                                       calls (atom {})]
                                   (doseq [fun fns
                                           :when (= "set" (subs (str fun) 0 3))]
                                     (swap! calls assoc
                                            (keyword (uncamelcaseize (subs (name fun) 3)))
                                            (eval `(fn [obj# arg#] (. obj# ~fun arg#)))))
                                   @calls))))

Constructor tools

(defn constructor-helper [clazz & args]
  (run-now (clojure.lang.Reflector/invokeConstructor (resolve clazz) (to-array (remove nil? args)))))
(defmacro construct [clazz keys]
  `(defmethod construct-node '~clazz [cl# ar#]
     (apply constructor-helper cl# (for [k# ~keys] (get ar# k#)))))
(defmulti construct-node (fn [class args] (resolve class)))
(defmethod construct-node :default [class _]
  (run-now (eval `(new ~class)))
  )
(construct javafx.scene.control.ColorPicker [:color])
(construct javafx.scene.layout.BackgroundImage [:image :repeat-x :repeat-y :position :size])
(construct javafx.scene.layout.BorderImage [:image :widths :insets :slices :filled :repeat-x :repeat-y]) ;; TODO Wrapper for BorderWidths, BorderRepeat and Insets

TODO Wrapper for BorderWidths, BorderRepeat and Insets

Builder API

(defn- symbolwalker [q]
  (if (symbol? q)
    (if-let [x (resolve q)]
      x
      q)
    q))
(defn- varwalker [q]
  (if (var? q)
    (deref q)
    q))

Content creation

(defn fx* [ctrl & args]
  (let [args# (if-not (and (nil? (first args)) (map? (first args))) (apply hash-map args) (first args))
        {:keys [bind listen content children]} args#
        props# bind
        listeners# listen
        content# (if (or (seq? content) (seq? children))
                   (-> [] (into content) (into children))
                   (cond (not (nil? content)) content
                         :else children))
        qualified-name# (get-qualified ctrl)
        methods# (get-method-calls ctrl)
        args# (dissoc args# :bind :listen)
        obj# (construct-node qualified-name# args#)]
    (run-now (doseq [arg# args#] ;; Apply arguments
               (if (contains? methods# (key arg#))
                 (((key arg#) methods#) obj# (wrap-arg (val arg#) (type obj#)))))
             (doseq [prop# props#] ;; Bind properties
               (bind-property!* obj# (key prop#) (val prop#)))
             (doseq [listener# listeners#] ;; Add listeners
               (set-listener!* obj# (key listener#) (val listener#)))
             (if (or (not (nil? content#))
                    (and (seq? content#) (not (empty? content#))))
               (swap-content!* obj# (fn [_] content#)))
             obj#)))

The central macro of ClojureFX. This takes the name of a node as declared in the pkgs atom and named arguments for the constructor arguments and object setters.

Special keys:

  • bind takes a map where the key is a property name (e.g. :text or :grid-lines-visible) and the value an atom. This internally calls bind-property!.
  • listen takes a map where the key is an event name (e.g. :on-action) and the value a function handling this event.
  • content or children (equivalent) must be a datastructure a function given to swap-content!* would return.
(defmacro fx  [ctrl & args]
`(fx* '~ctrl ~@args))
(defmacro deffx [name ctrl & props]
  `(def ~name (fx ~ctrl ~@props)))

Class-specific wrappers

Stage

(construct javafx.stage.Stage [:stage-style])
(defmethod wrap-arg :stage-style [arg class]
  (clojure.lang.Reflector/getStaticField javafx.stage.StageStyle (-> arg name str/upper-case)))
(defmethod swap-content!* javafx.stage.Stage [obj fun]
  (.setScene obj (fun (.getScene obj))))

Scene

(construct javafx.scene.Scene [:root :width :height :depth-buffer :scene-antialiasing])
(defmethod wrap-arg :scene-antialiasing [arg class]
  (clojure.lang.Reflector/getStaticField javafx.scene.SceneAntialiasing (-> arg name str/upper-case)))
(defmethod swap-content!* javafx.scene.Scene [obj fun]
  (.setRoot obj (fun (.getRoot obj))))

Image

(defmethod construct-node javafx.scene.image.Image [c {:keys [is requested-width requested-height preserve-ratio smooth url background-loading] :as args}]
  (cond
   (contains? args :is) (constructor-helper c [is requested-width requested-height preserve-ratio smooth])
   (and (contains? args :url)
      (= 2 (count (keys args)))) (constructor-helper c [url background-loading])
   :else (constructor-helper c [url requested-width requested-height preserve-ratio smooth background-loading])))

LinearGradient

(defmethod construct-node javafx.scene.paint.LinearGradient [c {:keys [start-x start-y end-x end-y proportional cycle-method stops]}]
  (let [cycle-method (if (= (type cycle-method) javafx.scene.paint.CycleMethod)
                       cycle-method
                       (keyword->enum javafx.scene.paint.CycleMethod cycle-method))]
    (constructor-helper c (double start-x) (double start-y) (double end-x) (double end-y) proportional cycle-method (into-array javafx.scene.paint.Stop stops))))

GridPane

(defmethod swap-content!* javafx.scene.layout.GridPane [obj fun]
  (run-now
   (let [mod-children? (= :primary (:modify (meta fun)))
         data (map #({:node %
                      :column-index (getfx obj :column-index %)
                      :row-index (getfx obj :row-index %)
                      :column-span (getfx obj :column-span %)
                      :row-span (getfx obj :row-span %)
                      :h-alignment (-> (getfx obj :halignment %) .toString str/lower-case keyword)
                      :v-alignment (-> (getfx obj :valignment %) .toString str/lower-case keyword)
                      :h-grow (getfx obj :hgrow %)
                      :v-grow (getfx obj :vgrow %)
                      :margin (getfx obj :margin %)
                      :fill-height? (getfx obj :fill-height? %)
                      :fill-width? (getfx obj :fill-width? %)}) (.getChildren obj))
         fun-input (if mod-children? (.getChildren obj) data)
         res (map #(if (map? %) % {:node %}) (fun fun-input))
         children (.getChildren obj)]
     (.setAll children (if mod-children? res (map :node res)))
     ;; TODO: WTF is going on below this line exactly?
     (doseq [child res
             :let [n (:node child)]]
       (doseq [[k v] child]
         (case k
           :column-index (setfx obj :column-index n (int v))
           :row-index (setfx obj :row-index n (int v))
           :column-span (setfx obj :column-span n (int v))
           :row-span (setfx obj :row-span n (int v))
           :h-alignment (setfx obj :halignment n (-> v name str/upper-case javafx.geometry.HPos/valueOf))
           :v-alignment (setfx obj :valignment n (-> v name str/upper-case javafx.geometry.VPos/valueOf))
           :h-grow (setfx obj :hgrow n v)
           :v-grow (setfx obj :vgrow n v)
           :margin (setfx obj :margin n v)
           :fill-height? (setfx obj :fill-height? n v)
           :fill-width? (setfx obj :fill-width? n v)
           nil)))))
  obj)
(defn swap-column-constraints! [obj fun]
  (let [data (map #({:column %
                     :h-alignment (.getHalignment %)
                     :h-grow (.getHgrow %)
                     :max-width (.getMaxWidth %)
                     :min-width (.getMinWidth %)
                     :percent-width (.getPercentWidth %)
                     :pref-width (.getPrefWidth %)
                     :fill-width? (.isFillWidth %)}) (.getColumnConstraints obj))
        res (fun data)]
    (.setAll (.getColumnConstraints obj) (to-array []))
    (doseq [col res
            :let [const (new javafx.scene.layout.ColumnConstraints)]]
      (doseq [[k v] col]
        (case k
          :h-alignment (.setHalignment const v)
          :h-grow (.setHgrow const v)
          :max-width (.setMaxWidth const v)
          :min-width (.setMinWidth const v)
          :percent-width (.setPercentWidth const v)
          :pref-width (.setPrefWidth const v)
          :fill-width? (.setFillWidth const v)
          nil))
      (.add (.getColumnConstraints obj) const)))
  obj)
(defn swap-row-constraints! [obj fun]
  (let [data (map #({:row %
                     :v-alignment (.getValignment %)
                     :v-grow (.getVgrow %)
                     :max-height (.getMaxHeight %)
                     :min-height (.getMinHeight %)
                     :percent-height (.getPercentHeight %)
                     :pref-height (.getPrefHeight %)
                     :fill-height? (.isFillHeight %)}) (.getRowConstraints obj))
        res (fun data)]
    (.setAll (.getRowConstraints obj) (to-array []))
    (doseq [row res
            :let [const (new javafx.scene.layout.RowConstraints)]]
      (doseq [[k v] row]
        (case k
          :v-alignment (.setValignment const v)
          :v-grow (.setVgrow const v)
          :max-height (.setMaxHeight const v)
          :min-height (.setMinHeight const v)
          :percent-height (.setPercentHeight const v)
          :pref-height (.setPrefHeight const v)
          :fill-height? (.setFillHeight const v)
          nil))
      (.add (.getRowConstraints obj) const)))
  obj)

Table view

(defmethod wrap-arg :items javafx.scene.control.TableView [arg clazz]
  (seq->observable arg))
(defmethod wrap-arg :columns javafx.scene.control.TableView [arg clazz]
  (seq->observable arg))
(defmethod swap-content!* javafx.scene.control.TableView [obj fun]
  (let [data {:items (into [] (.getItems obj))
              :columns (into [] (.getColumns obj))
              :sort-order (into [] (.getSortOrder obj))
              :visible-leaf-columns (into [] (.getVisibleLeafColumns obj))}
        res (fun data)]
    (.setAll (.getItems obj) (:items res))
    (.setAll (.getColumns obj) (:columns res))
    (.setAll (.getSortOrder obj) (:sort-order res))
    (.setAll (.getVisibleLeafColumns obj) (:visible-leaf-columns res))))
 
(ns clojurefx.extensions.core-async)
 
(ns clojurefx.extensions.meltdown)