Self rendering objects?


#1

Hello everybody!

I’m working on my own GUI project. Can someone point me on the right direction to make “self rendering objects”?

For example, ive got a class Buttons, i would like to make an instance of that class and the button render on the screen each frame without me having to call a MyButton.Render() function.

Thank you all.


#2

The following code shows how an object can ‘self-register’ to draw itself. In a GUI you will also need to register for mouse and key events more info can be found here.

Button b;

void setup() {
  size(300, 200);
  b = new Button(this, 30, 20, 200, 30);
}

void draw() {
  background(255, 255, 200);
}

public class Button {

  PApplet pa;
  int x, y, w, h;

  public Button(PApplet pa, int x, int y, int w, int h) {
    this.pa = pa;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h; 
    pa.registerMethod("draw", this);
  }

  public void draw() {
    pa.pushMatrix();
    pa.pushStyle();
    pa.fill(180, 180, 255);
    pa.stroke(0, 0, 128);
    pa.strokeWeight(2);
    pa.translate(x, y);
    pa.rect(0, 0, w, h);
    pa.popStyle();
    pa.popMatrix();
  }
}

Processing already has some GUI libraries you might look at G4P and ControlP5 :grinning:


#3

Hello quark!

Thank you for your reply! This will very helpfull.

Yes, i know there are some GUI libraries already, in fact, i’ve been using G4P for a long time and i love it, but i just wanted to do my own modest version :slight_smile:

Thank you again


#4

One thing to think hard about when developing a collection of self-rendering objects is whether you want them to be responsible for their own positions, or whether you want a container (or the sketch) to manage positioning them.

It is often the case that, reflexively, one may start passing lots of xywh around everywhere for everything. That isn’t always necessary.

For example, in the Processing, rect() is xywh, while box() and sphere do not have an xyz – you position them with translate() calls. Both methods are effective, but different. In UI, an interesting example might be an accordion or drop-down menu items – you might want them to have relative offsets, or just indexes and no positions at all.


#5

For me it is an easy choice because good OO (object orientation) design requires classes to be autonomous so a self-rendering object (i.e. a GUI control) must be responsible for its own position, size, colour etc. The trick here is to realise that the position of the object need not be the actual screen position but relative to some parent control.

In G4P all controls have their own [x,y] position attributes / fields.

G4P has the GPanel class which implements a floating panel to which any other G4P control can be added. In this case the panel position [x,y] holds the actual screen position and the [x,y] fields of any added control are relative to the panels position.

To enable this the GPanel class maintains a list of other controls that have been added to it.

The scrollbars (GSlider class) used in GTextArea, GTextField and GDropList work in a similar manner.


#6

Right, it depends on your goals: if you want your objects to be autonomously self-rendering, they should have what they need.

An interesting recent counter-use-case was this animated text stream.

OP set out to make the letter autonomously renderable, but that was needlessly complex – in fact, they were always being rendered relative to an accumulation of shifting previous objects, so one alternate approach that made sense was for them not to store their own location, but instead be positioned in a typing stream.

Of course, that approach makes more sense if you are changing the locations of every object every frame or constantly re-sequencing – not common for most GUI control elements.


#7

I saw the discussion about animated text and what you say is reasonable.

In this case I would have a class called AnimatedText which would have a private inner class to represent a single character lets call it Character so can only be accessed by the enclosing class AnimatedText.

So where do we store the display coordinates for individual characters?
Two options

  1. Inside the Character class and updated by the AnimatedText class.
  2. Inside and updated by the AnimatedText class as a collection.

For me option (1) is the correct choice.

Why not option (2), because it requires two collections one for coordinates and one for characters and they must synchronised / linked to maintain the position <> character link.

Option (1) requires just one collection so easily enables lots of clever text manipulation e.g. inserting / deleting / reordering characters etc.


#8

@quark i like your example and used it, until i got stuck:

as a not “self rendering” i would have to call the .draw methode manually,
( and when ever I WANT. )
with this one i not need, it draws always
and that’s the point, it must have a show & hide methode add to the
auto draw.

could you pls help to add this for this example?


#9

I have modified the sketch so the self-rendering object can be invisible. The ‘v’ key toggles visibility.

Button b;

void setup() {
  size(300, 200);
  b = new Button(this, 30, 20, 200, 30);
}

void draw() {
  background(255, 255, 200);
  fill(0);
  textSize(16);
  text("Hit 'v' to make visible / invisible", 30, 100);
}

void keyReleased() {
  if (key == 'v') {
    b.setVisible(!b.isVisible());
  }
}

public class Button {

  PApplet pa;
  int x, y, w, h;
  boolean visible = true;

  public Button(PApplet pa, int x, int y, int w, int h) {
    this.pa = pa;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h; 
    pa.registerMethod("draw", this);
  }

  public void setVisible(boolean visible) {
    this.visible = visible;
  }

  public boolean isVisible() {
    return visible;
  }

  public void draw() {
    if (visible) {
      pa.pushMatrix();
      pa.pushStyle();
      pa.fill(180, 180, 255);
      pa.stroke(0, 0, 128);
      pa.strokeWeight(2);
      pa.translate(x, y);
      pa.rect(0, 0, w, h);
      pa.popStyle();
      pa.popMatrix();
    }
  }
}

Functions that don't have to be called
#10

now it looks so easy… perfect,
i was thinking there is a “unregistering” of the draw needed.


#11

Hello @quark, I tried your code and did some experiments with it, and I tried to follow the tutorial you quoted here. I have some questions and I know that you are an expert on this subject, so if you can help me clarify these questions I will be very grateful. I have been trying to understand this subject for a long time. Here is the code:

Button btn;
color c;
int num;

void setup() {
  size(300, 200);
  btn = new Button(this, width/2, height/2, 150, 30);
  textSize(50);
  textAlign(CENTER);
}

void draw() {
  background(c);
  text(num, width/2, height/3);
}

void buttonMouseover(Button b) {
  if(frameCount % 60 == 0) c = color(random(255), random(255), random(255));
  num++;
}

// *** BUTTON CLASS ***
import java.lang.reflect.*;

public class Button {
  PApplet pa;
  Method myEventMethod;

  float x, y, w, h;
  int fillColor;
  boolean visible = true;

  public Button(PApplet pa, float x, float y, float w, float h) {
    this.pa = pa;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h; 
    fillColor = 0xff04B4FF;

    // check to see if the host applet implements
    // public void buttonMouseover(Button b)
    try {
      myEventMethod = pa.getClass().getMethod("buttonMouseover", new Class[] { Button.class });
    } 
    catch (Exception e) {
      // no such method, or an error.. which is fine, just ignore
    }

    pa.registerMethod("draw", this);
    pa.registerMethod("mouseEvent", this);
  }

  public void setVisible(boolean visible) {
    this.visible = visible;
  }
  public boolean isVisible() {
    return visible;
  }

  public void draw() {
    if (visible) {
      pa.pushMatrix();
      pa.pushStyle();
      pa.rectMode(PConstants.CENTER);
      pa.fill(fillColor);
      pa.translate(x, y);
      pa.rect(0, 0, w, h);
      pa.popStyle();
      pa.popMatrix();

      makeEvent();
    }
  }

  public boolean isInside() {
    boolean horizontal = pa.mouseX > x - w/2 && pa.mouseX < x + w/2;
    boolean vertical   = pa.mouseY > y - h/2 && pa.mouseY < y + h/2;
    return horizontal && vertical;
  }

  public void mouseEvent(MouseEvent m) {
    //if (isInside() && m.getAction() == MouseEvent.PRESS) setVisible(!isVisible());
    if (isInside() && m.getAction() == MouseEvent.PRESS) fillColor = 0xffFF0000;
    if (isInside() && m.getAction() == MouseEvent.RELEASE) fillColor = 0xff04B4FF;
  }

  public void makeEvent() {
    if (myEventMethod != null) {
      try {
        if (isInside()) myEventMethod.invoke(pa, new Object[] { this });
      } 
      catch (Exception e) {
        System.err.println("Disabling buttonMouseover() because of an error.");
        e.printStackTrace();
        myEventMethod = null;
      }
    }
  }
}

I added a mouseEvent() through registerMethod() just like you did with draw(). Is it correct the way I did it? I’ve also added a buttonMouseover() through java.lang.reflect.*; following the tutorial. I do not know what makeEvent() does, can you help me understand what it does? Do I have to call it in draw() inside the button class, just like I did in the code above? Because only in that way I was able to make it work … is this method supposed to be invoked elsewhere?
Finally, regarding the buttonMouseover() method, is this a good example - in this specific case for a button class - to use this type of technique? What’s the name of this concept and where can i find good source to read and learn? Thank you in advance :slight_smile:


#12

@Ov3rM1nD
Congratulations on getting as far as you have. First things first

Yes that’s very good.

This technique uses Java Reflection and can be a difficult topic to master but you have made an excellent start. Java Reflection allows the user to examine the source code (classes, attributes, methods etc) at runtime and is really neat. It is the main technique I use in G4P and GUI Builder.

in the constructor this code looks for our event handler and if found stores a reference to it in myEventMethod, if it can’t find the method myEventMethod will be initialised to null.

myEventMethod = pa.getClass().getMethod("buttonMouseover", new Class[] { Button.class });

So in the makeEvent method you first test to make sure the method exists then the crunch comes here

myEventMethod.invoke(pa, new Object[] { this });

myEventMethod.invoke - execute the method referenced by myEventMethod
pa - is the object the underlying method is invoked from
new Object[] { this } - a list or parameters to be passed to the method, in this case a single parameter of type Button

Two questions here, the first answer is do not call it from the draw() because you already have registered your button for mouse events so remove the call from draw and changeb the mouseEvent to

public void mouseEvent(MouseEvent m) {
    //if (isInside() && m.getAction() == MouseEvent.PRESS) setVisible(!isVisible());
    if (isInside() && m.getAction() == MouseEvent.PRESS) fillColor = 0xffFF0000;
    if (isInside() && m.getAction() == MouseEvent.RELEASE) fillColor = 0xff04B4FF;
    if (isInside() && visible && m.getAction() == MouseEvent.MOVE) makeEvent();
  }

I would also modify this method further to use a switch statement to avoid multiple calls to isInsie and getAction like this

  public void mouseEvent(MouseEvent m) {
    if (isInside()) {
      switch (m.getAction()) {
      case MouseEvent.PRESS:
        fillColor = 0xffFF0000;
        break;
      case MouseEvent.RELEASE:
        fillColor = 0xff04B4FF;
        break;

      case MouseEvent.MOVE:
        if (visible) makeEvent();
        break;
      }
    }
  }

It is possible to call the method makeEvent from outside the class but it doesn’t make sense to do so and would only confuse someone reading your code.

It is a good example and the concept is called Reflection

If you google ‘java reflection tutorial’ there and lots of sites listed that might help.