I'm using JRuby with the libGDX framework, and I've gotten stuck trying to setup some InputListeners that worked just fine when I was using Java. In Java, the process looks like this: actor.addListener(new InputListener() { public boolean touchDown ( InputEvent event, float x, float y, int pointer, int button) { System.out.println("working"); }); where the touchDown method is overidden as it is instantiated. In Ruby, I've tried to use: listener = InputListener.new def listener.touchDown(event, x, y, pointer, button) puts 'working' end @inv_button.add_listener(listener) and all of its variants like class << listener def...end, and I've got nothing. I have got it to respond using: @inv_button.add_listener( Class.new(InputListener) do def touchDown(event, x, y, pointer, button) puts 'working' end end.new end But the problem is that I'm now stuck in the scope of the InputListener class when redefining touchDown so I can't use it to setup the listener as is intended. I don't understand why this isn't a problem with the Java method. If I could pass something along to the new class in a proc that would work, but I can't figure out how to do that. Any suggestions to try a completely different method are appreciated as well.
on 2014-05-19 04:36
on 2014-05-19 04:57

There you're just passing a (n inputlistener sub)class to that addListener(). In the Java, you instead create an anonymous inner class AND instantiate it.. that latter part is missing in your ruby snippet. So the smallest change is to simply call new(): @inv_button.add_listener( Class.new(InputListener) do def touchDown(event, x, y, pointer, button) puts 'working' end end.new end but that's not pretty.. Some(maybe me) sure will come back a nicer alternative. Good luck.
on 2014-05-19 05:04
Gergely Nagy wrote in post #1146420: > There you're just passing a (n inputlistener sub)class to that > addListener(). > In the Java, you instead create an anonymous inner class AND instantiate > it.. that latter part is missing in your ruby snippet. > So the smallest change is to simply call new(): > @inv_button.add_listener( > Class.new(InputListener) do > def touchDown(event, x, y, pointer, button) > puts 'working' > end > end.new > end > > but that's not pretty.. Some(maybe me) sure will come back a nicer > alternative. > Good luck. Thanks. That looks like my second example, which does respond, but the problem is that I don't know how to pass arguments in to the overridden definition. So it's out of scope and I can't reach any of the data I need to setup the method. But even then, I'd have to write a subclass of InputListener that takes custom arguments. I think. Which seems really ugly compared to the java.
on 2014-05-19 05:43

Is InputListener a Java interface (rather than a class)? If so, I think that's the source of the issue. In Java, your anonymous class implements the interface. Ruby has no analog to Java interfaces, nor does it have anonymous classes (although instances may resemble them a little since one can add methods to them, as you have done). So it would make sense to me to have to create a custom Ruby class. There is a section on implementing Java interfaces in the "Using JRuby" book with some conveniences. Gotta go now though...
on 2014-05-19 05:51
InputListener is a class that implements an interface. I don't know if that matters. This does work: @inv_button.add_listener( Class.new(InputListener) do def touchDown(event, x, y, pointer, button) puts 'working' end end.new ) But I'm stuck in the scope of InputListener then and I have no access to the data I need to setup. I've tried to do this: @inv_button.add_listener( Class.new(Listener) do def touchDown(event, x, y, pointer, button) puts 'working' end end.new(@mgr) ) Where Listener is a class that extends InputListener, but has an argument passed to its initialize method, but then it stops working and no longer prints out working. It gives no error though. I've also tried to have Listener implement EventListener using the JRuby method java_implements EventListener, which is the interface that InputListener implements, but it still ignores it. Here are the specs for InputListener: http://libgdx.badlogicgames.com/nightlies/docs/api... and TextButton, the class with the add_listener method: http://libgdx.badlogicgames.com/nightlies/docs/api...
on 2014-05-19 06:11

Michael, The simplest and most idiomatic way to do this with JRuby is to pass in a block which will get used as the method being over-riden on the InputListener object: @inv_button.add_listener() do |event, x, y, pointer, button| puts 'working'end I’ve written about all this in a pretty thorough blogpost at: Ruby, Duck-Typing, Java, Interfaces and JRuby <http://nodehed.com/?p=127> . It’s a bit long, but I think you will find it very helpful.
on 2014-05-19 06:21
Eric West wrote in post #1146427: > Michael, The simplest and most idiomatic way to do this with JRuby is to > pass in a block which will get used as the method being over-riden on > the > InputListener object: > > @inv_button.add_listener() do |event, x, y, pointer, button| > puts 'working'end > > I’ve written about all this in a pretty thorough blogpost at: Ruby, > Duck-Typing, Java, Interfaces and JRuby <http://nodehed.com/?p=127> . > It’s > a bit long, but I think you will find it very helpful. > Thanks, Eric. I'll spend some time reading your article. In your example using the block, what happened to the reference to the actual method I'm trying override? How will it know the block is intended for the touchDown method of the InputListener?
on 2014-05-19 06:55

Typically these listeners are written to accept an interface which defines a single method, so the block we pass to the method is used as the implementation of that method. If the interface defined multiple methods, then passing a block might not work. I'm actually unsure. I don't think I've ever run into that though. The single-method interface is just such a common pattern for this situation. So even if the class implementing the interface also implements multiple other methods, usually the interface the listener is looking for only defines one, and jruby has all this information at run time so its easy for it to pass what is needed.
on 2014-05-19 07:05
Ahh, I see. Yeah, this input listener is written to catch all of the input events like touchDown, touchUp, keyDown, keyUp in separate methods. There is a subclass of InputListener called ClickListener that only has a single click method. I could try and set up the block with that listener, but there are not separate interfaces for all of the other input events. I'd still need a solution for the key and touch events.
on 2014-05-19 07:10

Yeah, your best bet in this situation , having looked at the docs you linked, is write a Ruby class which inherits from InputListener and implements all the methods your interested in, then pass an instance of that. My guess is that, if you pass a block, the block will be treated as an implementation of "handle" which I would wager would then get called in the event of any of the events, but yeah, if you need different behaviors for each of those, which I'm sure you do, writing a class which implements the needed methods is just the cleanest/most straightforward way to do it.
on 2014-05-19 07:35
Thanks for all your help. I've been trying to do that, and I must be missing something still. I have inherited from InputListener like this: class Listener < InputListener def initialize(mgr) super() @mgr = mgr end def keyDown(event, keycode) puts keycode end end And then set it up like this: @inv_button.add_listener(Listener.new(@mgr)) And there is no error and no response from the listener. It does work when I use the Class.new method: @inv_button.add_listener( Class.new(InputListener) do def touchDown(event, x, y, pointer, button) puts 'working' end end.new) But the problem is that I'm stuck in the scope of InputListener and I don't know how to pass anything into it, because this strategy does not work when I use a Ruby class I inherited from InputListener which takes arguments to initialize. It just ignores touchDown events again. I'm rewriting this game, and it was originally written in JRuby as well, and I swear I got this to work using something like this: listener = InputListener.new def listener.touchDown(event, x, y, pointer, button) puts 'working' end @inv_button.add_listener(listener) Where the definition of touchDown was reachable from the calling script, but something must be different this time.
on 2014-05-19 17:31

Does the InputListener's constructor require any arguments? It looks like not. I'd suggest experimenting with trying super without the parentheses and omitting super altogether. I think I remember issues regarding super in some situations.
on 2014-05-19 20:23

@michael So there’s some weirdness happening here I think. I believe your issue may be that your intialize method takes an arg, whereas the original constructor doesn’t. There was a bug related to this at one point, when subclassing Java classes and maybe its still around. Other potential gotchas I’m seeing is wrt naming. Listener is definitely common enough you could be hitting some kind of collision there, so I’d either namespace it or modify it a little. Then, try using the no parens version of super in your initializer. Also, you might try the fully qualified name for InputListener, so: require "java" class GameListener < Java::com.badlogic.gdx.scenes.scene2d::InputListener attr_accessor :mgr def initialize() super end def keyDown(event, keycode) puts keycode endend listener = GameListener.new listener.mgr = "manager" #pass whatever object you need here.@inv_button.add_listener(listener) Finally, if that doesn’t work my last suggestion would be to try calling super inside the handler methods, so keyDown in this instance.
on 2014-05-19 21:06

I don't see how the straightforward Listener subclassing doesn't work... (btw you're consistently referring to touchDown() in the case it does work, and keyDown() where it does not... but I guess it's not as simple as that ;-) About passing parameter to the inline class - have you tried to just add a constructor? outer_mgr = @mgr add_listener(Class.new(InputListener) do def initialize(mgr) @mgr = mgr end def touchDown(event, x, y, pointer, button) puts "touchdown working #{@mgr}" end end.new(outer_mgr)) Another way (bit more magical): outer_mgr = @mgr add_listener( Class.new(InputListener) do define_method :touchDown do |event, x, y, pointer, button| puts "touchdown working #{outer_mgr}" end end.new ) And BTW, some "macro" weirdness: outer_mgr = @mgr add_listener( listener_for(:touchDown) do |event, x, y, pointer, button| puts "touchdown working #{outer_mgr}" end) #where: def listener_for(method, &block) Class.new(InputListener) do define_method(method, &block) end.new end # etc... There must be other, possibly cleaner ways (again the plain class should be fine), just checkin if we are on track. HTH
on 2014-05-20 01:16
Great thinking, Nagy! Passing the constructor worked when combined with the other suggestions to play around with calling super(), then I got the macro solution to work like this: @inv_button.add_listener( listener_for(:touchDown) do |event, x, y, pointer, button| puts @mgr end) def listener_for(method, &block) Class.new(InputListener) do define_method(method, &block) def initialize(mgr) super() @mgr = mgr end end.new(@mgr) end The button now responds and I have access to @mgr. Without the call to super() it still failed. I'm definitely still interested in sleeker solutions, but at least I don't have to create a custom class and can do it all on the fly the way libGDX intended. Plus, after I setup 3 or 4 buttons, the Ruby way will already be shorter than the Java way. You've all been extremely helpful. Thanks!
on 2014-05-20 04:44
I've realized that I still can't override more than one method on a listener. Even if I call listener_for(...) every time and create a new listener for each method, which seems excessive, it only responds to the first listener on the button (libGDX does allow multiple listeners on an actor). This is what I've been trying: @inv_listener = listener_for(:touchDown) do |event, x, y, pointer, button| puts "starboard" true end @inv_listener.create_method(:touchUp) do |event, x, y, pointer, button| puts "stankboard" true end def listener_for(method, &block) Class.new(InputListener) do define_method(method, &block) def initialize(mgr) super() @mgr = mgr end def create_method(name, &inner_block) self.class.send(:define_method, name, &inner_block) end end.new(@mgr) end I can see that the method is being created by calling it: @inv_listener.touchUp(...) It prints "starboard". It's strange. It's almost like anything I do to the class after it's initialization is ignored by the listener. I tried to pass more than one block and a list of method names, but I couldn't figure out how to do it. I also tried calling super in the new methods block.
on 2014-05-20 05:20
This completely works, but is a bit cumbersome. Both touchDown and enter now respond. Any insights are appreciated. @inv_button.add_listener( Class.new(InputListener) do def initialize(mgr) super() @mgr = mgr end def touchDown(event, x, y, pointer, button) puts "touchDown" end def enter(event, x, y, pointer, fromActor) puts "enter" end end.new(@mgr))
on 2014-05-20 14:14

In my opinion, it's clearer to have all the event handlers in one class (as you now have it) rather than having a separate class for each listener method. I would probably even prefer an explicitly defined class (could be an inner class), rather than the dynamically created class. I think there would be less cognitive effort required to parse and understand it. But this did not work for you, right? Second best might be to encapsulate the class definition in its own method, assuming you have other things going on in the method in which you call @inv_button.add_listener.
on 2014-05-20 20:39

> > . Even if I call listener_for(...) every time and create a new > listener for each method, which seems excessive, may be, but that's what your original java code did (new anonymous classes for each addListener call). > it only responds to the first listener on the button. So you're creating a new separate anonymous class each time, and it only notifies the first one? Haven't read the spec, are you sure multiple listeners are supported? then (of course that would be expected). Otherwise I'd fine it extremely strange. I can see that the method is being created by calling it: > @inv_listener.touchUp(...) > It prints "starboard". It's strange Well, it prints "skankboard" for me. Just went ahead and tried it myself, took your snippet, fixed up (basically moved the method defs above their invocations - BTW I hope you didn't paste it directly because than I would totally be puzzled why it didn't throw an error). Anyway, I ran it standalone using Jruby 1.7.9: Works For Me. Please use this gist to re-test it: https://gist.github.com/inger/07a1ce0973b41bdc3946... What Jruby version are you on? BTW, I don't actually see what you want to do with that create_method magic there - to dynamically add a new method to an already dynamically created class? My idea behind that Class.new stuff (per listener) is to emulate to java code, making it a more or less straight port. However IMHO that create method magic is definitely excessive and defeats the purpose. (again, I do agree with the guys that cleaner solutions should work - but i'd suggest to nail down first, iow MakeItWorkMakeItRightMakeItFast<http://c2.com/cgi/wiki?MakeItWorkMakeItRightMakeItFast> ) HTH
on 2014-05-20 23:31
>Well, it prints "skankboard" for me. Sorry, that was a typo. It also printed "stankboard" for me when I manually called the method after creating it. I could use the method and see that it was added to the class, but the listener ignored it whenever I used my own subclass of InputListener. >may be, but that's what your original java code did (new anonymous >classes I meant creating a new anonymous class for every *method* that needed to be added. The only reason I was using the whole create_method thing was to try and get your macro version working with more than one overridden method. The way you suggested listener_for had only one block and one method being defined. I needed to be able to override more than one at a time. Like touchUp and touchDown. I just couldn't figure out how to pass in multiple method names and blocks. So I tried to add new methods after initially using listener_for. Otherwise, your first method where I just setup a new class with all the needed methods worked great, but if I could get the macro version working for multiple methods it would be like 10 times less code. Not a big deal though. You guys have essentially solved my problem. Thanks. >basically moved the method defs above their invocations - BTW I hope you >didn't >paste it directly because than I would totally be puzzled why it didn't >throw an >error). Is that unusual that I can define a method after it's used in a class's script? I know in python I had to use self.method() to get that to work, but it seems to work just fine in Ruby... For instance, I'll often use a method in initialize that is defined later in the script. I'm only using 1.9.3.