Java snail line layout manager - java

i am looking for a java custom layout manager that arranges a couple of data (e.g. jlabels) in a way similar to the snail's back line.
So far, i have tried to work on the circle layout that i found on the internet to customize it but with no luck.. Any ideas???

You can write your own layouts. Spiral formula i got from Spirals. It Archimedean while a snail is more like Fermat's spiral, you could change the calculatePoint() method to return a different spiral.
Note: this layout is a bit inefficient since it recalculates all components positions every times instead of caching it.
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class SpiralLayout implements LayoutManager2 {
private enum Size { MIN, MAX, PREF }
private double radiusStep;
private double angleStep;
public SpiralLayout() {
this(10, Math.toRadians(15.0));
}
public SpiralLayout(double radius, double stepSize) {
this.radiusStep = radius;
this.angleStep = stepSize;
}
public void setRadiusStep(double radiusStep) {
this.radiusStep = radiusStep;
}
public void setAngleStep(double angleStep) {
this.angleStep = angleStep;
}
#Override
public void addLayoutComponent(String name, Component comp) {
// calculated on the fly
}
#Override
public void removeLayoutComponent(Component comp) {
// calculated on the fly
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return getSize(parent, Size.PREF);
}
private Dimension getSize(Container parent, Size size) {
doLayoutContainer(parent, Short.MAX_VALUE, Short.MAX_VALUE, size);
Point min = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
Point max = new Point(0, 0);
for(Component component : parent.getComponents()) {
Dimension preferredSize = getSize(component, size);
min.x = Math.min(min.x, component.getX());
min.y = Math.min(min.y, component.getY());
max.x = Math.max(max.x, component.getX() + preferredSize.width);
max.y = Math.max(max.y, component.getY() + preferredSize.height);
}
int center = Short.MAX_VALUE / 2;
return new Dimension(
Math.max(Math.abs(center - min.x), Math.abs(center - max.x) * 2),
Math.max(Math.abs(center - min.y), Math.abs(center - max.y) * 2));
}
private Dimension getSize(Component component, Size size) {
switch(size) {
case MAX:
return component.getMaximumSize();
case MIN:
return component.getMinimumSize();
case PREF:
return component.getPreferredSize();
default:
assert false : "Unknown size: " + size;
return new Dimension();
}
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return getSize(parent, Size.MIN);
}
#Override
public void layoutContainer(Container parent) {
doLayoutContainer(parent,
parent.getWidth(), parent.getHeight(), Size.PREF);
}
private List<Rectangle> doLayoutContainer(Container parent,
int width, int height, Size size) {
Point offset = new Point(width / 2, height / 2);
List<Rectangle> componentBounds = new ArrayList<Rectangle>();
double angle = 0;
double radius = 1;
for(Component component : parent.getComponents()) {
Dimension preferredSize = getSize(component, size);
Rectangle bounds;
do {
bounds = new Rectangle(
add(calculatePoint(angle, radius), offset),
preferredSize);
angle += angleStep;
radius += radiusStep;
}
while(overlaps(bounds, componentBounds));
component.setBounds(bounds);
componentBounds.add(bounds);
}
return componentBounds;
}
private Point calculatePoint(double angle, double radius) {
double x = radius * Math.cos(angle);
double y = radius * Math.sin(angle);
return new Point((int) x, (int) y);
}
private boolean overlaps(Rectangle bounds, List<Rectangle> componentBounds) {
for(Rectangle other : componentBounds) {
if(other.intersects(bounds)) {
return true;
}
}
return false;
}
private Point add(Point a, Point b) {
return new Point(a.x + b.x, a.y + b.y);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
// calculated on the fly
}
#Override
public Dimension maximumLayoutSize(Container parent) {
return getSize(parent, Size.MAX);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
// calculated on the fly
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
final SpiralLayout layout = new SpiralLayout();
final JPanel panel = new JPanel(layout);
final JSpinner angleSpinner = new JSpinner(new SpinnerNumberModel(Math.toDegrees(layout.angleStep), 1.0, 360.0, 5.0));
angleSpinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
double angle = (Double) angleSpinner.getValue();
layout.setAngleStep(Math.toRadians(angle));
panel.revalidate();
}
});
final JSpinner radiusSpinner = new JSpinner(new SpinnerNumberModel((int) layout.radiusStep, 1, 1000, 1));
radiusSpinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int radius = (Integer) radiusSpinner.getValue();
layout.setRadiusStep(radius);
panel.revalidate();
}
});
JPanel buttons = new JPanel();
buttons.add(new JLabel("Radius step:"));
buttons.add(radiusSpinner);
buttons.add(new JLabel("Angle step"));
buttons.add(angleSpinner);
for(int i = 1; i <= 25; i++) {
panel.add(new JLabel("Label " + i));
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(buttons, BorderLayout.PAGE_START);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Related

get width and height of JPanel outside of the class

So I created a simple simple simulation where squares are spawned randomly with random vectors and bounce of the edges of the window.
I wanted it to take into account the window being resized. So that if I change the dimensions of the window from 600x600 to 1200x600 the squares will bounce of the new border rather than 600x600.
I tried doing getWidth() getHeight() but it would return 0.
So I put it in the pain() (since it gets called on window resize) method and saved the return values as local variables. But I cannot call getjpWidth() from the Rect class.
So basically what I need is to get new window dimension into the move() method in the Rect class.
Please feel free to point out any other mistakes and things that can be done better. I'm new to 2D programming (studying Computer Science)
Application
import javax.swing.*;
public class Application {
private Application(){
//create a JFrame window
JFrame frame = new JFrame("Moving Squares");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//add a JPanel
GamePanel gamePanel = new GamePanel();
frame.add(gamePanel);
//pack the window around the content
frame.pack();
//center
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String args[]){
new Application();
}
}
GamePanel
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
public class GamePanel extends JPanel implements Runnable{
private int jpWidth=0, jpHeight=0;
//set JPanel size
private static final Dimension DESIRED_SIZE = new Dimension(600,600);
#Override
public Dimension getPreferredSize(){
return DESIRED_SIZE;
}
//constructor
GamePanel(){
Thread t = new Thread(this);
t.start();
}
private ArrayList <Rect> rect=new ArrayList<>();
public void run(){
for(int i=0; i<15; i++){
rect.add(new Rect());
}
while(true){
for(Rect rect:rect){
rect.move();
}
//repaint still image for better frames
//should be 100fps instead it's >144fps
repaint();
try{Thread.sleep(10);}
catch(InterruptedException e){/**/};
repaint();
try{Thread.sleep(10);}
catch(InterruptedException e){/**/};
repaint();
try{Thread.sleep(10);}
catch(InterruptedException e){/**/};
}
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g.create();
jpWidth=getWidth();
jpHeight=getHeight();
g2d.setColor(Color.white);
g2d.fillRect(0,0,jpWidth,jpHeight);
for(Rect rect:rect) {
g2d.setColor(Color.black);
g2d.fillRect(rect.getXcord()-1, rect.getYcord()-1, rect.getWidth()+2, rect.getHeight()+2);
g2d.setColor(Color.getHSBColor(rect.getR(), rect.getG(), rect.getB()));
g2d.fillRect(rect.getXcord(), rect.getYcord(), rect.getWidth(), rect.getHeight());
}
}
public int getJpWidth() {
return jpWidth;
}
public int getJpHeight() {
return jpHeight;
}
}
Rect
import java.util.Random;
public class Rect {
//properties
private int width=30, height=30;
private int R, G, B;
//movement
private int xCord, yCord;
private int xVector, yVector;
private int xSlope, ySlope;
public Rect(){
Random rand = new Random();
//random color
R=rand.nextInt(255);
G=rand.nextInt(255);
B=rand.nextInt(255);
//random spawn position
xCord=rand.nextInt(600-width);
yCord=rand.nextInt(600-height);
//direction
do{
xVector=rand.nextInt(3) - 1;
yVector=rand.nextInt(3) - 1;
}while(xVector==0 || yVector==0);
//slope
do{
xSlope=rand.nextInt(3);
ySlope=rand.nextInt(3);
}while(xSlope==0 || ySlope==0);
xVector*=xSlope;
yVector*=ySlope;
}
public void move(){
//if(xCord>=//how to get screen width ? ){}
if((xCord>=600-width) || (xCord<=0)){
bounceX();
}
if((yCord>=600-height) || (yCord<=0)) {
bounceY();
}
xCord+=xVector;
yCord+=yVector;
}
public void bounceX(){
xVector*=-1;
}
public void bounceY(){
yVector*=-1;
}
public int getR() {
return R;
}
public int getG() {
return G;
}
public int getB() {
return B;
}
public int getXcord() {
return xCord;
}
public int getYcord() {
return yCord;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
}
So basically what I need is to get new window dimension into the move() method in the Rect class.
Don't know if it is the best design but I pass the "panel" as a parameter to the "move()" method so its width/height can be used.
Here is some old code I have lying around that shows this approach:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class BallAnimation4
{
private static void createAndShowUI()
{
BallPanel panel = new BallPanel();
JFrame frame = new JFrame("BallAnimation4");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.pack();
frame.setLocationRelativeTo( null );
//frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible( true );
panel.addBalls(5);
panel.startAnimation();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
class BallPanel extends JPanel implements ActionListener
{
private ArrayList<Ball> balls = new ArrayList<Ball>();
public BallPanel()
{
setLayout( null );
// setBackground( Color.BLACK );
}
public void addBalls(int ballCount)
{
Random random = new Random();
for (int i = 0; i < ballCount; i++)
{
Ball ball = new Ball();
ball.setRandomColor(true);
ball.setLocation(random.nextInt(getWidth()), random.nextInt(getHeight()));
// ball.setMoveRate(32, 32, 1, 1, true);
ball.setMoveRate(16, 16, 1, 1, true);
// ball.setSize(32, 32);
ball.setSize(64, 64);
balls.add( ball );
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball ball: balls)
{
ball.draw(g);
}
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(800, 600);
}
public void startAnimation()
{
Timer timer = new Timer(1000/60, this);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
private void move()
{
for (Ball ball : balls)
{
ball.move(this);
}
}
class Ball
{
public Color color = Color.BLACK;
public int x = 0;
public int y = 0;
public int width = 1;
public int height = 1;
private int moveX = 1;
private int moveY = 1;
private int directionX = 1;
private int directionY = 1;
private int xScale = moveX;
private int yScale = moveY;
private boolean randomMove = false;
private boolean randomColor = false;
private Random myRand = null;
public Ball()
{
myRand = new Random();
setRandomColor(randomColor);
}
public void move(JPanel parent)
{
int iRight = parent.getSize().width;
int iBottom = parent.getSize().height;
x += 5 + (xScale * directionX);
y += 5 + (yScale * directionY);
if (x <= 0)
{
x = 0;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (x >= iRight - width)
{
x = iRight - width;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (y <= 0)
{
y = 0;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
if (y >= iBottom - height)
{
y = iBottom - height;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, width, height);
}
public void setColor(Color c)
{
color = c;
}
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
}
public void setMoveRate(int xMove, int yMove, int xDir, int yDir, boolean randMove)
{
this.moveX = xMove;
this.moveY = yMove;
directionX = xDir;
directionY = yDir;
randomMove = randMove;
}
public void setRandomColor(boolean randomColor)
{
this.randomColor = randomColor;
switch (myRand.nextInt(3))
{
case 0: color = Color.BLUE;
break;
case 1: color = Color.GREEN;
break;
case 2: color = Color.RED;
break;
default: color = Color.BLACK;
break;
}
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
}
}
Also, note that for animation you should be using a Swing Timer to schedule the animation. Updates to Swing components should be done on the Event Dispatch Thread (EDT). While not likely to cause a problem with this simple application it is a good habit to make sure this basic rule is followed otherwise you can have random problems and it is never easy to debug a random problem.

How to get my Buffered Image class to display in my GUI?

I have a program that does an animation using timers switching images. When the program is on its last image I use a class to create a buffered image of that image with text over it. When the last image of the animation is displayed I want to change the image displayed to the buffered image. I can't get it to work. The code as is plays as if the bolded section isnt there. If I delete the line above it, it displays the image with text over it and nothing else. What edits should I make to my code to fix this?
The Class that does the animation
**import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.image.*;
import java.io.*;
import java.io.File;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.swing.*;
import javax.swing.*;
import javax.imageio.ImageIO;
/**
* Write a description of class Reveal here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Reveal extends JPanel
{
private JPanel panel = new JPanel(); //a panel to house the label
private JLabel label = new JLabel(); //a label to house the image
private String[] image = {"Jack in the Box 1.png","Jack in the Box 2.png","Jack in the Box 3.png","Jack in the Box 4.png","Jack in the Box 5.png","Jack in the Box 6.png","Jack in the Box 7.png"}; //an array to hold the frames of the animation
private ImageIcon[] icon = new ImageIcon[7]; //an array of icons to be the images
private JFrame f;
private TextOverlay TO;
private Timer timer;
private Timer timer2;
int x = 0;
int y = 4;
int counter = 0;
/**
* Constructor for objects of class Reveal
*/
public Reveal(String name, int number)
{
TO = new TextOverlay("Jack in the Box 7.png", name, number);
for (int h = 0; h < 7; h++){
icon[h] = new ImageIcon(image[h]);
icon[h].getImage();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//Sets the size of the window
f.setSize(800,850);
panel = new JPanel();
label = new JLabel();
label.setIcon( icon[x] );
panel.add(label);
setVisible(true);
f.add(panel);
display(name, number);
**f.add(TO);**
}
public void display(String name, int number){
timer = new Timer(150, new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (counter > 27){
timer.stop();
timer2.start(); //starts the second half of the animation
}else{
if (x != 3){
x++;
}else{
x = 0;
}
label.setIcon( icon[x] );
counter++;
} //ends if-else
} //ends action method
}); //ends timer
timer2 = new Timer(250, new ActionListener(){
public void actionPerformed(ActionEvent e){
if (y > 6) {
timer2.stop();
}else{
label.setIcon( icon[y] );
y++;
} //ends if-else
} //ends action method
}); //ends timer2
timer.start();
}
}
**
The class that puts text over an image
import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
/**
* #see https://stackoverflow.com/questions/2658663
*/
public class TextOverlay extends JPanel {
private BufferedImage image;
private String name;
private String fileX;
private int number;
public TextOverlay(String f, String s, int n) {
name = s;
number = n;
fileX = f;
try {
image = ImageIO.read(new File(fileX));
} catch (IOException e) {
e.printStackTrace();
}
image = process(image, name, number);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
private BufferedImage process(BufferedImage old, String name, int number) {
int w = old.getWidth();
int h = old.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.black);
g2d.setFont(new Font("Franklin Gothic Demi Cond", Font.PLAIN, 30));
String s1 = name;
String s2 = Integer.toString(number);;
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(s1, 40, 90);
g2d.drawString(s2, 40, 140);
g2d.dispose();
return img;
}
}
So, you seem to have a misunderstanding of how Swing works, you might find How to Use Swing Timers and Concurrency in Swing of some assistance.
Basically, when you start a Timer, it doesn't block at this point until the timer ends (and even if it did, your wouldn't work the way you wanted it to). Instead, a new thread is created and after the specified period a request is placed on Event Dispatching Thread to execute the supplied Runnable.
This means that when you do something like...
f.add(panel);
display(name, number);
f.add(TO);
You are actually adding the TO component onto of the JLabel (because the frame is using a BorderLayout and the CENTRE position is the default position.
Instead, in your second timer completes, you need to remove the label and add the TO component...
timer2 = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (y > 6) {
timer2.stop();
Container parent = label.getParent();
parent.remove(label);
parent.add(TO);
parent.revalidate();
} else {
label.setIcon(icon[y]);
y++;
} //ends if-else
} //ends action method
}); //ends timer2
Runnable Example...
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.LineBorder;
public class Reveal extends JPanel {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new Reveal("Test", 5);
}
});
}
private JPanel panel = new JPanel(); //a panel to house the label
private JLabel label = new JLabel(); //a label to house the image
private ImageIcon[] icon = new ImageIcon[7]; //an array of icons to be the images
private JFrame f;
private TextOverlay TO;
private Timer timer;
private Timer timer2;
int x = 0;
int y = 4;
int counter = 0;
/**
* Constructor for objects of class Reveal
*/
public Reveal(String name, int number) {
TO = new TextOverlay("Jack in the Box 7.png", name, number);
for (int h = 0; h < 7; h++) {
icon[h] = new ImageIcon(makeImage(h));
icon[h].getImage();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//Sets the size of the window
f.setSize(800, 850);
panel = new JPanel(new GridBagLayout());
label = new JLabel();
label.setIcon(icon[x]);
label.setBorder(new LineBorder(Color.RED));
panel.add(label);
f.add(panel);
display(name, number);
// f.add(TO);
setVisible(true);
}
public void display(String name, int number) {
timer = new Timer(150, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (counter > 27) {
timer.stop();
timer2.start(); //starts the second half of the animation
} else {
if (x != 3) {
x++;
} else {
x = 0;
}
label.setIcon(icon[x]);
counter++;
} //ends if-else
} //ends action method
}); //ends timer
timer2 = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (y > 6) {
timer2.stop();
Container parent = label.getParent();
parent.remove(label);
parent.add(TO);
parent.revalidate();
} else {
label.setIcon(icon[y]);
y++;
} //ends if-else
} //ends action method
}); //ends timer2
timer.start();
}
protected BufferedImage makeImage(int h) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(h);
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, 100, 100);
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.dispose();
return img;
}
public class TextOverlay extends JPanel {
private BufferedImage image;
private String name;
private String fileX;
private int number;
public TextOverlay(String f, String s, int n) {
name = s;
number = n;
fileX = f;
image = makeImage(n);
image = process(image, name, number);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
private BufferedImage process(BufferedImage old, String name, int number) {
int w = old.getWidth();
int h = old.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.black);
g2d.setFont(new Font("Franklin Gothic Demi Cond", Font.PLAIN, 30));
String s1 = name;
String s2 = Integer.toString(number);;
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(s1, 40, 90);
g2d.drawString(s2, 40, 140);
g2d.dispose();
return img;
}
}
}
A "slightly" different approach...
Animation is actually a really complex subject which is not easy to implement well.
This is why, when faced with problems like these, I prefer to look at libraries which have already been implemented to help solve them. I'd recommend having a look at:
The Timing Framework
Trident
universal-tween-engine
as some starting points.
While I prefer to use libraries, sometimes it's not possible or the libraries don't fit my overall needs ... that and I like to dabble ... it's kind of a hobby.
Based on what I can understand from your code, you're trying to start out with a fast animation and then slow it down till you get to the last frame. In animation theory, this is commonly known as easement, more specifically, "slow/ease out".
The following borrows from a bunch of snippets I've been playing with (to devise a more reusable library) that will basically (randomly) display the images over a period of 4 seconds, with the animation slowing down and finally, presenting the "lucky" number
nb The gif animation is actually really slow, you'll need to run it to see the difference
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.*;
public class Reveal extends JPanel {
public static void main(String[] args) {
new Reveal();
}
public Reveal() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private IntAnimatable animatable;
private List<ImageIcon> icons = new ArrayList<>(25);
private JLabel label = new JLabel();
public TestPane() {
setLayout(new GridBagLayout());
IntRange range = new IntRange(0, 111);
animatable = new IntAnimatable(range, Duration.ofSeconds(4), Easement.SLOWOUT, new AnimatableListener<Integer>() {
#Override
public void animationChanged(Animatable<Integer> animator) {
int value = animator.getValue();
int index = value % 7;
ImageIcon icon = icons.get(index);
if (label.getIcon() != icon) {
label.setIcon(icon);
}
}
}, new AnimatableLifeCycleAdapter<Integer>() {
#Override
public void animationCompleted(Animatable<Integer> animator) {
BufferedImage img = makeImage(3);
writeTextOverImage("Lucky number", img);
ImageIcon luckNumber = new ImageIcon(img);
label.setIcon(luckNumber);
}
});
for (int index = 0; index < 7; index++) {
icons.add(new ImageIcon(makeImage(index)));
}
Collections.shuffle(icons);
add(label);
Animator.INSTANCE.add(animatable);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
protected void writeTextOverImage(String text, BufferedImage img) {
Graphics2D g2d = img.createGraphics();
Font font = g2d.getFont();
font = font.deriveFont(Font.BOLD, font.getSize2D() + 2);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int width = img.getWidth();
int height = img.getWidth();
int x = (width - fm.stringWidth(text)) / 2;
int y = fm.getAscent();
g2d.setColor(Color.YELLOW);
g2d.drawString(text, x, y);
g2d.dispose();
}
protected BufferedImage makeImage(int h) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(h);
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, 100, 100);
g2d.setColor(Color.WHITE);
g2d.drawString(text, x, y);
g2d.dispose();
return img;
}
/**** Range ****/
/*
A lot of animation is done from one point to another, this just
provides a self contained concept of a range which can be used to
calculate the value based on the current progression over time
*/
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
#Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
}
public class IntRange extends Range<Integer> {
public IntRange(Integer from, Integer to) {
super(from, to);
}
public Integer getDistance() {
return getTo() - getFrom();
}
#Override
public Integer valueAt(double progress) {
int distance = getDistance();
int value = (int) Math.round((double) distance * progress);
value += getFrom();
return value;
}
}
/**** Animatable ****/
/*
The core concept of something that is animatable. This basic wraps up the
logic for calculating the progression of the animation over a period of time
and then use that to calculate the value of the range and then the observers
are notified so they can do stuff
*/
public class IntAnimatable extends AbstractAnimatableRange<Integer> {
public IntAnimatable(IntRange animationRange, Duration duration, Easement easement, AnimatableListener<Integer> listener, AnimatableLifeCycleListener<Integer> lifeCycleListener) {
super(animationRange, duration, easement, listener, lifeCycleListener);
}
}
public interface AnimatableListener<T> {
public void animationChanged(Animatable<T> animator);
}
public interface AnimatableLifeCycleListener<T> {
public void animationStopped(Animatable<T> animator);
public void animationCompleted(Animatable<T> animator);
public void animationStarted(Animatable<T> animator);
public void animationPaused(Animatable<T> animator);
}
public interface Animatable<T> {
public T getValue();
public void tick();
public Duration getDuration();
public Easement getEasement();
// Wondering if these should be part of a secondary interface
// Provide a "self managed" unit of work
public void start();
public void stop();
public void pause();
}
public class AnimatableLifeCycleAdapter<T> implements AnimatableLifeCycleListener<T> {
#Override
public void animationStopped(Animatable<T> animator) {
}
#Override
public void animationCompleted(Animatable<T> animator) {
}
#Override
public void animationStarted(Animatable<T> animator) {
}
#Override
public void animationPaused(Animatable<T> animator) {
}
}
public abstract class AbstractAnimatable<T> implements Animatable<T> {
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private AnimatableListener<T> animatableListener;
private AnimatableLifeCycleListener<T> lifeCycleListener;
private Easement easement;
private double rawOffset;
public AbstractAnimatable(Duration duration, AnimatableListener<T> listener) {
this.animatableListener = listener;
this.duration = duration;
}
public AbstractAnimatable(Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(duration, listener);
this.lifeCycleListener = lifeCycleListener;
}
public AbstractAnimatable(Duration duration, Easement easement, AnimatableListener<T> listener) {
this(duration, listener);
this.easement = easement;
}
public AbstractAnimatable(Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(duration, easement, listener);
this.lifeCycleListener = lifeCycleListener;
}
public void setEasement(Easement easement) {
this.easement = easement;
}
#Override
public Easement getEasement() {
return easement;
}
public Duration getDuration() {
return duration;
}
protected void setDuration(Duration duration) {
this.duration = duration;
}
public double getCurrentProgress(double rawProgress) {
Easement easement = getEasement();
double progress = Math.min(1.0, Math.max(0.0, getRawProgress()));
if (easement != null) {
progress = easement.interpolate(progress);
}
return Math.min(1.0, Math.max(0.0, progress));
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
double progress = rawOffset + (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
#Override
public void tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
fireAnimationStarted();
}
double rawProgress = getRawProgress();
double progress = getCurrentProgress(rawProgress);
if (rawProgress >= 1.0) {
progress = 1.0;
}
tick(progress);
fireAnimationChanged();
if (rawProgress >= 1.0) {
fireAnimationCompleted();
}
}
protected abstract void tick(double progress);
#Override
public void start() {
if (startTime != null) {
// Restart?
return;
}
Animator.INSTANCE.add(this);
}
#Override
public void stop() {
stopWithNotitifcation(true);
}
#Override
public void pause() {
rawOffset += getRawProgress();
stopWithNotitifcation(false);
double remainingProgress = 1.0 - rawOffset;
Duration remainingTime = getDuration().minusMillis((long) remainingProgress);
setDuration(remainingTime);
lifeCycleListener.animationStopped(this);
}
protected void fireAnimationChanged() {
if (animatableListener == null) {
return;
}
animatableListener.animationChanged(this);
}
protected void fireAnimationCompleted() {
stopWithNotitifcation(false);
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationCompleted(this);
}
protected void fireAnimationStarted() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStarted(this);
}
protected void fireAnimationPaused() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationPaused(this);
}
protected void stopWithNotitifcation(boolean notify) {
Animator.INSTANCE.remove(this);
startTime = null;
if (notify) {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStopped(this);
}
}
}
public abstract class AbstractAnimatableRange<T> extends AbstractAnimatable<T> {
private Range<T> range;
private T value;
public AbstractAnimatableRange(Range<T> range, Duration duration, AnimatableListener<T> listener) {
super(duration, listener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
super(duration, listener, lifeCycleListener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener) {
super(duration, easement, listener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
super(duration, easement, listener, lifeCycleListener);
this.range = range;
}
protected void tick(double progress) {
setValue(range.valueAt(progress));
}
protected void setValue(T value) {
this.value = value;
}
#Override
public T getValue() {
return value;
}
}
/*
Easement, complicated, but fun
*/
public enum Easement {
SLOWINSLOWOUT(1d, 0d, 0d, 1d), FASTINSLOWOUT(0d, 0d, 1d, 1d), SLOWINFASTOUT(0d, 1d, 0d, 0d), SLOWIN(1d, 0d, 1d, 1d), SLOWOUT(0d, 0d, 0d, 1d);
private final double[] points;
private final List<PointUnit> normalisedCurve;
private Easement(double x1, double y1, double x2, double y2) {
points = new double[]{x1, y1, x2, y2};
final List<Double> baseLengths = new ArrayList<>();
double prevX = 0;
double prevY = 0;
double cumulativeLength = 0;
for (double t = 0; t <= 1; t += 0.01) {
Point2D xy = getXY(t);
double length = cumulativeLength + Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX) + (xy.getY() - prevY) * (xy.getY() - prevY));
baseLengths.add(length);
cumulativeLength = length;
prevX = xy.getX();
prevY = xy.getY();
}
normalisedCurve = new ArrayList<>(baseLengths.size());
int index = 0;
for (double t = 0; t <= 1; t += 0.01) {
double length = baseLengths.get(index++);
double normalLength = length / cumulativeLength;
normalisedCurve.add(new PointUnit(t, normalLength));
}
}
public double interpolate(double fraction) {
int low = 1;
int high = normalisedCurve.size() - 1;
int mid = 0;
while (low <= high) {
mid = (low + high) / 2;
if (fraction > normalisedCurve.get(mid).getPoint()) {
low = mid + 1;
} else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) {
high = mid - 1;
} else {
break;
}
}
/*
* The answer lies between the "mid" item and its predecessor.
*/
final PointUnit prevItem = normalisedCurve.get(mid - 1);
final double prevFraction = prevItem.getPoint();
final double prevT = prevItem.getDistance();
final PointUnit item = normalisedCurve.get(mid);
final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction);
final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT));
return getY(interpolatedT);
}
protected Point2D getXY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3);
return xy;
}
protected double getY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
return (b1 * points[2]) + (b2 * points[3]) + b3;
}
protected class PointUnit {
private final double distance;
private final double point;
public PointUnit(double distance, double point) {
this.distance = distance;
this.point = point;
}
public double getDistance() {
return distance;
}
public double getPoint() {
return point;
}
}
}
/**** Core Animation Engine ****/
public enum Animator {
INSTANCE;
private Timer timer;
private List<Animatable> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
List<Animatable> copy = new ArrayList<>(properies);
Iterator<Animatable> it = copy.iterator();
while (it.hasNext()) {
Animatable ap = it.next();
ap.tick();
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(Animatable ap) {
properies.add(ap);
timer.start();
}
protected void removeAll(List<Animatable> completed) {
properies.removeAll(completed);
}
public void remove(Animatable ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
}

Why is the text in the JPanel Clipping after a certain point?

If we drag the scroll bar past a certain point the text displayed becomes "mushed." I manually placed the string at the far end to see if it can be displayed and it worked.
It draws fine from those coordinates when I manually set it (as in the example) but clips when I change the x-coordinate with the scroll bar).
This is the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ScrollBarDemo2 extends JFrame {
private MessagePanel messagePanel = new MessagePanel();
private JScrollBar jscbHorizontal = new JScrollBar(JScrollBar.HORIZONTAL);
private JScrollBar jscbVertical = new JScrollBar(JScrollBar.VERTICAL);
private JTextField jtfMessage = new JTextField("Example String");
public ScrollBarDemo2() {
// Add components to the frame
add(messagePanel, BorderLayout.CENTER);
add(jscbHorizontal, BorderLayout.SOUTH);
add(jscbVertical, BorderLayout.EAST);
add(jtfMessage, BorderLayout.NORTH);
// Register listener to scroll bars
ScrollBarListener jscbListener = new ScrollBarListener();
jscbHorizontal.addAdjustmentListener(jscbListener);
jscbVertical.addAdjustmentListener(jscbListener);
// Register a listener in text field
jtfMessage.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Set the text in messagePanel to the given text
messagePanel.setText(e.getActionCommand());
}
});
}
private class ScrollBarListener implements AdjustmentListener {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Determine the orientation of the event source
JScrollBar scrollBar = (JScrollBar)e.getAdjustable();
if (scrollBar.getOrientation() == JScrollBar.HORIZONTAL) {
// Obtain the horizontal space remaining
double spaceAvailable = (double)messagePanel.getHorizontalEmptySpace();
// Find how much to scale each value of the scroll bars (since we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() * spaceAvailable / (double)scrollBar.getMaximum());
// Set new x coordinate
messagePanel.setX((int)scaledValue);
}
else if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
// Obtain the vertical space remaining
double spaceAvailable = (double)messagePanel.getVerticalEmptySpace();
// Find how much to scale each value of the scroll bars (since we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() / (double)scrollBar.getMaximum()) * spaceAvailable;
// Set new x coordinate
messagePanel.setY((int)scaledValue);
}
}
}
/** main method **/
public static void main(String[] args) {
ScrollBarDemo2 frame = new ScrollBarDemo2();
frame.setSize(500, 200);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class MessagePanel extends JPanel {
private FontMetrics fm;
private String message = "";
private int messageX = -1;
private int messageY = -1;
public MessagePanel() {
this("Welcome to Java");
}
public MessagePanel(String message) {
this.message = message;
}
public void moveRight() {
messageX += getWidth() / 50;
repaint();
}
public void moveLeft() {
messageX -= getWidth() / 50;
repaint();
}
public void moveUp() {
messageY -= getHeight() / 100;
repaint();
}
public void moveDown() {
messageY += getHeight() / 100;
repaint();
}
public int getX() {
return messageX;
}
public int getY() {
return messageY;
}
public void setX(int x) {
messageX = x;
repaint();
}
public void setY(int y) {
messageY = y;
repaint();
}
public void setText(String newMessage) {
message = newMessage;
repaint();
}
public int getVerticalEmptySpace() {
return getHeight() - fm.getAscent();
}
public int getHorizontalEmptySpace() {
return getWidth() - fm.stringWidth(message);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (messageX < 0 && messageY < 0) { // Check to initialize centered position
fm = g.getFontMetrics();
messageX = getWidth() - fm.stringWidth(message); // Manually setting it to the very end coordinate
messageY = getHeight() / 2 - fm.getAscent() / 2;
}
g.drawString(message, messageX, messageY);
}
}
I made a few changes to your code. Here's the GUI I created.
I formatted your code.
I enclosed your Swing code in a Runnable, so I could start your Swing application on the Event Dispatch thread using the SwingUtilities invokeLater method. Oracle and I insist that all Swing applications start on the Event Dispatch thread.
As camickr said in his answer, you accidentally overrode the getX, getY, setX, and setY methods of JPanel. I renamed your methods.
I used the action listener of the underlying JTextField Document so that whatever you type in the JTextField gets drawn on the JPanel.
There is still a problem with your messageX being set to less than zero. I'm leaving this problem for you to solve.
Here's the corrected code.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class ScrollBarDemo2 extends JFrame {
private static final long serialVersionUID = -3189856074534869132L;
private JScrollBar jscbHorizontal = new JScrollBar(JScrollBar.HORIZONTAL);
private JScrollBar jscbVertical = new JScrollBar(JScrollBar.VERTICAL);
private JTextField jtfMessage = new JTextField("Example String");
private MessagePanel messagePanel = new MessagePanel(jtfMessage.getText());
public ScrollBarDemo2() {
// Add components to the frame
add(messagePanel, BorderLayout.CENTER);
add(jscbHorizontal, BorderLayout.SOUTH);
add(jscbVertical, BorderLayout.EAST);
add(jtfMessage, BorderLayout.NORTH);
// Register listener to scroll bars
ScrollBarListener jscbListener = new ScrollBarListener();
jscbHorizontal.addAdjustmentListener(jscbListener);
jscbVertical.addAdjustmentListener(jscbListener);
// Register a listener in text field
jtfMessage.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
messagePanel.setText(jtfMessage.getText());
}
#Override
public void removeUpdate(DocumentEvent e) {
messagePanel.setText(jtfMessage.getText());
}
#Override
public void changedUpdate(DocumentEvent e) {
messagePanel.setText(jtfMessage.getText());
}
});
}
private class ScrollBarListener implements AdjustmentListener {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Determine the orientation of the event source
JScrollBar scrollBar = (JScrollBar) e.getAdjustable();
if (scrollBar.getOrientation() == JScrollBar.HORIZONTAL) {
// Obtain the horizontal space remaining
double spaceAvailable = (double) messagePanel
.getHorizontalEmptySpace();
// Find how much to scale each value of the scroll bars (since
// we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() * spaceAvailable / (double) scrollBar
.getMaximum());
// Set new x coordinate
messagePanel.setMessageX((int) scaledValue);
} else if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
// Obtain the vertical space remaining
double spaceAvailable = (double) messagePanel
.getVerticalEmptySpace();
// Find how much to scale each value of the scroll bars (since
// we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() / (double) scrollBar
.getMaximum()) * spaceAvailable;
// Set new x coordinate
messagePanel.setMessageY((int) scaledValue);
}
}
}
/** main method **/
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
ScrollBarDemo2 frame = new ScrollBarDemo2();
frame.setTitle("Scroll Bar Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
};
SwingUtilities.invokeLater(runnable);
}
}
class MessagePanel extends JPanel {
private static final long serialVersionUID = -2743160276473942475L;
private FontMetrics fm;
private String message = "";
private int messageX = -1;
private int messageY = -1;
public MessagePanel() {
this("Welcome to Java");
}
public MessagePanel(String message) {
this.message = message;
this.setPreferredSize(new Dimension(500, 200));
}
public void moveRight() {
messageX += getWidth() / 50;
repaint();
}
public void moveLeft() {
messageX -= getWidth() / 50;
repaint();
}
public void moveUp() {
messageY -= getHeight() / 100;
repaint();
}
public void moveDown() {
messageY += getHeight() / 100;
repaint();
}
public int getMessageX() {
return messageX;
}
public int getMessageY() {
return messageY;
}
public void setMessageX(int x) {
messageX = x;
repaint();
}
public void setMessageY(int y) {
messageY = y;
repaint();
}
public void setText(String newMessage) {
message = newMessage;
repaint();
}
public int getVerticalEmptySpace() {
return getHeight() - fm.getAscent();
}
public int getHorizontalEmptySpace() {
return getWidth() - fm.stringWidth(message);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (messageX < 0 && messageY < 0) { // Check to initialize centered
// position
fm = g.getFontMetrics();
messageX = getWidth() - fm.stringWidth(message); // Manually setting
// it to the
// very end
// coordinate
messageY = getHeight() / 2 - fm.getAscent() / 2;
}
g.drawString(message, messageX, messageY);
}
}
getX() and getY are methods of the JComponent class and you should not be overriding them.
Rename the methods, maybe something like getMessageX() and getMessageY(). You should also rename the setX() and setY() methods to be consistent with whatever getter names you choose.

Thread is failing to work in the required circumstances

In my code I have the following statements inside my Mouse Listener
class CustomMouseListener extends MouseAdapter {
Menu m;
MenuDesign mD;
SlideInLayout s;
public CustomMouseListener(Menu m, MenuDesign mD, SlideInLayout s) {
this.m = m;
this.mD = mD;
this.s = s;
}
public void mousePressed(MouseEvent mE) {
if(m != null && mD != null) {
if(mE.getSource() == mD) {
if(mD.getInOut()) {
mD.setFade(false);
Thread runSwap = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.show(m.getFirstPanel());
}
});
runSwap.start();
try {
runSwap.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
mD.setFade(true);
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.show(m.getOverlay());
}
}).start();
}
mD.fade();
m.getMainWindow().validate();
m.getMainWindow().repaint();
}
}
}
}
Ideally I would like my runnable animation to run at the same time as mD.fade(); so to do this I would write
Thread runSwap = new Thread(new Runnable() {
public void run() {
s.show(m.getFirstPanel());
}
});
instead of this.
Thread runSwap = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.show(m.getFirstPanel());
}
});
However this causes the end result of the animation to happen instantly. The other problem is that when I need to repaint the screen at the end as it ends up like the first frame in the picture below instead of the second frame however using the repaint() and validate() methods causes the animation in the runnable not to happen and the end result just appears again, even after a .join() is used with the thread.
I would appreciate any help in fixing the problem
Full Code if needed
package menutest;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class Menu {
JFrame myMainWindow = new JFrame("Menu Slide Test");
GridBagConstraints c;
MyFirstPanel fP;
MyOverlay oL;
MyMenuButton mB;
GridBagLayout baseLayout;
GridBagLayout overlayLayout;
SlideInLayout slideIn = new SlideInLayout();
JPanel tempHold = new JPanel(slideIn);
/* Height and Width */
int height = 600;
int width = 600;
private void runGUI(Menu m) {
myMainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myMainWindow.setLayout(new GridBagLayout());
setContraints();
createPanels(m);
myMainWindow.getContentPane().add(tempHold, c);
myMainWindow.getContentPane().add(mB, c, 0);
slideIn.show(fP);
myMainWindow.pack();
myMainWindow.setVisible(true);
myMainWindow.setLocationRelativeTo(null);
}
private void setContraints() {
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
c.weightx = 1;
c.weighty = 1;
c.fill = GridBagConstraints.BOTH;
}
private void createPanels(Menu m) {
createFirstPanel(m);
createOverlay(m);
createMenuButton(m);
tempHold.add(fP);
tempHold.add(oL);
}
private void createFirstPanel(Menu m) {
baseLayout = new GridBagLayout();
fP = new MyFirstPanel(baseLayout, m);
}
private void createOverlay(Menu m) {
overlayLayout = new GridBagLayout();
oL = new MyOverlay(overlayLayout, m);
}
private void createMenuButton(Menu m) {
mB = new MyMenuButton(m);
}
public static void main(String[] args) {
Menu menu = new Menu();
menu.runGUI(menu);
}
/* Getters and Setters */
public JPanel getFirstPanel() {
return fP;
}
public JPanel getOverlay() {
return oL;
}
public JFrame getMainWindow() {
return myMainWindow;
}
public int myGetHeight() {
return height;
}
public int myGetWidth() {
return width;
}
public SlideInLayout getSlide() {
return slideIn;
}
}
class MyFirstPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 2897488186622284953L;
MyFirstPanel(GridBagLayout layout, Menu mM) {
setLayout(layout);
setBackground(Color.RED);
setPreferredSize(new Dimension(mM.myGetWidth(), mM.myGetHeight()));
}
}
class MyOverlay extends JPanel {
/**
*
*/
private static final long serialVersionUID = 4595122972358754430L;
MyOverlay(GridBagLayout layout, Menu mM) {
setLayout(layout);
setBackground(Color.GREEN);
setPreferredSize(new Dimension(mM.myGetWidth(), mM.myGetHeight()));
}
}
class MyMenuButton extends JPanel {
/**
*
*/
private static final long serialVersionUID = -4986432081497113479L;
MyMenuButton(Menu mM) {
setLayout(null);
setBackground(new Color(0, 0, 0, 0));
setPreferredSize(new Dimension(mM.myGetWidth(), mM.myGetHeight()));
add(new MenuDesign(15, 15, mM, mM.myGetWidth()));
}
}
class MenuDesign extends JLabel {
/**
*
*/
private static final long serialVersionUID = 2255075501909089222L;
boolean inOut = false; //true for fade out, false for fade in
//starts on false because it changes to true on first click on label
float alpha = 1F;
Timer timer;
//Start Points
double[] r1Points = {0, 6.67766953};
double[] r2Points = {0, 16.67766953};
double[] r3Points = {0, 26.67766953};
//Current Points
double[] curR1Points = {r1Points[0], r1Points[1]};
double[] curR3Points = {r3Points[0], r3Points[1]};
//End Points
double[] endR1Points = {2.828427125, 0};
double[] endR3Points = {0, 35.35533906};
//Angles
double ang1 = 0;
double ang2 = 0;
//Height and width of component to make it as efficient as possible
int width = 50;
int height = 40;
MenuDesign(int x, int y, Menu m, int width) {
setBounds(x, y, this.width, height);
setCursor(new Cursor(Cursor.HAND_CURSOR));
addMouseListener(new CustomMouseListener(m, this, m.getSlide()));
timer = new Timer(5, new CustomActionListener(m, this, r1Points, r3Points, endR1Points, endR3Points, x, width - 60));
}
public void fade() {
timer.start();
System.out.println("Start");
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setColor(Color.WHITE);
Rectangle2D r1 = new Rectangle2D.Double(curR1Points[0], curR1Points[1], width, 4);
Rectangle2D r2 = new Rectangle2D.Double(r2Points[0], r2Points[1], width, 4);
Rectangle2D r3 = new Rectangle2D.Double(curR3Points[0], curR3Points[1], width, 4);
//r1
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1F));
g2d.rotate(Math.toRadians(ang1), endR1Points[0], endR1Points[1]);
g2d.fill(r1);
//r3
g2d.rotate(Math.toRadians(-ang1), endR1Points[0], endR1Points[1]);
g2d.rotate(Math.toRadians(-ang2), endR3Points[0], endR3Points[1]);
g2d.fill(r3);
//r2
g2d.rotate(Math.toRadians(ang2), endR3Points[0], endR3Points[1]);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.fill(r2);
}
public Timer getTimer() {
return timer;
}
public float getAlpha() {
return alpha;
}
public void setAplha(float a) {
this.alpha = a;
if(a < 0) {
setAplha(0F);
}
if(a > 1) {
setAplha(1F);
}
validate();
repaint();
}
public boolean getInOut() {
return inOut;
}
public void setFade(boolean b) {
this.inOut = b;
}
public double[] getTransR1() {
return curR1Points;
}
public double[] getTransR3() {
return curR3Points;
}
public void setTransR1(double[] d) {
this.curR1Points = d;
}
public void setTransR3(double[] d) {
this.curR3Points = d;
}
public void setAng1(double d) {
this.ang1 = d;
}
public void setAng2(double d) {
this.ang2 = d;
}
public void stopTheTimer(int i) {
if(i == 101) {
timer.stop();
System.out.println("stop");
}
}
}
class CustomActionListener implements ActionListener {
Menu m;
MenuDesign mD;
MyFirstPanel mFP;
double[] a, b, c, d;
double incrementX1;
double incrementY1;
double incrementX2;
double incrementY2;
double incrementX3;
double incrementY3;
double incrementX4;
double incrementY4;
double angInc = 45.0 / 100.0;
double moveInc;
int i = 0;
int startPoint;
public CustomActionListener(Menu m, MyFirstPanel mFP) {
this.m = m;
this.mFP = mFP;
}
public CustomActionListener(Menu m, MenuDesign mD, double[] a, double[] b, double[] c, double[] d, int startPoint, int endPoint) {
this.m = m;
this.mD = mD;
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.startPoint = startPoint;
//Increments
int incTot = 100;
//r1 increments
incrementX1 = (c[0] - a[0]) / incTot;
incrementY1 = (c[1] - a[1]) / incTot;
incrementX2 = (a[0] - c[0]) / incTot;
incrementY2 = (a[1] - c[1]) / incTot;
//r2 increments
incrementX3 = (d[0] - b[0]) / incTot;
incrementY3 = (d[1] - b[1]) / incTot;
incrementX4 = (b[0] - d[0]) / incTot;
incrementY4 = (b[1] - d[1]) / incTot;
//Movement
moveInc = (endPoint - startPoint) / incTot;
}
public void actionPerformed(ActionEvent e) {
if(m != null && mD != null) {
if(e.getSource() == mD.getTimer()) {
if(mD.getInOut()) { //Start of transform into x
//r1
mD.setTransR1(new double[] {a[0] + (i * incrementX1), a[1] + (i * incrementY1)});
mD.setAng1(i * angInc);
//r2
mD.setAplha(mD.getAlpha() - 0.02F);
//r3
mD.setTransR3(new double[] {b[0] + (i * incrementX3), b[1] + (i * incrementY3)});
mD.setAng2(i * angInc);
//Location
mD.setLocation((int) (startPoint + (i * moveInc)), startPoint);
i++;
} else { //Start of transform into three lines
//r1
mD.setTransR1(new double[] {c[0] + (i * incrementX2), c[1] + (i * incrementY2)});
mD.setAng1((100 - i) * angInc);
//r2
if(i >= 50) {
mD.setAplha(mD.getAlpha() + 0.02F);
}
//r3
mD.setTransR3(new double[] {d[0] + (i * incrementX4), d[1] + (i * incrementY4)});
mD.setAng2((100 - i) * angInc);
//Location
mD.setLocation((int) (540 + (i * -moveInc)), 15);
i++;
}
if(i == 101) {
mD.stopTheTimer(i);
i = 0;
}
m.getMainWindow().validate();
m.getMainWindow().repaint();
}
}
}
}
class CustomMouseListener extends MouseAdapter {
Menu m;
MenuDesign mD;
SlideInLayout s;
public CustomMouseListener(Menu m, MenuDesign mD, SlideInLayout s) {
this.m = m;
this.mD = mD;
this.s = s;
}
public void mousePressed(MouseEvent mE) {
if(m != null && mD != null) {
if(mE.getSource() == mD) {
if(mD.getInOut()) {
mD.setFade(false);
Thread runSwap = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.show(m.getFirstPanel());
}
});
runSwap.start();
try {
runSwap.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
mD.setFade(true);
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.show(m.getOverlay());
}
}).start();
}
mD.fade();
m.getMainWindow().validate();
m.getMainWindow().repaint();
}
}
}
}
/////////////// Below here is not my code
class SlideInLayout implements LayoutManager {
private Component focusedComponent;
#Override
public void addLayoutComponent(String name, Component comp) {}
#Override
public void removeLayoutComponent(Component comp) {}
#Override
public void layoutContainer(Container parent) {
setSizes(parent);
if (hasFocusedComponent()) {
focusedComponent.setVisible(true);
}
}
private void setSizes(Container parent) {
Insets insets = parent.getInsets();
int maxWidth = parent.getWidth() - (insets.left + insets.right);
int maxHeight = parent.getHeight() - (insets.top + insets.bottom);
for (Component component : parent.getComponents()) {
component.setBounds(0, 0, maxWidth, maxHeight);
}
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return new Dimension(0, 0);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
Dimension preferredSize = new Dimension(0, 0);
if (hasFocusedComponent()) {
preferredSize = focusedComponent.getPreferredSize();
}
else if (parent.getComponentCount() > 0) {
int maxWidth = 0;
int maxHeight = 0;
for (Component component : parent.getComponents()) {
Dimension componentSize = component.getPreferredSize();
maxWidth = Math.max(maxWidth, componentSize.width);
maxHeight = Math.max(maxHeight, componentSize.height);
}
preferredSize = new Dimension(maxWidth, maxHeight);
}
return preferredSize;
}
private boolean hasFocusedComponent() {
return focusedComponent != null;
}
public void show(Component component) {
if (hasFocusedComponent())
swap(focusedComponent, component);
focusedComponent = component;
}
private void swap(Component transitionOut, Component transitionIn) {
new SwapTimerAction(transitionOut, transitionIn).start();
}
private class SwapTimerAction implements ActionListener {
private Timer timer;
private Component transitionOut;
private Component transitionIn;
private static final int tick = 16; //16ms
private static final int speed = 50;
public SwapTimerAction(Component transitionOut, Component transitionIn) {
this.transitionOut = transitionOut;
this.transitionIn = transitionIn;
}
public void start() {
Container container = transitionOut.getParent();
container.setComponentZOrder(transitionOut, 1);
container.setComponentZOrder(transitionIn, 0);
transitionIn.setBounds(-transitionOut.getWidth(), 0, transitionOut.getWidth(), transitionOut.getHeight());
timer = new Timer(tick, this);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
int newX = Math.min(transitionIn.getX() + speed, transitionOut.getX());
transitionIn.setLocation(newX, 0);
if (newX == transitionOut.getX()) {
timer.stop();
}
}
}
}

I try to use the method repaint after paintComponent but its not work

First it will be a little longer because I want to show all the code I have done until now, so excuse me..
This is my first time in java.
I'm trying to build an aquarium with fish and jellyfish drawing and use threads.
When I try to add animal I want to paint it but without success, I built PaintComponent method but when I try to use repaint I can not draw..
What am I missing?
It looks that way, when I click on Add Animal window opens , I choose the values and after I click OK, need to draw the animal
I hope that the rest of what I did was okay .. thank you so much for helping!
paintComponent and repaint is at AquaPanel Class.
public class AquaFrame extends JFrame {
private AquaPanel mPanel;
private JLabel label1;
private ImageIcon icon;
private BufferedImage imag;
public AquaFrame() {
super("my Aquarium");
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(700, 600);
setResizable(false);
setVisible(true);
setLocationRelativeTo(null);
mPanel = new AquaPanel(getGraphics());
add(mPanel);
}
public void buildFrame(){
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu file = new JMenu("File");
menuBar.add(file);
JMenuItem exItem = new JMenuItem("Exit");
file.add(exItem);
exItem.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
JMenu background = new JMenu("Background");
menuBar.add(background);
JMenuItem blue = new JMenuItem("Blue");
blue.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
mPanel.setBackground(Color.BLUE);
}
});
JMenuItem none = new JMenuItem("None");
none.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
mPanel.setBackground(null);
}
});
JMenuItem image = new JMenuItem("Image");
image.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
imag = ImageIO.read(new File("aquarium_background.jpg"));
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});
background.add(image);
background.add(blue);
background.add(none);
JMenu help = new JMenu("Help");
JMenuItem helpItem = new JMenuItem("help");
helpItem.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null,"GUI # Threads");
}
});
menuBar.add(help);
help.add(helpItem);
setVisible(true);
}
public void paint(Graphics g){
g.drawImage(imag, 0, 0, null);
}
public static void main(String[] args) {
AquaFrame mFrame = new AquaFrame();
mFrame.buildFrame();
}
}
/******************************************/
public class AquaPanel extends JPanel {
private JFrame infoTableFrame;
private JTable infoTable;
private Set<Swimmable > swimmables = new HashSet<Swimmable>();
private AddAnimalDialog animalDialog;
private int totalEatCounter;
private Graphics g;
public AquaPanel(Graphics g) {
this.g = g;
totalEatCounter = 0;
infoTableFrame = new JFrame();
infoTableFrame.setVisible(false);
setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.anchor = GridBagConstraints.PAGE_END;
constraints.weighty = 1;
JButton btAdd = new JButton("Add Animal");
btAdd.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (swimmables.size() >= 5)
{
JOptionPane.showMessageDialog(null, "You can't have more than 5 animals at the same time.");
}
else
{
animalDialog = new AddAnimalDialog(AquaPanel.this);
animalDialog.setVisible(true);
}
}
});
JButton btSleep = new JButton("Sleep");
btSleep.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Swimmable swimmable : swimmables)
{
swimmable.setSuspend();
}
}
});
JButton btWakeup = new JButton("Wake Up");
btWakeup.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Swimmable swimmable : swimmables)
{
swimmable.setResume();
}
}
});
JButton btRst = new JButton("Reset");
btRst.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Swimmable swimmable : swimmables)
{
swimmable.kill();
}
swimmables.clear();
}
});
JButton btFood = new JButton("Food");
btFood.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (swimmables.size() > 0)
{
CyclicBarrier barrier = new CyclicBarrier(swimmables.size());
for (Swimmable swimmable : swimmables)
{
swimmable.setBarrier(barrier);
swimmable.setFood(true);
}
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(3));
g2.setColor(Color.red);
g2.drawArc(getWidth() / 2, getHeight() / 2 - 5, 10, 10, 30, 210);
g2.drawArc(getWidth()/2, getHeight()/2+5, 10, 10, 180, 270);
g2.setStroke(new BasicStroke(1));
}
}
});
JButton btInfo = new JButton("Info");
btInfo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
showInfoTable();
}
});
JButton btExit = new JButton("Exit");
btExit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
add(btAdd,constraints);
add(btSleep,constraints);
add(btWakeup,constraints);
add(btRst,constraints);
add(btFood,constraints);
add(btInfo,constraints);
add(btExit,constraints);
}
public void showInfoTable(){
if (infoTableFrame.isVisible())
{
infoTableFrame.remove(infoTable);
infoTableFrame.setVisible(false);
}
else
{
String[] col = {"Animal","Color","Size","Hor.speed","Ver.speed","Eat counter"};
String[][] data = new String[swimmables.size()][col.length];
int i=0;
for (Swimmable swimmable : swimmables)
{
data[i][0] = swimmable.getAnimalName();
data[i][1] = swimmable.getColor();
data[i][2] = "" + swimmable.getSize();
data[i][3] = "" + swimmable.getHorSpeed();
data[i][4] = "" + swimmable.getVerSpeed();
data[i][5] = "" + swimmable.getEatCount();
++i;
}
infoTable = new JTable(data, col);
//TODO - not overriding values
JScrollPane jPane = new JScrollPane(infoTable);
infoTableFrame.add(jPane, BorderLayout.CENTER);
infoTableFrame.setSize(300, 150);
infoTableFrame.setVisible(true);
}
}
public void addAnimal(Swimmable animal)
{
animal.setAquaPanel(this);
swimmables.add(animal);
animal.run();
//myrepaint();
/**********************************/
repaint();
/**********************************/
}
public void onEatFood(Swimmable eater)
{
eater.eatInc();
++totalEatCounter;
for (Swimmable swimmable : swimmables)
{
swimmable.setFood(false);
}
}
/*public void myrepaint()
{
for (Swimmable swimmable : swimmables)
{
swimmable.drawAnimal(g);
}
}*/
/************************************************/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Swimmable swimmable : swimmables)
{
swimmable.drawAnimal(g);
}
}
/*********************************************/
}
public abstract class Swimmable extends Thread {
protected AquaPanel aquaPanel;
protected boolean isFood;
protected boolean isSuspended;
protected int horSpeed;
protected int verSpeed;
protected int x_dir, y_dir;
protected int eatCount;
protected CyclicBarrier barrier;
protected int x0, y0;
protected boolean isAlive;
public Swimmable() {
horSpeed = 0;
verSpeed = 0;
x_dir = 1;
y_dir = 1;
eatCount = 0;
}
public Swimmable(int hor, int ver) {
horSpeed = hor;
verSpeed = ver;
x_dir = 1;
y_dir = 1;
eatCount = 0;
}
#Override
public void run() {
isAlive = true;
while (isAlive) {
try {
sleep(10);
if (isSuspended)
{
wait();
}
else
{
if (isFood)
{
barrier.await();
updateFrontsTowardsFood();
if (isNearFood())
{
aquaPanel.onEatFood(this);
}
}
else
{
updateFronts();
}
//aquaPanel.myrepaint();
aquaPanel.repaint();
}
}
catch (Exception e)
{
//TODO - handle exception
}
}
}
public void kill()
{
isAlive = false;
}
public int getHorSpeed() {
return horSpeed;
}
public int getVerSpeed() {
return verSpeed;
}
public void setHorSpeed(int hor) {
horSpeed = hor;
}
public void setVerSpeed(int ver) {
verSpeed = ver;
}
abstract public int getSize();
abstract public String getColor();
public void setSuspend()
{
isSuspended = true;
}
public void setResume()
{
isSuspended = false;
}
public void eatInc()
{
++eatCount;
}
public int getEatCount()
{
return eatCount;
}
public void setBarrier(CyclicBarrier b)
{
barrier = b;
}
public void setFood(boolean isFood)
{
this.isFood = isFood;
}
public void setAquaPanel(AquaPanel panel)
{
aquaPanel = panel;
x0 = aquaPanel.getWidth() / 2;
y0 = aquaPanel.getHeight() / 2;
}
abstract public String getAnimalName();
abstract public void drawAnimal(Graphics g);
abstract protected void updateFronts();
abstract protected void updateFrontsTowardsFood();
abstract protected boolean isNearFood();
}
public class Fish extends Swimmable {
protected int x_front , y_front;
private int size;
private Color color;
public Fish(Color col, int sz, int hor, int ver) {
super(hor, ver);
size = sz;
color = col;
x_front = 0;
y_front = 0;
}
public void drawAnimal(Graphics g) {
g.setColor(color);
if (x_dir == 1) // fish swims to right side
{
// Body of fish
g.fillOval(x_front - size, y_front - size / 4, size, size / 2);
// Tail of fish
int[] x_t = { x_front - size - size / 4, x_front - size - size / 4, x_front - size };
int[] y_t = { y_front - size / 4, y_front + size / 4, y_front };
Polygon t = new Polygon(x_t, y_t, 3);
g.fillPolygon(t);
// Eye of fish
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(255 - color.getRed(),255 - color.getGreen(),255- color .getBlue()));
g2.fillOval(x_front-size/5, y_front-size/10, size/10, size/10);
//Mouth of fish
if(size>70)
g2.setStroke(new BasicStroke(3));
else if(size>30)
g2.setStroke(new BasicStroke(2));
else
g2.setStroke(new BasicStroke(1));
g2.drawLine(x_front, y_front, x_front-size/10, y_front+size/10);
g2.setStroke(new BasicStroke(1));
}
else // fish swims to left side
{
// Body of fish
g.fillOval(x_front, y_front - size / 4, size, size / 2);
// Tail of fish
int[] x_t = { x_front + size + size / 4, x_front + size + size / 4, x_front + size };
int[] y_t = { y_front - size / 4, y_front + size / 4, y_front };
Polygon t = new Polygon(x_t, y_t, 3);
g.fillPolygon(t);
// Eye of fish
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue()));
g2.fillOval(x_front+size/10, y_front-size/10, size/10, size/10);
// Mouth of fish
if (size > 70)
g2.setStroke(new BasicStroke(3));
else if (size > 30)
g2.setStroke(new BasicStroke(2));
else
g2.setStroke(new BasicStroke(1));
g2.drawLine(x_front, y_front, x_front + size / 10, y_front + size / 10);
g2.setStroke(new BasicStroke(1));
}
}
public String getAnimalName() {
return "Fish";
}
public int getSize() {
return size;
}
public String getColor() {
if (color == Color.RED)
return "RED";
else if (color == Color.GREEN)
return "GREEN";
else if (color == Color.ORANGE)
return "ORANGE";
else
return "UNKNOWN";
}
protected void updateFronts()
{
x_front += x_dir * horSpeed;
y_front += y_dir * verSpeed;
if (x_front > x0*2 || x_front < 0)
x_dir *= -1;
if (y_front > y0*2 || y_front < 0)
y_dir *= -1;
}
protected void updateFrontsTowardsFood()
{
//TODO - copy from word
}
protected boolean isNearFood()
{
return ((Math.abs(x_front-x0) <= 5) && (Math.abs(y_front-y0) <= 5));
}
}
public class Jellyfish extends Swimmable {
private int x_front , y_front;
private int size;
private Color color;
public Jellyfish(Color col, int sz, int hor, int ver) {
super(hor, ver);
size = sz;
color = col;
x_front = 0;
y_front = 0;
}
public void drawAnimal(Graphics g) {
int numLegs;
if (size < 40)
numLegs = 5;
else if (size < 80)
numLegs = 9;
else
numLegs = 12;
g.setColor(color);
g.fillArc(x_front - size / 2, y_front - size / 4, size, size / 2, 0, 180);
for (int i = 0; i < numLegs; i++)
g.drawLine(x_front - size / 2 + size / numLegs + size * i / (numLegs + 1), y_front,
x_front - size / 2 + size / numLegs + size * i / (numLegs + 1), y_front + size / 3);
}
public String getAnimalName() {
return "Jellyfish";
}
public int getSize() {
return size;
}
public String getColor() {
if (color == Color.RED)
return "RED";
else if (color == Color.GREEN)
return "GREEN";
else if (color == Color.ORANGE)
return "ORANGE";
else
return "UNKNOWN";
}
public void updateFronts()
{
x_front += x_dir * horSpeed;
y_front += y_dir * verSpeed;
if (x_front > x0*2 || x_front < 0)
x_dir *= -1;
if (y_front > y0*2 || y_front < 0)
y_dir *= -1;
}
protected void updateFrontsTowardsFood()
{
//TODO - copy from word
}
protected boolean isNearFood()
{
return ((Math.abs(x_front-x0) <= 5) && (Math.abs(y_front-y0) <= 5));
}
}
public class AddAnimalDialog extends JDialog {
private JButton btOk, btCancel;
private JTextField tfSize, tfHspeed, tfVspeed;
private ButtonGroup groupType, groupColor;
private JRadioButton rbFish, rbJellyfish, rbRed, rbGreen, rbOrange;
JPanel panel;
public AddAnimalDialog(AquaPanel aquaPanel) {
this.setLayout(new FlowLayout());
panel = new JPanel();
panel.setLayout(new GridLayout(10, 20));
panel.add(new JLabel("Size(20-320): "));
panel.add(tfSize = new JTextField());
panel.add(new JLabel("Horizontal speed(1-10): "));
panel.add(tfHspeed = new JTextField());
panel.add(new JLabel("Vertical speed(1-10): "));
panel.add(tfVspeed = new JTextField());
panel.add(new JLabel("Type: "));
panel.add(new JLabel(" "));
panel.add(rbFish = new JRadioButton("fish"));
panel.add(rbJellyfish = new JRadioButton("jellyfish"));
panel.add(new JLabel("Color: "));
panel.add(new JLabel(" "));
panel.add(rbRed = new JRadioButton("Red"));
panel.add(rbGreen = new JRadioButton("Green"));
panel.add(rbOrange = new JRadioButton("Orange"));
// Group the radio buttons.
groupType = new ButtonGroup();
groupType.add(rbFish);
groupType.add(rbJellyfish);
rbFish.setSelected(true);
groupColor = new ButtonGroup();
groupColor.add(rbRed);
groupColor.add(rbGreen);
groupColor.add(rbOrange);
rbRed.setSelected(true);
panel.add(new JLabel(""));
panel.add(btOk = new JButton("OK"));
panel.add(btCancel = new JButton("Cancel"));
this.add(panel);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.pack();
this.btOk.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
try {
int size = Integer.parseInt(tfSize.getText());
int hSpeed = Integer.parseInt(tfHspeed.getText());
int vSpeed = Integer.parseInt(tfVspeed.getText());
if (checkValues(size, hSpeed, vSpeed)) {
Color clr = Color.RED;
if (rbRed.isSelected()) {
clr = Color.RED;
} else if (rbGreen.isSelected()) {
clr = Color.GREEN;
} else if (rbOrange.isSelected()) {
clr = Color.ORANGE;
} else {
JOptionPane.showMessageDialog(null, "You must choose a color!");
}
if (rbFish.isSelected()) {
setVisible(false);
aquaPanel.addAnimal(new Fish(clr, size, hSpeed, vSpeed));
} else if (rbJellyfish.isSelected()) {
setVisible(false);
aquaPanel.addAnimal(new Jellyfish(clr, size, hSpeed, vSpeed));
} else {
JOptionPane.showMessageDialog(null, "You must choose animal type!");
}
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null, "One of the number values has wrong format, please check it");
}
}
});
this.btCancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
setVisible(false);
System.out.println("Click the Close button if you want to stop adding windows");
}
});
}
private boolean checkValues(int size, int hspeed, int vspeed) {
if ((size > 320 || size < 20) || (hspeed < 1 || hspeed > 10) || (vspeed < 1 || vspeed > 10)) {
JOptionPane.showMessageDialog(null, "One of the values is out of bounds, please follow the restrictions.");
return false;
}
return true;
}
}
There are a number of basic problems...
The main problem is with your Swimmable run method, it is blocking the Event Dispatching Thread, preventing it from been able to respond to UI events, including repaint events. See Concurrency in Java for more details.
You should NEVER use getGraphics, apart from been able to return null, anything you paint to it is painted outside of the normal paint cycle and will painted over the next time component is repainted. See Painting in AWT and Swing and Performing Custom Painting for more details.
Your current approach won't scale well, the more fish you add, the more resources they will consume and will affect the ability for the program to keep up with all the different changes and repaint requests.
A better solution would be to have a single "update" loop which takes care of telling each of the Swimmables that it needs to update and then schedules a single repaint each cycle. Personally, I'd start with a Swing Timer as it "ticks" within the EDT, making it much safer to modify the state of the objects which the paintComponent method needs.
See How to use Swing Timers for more details
I would suggest having a look at this, which basically describes what you're trying to do and what I'm suggesting you should do instead
Try calling repaint in your JFrame class after calling the addAnimal method. There is also revalidate() method in there as well and their difference is in this article: Java Swing revalidate() vs repaint()

Categories

Resources