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.
;; 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)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!"
fx/create-component+fx/instance
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.
So it should be
(fx/create-component (fx/instance dialog))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.
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.
The other way around
create-component creates a "component", instance gets a component's javafx object
Aha! Somehow I got that to work though, I don't know
Sorry, I'm still really confused here and I'm not sure I've really made very much progress at all on this.
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?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?
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.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?
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?