I would like to draw some infinite object in Piccolo2D, like endless rectangular (cartesian) grid. It would be greate to have some geometric object on this grid, like in graphics editor.
Unfortunately, Piccolo somehow determines, whether it is required to call paint and does not do this appropriatedly for me.
The program below contains yellow object, which I want to make endless. It's type is PEndless. I am adding circle to it as a child.
package tests.endless;
import java.awt.Color;
import java.awt.geom.Rectangle2D;
import org.piccolo2d.PNode;
import org.piccolo2d.extras.PFrame;
import org.piccolo2d.nodes.PPath;
import org.piccolo2d.util.PPaintContext;
public class Try_PGrid {
public static class PEndless extends PNode {
#Override
protected void paint(PPaintContext paintContext) {
Rectangle2D localClip = paintContext.getLocalClip();
paintContext.getGraphics().setColor(Color.yellow);
paintContext.getGraphics().fill(localClip);
}
}
public static void main(String[] args) {
new PFrame() {
#Override
public void initialize() {
PPath circle = PPath.createEllipse(0, 0, 100, 100);
PEndless grid = new PEndless();
grid.addChild(circle);
//grid.setBounds(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); // not working at all
//grid.setBounds(-10, -10, 50, 50); // yellow until circle visible
grid.setBounds(-1000, -1000, 2000, 2000); // yellow until bounds
getCanvas().getLayer().addChild(grid);
}
};
}
}
Unfortunately, I found that:
1) if bounds are default (empty) then yellow paints only if circle is visible
2) if bounds are big and finite, then yellow paints withing bounds
3) it is not supporting infinite doubles, although this is legal in principle; if set infinite, it does no draw anything at all.
How to implement?
It's part of how Java handles the drawing.
Look here for more information on this and a guide on how to prevent this issue.
Related
I was programming a game inspired by Conway's "Game of Life".
Although I have the overall game logic figured out (but not coded), I am still having trouble with getting the fill colors of my rectangle objects to change once the player's first turn is over. When I run my program it skips over the requirement for player one's color (Color.BLUE) and goes straight to player two's color (Color.RED).
Here is the code:
//William Fisher
//July.11.2017
package cellularautomatagame;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.canvas.*;
import javafx.scene.input.MouseEvent;
import static javafx.scene.paint.Color.*;
public class CellularAutomataGame extends Application {
#Override
public void start(Stage stage) {
Group root = new Group();
Scene s = new Scene(root, 300, 300, Color.BLACK);
Canvas canvas = new Canvas(1280,720);
GraphicsContext gc = canvas.getGraphicsContext2D();
root.getChildren().add(canvas);
stage.setScene(s);
stage.show();
gc.setFill(WHITE);
gc.fillRect(0, 0, 5, 720);
gc.fillRect(0, 0, 1280, 5);
gc.fillRect(0, 715, 1280, 5);
gc.fillRect(1275, 0, 5, 720);
Player player1 = new Player();
Player player2 = new Player();
player1.playerFirstMove(root,canvas,Color.BLUE);
player2.playerFirstMove(root,canvas,Color.RED);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
//William Fisher
// July.11.2017
package cellularautomatagame;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import static javafx.scene.paint.Color.*;
import javafx.scene.shape.Rectangle;
public class Player {
int firstMove = 0;
public void playerFirstMove(Group root,Canvas canvas,Color color){
canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>(){
#Override
public void handle (MouseEvent e){
while(firstMove < 1){
if(e.getClickCount() == 1){
Rectangle r = new Rectangle(e.getX(),e.getY(),5,5);
r.setFill(color);
root.getChildren().add(r);
firstMove++;
}
}
}
});
firstMove--;
}
}
/** (07/11/2017)Current Problem: The first player is unable to make their first move. Only the
* second player is able to make a first move.
*
* (07/16/2017)Current Problem: Same as previous problem, changed the code so that a rectangle
* object would spawn upon mouse click event. Problem possibly has to do with "setFill()" function.
*/
On line 52 of the main JavaFX method where it shows player1's first turn, it should call the playerFirstMove method and allow a blue rectangle to spawn once the mouse is clicked, as shown in the playerFirstMove method starting on line 18 of the Player class. However when the mouse is clicked, one red rectangle is spawned instead of a blue one. It is as though the program skipped over player1.playerfirstMove(...) on line 52 and went straight to the player2.playerfirstMove(...) on line 53. I've tried for hours to fix this small problem, reading the JavaFX API and searching the internet. The program is doing what I want it to do (spawn only one rectangle per mouse click) but it seems as though it is skipping the instructions on line 52 (player1.playerfirstMove(...)).
To me it seems as though there may be a bug involving the setFill() function?
I would deeply appreciate any help.
Thanks All!
If I understand correctly every mouse click should draw a rectangle of the current player and pass turn to the next player. If so, I reworked your code to have Player with color and draw rectangle logic only:
class Player {
private final Color color;
Player(final Color color) {
this.color = color;
}
void doSomething(final Group root, final double x, final double y) {
Rectangle r = new Rectangle(x, y, 5, 5);
r.setFill(color);
root.getChildren().add(r);
}
}
In main class I have organized cycled iteration (by using Google guava collection utils) and the iterator allows to work only with the current player:
Player player1 = new Player(Color.BLUE);
Player player2 = new Player(Color.RED);
Player player3 = new Player(Color.YELLOW);
final Iterator<Player> playerIterator = Iterators.cycle(player1, player2, player3);
canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getClickCount() == 1) {
playerIterator.next().doSomething(root, e.getX(), e.getY());
}
});
As result I may have even 3 players and each click triggers only the next one:
BTW, this solution allows to have as many players as needed.
I am trying to write a game where the player is a character, falling interminably through the Earth. However, I am getting stuck at the very beginning, programming the scrolling background. My current approach is to create a JFrame and add to it an object of a class that extends JFrame. In this second class, I open the background image in the constructor. Then, in the first class, I create a new thread for the second class, and alternate sleeping and moving the y-coordinate of the background. Back in the second class, this movement triggers a repaint, and the image is drawn twice, once at the y-coordinate, and once at the y-coordinate minus the height of the JPanel. This current code gives the desired affect at any given frame, but the movement is slow and uneven. I think it has something to do with the amount of repaint requests, but I am fairly new to graphics in java. My question is, can this be fixed so the image move steadily across the screen, or should I try a completely different approach? If my current method is fundamentally flawed and can not be fixed, could you provide some incite as to how I can produce a smooth moving image? Thank you in advance.
This is my current code:
//ScrollingImage.java
import javax.swing.JFrame;
public class ScrollingImage {
public static void main(String[] args){
JFrame holder = new JFrame("New Game");
Background background = new Background();
Thread thread = new Thread(background);
holder.setSize(400, 400);
holder.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
holder.add(background);
holder.setVisible(true);
thread.start();
while (true){
background.move();
try {
thread.sleep(100);
}
catch (InterruptedException e){}
}
}
}
and:
//Background.java
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
public class Background extends JPanel implements Runnable {
private BufferedImage image;
private int topEdgeY;
public Background(){
try {
image = ImageIO.read(new File("background.png"));
}
catch (Exception e){}
topEdgeY = 0;
}
public void run(){
repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(image, 0, topEdgeY, getWidth(), getHeight(), this);
g.drawImage(image, 0, topEdgeY - getWidth(), getWidth(), getHeight(), this);
}
public void move(){
topEdgeY += 5;
if (topEdgeY == getWidth())
topEdgeY = 0;
repaint();
}
}
You need a proper game loop. Currently, your game sleeps 100ms after every iteration, that is (even if your updating and rendering takes no time) 10fps and will go even lower as your game doing more job.
Game loops is a huge topic and there are several good approaches for different situations like is it a multiplayer game or how much physics involved in the game but for your case a simple one will just be fine.
A good paper about game loops, hopefully this will makes you understand:
http://gameprogrammingpatterns.com/game-loop.html
And a pretty good example written in Java:
http://www.java-gaming.org/index.php?topic=24220.0
I'm trying to make a drawing program that will draw circles onto a canvas using the mouseDragged() method from MouseMotionListener. Inside my init() method, I put in this.addMouseMotionListener(this) and got this error message:
Cannot find symbol - method addMouseMotionListener(Canvas).
I am trying to make it so that every time the mouse is dragged, the Brush (which is just a circle), will draw onto the DrawingBoard which has a Canvas on it
Here is the code for the DrawingBoard:
import java.awt.*;
import java.awt.geom.*;
import java.awt.PointerInfo;
import java.awt.MouseInfo;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
public class DrawingBoard implements MouseMotionListener
{
private Canvas myCanvas;
private Brush myBrush;
private PointerInfo a = MouseInfo.getPointerInfo();
private Point p = a.getLocation();
private int x = (int)p.getX();
private int y = (int)p.getY();
private Brush b1 = new Brush(x, y, 10, Color.red, myCanvas);
// instance variables - replace the example below with your own
public void init() {
this.addMotionListener(this);
}
public DrawingBoard(int canvasSizeX, int canvasSizeY)
{
myCanvas = new Canvas("Drawing Board", canvasSizeX, canvasSizeY);
myCanvas.setVisible(true);
myCanvas.setForegroundColor(Color.lightGray);
}
public void mouseMoved(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
b1.draw();
}
The code for the Canvas can be found here:
http://pastebin.com/RzMpkKhy
Your DrawingBoard needs to extend some kind of AWT/Swing-Component like Frame, JFRame, Panel etc, that contains the method addMouseMotionListener(). Because your not doing this, the compiler assumes that addMouseMotionListener() is a method you defined somewhere in your class. But he can't find it (because it doesn't exist) so it throws an error. Try adding this method to the Canvas code:
public void addMouseMotionListener(MouseMotionListener ml){
canvas.addMouseMotionListener(ml);
}
and put
myCanvas.addMouseMotionListener(this);
into your init() method.
Because the Canvas you are using is a custom class (there is another Canvas in the java.awt package), you have to alter its code to support user input (it doesn't look like it was designed to do this).
I am writing an game in java with Libgdx and have a question about how to have multiple instances of the same object in an ArrayList without abusing the garbage collector.
this is the code of my main game state. I am still really new, so I assume my coding habits are downright awful:
package com.frog.game;
import java.util.ArrayList;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.math.MathUtils;
import com.frog.entities.Frog;
import com.frog.entities.Grass;
import com.frog.entities.LilyPad;
import com.frog.gamestates.GameState;
import com.frog.managers.GameStateManager;
import com.frog.managers.Jukebox;
import com.frog.managers.Save;
import com.frog.managers.SimpleDirectionGestureDetector;
import com.frog.managers.SpeedManager;
import com.frog.game.Game;
public class PlayState extends GameState {
private SpriteBatch sb;
private Frog frog;
private BitmapFont font;
private ArrayList<LilyPad> lilypads;
private Grass grass;
private float hopY;
public boolean tmp;
private SpeedManager speedManager;
private float speed;
private float[] placement;
public PlayState(GameStateManager gsm) {
super(gsm);
}
#SuppressWarnings("deprecation")
public void init() {
speedManager = new SpeedManager();
lilypads = new ArrayList<LilyPad>();
sb = new SpriteBatch();
frog = new Frog();
frog.init();
grass = new Grass();
grass.init();
hopY = Game.SIZE * 0.8f;
placement = new float[] {
0,
Game.SIZE,
Game.SIZE * 2
};
addPad();
FreeTypeFontGenerator gen = new FreeTypeFontGenerator(
Gdx.files.internal("PressStart2P-Regular.ttf")
);
font = gen.generateFont((Game.WIDTH / 10));
gen.dispose();
Gdx.input.setInputProcessor(new SimpleDirectionGestureDetector(new SimpleDirectionGestureDetector.DirectionListener() {
#Override
public void onUp() {
// TODO Auto-generated method stub
}
#Override
public void onRight() {
frog.moveRight();
}
#Override
public void onLeft() {
frog.moveLeft();
}
#Override
public void onDown() {
}
}));
}
This is the method I use to add another lilypad at the top of the screen. I make it appear in one of three places randomly. Since the main function of these lilypads is to scroll down the screen, instances of the lilypad are being added and removed from the array non stop. I know this kills because each time I add a new lilypad, I have to run init() for it, otherwise I will get a nullpointerexception. init() instantiates a bunch of objects in order to make that lilypad render, such as textures, the default Y value etc. Is there any way I could run the init() method once for the entire arraylist, even though I am constantly adding & removing them? I have considered wrapping the same lilypads around the screen when they hit the bottom, so I would only need like 7, and then I wouldn't have to add or remove anymore, but would have to rework a big chunk of code for that, and consider it a last resort. The game already runs smooth, it just has a few barely noticeable stutters here and there that I would like to avoid.
private void addPad() {
lilypads.add(new LilyPad(placement[MathUtils.random(0, 2)], 300, false));
lilypads.get(lilypads.size() - 1).init();
}
public void update(float dt) {
speed = speedManager.speed(dt);
speedManager.update(dt);
// update grass
if(!grass.shouldStop()) {
grass.update(dt, speed);
frog.introPlay(speed);
}
// update lily pads
for(int i = 0; i < lilypads.size(); i++) {
lilypads.get(i).update(dt, speed);
This is where I call the addPad() method. It basically says if the last added pad is fully visible on screen, add the next.
if(lilypads.get(i).getY() < (Game.HEIGHT - Game.SIZE) && lilypads.get(i).past()) {
addPad();
}
// hop frog
if(lilypads.get(i).getY() < hopY && lilypads.get(i).past2()) {
// play hop
if(lilypads.get(i).getX() > frog.getX() - Game.SIZE / 2 && lilypads.get(i).getX() < frog.getX() + Game.SIZE / 2){
frog.incrementScore();
Jukebox.play("hop");
lilypads.get(i).splash();
frog.play(speed);
} else {
Jukebox.stopAll();
Save.gd.setTempScore(frog.getScore());
gsm.setState(GameStateManager.GAMEOVER);
return;
}
return;
}
if(lilypads.get(i).getY() < (-Game.SIZE)){
lilypads.remove(i);
}
}
}
public void draw() {
// draw grass
if(!grass.shouldStop()) {
grass.draw(sb);
}
// draw pads
for(int i = 0; i < lilypads.size(); i++){
lilypads.get(i).draw(sb, true);
}
// draw frog
frog.draw(sb, speed, true);
sb.setColor(1, 1, 1, 1);
sb.begin();
font.draw(sb, Long.toString(frog.getScore()),
Game.WIDTH * 0.92f -font.getBounds(Long.toString(frog.getScore())).width, Game.HEIGHT * 0.958f);
sb.end();
}
public void handleInput() {
}
public void dispose() {
sb.dispose();
Jukebox.stopAll();
}
Thanks for the support.
Use libGDX's custom collections for that. You can find all of them here.
In your case you want to use an Array. They are optimized to not cause the garbage collector to kick in, for example by re-using the iterators.
With a standard ArrayList the following piece of code in your render method would create a new Iterator in every frame.
for(LilyPad lilyPad : lilipads){
lilypad.draw(sb, true);
}
In general try to avoid using new whenever you can. This wiki article might help you with it and it explains how pooling works.
Another common trick to avoid GC especially when working with Vector2 or Vector3 is keeping a private final Vector2 tmp = new Vector2() and always only using tmp.set(x, y) to change it. This principle could also be applied to any other custom class that you are creating yourself to hold some data.
I've written a small Swing program that draws a head and when a JCheckBox instance is selected/unselected by the user a hat is drawn or removed from on top of the head. I'm having some trouble taking the next step with this program -- I'd like to add a boolean field to the Head class that causes this component to listen to mouse events with a MouseListener. From there, I'd like to use two methods to set this field to true/false and render the remaining three methods lame ducks. Also, how would I change the paintComponent method so that if the boolean value is true the object is drawn with open eyes, and if it's false, the head is drawn with the eyes closed? Please provide any advice you have. Many thanks!
import javax.swing.*;
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class Head extends JPanel {
Rectangle2D.Double chapeau, chapeau2;
Ellipse2D.Double bouche, visage, oeil1, oeil2;
JCheckBox box;
public Head(){
this.setBackground(Color.WHITE);
visage = new Ellipse2D.Double (150,130,100,100);
bouche = new Ellipse2D.Double (170,180,60,27);
chapeau = new Rectangle2D.Double (170,80,60,40);
chapeau2 = new Rectangle2D.Double (125,120,150,10);
oeil1 = new Ellipse2D.Double (170,150,20,20);
oeil2 = new Ellipse2D.Double (210,150,20,20);
box = new JCheckBox("Hat");
this.add(box);
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent ie){
repaint();
}
});
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3.0f));
g2.setPaint(Color.BLUE);
g2.draw(visage);
g2.draw(oeil1);
g2.draw(oeil2);
g2.draw(bouche);
if(box.isSelected()){
g2.draw(chapeau);
g2.draw(chapeau2);
}
}
public static void main(String[] args){
JFrame f = new JFrame("Face Display Window");
f.setSize(425,285);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
f.add(new Head());
}
}
----------------------------------- Second Try
import javax.swing.*;
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class Head extends JPanel implements MouseListener {
Rectangle2D.Double chapeau, chapeau2;
Ellipse2D.Double bouche, visage, oeil1, oeil2, oeil3, oeil4;
JCheckBox box;
boolean isClosed = false;
public Head(){
this.setBackground(Color.WHITE);
visage = new Ellipse2D.Double (150,130,100,100);
bouche = new Ellipse2D.Double (170,180,60,27);
chapeau = new Rectangle2D.Double (170,80,60,40);
chapeau2 = new Rectangle2D.Double (125,120,150,10);
oeil1 = new Ellipse2D.Double (170,150,20,20);
oeil2 = new Ellipse2D.Double (210,150,20,20);
oeil3 = new Ellipse2D.Double (175,155,25,25);
oeil4 = new Ellipse2D.Double (215,155,25,25);
box = new JCheckBox("Hat");
this.addMouseListener(this);
this.add(box);
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent ie){
repaint();
}
});
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3.0f));
g2.setPaint(Color.BLUE);
g2.draw(visage);
g2.draw(oeil1);
g2.draw(oeil2);
g2.draw(bouche);
if(box.isSelected()){
g2.draw(chapeau);
g2.draw(chapeau2);
if(isClosed) {
g2.draw(oeil3);
g2.draw(oeil4);
}
else {
g2.draw(oeil1);
g2.draw(oeil2);
}
}
}
#Override
public void mouseClicked(MouseEvent e) {
isClosed = !isClosed;
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
public static void main(String[] args){
JFrame f = new JFrame("Face Display Window");
f.setSize(425,285);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
f.add(new Head());
}
}
I'm intentionally being a little vague here because I'm not sure if this is homework or not since you already have a fair amount of code that does a lot of stuff that is very similar to what you want it to and modifying it shouldn't be very difficult. However, if you're actually stuck, please clarify and I'll add more details if required.
I'd like to add a boolean field to the Head class that causes this component to listen to mouse events with a MouseListener.
This isn't too hard, let's walk through it. It's trivial to add a boolean field to your Head class - you simply declare boolean isClosed = false; - indicating that you begin the execution with the field set to false which your code will later interpret as the instruction to draw eyes open.
Your core requirement is the MouseListener. If you haven't already, check out the Java Trail for events; it explains how to implement a simple MouseListener. At this point, note that MouseListener is an interface and thus, you'd necessarily need to provide an implementation for all it's methods, even if they're empty-bodied methods. You may want to check out the MouseAdapter abstract class. It provides empty implementations of all these methods (and more) so that you only need to override the ones you need - this makes your code cleaner since you don't have a bunch of empty methods just to satisfy the compiler. This would solve the problem I believe you're referring to when you say 'and render the remaining three methods lame ducks' Of course, since you're already extending JPanel, you can't extend MouseAdapter as well so this doesn't apply here. But this (with other Adapters) is a useful class to keep in mind for later.
From there, I'd like to use two methods to set this field to true/false
If I understand you correctly, what you want is to toggle the value of isClosed on mouse clicks. So right now, you have a MouseListener/ MouseAdapter that doesn't really do anything. What you need to do next is provide an implementation for, lets say, the MouseClicked() method where you toggle the value of your boolean field. This is really easy as well - you simply invert the current value using the ! (NOT) operator and assign it back the variable - isClosed = !isClosed;. You may wish to read up on operators in Java in more detail.
Also, how would I change the paintComponent method so that if the boolean value is true the object is drawn with open eyes, and if it's false, the head is drawn with the eyes closed?
One way of doing this is to create two more shapes for the two closed eyes, similar to the ones you have for open eyes. Once you've done that, it's a simple matter of deciding which ones to draw (i.e. the closed eyes or the open ones) on the basis of the value of isClosed. So you'd use an if clause to check the value of isClosed and draw the open eyes when it's false and the closed eyes when true. Note that since your value of isClosed is being modified in your click handler, you need to make sure that you call repaint() when you change the value otherwise Swing may not update the panel immediately to show the change so then you won't see anything happen.
To sum it up, one way to do what you want is to make the following modifications to Head:
public class Head
extends JPanel
implements MouseListener {
//...all your other declarations still go here
boolean isClosed = false;
//also declare new 'eyes' which are closed
public Head() {
//..all your existing code is still here
//add code to instantiate closed eyes
//need to register a new MouseListener
//since head itself is a MouseListener, we can pass this as the argument
this.addMouseListener(this);
}
//...all your existing code still goes here
public void paintComponent(Graphics g) {
//...all your existing code still goes here
//decide which eyes to draw depending on isClosed
if(isClosed) {
//draw closed eyes
}
else {
//draw open eyes
}
//draw everything else as before
}
//implementation for MouseListener
//don't forget the rest of the MouseListener events
//mousePressed, mouseReleased, mouseEntered, mouseExited
public void mouseClicked(MouseEvent e) {
//toggle the value of isClosed
isClosed = !isClosed;
//must repaint
repaint();
}