// 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.");
}
}
I’ve moved class Button outside class MenuBar and your code still worked after some few small changes to variables’ declaration & instantiation:
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!
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
// 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);
}
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
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…
I would say even the MenuButton is an abstract base class (or Interface) to many (ie TextButton, ImageButton, OptionButton, etc.)
Cheers
— mnse
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
//
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
.
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:
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);
}
Hi,
Yet another sample menu implementation (based on @svan’s version) using swing …
Cheers
— mnse
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);
}