Enriched adapters and events (uid + specify listener)

Was asked if I could post my “enriched” adapters. So, here they are. Nothing glorious or anything, but I do only use those now.
Wasn’t made generic for all needs but work fine for me:

Basically, GUIXXXAdapters have a listener constructor parameter and add a uid info to the events they trigger.

  • adapters have a constructor to accept a listener, and their events add the uid info:
    [java]
    public class GUIButtonAdapter extends ButtonAdapter{
    private ScreenEventListener mouseButtonListener;

    public GUIButtonAdapter(Screen screen, String UID, Vector2f position, ScreenEventListener mouseButtonListener) {
    super(screen, UID, position);
    this.mouseButtonListener = mouseButtonListener;
    }

    public GUIButtonAdapter(Screen screen, String UID, Vector2f position, Vector2f dimensions, ScreenEventListener mouseButtonListener) {
    super(screen, UID, position, dimensions);
    this.mouseButtonListener = mouseButtonListener;
    }

    public GUIButtonAdapter(Screen screen, String UID, Vector2f position, Vector2f dimensions, Vector4f resizeBorders, String defaultImg, ScreenEventListener mouseButtonListener) {
    super(screen, UID, position, dimensions, resizeBorders, defaultImg);
    this.mouseButtonListener = mouseButtonListener;
    }

    @Override
    public void onMouseLeftReleased(MouseButtonEvent evt) {
    GUIMouseButtonEvent buttonEvent = new GUIMouseButtonEvent(evt.getButtonIndex(), evt.isPressed(),
    evt.getX(), evt.getY(), this.getUID());
    mouseButtonListener.onMouseLeftReleased(buttonEvent);
    }
    }
    [/java]

  • Events have been modified to accept a uid
    [java]
    public class GUIMouseButtonEvent extends MouseButtonEvent {
    private String source;

    public GUIMouseButtonEvent(int btnIndex, boolean pressed, int x, int y, String source){
    super(btnIndex, pressed, x, y);
    this.source = source;
    }

    public String getUID(){
    return source;
    }
    }
    [/java]

  • Listeners must extend the ScreenEventListener class:
    [java]
    public class ScreenEventListener implements RawInputListener, MouseButtonListener, MouseFocusListener
    {
    private E gui;

    public ScreenEventListener(E gui){
    this.gui = gui;
    }

    public E getGUI(){
    return gui;
    }

    public void onMouseLeftPressed(MouseButtonEvent evt) {
    }

    public void onMouseLeftReleased(MouseButtonEvent evt) {
    }

    public void onMouseRightPressed(MouseButtonEvent evt) {
    }

    public void onMouseRightReleased(MouseButtonEvent evt) {
    }

    public void beginInput() {
    }

    public void endInput() {
    }

    public void onJoyAxisEvent(JoyAxisEvent evt) {
    }

    public void onJoyButtonEvent(JoyButtonEvent evt) {
    }

    public void onMouseMotionEvent(MouseMotionEvent evt) {
    }

    public void onMouseButtonEvent(MouseButtonEvent evt) {
    }

    public void onKeyEvent(KeyInputEvent evt) {
    }

    public void onTouchEvent(TouchEvent evt) {
    }

    public void onGetFocus(MouseMotionEvent evt) {
    }

    public void onLoseFocus(MouseMotionEvent evt) {
    }

}
[/java]
NB: the GUIAbstract thingy is linked to my small framework used to build my windows, so you can forget about that.
NB2: you can make it implement any listerner you wish

  • Example of usage:
    [java]
    public class ProfilesScreenEventListener extends ScreenEventListener {
    public ProfilesScreenEventListener(ProfilesGUI gui){
    super(gui);
    }

    @Override
    public void onMouseLeftReleased(MouseButtonEvent evt) {
    if (evt instanceof GUIMouseButtonEvent) {

          GUIMouseButtonEvent mouseEvent = (GUIMouseButtonEvent) evt;
          ProfilesGUI profilesGUI = (ProfilesGUI)getGUI();
          
          if (mouseEvent.getUID().equals("saveProfilesButton")) {
              profilesGUI.saveProfilesProperties();
          } else if(mouseEvent.getUID().endsWith("ProfilesButton")){
              profilesGUI.setSelectedProfile(mouseEvent.getUID());
          }
      }
    

    }

}
[/java]

1 Like

I kind of make them and add support for events as my needs occur. Anyway, here is the rest of them, in case it is of interest:

[java]
public class GUITextFieldAdapter extends TextField {

private ScreenEventListener screenEventListener;

public GUITextFieldAdapter(Screen screen, String UID, Vector2f position, ScreenEventListener mouseButtonListener) {
    super(screen, UID, position);
    this.screenEventListener = mouseButtonListener;
}

public GUITextFieldAdapter(Screen screen, String UID, Vector2f position, Vector2f dimensions, ScreenEventListener mouseButtonListener) {
    super(screen, UID, position, dimensions);
    this.screenEventListener = mouseButtonListener;
}

public GUITextFieldAdapter(Screen screen, String UID, Vector2f position, Vector2f dimensions, Vector4f resizeBorders, String defaultImg, ScreenEventListener mouseButtonListener) {
    super(screen, UID, position, dimensions, resizeBorders, defaultImg);
    this.screenEventListener = mouseButtonListener;
}

@Override
public void onMouseLeftReleased(MouseButtonEvent evt) {
    GUIMouseButtonEvent buttonEvent = new GUIMouseButtonEvent(evt.getButtonIndex(), evt.isPressed(),
            evt.getX(), evt.getY(), this.getUID());
    screenEventListener.onMouseLeftReleased(buttonEvent);
}

@Override
public void onMouseLeftPressed(MouseButtonEvent evt) {
    screenEventListener.onMouseLeftPressed(evt);
}

@Override
public void onLoseFocus(MouseMotionEvent evt) {
    GUIMouseMotionEvent mouseEvent = new GUIMouseMotionEvent(evt.getX(), evt.getY(), 
            evt.getDX(), evt.getDY(), evt.getWheel(), evt.getDeltaWheel(), this.getUID());
    
    screenEventListener.onLoseFocus(mouseEvent);
}

}
[/java]

  • and an event:
    [java]
    public class GUIMouseMotionEvent extends MouseMotionEvent {

    private String source;

    public GUIMouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel, String source) {
    super(x, y, dx, dy, wheel, deltaWheel);
    this.source = source;
    }

    public String getUID() {
    return source;
    }
    }
    [/java]

1 Like

Why would you pass the UID as the source instead of just passing the element itself? Surely the UID is available on the element if you need it but element is more like other GUIs and has even more information if you need it.

1 Like

If listeners, then please allow multiple of them with add/removeListener instead of passing them in constructors.
Regarding source - I think it will be a lot better to make it Element, rather than String and just have utility method to retrieve UID. More power this way.
And all that will probably be many times less work if done as a small patch on top of Screen and Element, rather than decorating every possible class?

There is a big question about event consumption here. Should user listeners run before or after the basic component routines? Will they be able to consume events before they reach the component or they won’t get notified about the events which were already consumed? Or maybe they will get notified afterwards and it is up to them to check if it was already consumed?

1 Like

Err haha, mm, just putting those classes I use there because I was asked to. I have no doubt it can be done better. No worries, this is not ending in tonegodGUI or at least not in that form.

@pspeed:

  • why not pass the element instead of the string: good question… the only reason I can give you is: I didn’t think about passing elements instead of the uid. I also think you are right… heck, I can tell you it would definitively be easier to use and offer a better decoupling… think I’m going to do that change even though I’ve somewhat finished my menus.

@abies:

  • multiple listeners: I had no need for multiple listeners for one adapter… really were just classes for my needs. Does sound a good idea though.
  • totally agree that it would be better if done at the element level, but I’m only a user of the (splendid) library and strongly believe my code should use the lib but not modify it.
  • I didn’t witness any consumption problems… I don’t think this code changes how events are consumed… but I may be wrong.

NB: I’ll have them use elements instead of uids and allow multiple elements… so I’m not posting too crappy code :).

Ya know… (about to show even MORE signs of my shortcomings as a developer here), the thought of extending the actual event and storing a reference to the source object never crossed my mind. I think this has probably been said about 50x in all of the conversations revolving around event handling, but for some reason it refused to register in my noggin.

With the way events are passed currently, I’m not completely sure there would be any benefit (this is a library issue, not a “why the hell would I do that?” issue). Events are passed directly to the element being manipulated and it is up to that element to inform others that may need to know. On a positive note, this keeps processing per loop down, as input events are being delegated on the main application thread… On a negative note… this puts the potential problem on the user on a case-by-case basis and requires them to write code to manage events past the single object being manipulated by the end-user.

Honestly, I feel like this subject is a little beyond my expertise (if I have an expertise)… and I am hoping one of you rocket surgeons comes up with the ideal solution.

1 Like

Oh and @loopies Thanks a ton for posting this. I couldn’t begin to say what is good/bad/indifferent about your event handling… but it definitely got me to understand something I think people have been slapping me over the head with for a while now and I just wasn’t following =) I’m looking forward to trying this out and playing around with the suggestions from @pspeed and @abies as well.

Thx for the kind words :).

Tbh, it is very possible that all of this is absolutely not needed and simply overwriting the adapters triggered methods with what they should do, while building the GUI, would be better, but for some reason, I absolutely wanted to reduce the size of the display code, and be able to centralize the listening. It allows me to more easily enable/disable the listening (which I don’t currently do :D) and adding sounds, logging, whatever, without having to inherit from the adapter for those reasons, or have to add those behaviors to all called methods.
There are costs to my method though, because it does end up heavily using screen.geElementById… which is not nearly as good, as decoupling goes, as having the overwritten triggered method call the controller, passing the elements needed as parameters. The compiler is also less able to find typos.

I’m clearly out of my league here, so don’t put too much value on my ramblings or my scribblings on my cave walls :D. *points to pspeed and abies for directions.

General advice from a few decades of experience:

  1. avoid monolithic listener interfaces with tons of methods. Favor multiple listener interfaces with as a few methods as possible that are targeted for a particular use. (They can always be recombined if needed in joining interfaces.)

  2. if you support listeners at all do it as a list of listeners. It is not any harder to support than a single listener reference and you will eventually wish you’d done it anyway.

  3. if you support lists of listeners, for the love of all the is holy, make it a mutable list like CopyOnWriteArray list or JME’s SafeArrayList. Otherwise, listeners will not be able to remove themselves… and this thing has a way of cascading into something that is unpredictable. (ie: this listener calling this control which causes this event which cascades to this other control which eventually does something that tries to remove the original listener.)

  4. Avoid listener methods with lots of parameters. Prefer event objects instead.

  5. From personal experience, consider a two parameter approach rather than putting the source in the event. ie: onChange( Object source, Event event ) is ultimately a little more useful than onChange( Event eventWithASourceInIt ). Having written many gui wrapper frameworks, it’s nice to be able to redispatch events with a different source. In Swing this is a pain because you have to copy the whole event.

As a general rule, composition should be preferred over inheritance. It’s generally no more difficult to code and is 100x more flexible, allows for more reuse, etc… Listeners are one of the easiest ways to compose event behavior. You could have a single “click sound” listener instance that you add to all of your buttons while still having another “send command to server” listener, etc…

Anyway, this gui took a different approach to event handling and it’s interesting to see it evolve.

2 Likes
@pspeed said: Having written many gui wrapper frameworks, it's nice to be able to redispatch events with a different source. In Swing this is a pain because you have to copy the whole event.

I can imagine this would be very useful!

1 Like

Well… based on my own decades of experience, including somewhere between one and two decades of using and building GUI libraries, I agree with pspeed except in one point:
Composition is considerably more difficult to understand than inheritance. Events are data objects, and they are typically handed through several data structures and call chains before they hit their intended target; unravelling that makes a library far more difficult to understand, and from my experience with both composition-based and subclassing-based event systems, I’d say that subclassing is far easier to write and use in the simple cases.
With subclassing, it’s harder to do something that the library originally wasn’t built for. This is bad if you want to do unorthodox things, but it is good if you want to understand what the library does.

Wrt. Paul’s example of adding a “click” sound: I don’t really see an advantage for composition-based event handling there. On those controls that handle the click event, it doesn’t matter whether you add a call inside the onClick function, or add a listener during setup; on those control that don’t handle the click event, you probably don’t want a clicking sound. (In those cases where you want the clicking even if a control doesn’t override the onClick function, then you have indeed some annoying boilerplate because overriding requires that.)
Composition-based events have a different use case in Swing: They allow inspecting, rescheduling and optimizing events before they are handled. Rescheduling in Swing happens as it first handles events triggered by user input (mouseMoved, mouseClicked etc.), then runnables scheduled via SwingUtilities.invokeLater, finally redrawing. Redrawing is optimized by coalescing overlapping redraw events, to avoid controls redrawing themselves a multitude of times. Again, this could have been done without event data objects: invokeLater Runnables could be queued up on a special queue, areas in need of redrawing could be collected in a per-screen data structure.
TonegodGUI doesn’t need any of this anyway. InvokeLater execution could happen near the end of update(); I dimly remember there’s a queue of Runnables executed on each update() in JME, if that can’t be used, the application can always define one on its own. Redrawing optimization is pointless, the entire canvas is redrawn anyway.

So… I don’t even see the standard use cases for event objects.
Is that my lack of imagination, or are the use cases for event objects really absent in a 3D application?

We probably have no common basis for discussion here. I haven’t subclassed a JButton in probably 10 years.

@abies
Not sure if that was what you were pointing at, but I did have one unintended consequence of my overwriting of the events methods in my “enriched” (I’m sure there’s a way better word for that) adapters, that I didn’t spot… by overwriting, they remove the effects putting the buttons etc back in position (visually toggling them on and off). So I had to add a call to super.event method, in those methods, so they behave correctly, visually.