Class Within A Class Demo (Menu / Button)

// Demonstrates Button class inside of MenuBar class
// https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

color BLUE = color(64, 124, 188);
color LTGRAY = color(185, 180, 180);
color YELLOW = color(245, 250, 13);
color RED = color(255, 0, 0);
color BLACK = color(0, 0, 0);
color WHITE = color(255, 255, 255);
color GREEN = color(0, 255, 0);

MenuBar menuBar;
MenuBar.Button _addBtn;
MenuBar.Button _saveBtn;
MenuBar.Button _clearBtn;
MenuBar.Button _openBtn;

class MenuBar {
  int x, y, w, h;
  color bkgrnd;
  
  // Constructor 
  MenuBar(int xpos, int ypos, int wide, int ht, color background) {
    x = xpos;
    y = ypos;
    w = wide;
    h = ht;
    bkgrnd = background;
  }

  void display() {
    noStroke();
    fill(bkgrnd);
    rect(x, y, w, h);
    stroke(0);
  }

  class Button {
    float x, y, w, h;
    String title;
    color btnColor;
    color txtColor;
    float txtSize;

    // Constructor
    Button(int xpos, int ypos, float wt, float ht, String titleStr, color background, color foreground, float textSize) {
      x = xpos;
      y = ypos;
      w = wt;
      h = ht;
      title = titleStr;
      btnColor = background;
      txtColor = foreground;
      txtSize = textSize;
    }

    void display() {
      fill(btnColor); // button color
      noStroke();
      rect( x, y, w, h, 15); // rounded rect
      fill(txtColor); // text color
      textSize(txtSize);
      textAlign(CENTER);
      text(title, x, y, w, h);
    }
  } // End of Button class
  
} // End of MenuBar class

void setup() {
  size(600, 600);
  menuBar = new MenuBar(0, 0, width, 60, color(180));
  _addBtn = menuBar.new Button(30, 15, 80, 24, "Add", YELLOW, BLACK, 18);
  _saveBtn = menuBar.new Button(120, 15, 80, 24, "Save", GREEN, BLACK, 18);
  _clearBtn = menuBar.new Button(210, 15, 80, 24, "Clear", RED, BLACK, 18);
  _openBtn = menuBar.new Button(300, 15, 80, 24, "Open", WHITE, BLACK, 18);
}

void draw() {
  menuBar.display();
  _addBtn.display();
  _saveBtn.display();
  _clearBtn.display();
  _openBtn.display();
}

void mousePressed() {
  if ((mouseX >= _addBtn.x) && (mouseX <= _addBtn.x + _addBtn.w) && (mouseY >= _addBtn.y) && (mouseY <= _addBtn.y + _addBtn.h)) {
    println("Add btn hit.");
  }
  if ((mouseX >= _saveBtn.x) && (mouseX <= _saveBtn.x + _saveBtn.w) && (mouseY >= _saveBtn.y) && (mouseY <= _saveBtn.y + _saveBtn.h)) {
    println("Save btn hit.");
  }
  if ((mouseX >= _clearBtn.x) && (mouseX <= _clearBtn.x + _clearBtn.w) && (mouseY >= _clearBtn.y) && (mouseY <= _clearBtn.y + _clearBtn.h)) {
    println("Clear btn hit.");
  }
  if ((mouseX >= _openBtn.x) && (mouseX <= _openBtn.x + _openBtn.w) && (mouseY >= _openBtn.y) && (mouseY <= _openBtn.y + _openBtn.h)) {
    println("Open btn hit.");
  }
}

1 Like

I’ve moved class Button outside class MenuBar and your code still worked after some few small changes to variables’ declaration & instantiation: :factory_worker:

MenuBar menuBar;
Button _addBtn;
Button _saveBtn;
Button _clearBtn;
Button _openBtn;
void setup() {
  size(600, 600);
  menuBar = new MenuBar(0, 0, width, 60, color(180));
  _addBtn = new Button(30, 15, 80, 24, "Add", YELLOW, BLACK, 18);
  _saveBtn = new Button(120, 15, 80, 24, "Save", GREEN, BLACK, 18);
  _clearBtn = new Button(210, 15, 80, 24, "Clear", RED, BLACK, 18);
  _openBtn = new Button(300, 15, 80, 24, "Open", WHITE, BLACK, 18);
}

So I don’t see the point in forcing Button to be an inner class of MenuBar! :man_shrugging:

2 Likes

You are correct and the point is well taken. I mainly just wanted to see if I could get a class within a class to work. It may have limited usefulness. The original MenuBar class used a function to create the buttons and that worked pretty well (made draw() simpler), except when it came time to trap button clicks in mousePressed() I had to copy all the coordinates from the class and hard code them. I like the myBtn.x, myBtn.y etc. coordinate method better, but the down side is I have to have a separate Button class either outside or within the MenuBar class. Thanks for taking the time to look at it.

Don’t forget that all classes within a '.pde" file are already inner classes of a PApplet subclass.

So, your class MenuBar is an inner class of subclass PApplet.
And class Button is an inner class of both class MenuBar & subclass PApplet.

That’s why both MenuBar & Button have direct access to all PApplet’s members, like fill(), rect(), etc., w/o needing an explicit PApplet reference.

Hi @svan,

Maybe you could reconsider your approach to your menu class. Typically, a class is designed to handle all associated tasks through its interface. In the case of a menu class, this means that instead of having the menu and buttons separate, the menu should manage the buttons. I currently don’t have the time to detail a fully clean implementation, but the following quick hack should give you an idea of what I mean…

Cheers
— mnse

19-09-2023_09-36-17

// Demonstrates Button class inside of MenuBar class
// https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

color BLUE = color(64, 124, 188);
color LTGRAY = color(185, 180, 180);
color YELLOW = color(245, 250, 13);
color RED = color(255, 0, 0);
color BLACK = color(0, 0, 0);
color WHITE = color(255, 255, 255);
color GREEN = color(0, 255, 0);

color MENU_DEFAULT_BGCOLOR = color(180);
int   BTN_DEFAULT_WIDTH = 80;
int   BTN_DEFAULT_HEIGHT = 24;
int   BTN_DEFAULT_TEXTSIZE = 18;

MenuBar menuBar;

class MenuBar {
  int x, y, w, h;
  int marginX, marginY;
  color bkgrnd;
  ArrayList<Button> buttons;
  
  // Constructor 
  MenuBar(int xpos, int ypos, color background) {
    x = xpos;
    y = ypos;
    w = 0;
    h = 0;
    marginX=10;
    marginY=10;
    bkgrnd = background;
    buttons = new ArrayList<Button>();
  }
  
  void addButton(String titleStr, color background, color foreground) {
    buttons.add(new Button(titleStr, background, foreground));
    updateParams();
  }

  void updateParams() {
    int bx = x+marginX;
    w=marginX;
    for (Button b: buttons) {
      b.x = bx;
      b.y = y+marginY;
      w+=b.w+marginX;
      if (b.h > h)
        h=b.h+2*marginY;
      bx+=b.w+marginX;      
    }
    w+=marginX;
  }

  void handleMouseEvent(int mx, int my, boolean isPressed) {
    for (Button b:buttons) {
      if (isPressed)
        b.handlePressed(mx,my);
      else
        b.handleHover(mx,my);
    }
  }

  void display() {
    noStroke();
    fill(bkgrnd);
    rect(x, y, w, h);
    for (Button b:buttons)
      b.display();
  }

  class Button {
    int x, y, w, h;
    String title;
    color btnColor;
    color txtColor;
    float txtSize;
    boolean hover;

    // Constructor
    Button(String titleStr, color background, color foreground) {
      w = BTN_DEFAULT_WIDTH;
      h = BTN_DEFAULT_HEIGHT;
      title = titleStr;
      btnColor = background;
      txtColor = foreground;
      txtSize = BTN_DEFAULT_TEXTSIZE;
      hover=false;
    }

    void handlePressed(int mx, int my) {
      if (mx > x && mx < x+w && my > y && my < y+h)
        buttonPressed(Button.this);      
    }

    void handleHover(int mx, int my) {
      hover = false;
      if (mx > x && mx < x+w && my > y && my < y+h)
        hover = true;      
    }

    void display() {
      pushStyle();
      fill(btnColor); // button color
      noStroke();
      if (hover) {
        strokeWeight(2);
        stroke(txtColor);
      }
        
      rect(x, y, w, h, 15); // rounded rect
      fill(txtColor); // text color
      textAlign(CENTER,CENTER);
      textSize(txtSize);
      text(title, x+w/2, y+h/2);
      popStyle();
    }
  } // End of Button class
  
} // End of MenuBar class

void setup() {
  size(400, 50);
  menuBar = new MenuBar(10, 10, color(180));
  menuBar.addButton("Add",YELLOW,BLACK);
  menuBar.addButton("Save",GREEN,BLACK);
  menuBar.addButton("Clear",RED,BLACK);
  menuBar.addButton("Open",WHITE,BLACK);
}

void draw() {
  background(225);
  menuBar.display();
}

public void buttonPressed(MenuBar.Button b) {
  println("Button pressed: " + b.title);
}

void mousePressed() {
  menuBar.handleMouseEvent(mouseX,mouseY, true);
}

void mouseMoved() {
  menuBar.handleMouseEvent(mouseX,mouseY, false);
}
4 Likes

Thank you for this concept and code.

That’s what I thought.

  • Buttons belong into the Menu class and the class handles the buttons

  • Usage of Arraylist is recommended

  • You can evaluate the return type b.title of the button using switch (){…

  • I also would advice against placing one class inside another.

  • I also place the classes at the end of the Sketch

2 Likes

Although it’s a start, I think we still need more compelling reasons to justify making Button a non-static inner class of MenuBar.

Again, I merely needed to cut ‘n’ paste class Button outside of MenuBar, and the whole sketch was still able to run!

The only extra tiny fix I had to do was in top-level function buttonPressed()'s parameter declaration, from MenuBar.Button b to just Button b:

public void buttonPressed(Button b) {
  println("Button pressed: " + b.title);
}

BtW, buttonPressed() makes more sense as an actual method of Button itself, given it’s merely a log for field Button::title:

void buttonPressed() {
  println("Button pressed:", title);
}

If we think those 2 classes as part of a bigger library, making Button an inner class would bar users of that library to use Button directly, unless we declare it as public.

They’d have to accept MenuBar as the only manager of instances of Button.

IMO, that’d be very selfish, given Button doesn’t depend on MenuBar for anything!

And users should be allowed to directly manage Button if they don’t like how MenuBar does so.

Hi,

just some quick notes…

Sure! But imagine that those kind of buttons are only related and designed to be used in this menu class. Sometime you don’t want internal classes to be provided to the outer world. But also in this case I would named the class MenuButton to be clear it is not a regular Button class.


in this case you want a simple interface in your sketch, when a menu button is pressed, so the Button should be a parameter to identify which menu button is pressed, hence

public void buttonPressed(Button b) {
 if (b.title.equals("Add")) {
  // do something related to Add Button
 }

or

public void buttonPressed(String id) {
 if (id.equals("Add")) {
  // do something related to Add Button
 }
}

Usually I would say the MenuButtons should have a @FunctionalInterface to call it like

 menuBar.addButton("Add",YELLOW,BLACK, () -> { 
    // do stuff for Add Button here
 });

For a menu class to be truly good, stable and flexible, there’s much more to be done… :wink:
I would say even the MenuButton is an abstract base class (or Interface) to many (ie TextButton, ImageButton, OptionButton, etc.) :slight_smile:

Cheers
— mnse

1 Like

my current version - a bit as described above



// Button class used by MenuBar class
// https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

final color BLUE = color(64, 124, 188);
final color LTGRAY = color(185, 180, 180);
final color YELLOW = color(245, 250, 13);
final color RED = color(255, 0, 0);
final color BLACK = color(0, 0, 0);
final color WHITE = color(255, 255, 255);
final color GREEN = color(0, 255, 0);

color MENU_DEFAULT_BGCOLOR = color(180);
int   BTN_DEFAULT_WIDTH = 80;
int   BTN_DEFAULT_HEIGHT = 24;
int   BTN_DEFAULT_TEXTSIZE = 18;

MenuBar menuBar;
int cmd = -1;

// -------------------------------------------------------------------------------------------------

void setup() {
  size(950, 450);
  background(225);

  menuBar = new MenuBar(10, 10, LTGRAY);

  menuBar.addButton("Add", YELLOW, BLACK);
  menuBar.addButton("Save", GREEN, BLACK);
  menuBar.addButton("Clear", RED, BLACK);
  menuBar.addButton("Open", WHITE, BLACK);
}

void draw() {
  menuBar.display();
  showCmdNumber();
}

// ---------------------------

void mousePressed() {
  String myCmd =
    menuBar.handleMouseEvent(mouseX, mouseY, true);
  buttonPressed( myCmd );
}

void mouseMoved() {
  menuBar.handleMouseEvent(mouseX, mouseY, false);
}

// ---------------------------

void buttonPressed(String b) {
  cmd = -1;
  println("Button pressed: " + b);

  switch (b) {
  case "Add":
    cmd = 0;
    rect(random(width), random(height), 22, 11);
    break;
  case "Save":
    cmd = 1;
    break;
  case "Clear":
    cmd = 2;
    background(225);
    break;
  case "Open":
    cmd = 3;
    break;
  }
}

void showCmdNumber() {
  // yellow box
  fill(YELLOW);
  stroke(BLACK);
  rect (width/2-100, height/2-20, 200, 40);

  // its text
  fill(0);
  textAlign(CENTER);
  text ("command: "
    + cmd, width/2, height/2);
  textAlign(LEFT);
} //func

// =========================================================================================

class MenuBar {

  // class MenuBar is using class Button

  int x, y,
    w, h;
  int marginX, marginY;
  color bkgrnd;

  ArrayList<Button> buttons;

  // ---------------------------

  // Constructor
  MenuBar(int xpos, int ypos, color background) {
    x = xpos;
    y = ypos;
    w = 0;
    h = 0;
    marginX=10;
    marginY=10;
    bkgrnd = background;
    buttons = new ArrayList<Button>();
  }  // Constructor

  // ---------------------------

  void addButton(String titleStr, color background, color foreground) {
    buttons.add(new Button(titleStr, background, foreground));
    updateParams();
  }

  void updateParams() {
    int bx = x+marginX;
    w=marginX;
    for (Button b : buttons) {
      b.x = bx;
      b.y = y+marginY;
      w+=b.w+marginX;
      if (b.h > h)
        h=b.h+2*marginY;
      bx+=b.w+marginX;
    }
    w+=marginX;
  }

  String handleMouseEvent(int mx, int my,
    boolean isPressed) {

    String result;

    for (Button b : buttons) {
      if (isPressed) {
        result = b.handlePressed(mx, my);
        if (!result.equals("NONE"))
          return result;
      } else {
        result = b.handleHover(mx, my);
        // return b.handleHover(mx, my);
      }//else
    }//for
    return "NONE";
  }

  void display() {
    noStroke();
    fill(bkgrnd);
    rect(x, y, w, h);
    for (Button b : buttons)
      b.display();
  }
} // End of MenuBar class

// =========================================================================================

class Button {

  // class Button is used by class MenuBar

  int x, y,
    w, h;
  String title;
  color btnColor;
  color txtColor;
  float txtSize;
  boolean hover;

  // ---------------------------

  // Constructor
  Button(String titleStr, color background, color foreground) {
    w = BTN_DEFAULT_WIDTH;
    h = BTN_DEFAULT_HEIGHT;
    title = titleStr;
    btnColor = background;
    txtColor = foreground;
    txtSize = BTN_DEFAULT_TEXTSIZE;
    hover=false;
  }  // Constructor

  // ---------------------------

  String handlePressed(int mx, int my) {
    if (mx > x &&
      mx < x+w &&
      my > y &&
      my < y+h)
      return title;
    else
      return "NONE";
  }

  String handleHover(int mx, int my) {
    hover = false;
    if (mx > x &&
      mx < x+w &&
      my > y &&
      my < y+h) {
      hover = true;
    }

    return "NONE";
  }

  void display() {
    pushStyle();
    fill(btnColor); // button color
    noStroke();

    if (hover) {
      strokeWeight(2);
      stroke(txtColor);
    }

    rect(x, y, w, h, 15); // rounded rect
    fill(txtColor); // text color
    textAlign(CENTER, CENTER);
    textSize(txtSize);
    text(title, x+w/2, y+h/2);
    popStyle();
  }
} // End of Button class
//

2 Likes

Well, in the context you’ve implemented buttonPressed(), I couldn’t guess it was supposed to be some kind of callback.

In such case, a functional interface is indeed the right path.
And class Button should have a method to register such callback.

There’s always a chance that parameter id might be null.

So, when we are comparing a variable to a literal String, always invoke equals() over the literal:
if ("Add".equals(id)) {

Or for multiple literal strings, prefer a switch () / case: block as @Chrisir did.

Still that’s not enough compelling reason to place it nested inside another top-level class: Just don’t declare it as public.

1 Like

Since this post seems somewhat controversial the following is an alternative that resembles a menubar. AWT buttons are used on the JFrame of the default window and the canvas is offset downward to expose them. Button events are trapped with a separate ActionListener on each button.

import java.awt.*;
import java.awt.event.*;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

int _wndW = 600;
int _wndH = 500;

void buildMenuBar() {

  Button addBtn = new Button("Add");
  addBtn.setBounds(30, 15, 80, 30);
  frame.add(addBtn);
  addBtn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      println("Add btn hit.");
    }
  }
  );

  Button saveBtn = new Button("Save");
  saveBtn.setBounds(120, 15, 80, 30);
  frame.add(saveBtn);
  saveBtn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      println("Save btn hit.");
    }
  }
  );

  Button clearBtn = new Button("Clear");
  clearBtn.setBounds(210, 15, 80, 30);
  frame.add(clearBtn);
  clearBtn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      println("Clear btn hit.");
    }
  }
  );
  
  Button openBtn = new Button("Open");
  openBtn.setBounds(300, 15, 80, 30);
  frame.add(openBtn);
  openBtn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent actionEvent) {
      println("Open btn hit.");
    }
  }
  );
}

void setup() {
  size(_wndW, _wndH);
  surface.setTitle("AWT Components with Default AWT Canvas");
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(100, 100, _wndW, _wndH);
  canvas.setBounds(0, 60, _wndW, _wndH - 60); 
  buildMenuBar();
}

void draw() {
  fill(0);
  rect(100, 100, 300, 200);
}

Output:

1 Like

Re-posted w/ minimal changes so it works in P3 as well:

import java.awt.*;
import java.awt.event.*;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

int _wndW = 600;
int _wndH = 500;

void buildMenuBar() {
  Button addBtn = new Button("Add");
  addBtn.setBounds(30, 15, 80, 30);
  frame.add(addBtn);
  addBtn.addActionListener( new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      println("Add btn hit.");
    }
  }
  );

  Button saveBtn = new Button("Save");
  saveBtn.setBounds(120, 15, 80, 30);
  frame.add(saveBtn);
  saveBtn.addActionListener( new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      println("Save btn hit.");
    }
  }
  );

  Button clearBtn = new Button("Clear");
  clearBtn.setBounds(210, 15, 80, 30);
  frame.add(clearBtn);
  clearBtn.addActionListener( new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      println("Clear btn hit.");
    }
  }
  );

  Button openBtn = new Button("Open");
  openBtn.setBounds(300, 15, 80, 30);
  frame.add(openBtn);
  openBtn.addActionListener( new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      println("Open btn hit.");
    }
  }
  );
}

void settings() {
  size(_wndW, _wndH);
}

void setup() {
  surface.setTitle("AWT Components with Default AWT Canvas");
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(100, 100, _wndW, _wndH);
  canvas.setBounds(0, 60, _wndW, _wndH - 60); 
  buildMenuBar();
  fill(0);
}

void draw() {
  rect(100, 100, 300, 200);
}
1 Like

Hi,

Yet another sample menu implementation (based on @svan’s version) using swing …

Cheers
— mnse

20-09-2023_13-53-32

import java.awt.*;
import java.awt.event.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.BorderFactory;
import java.awt.geom.RoundRectangle2D;

JButton createButton(String t, int w, int h, Color bg, Color fg, ActionListener a) {
  JButton btn = new JButton(t) {
    private Shape shape;

    @Override
      protected void paintComponent(Graphics g) {
      Graphics2D g2d = (Graphics2D) g;
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      g2d.setColor(getBackground());
      g2d.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25);
      super.paintComponent(g);
    }

    @Override
      protected void paintBorder(Graphics g) {
      if (getModel().isRollover()) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.MAGENTA);
        g2d.setStroke(new BasicStroke(3));
        g2d.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 25, 25);
      }
    }

    @Override
      public boolean contains(int x, int y) {
      if (shape == null || !shape.getBounds().equals(getBounds())) {
        shape = new RoundRectangle2D.Float(0, 0, getWidth(), getHeight(), 25, 25);
      }
      return shape.contains(x, y);
    }
  };
  btn.setOpaque(false);
  btn.setBorderPainted(false);
  btn.setContentAreaFilled(false);
  btn.setBackground(bg);
  btn.setForeground(fg);
  btn.setSize(w, h);
  btn.addActionListener(a);
  return btn;
}

void buildMenuBar(JFrame frame) {
  Dimension d = frame.getSize();
  JMenuBar bar = new JMenuBar();
  bar.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
  bar.setBackground(Color.DARK_GRAY);
  bar.add(createButton("Add", 80, 30, Color.yellow, Color.black, new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      buttonPressed(((JButton)actionEvent.getSource()).getText());
    }
  }
  ));
  bar.add(createButton("Save", 80, 30, Color.green, Color.black, new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      buttonPressed(((JButton)actionEvent.getSource()).getText());
    }
  }
  ));
  bar.add(createButton("Clear", 80, 30, Color.red, Color.black, new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      buttonPressed(((JButton)actionEvent.getSource()).getText());
    }
  }
  ));
  bar.add(createButton("Open", 80, 30, Color.white, Color.black, new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      buttonPressed(((JButton)actionEvent.getSource()).getText());
    }
  }
  ));
  frame.setJMenuBar(bar);
  frame.pack();
  frame.setSize(new Dimension((int)d.getWidth(), (int)(d.getHeight() + bar.getPreferredSize().getHeight())));
  frame.validate();
}

String recentButton = "";

void setup() {
  size(500, 500);
  textSize(50);
  textAlign(CENTER, CENTER);
  surface.setTitle("individual Menu using Swing components");
  buildMenuBar((JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame());
}

void buttonPressed(String id) {
  println(id + " button pressed");
  recentButton = id;
}

void draw() {
  background(0);
  stroke(255, 0, 0);
  noFill();
  rect(0, 0, width-1, height-1);
  fill(255);
  text(recentButton, width/2, height/2);
}
1 Like