Okay, so this may be a silly one, but I am at such a loss that I created a SOF account for that.
Here's a thing that does almost what I want:
(let [lb (listbox :model ["a" "b" "c"])]
(listen lb :selection
(fn [e] (alert (selection lb))))
(-> (frame :content lb)
pack! show!))
If you run this code you'll see a listbox with three entries (a, b, c). If you click on any one of them an alert pops up with that entry in it.
What I want to do is make the listbox react in this way to DOUBLE-clicks, not single-clicks. How should I go about it?
Extra kudos to those who tell me how to make the number of the double-clicked item appear in the popup (0 for a, 1 for b, 2 for c).
Seesaw's listbox function returns a JList. A JList's ListSelectionModel does not provide a way to determine whether the ListSelectionEvent was the result of a double-click. So a :selection listener won't help here.
On the other-hand, MouseEvent does provide getClickCount, which can be used to detect a double-click. So you can use a :mouse-clicked listener instead, and filter for double-clicks. Then all you need to do is find the ListItem that corresponds with the click location. Fortunately, JList provides a locationToIndex method that can be used for this purpose. This answer to "Double-click event on JList element" puts those pieces together for Java. A translation to Clojure/Seesaw would look something like this:
(listen lb :mouse-clicked
(fn [ev]
(when (= 2 (. ev getClickCount))
(let [index (. list locationToIndex (. ev getPoint))]
<... do something with the index / list item ...>))))
Related
a new to Clojure here.
I would like to share a behaviour which seems strange to me, but may be it's totally ok.
I followed the tutorial on github https://gist.github.com/daveray/1441520#file-seesaw-repl-tutorial-clj-L381 , and more precisely the part where I am supposed to add a Listener to a Label. Let's make a constructor and display helper:
(defn make-lb [s]
(listbox :model (-> (symbol s) ns-publics keys sort)))
(defn display [content frame]
(config! frame :content content)
content)
This works perfectly:
(def lb (make-lb "clojure.core"))
(display (scrollable lb) f)
(listen lb :selection (fn [e] (println "Selection is " (selection e))))
Howevever, this doesn't:
(def lb (scrollable (make-lb "clojure.core")))
(display lb f)
(listen lb :selection (fn [e] (println "Selection is " (selection e))))
Notice the different "Scrollable" emplacement.
In the second case, compilier tells me "Unknown event type :selection seesaw.util/illegal-Argument (utils.clj:19)"
I don't see any reason why the first snippet works, and the second doesn't. I don't have any knowledge of Swing and/or other Java libraries
Why doesn't this work? (implied)
tl;dr
listbox and scrollable return different things
Details
Check out the return values of the different calls (original make-lb included for clarity):
(defn make-lb [s]
(listbox :model (-> (symbol s) ns-publics keys sort)))
(class (make-lb "clojure.core"))
;;=> seesaw.core.proxy$javax.swing.JList$Tag$fd407141
(class (scrollable (make-lb "clojure.core")))
;;=> seesaw.core.proxy$javax.swing.JScrollPane$Tag$fd407141
For our purposes, we'll just say that listbox returns a JList and scrollable returns a JScrollPane
Given that, the calls to display are equivalent
However, the calls to listen are not equivalent
In the first case, lb resolves to a JList, and in the second case, lb resolves to a JScrollPane
More details
If we look in the source for seesaw.event, we'd see the following:
; :selection is an artificial event handled specially for each class
; of widget, so we hack...
What I'll call a "real selection type" is resolved in resolve-event-aliases
You'll notice that there's a case for JList, but not for JScrollPane
In the JScrollPane case, the artificial :selection is simply handed back from the call to resolve-event-aliases
Since this isn't a "real selection type", it's only a matter of time before things go pear-shaped
And sure enough, get-or-install-handlers attempts to look up :selection, gets nothing back, and calls (illegal-argument "Unknown event type %s" event-name) where event-name is bound to :selection, which matches the exception that you were receiving
I'm trying to listen to the :key-pressed and :key-released events on my Seesaw frame, but the events aren't firing. I've narrowed the problem down to a listbox -- when the listbox is present, the frame no longer captures the key events. Here's a simplified version of my code that shows the behavior:
(ns ainur.example
(:use seesaw.core))
(let [lst (listbox :model ["Chiptune" "Sinewave"])
f (frame :title "Ainur"
:on-close :exit
:size [1024 :by 768]
:content (border-panel :hgap 10 :vgap 10
:center (label "Center")
:north (label "North")
:south (label "South")
:west lst))]
(listen lst :selection (fn [e]
(let [active-inst (selection e)]
(println active-inst))))
(listen f
:key-pressed (fn [e]
(println "Key pressed"))
:key-released (fn [e]
(println "Key released")))
(invoke-later
(native!)
(show! f)))
Can anyone help me figure out why the key events aren't triggered? Any help would be really appreciated. Thanks in advance!
I posted this question in seesaw's Google Group, and received an excellent answer from Seesaw's creator, Dave Ray, himself. I'm posting it here in case anyone else runs into this issue:
"Hi. Once there's another widget in the hierarchy like a listbox, it grabs keyboard focus so the events never get to the frame. I think the best bet would be to put the key listener on a nested widget like a panel and then give it keyboard focus. A kind of similar example can be seen here:
https://github.com/daveray/regenemies/blob/master/src/regenemies/ui.clj#L163
The :key-typed event is bound to the canvas and then .requestFocusInWindow is used to give it keyboard focus."
Many thanks, Dave!
I am looking for a nice way of only responding to mouseover events but only when the mouse is down. I'm looking for a more core.async way of doing this.
I have two channels in an om component:
om/IDidMount
(did-mount [_]
(let [canvas (q ".tutorial")
_ref (js/Firebase. "https://conseal.firebaseio.com/scribble")
ctx (.getContext canvas "2d")
mouse-down (om/get-state owner :mouse-down)
mouse-chan (om/get-state owner :mouse-chan)]
(listen canvas EventType.MOUSEDOWN #(put! mouse-down [:down %]))
(listen canvas EventType.MOUSEMOVE #(put! mouse-chan [:move %]))
I then have the following go-loop in IWillMount:
om/IWillMount
(will-mount [_]
(let [mouse-down (om/get-state owner :mouse-down)
mouse-chan (om/get-state owner :mouse-chan) ]
(go-loop []
(<! mouse-down)
(let [[evt-type e] (<! mouse-chan)]
(.log js/console (str evt-type " we are over " e)))
(recur))))
The above does not work because mousedown events are not continually sent. I'm looking for a core.async idiomatic way of handling this.
If I was using rxjs I could do something like this:
var mouseDrags = mouseDowns.select(function (downEvent) {
return mouseMoves.takeUntil(mouseUps).select(function (drag) {
return functionToGetStuffWeWant(drag);
});
});
Keeping your current code structure, one way to do this is to add a mouse-up channel (or combine this with mouse-down into a mouse-buttons channel or something).
Then in your go loop, use the following logic:
Read from mouse-down and mouse-channel (using alt). If mouse-channel was read, ignore the value and loop (to 1). If mouse-down was read, break out of the loop and continue with 2.
Read from mouse-up and mouse-channel. If mousechannelwas read, process the current position and loop (to 2). Ifmouse-up` was read, break out of the inner loop, and loop back to 1.
To put this in concrete terms, you might do something like this (untested):
(go-loop []
(loop []
(alt! [mouse-down] down nil ;; stop looping when mouse-down is read
[mouse-channel] event (recur))) ;; loop until mouse-down
(loop []
(alt! [mouse-up] up nil ;; stop looping when mouse-up is read
[mouse-chan] event ;; process current position
(do
(.log js/console (str evt-type " we are over " event))
(recur)))) ;; loop until mouse-up
(recur)) ;; start the process all over again
For robustness, you may want to read from mouse-up in the first loop and mouse-down in the second loop, in case there are ever two consecutive mouse-up (or mouse-down) events. Otherwise, it's possible for them to get out of sync. In both cases, simply discard the value and recur.
On the other hand, you may prefer an approach similar to your rxjs example. In this case, note that several of the seq operations (map, take, etc) from core clojure have channel analogs in core.async. However, I don't think take-until is one of them. Still, it should be pretty easy to implement your own version of take-until that operates over channels.
To add a listener to a UI element in Seesaw you do this:
(listen ui-element :action (fn [_] (...)))
listen attaches a listener that calls the provided function when :action is triggered on `ui-element1. It also returns a function. If you execute that function it removes the listener that was added with the original call.
I've been prototyping UIs in the REPL using Seesaw, and I haven't kept the return values from listen.
If I don't have the returned function, how can I remove listeners?
You can manually remove listeners in the following crude way:
user=> (def b (button :text "HI"))
user=> (listen b :action #(alert % "HI!"))
user=> (-> (frame :content b) pack! show!)
; click the button, see the alert
; manually remove listeners
user=> (doseq [l (.getActionListeners b)] (.removeActionListener b l))
; click the button, nothing happens
You could put this in a helper function and use it whenever. Having this built-in somehow to seesaw.event or seesaw.dev would also be nice. Patches welcomed. :)
You cannot do that if you don't have that function reference. What you can do is use *1 special vara in REPL, which basically stores the result of last executed expression, to remove the handlers from the REPL.
I want to create an object with properties and methods in Clojure, I read that gen-class and proxy can do the job I need but its implementation is very confusing for me.
I want to use proxy to avoid AOT compilation steps, I read about it and I though I better learn how to use the easier of the two
Here is what I want to do in Clojure
Java code:
public class MyClass {
public float myFloat;
MyClass( float _myFloat ) {
myFloat = _myFloat
}
public void showNumber() {
println( myFloat );
}
}
I'm struggling to translate that code to Clojure using proxys, any help will be much appreciated
UPDATE:
Apparently deftype is more suitable for my purposes, but I'm still struggling with its implementation
Here is my Clojure code:
(deftype Particle [x y]
Object
(render [this]
(no-stroke)
(fill 200 30 180)
(ellipse x y 200 200)))
Thing is I need to specify a protocol which I'm not sure which one to use, so I'm using Object as I'm trying to create a java-class like object but I get the folloiwng error message:
Can't define method not in interfaces: render
I'm using quill which is a Processing port for Clojure if that helps
UPDATE 2:
OK I manage to get a working defprotocol and deftype combo, but there is 1 more thing I need to leran how to do and that is to add member variables or properties to my class, here is my clojure code:
(defprotocol ParticleProtocol
(update [this])
(render [this]))
(deftype Particle [position]
ParticleProtocol
(update [this])
(render [this]
(no-stroke)
(fill 200 30 180)
(ellipse (.x position) (.y position) 20 20)))
To this object I would like to add a couple of variables like radius among others, any ideas?
I agree that deftype (or possibly defrecord) is a better than proxy to do this in Clojure, but see my comments at the end to consider all possibilities.
For your question after UPDATE 2.
You can add "properties" to records by specifying them in the arglist:
(deftype Particle [position radius prop3 prop4]
...
)
Remember that types in Clojure are immutable, so there is no concept of setting properties after creating the entity. If some of the properties are optional, it is recommended best practice to create helper "factory" methods, like:
(defn make-particle
([position] (Particle. position nil nil nil))
([position radius] (Particle. position radius nil nil))
;; etc. add more here as needed
)
An option to consider is to drop types entirely and just use maps, which have within them whatever "properties/fields" you need. Types are useful when you need to implement abstractions. For your ParticleProtocol - what is the value it is providing? Protocols are meant to provide a way to have polymorphism, so will you have multiple implementations of this protocol?
Chas Emerick did an in depth flowchart of how to choose a data type in Clojure that may help you: http://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/
[Update showing example map implementation]:
To construct a map with a "property" and retrieve that property you would do:
(def mymap {:myfloat 3.1415926})
(println "myfloat has value:" (:myfloat mymap))
To provide additional functionality, such as a "render" function, just create a fn that accepts a map with the desired keys:
;; the details are bogus, just showing the syntax
(defn render [m]
(no-stroke)
(fill (:radius m) (:position m))
(do-something-else (:position m)))
For your update, if you meant to update the values in the particle map, then you need to create a new map, rather than updating an existing one.
(def myparticle {:position 100 :radius 25})
(defn change-pos [particle-map new-pos]
(assoc-in particle-map [:position] new-pos))
(let [new-particle (change-pos myparticle 300)]
(println new-particle))
;; prints out {:position 300 :radius 25}
;; orig myparticle still has value {:position 100 :radius 25}
;; or do it directly
(println (assoc myparticle :position 300))
;; prints out {:position 300 :radius 25}
You can add the "variables" next to position, like this:
(deftype Particle [position radius]
...
)
position and radius aren't really variables, they are more like final attributes. If you need to "vary" them, you should store atoms in them, like this:
(Particle. (atom (Position. 3 4)) (atom 5.0))
But you should really heed the advise of #m0skit0 to stop thinking in terms of objects and classes and start thinking in functions and immutable data structures.