cljfx

Jonathan Bennett 2024-07-11T20:08:06.900009Z

Next question: I'm trying to figure out how to use the choice-dialog from example 17 with the pure event handling from e18 and I'm struggling to figure out how this should work. The primary cause of my confusion is that I the event which launches the choice dialog should only launch the event dialog when certain things are true. Otherwise, it does other things. Because this is longer, I'll make this a thread and paste the code I have in it and the error I'm getting.

👍 1
Jonathan Bennett 2024-07-11T20:11:11.752929Z

;; Here is the dispatching event
(defmethod event-handler ::unit-clicked
  [{:keys [fx/context unit fx/event]}]
   (let [phase (subs/phase context)
         active-force (first (subs/turn-order context))
         active-unit (subs/active-unit context)]
     (when (and (= active-force (:force unit)) 
                (not (:acted unit)))
       {:context (fx/swap-context context assoc :active-unit (:id unit))})
     (when (and (= phase :combat) 
                (not (= active-force (:force unit))))
      {:dispatch {:event-type ::attack-dialog :unit unit :event event}})))

;; Here is the event which should open the dialog
(defmethod event-handler ::attack-dialog
  [{:keys [fx/context unit ^ActionEvent event]}]
  (let [active-unit (subs/active-unit context)
        active-id (subs/active-id context)
        board (subs/board context)
        window (.getWindow (.getScene ^Node (.getTarget event)))
        dialog {:fx/type :choice-dialog 
                :on-close-request (fn [^DialogEvent event] 
                                    (when (nil? (.getResult ^Dialog (.getSource event))) 
                                      (.consume event))) 
                :header-text "Select Attack" 
                :items (reports/attack-confirmation-choices active-unit unit board)}]
    (when-let [choice (.showOpenDialog dialog window)]
      (let [upd (merge active-unit {:target unit :attack (first (keys choice))})]
        {:context (fx/swap-context context assoc-in [:units active-id] upd)})))) 
And here is the error I get when the attack-dialog event is dispatched
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: No matching method showOpenDialog found taking 1 args for class clojure.lang.PersistentArrayMap
	at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:127)
	at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:102)
	at megastrike.gui.events$eval774$fn__776.invoke(events.clj:58)
	at clojure.lang.MultiFn.invoke(MultiFn.java:229)
	at cljfx.event_handler$wrap_co_effects$fn__2425.invoke(event_handler.clj:10)
	at cljfx.event_handler$wrap_effects$dispatch_sync_BANG___2438.invoke(event_handler.clj:27)
	at cljfx.event_handler$wrap_effects$dispatch_sync_BANG___2438.invoke(event_handler.clj:25)
	at cljfx.event_handler$dispatch_effect.invokeStatic(event_handler.clj:20)
	at cljfx.event_handler$dispatch_effect.invoke(event_handler.clj:19)
	at cljfx.event_handler$wrap_effects$dispatch_sync_BANG___2438.invoke(event_handler.clj:30)
	at cljfx.event_handler$wrap_effects$dispatch_sync_BANG___2438.invoke(event_handler.clj:25)
	at cljfx.lifecycle$make_handler_fn$fn__2097.invoke(lifecycle.clj:131)
	at cljfx.coerce$event_handler$reify__1703.handle(coerce.clj:135)
	at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
	at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
	at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
	at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
	at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
	at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
	at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
	at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
	at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
	at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
	at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
	at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
	at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
	at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
	at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
	at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
	at javafx.event.Event.fireEvent(Event.java:198)
	at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3599)
	at javafx.scene.Scene$MouseHandler.process(Scene.java:3903)
	at javafx.scene.Scene.processMouseEvent(Scene.java:1887)
	at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2620)
	at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
	at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
	at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
	at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
	at com.sun.glass.ui.View.handleMouseEvent(View.java:551)
	at com.sun.glass.ui.View.notifyMouse(View.java:937)
	at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
	at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:316)
	at java.base/java.lang.Thread.run(Thread.java:840)

Jonathan Bennett 2024-07-11T20:12:26.161669Z

I get why I'm getting the error. I'm giving it a map instead of a JavaFX object. What I don't know is how to say to the map, "Hey, map! Your turn. Pop on up there!"

vlaaad 2024-07-11T20:13:13.105999Z

fx/create-component+fx/instance

Jonathan Bennett 2024-07-11T20:13:33.556369Z

I've tried it with the map in a different function as well, though that was even more confusing because I'm trying to do the separate events and views of example 18 and so if I put the popup in the views file, then I've got a circular import.

Jonathan Bennett 2024-07-11T20:14:09.508139Z

So it should be

(fx/create-component (fx/instance dialog))

Jonathan Bennett 2024-07-11T20:25:18.088269Z

Ok, I tried it with that. I got no dialog, instead I get an error that suggests that the event which fires every time the board or a unit on the board is changed has fired with incomplete information. Which it has, because I didn't get to set what I want in the dialog.

Jonathan Bennett 2024-07-11T20:28:23.609729Z

Ah! I was missing :showing true! Ok, I'm pretty sure from here that I can debug my way to a solution, but I will come back if I get lost.

vlaaad 2024-07-11T20:35:22.863899Z

The other way around

vlaaad 2024-07-11T20:36:18.449299Z

create-component creates a "component", instance gets a component's javafx object

Jonathan Bennett 2024-07-11T20:47:35.521469Z

Aha! Somehow I got that to work though, I don't know

Jonathan Bennett 2024-07-12T00:11:00.870719Z

Sorry, I'm still really confused here and I'm not sure I've really made very much progress at all on this.

Jonathan Bennett 2024-07-12T00:20:28.313329Z

I don't want to put a :desc map inside the events code because that feels wrong to me. I think it should be a separate function inside the views code. So I tried to move it over, and here's what I've come up with.

(defn attack-selection-dialog 
  [{:keys [fx/context state-id]}]
  (let [active-unit (subs/active-unit context)
        unit (get-in context [:internal state-id :unit])
        board (subs/board context)]
    {:fx/type :choice-dialog 
     :showing (fx/sub-val context get-in [:internal state-id :showing] false) 
     :on-close-request (fn [^DialogEvent event] 
                         (when (nil? (.getResult ^Dialog (.getSource event))) 
                           (.consume event))) 
     :header-text "Select Attack" 
     :on-hidden {:event-type ::events/set-attack 
                 :state-id state-id}
     :items (reports/attack-confirmation-choices active-unit unit board)}))
The [:internal state-id] stuff was taken from Example 22. As far as I understand, this should make what I want to make. But now, how do I use it?

Jonathan Bennett 2024-07-12T00:23:33.793349Z

Example 22 uses ext-let-refs to link the dialog to a button. Does that mean I need to do the same for each sprite that can be clicked?

Jonathan Bennett 2024-07-12T01:33:39.229429Z

Ok, I tried adding it to the sprite for each unit:

(defn draw-unit [{:keys [fx/context unit layout]}]
  (let [active-unit (subs/active-unit context)
        board (subs/board context)
        hex (hex/hex-points unit layout)
        forces (subs/forces context)
        force (forces (unit :force))] 
    {:fx/type fx/ext-let-refs
     :refs {::dialog {:fx/type :choice-dialog 
                      :showing (fx/sub-val context get-in [:internal :attack-selection-dialog :showing] false) 
                      :on-close-request (fn [^DialogEvent event] 
                                          (when (nil? (.getResult ^Dialog (.getSource event))) 
                                            (.consume event))) 
                      :header-text "Select Attack" 
                      :on-hidden {:event-type ::events/hide-popup 
                                  :state-id :attack-selection-dialog 
                                  :on-confirmed (fn [^DialogEvent event]
                                                  (when-let [selected (.getSelectedItem ^ChoiceDialog (.getTarget event))]
                                                    {:event-type ::events/set-attack 
                                                     :unit unit
                                                     :selected selected}))} 
                      :items (if (and active-unit (not (= (:force active-unit) (:force unit))))
                               (reports/attack-confirmation-choices active-unit unit board)
                               [])}} 
     :desc {:fx/type :group
      :on-mouse-clicked {:event-type ::events/unit-clicked :unit unit}
      :children [{:fx/type common/draw-sprite 
                  :unit unit 
                  :force force 
                  :x (nth hex 8)
                  :y (nth hex 9)
                  :direction true
                  :shift (/ (* (layout :y-size) (:scale layout)) 3)}
                 {:fx/type :label
                  :text (unit :full-name)
                  :layout-x (nth hex 8)
                  :layout-y (nth hex 9)
                  :font 16
                  :translate-y (/ (* (layout :y-size) (:scale layout)) 3)}
                 {:fx/type :label 
                  :text (if (:movement-mode unit)
                          (name (:movement-mode unit))
                          "Did not move")
                  :layout-x (nth hex 4)
                  :layout-y (nth hex 5)
                  :font 16
                  :translate-y (* (/ (* (layout :y-size) (:scale layout)) 3) -2)}]}}))
And then had this for the event
(defmethod event-handler ::unit-clicked
  [{:keys [fx/context unit fx/event]}]
   (let [phase (subs/phase context)
         active-force (first (subs/turn-order context))
         active-unit (subs/active-unit context)]
     (when (and (= active-force (:force unit)) 
                (not (:acted unit)))
       {:context (fx/swap-context context assoc :active-unit (:id unit))})
     (when (and (= phase :combat) 
                (not (= active-force (:force unit)))) 
       (prn event) 
       (:context (fx/swap-context context assoc-in [:internal :attack-selection-dialog :showing] true))
      ;;  (let [upd (assoc (subs/units context) (:id active-unit) (assoc active-unit :target (:id unit)))]
      ;;    {:context (fx/swap-context context assoc :units upd)})
       )))
And when I click on a unit when the event should fire, I get nothing.

Jonathan Bennett 2024-07-11T20:15:46.780479Z

Now, if I do that, does it have the full context? Or do I need to pass it in everything it needs. i.e. will fx/sub-val and fx/swap-context work as I've used them?

Jonathan Bennett 2024-07-11T20:29:35.838759Z

One last, unrelated question, before I go. If I want to detect key chords ("Shift + =" to detect pressing the "+" key on a typical keyboard, for instance), how do I do that?