newbie programmer here.
I'm making a program that renders user-inputted equations in a Cartesian coordinate system. At the moment I'm having some issues with letting the user move the view around freely in the coordinate. Currently with mouseDragged the user can drag the view around a bit, but once the user releases the mouse and tries to move the view again the origin snaps back to the current position of the mouse cursor. What is the best way to let the user move around freely? Thanks in advance!
Here's the code for the drawing area.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JPanel;
public class DrawingArea extends JPanel implements MouseMotionListener {
private final int x_panel = 350; // width of the panel
private final int y_panel = 400; // height of the panel
private int div_x; // width of one square
private int div_y; // height of one square
private int real_y;
private int real_x;
private Point origin; // the origin of the coordinate
private Point temp; // temporary point
private static int y = 0;
private static int x = 0;
DrawingArea() {
setBackground(Color.WHITE);
real_x = x_panel;
real_y = y_panel;
setDivisionDefault();
setOrigin(new Point((real_x / 2), (real_y / 2)));
setSize(x_panel, y_panel);
addMouseMotionListener(this);
}
DrawingArea(Point origin, Point destination) {
this.origin = origin;
this.destination = destination;
panel = new JPanel();
panel.setSize(destination.x, destination.y);
panel.setLocation(origin);
this.panel.setBackground(Color.red);
panel.setLayout(null);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D line = (Graphics2D) g;
temp = new Point(origin.x, origin.y);
line.setColor(Color.red);
drawHelpLines(line);
line.setColor(Color.blue);
drawOrigin(line);
line.setColor(Color.green);
for (int i = 0; i < 100; i++) { // This is a test line
//temp = this.suora();
temp.x++;
temp.y++;
line.drawLine(temp.x, temp.y, temp.x, temp.y);
}
}
public void setOrigin(Point p) {
origin = p;
}
public void drawOrigin(Graphics2D line) {
line.drawLine(origin.x, 0, origin.x, y_panel);
line.drawLine(0, origin.y, x_panel, origin.y);
}
public void drawHelpLines(Graphics2D line) {
int xhelp= origin.x;
int yhelp= origin.y;
for (int i = 0; i < 20; i++) {
xhelp+= div_x;
line.drawLine(xhelp, 0, xhelp, y_panel);
}
xhelp= origin.x;
for (int i = 0; i < 20; i++) {
xhelp-= div_x;
line.drawLine(xhelp, 0, xhelp, y_panel);
}
for (int i = 0; i < 20; i++) {
yhelp-= div_y;
line.drawLine(0, yhelp,x_panel, yhelp);
}
yhelp= origin.y;
for (int i = 0; i < 20; i++) {
yhelp+= div_y;
line.drawLine(0, yhelp, x_panel, yhelp);
}
}
public void setDivisionDefault() {
div_x = 20;
div_y = 20;
}
#Override
public void mouseDragged(MouseEvent e) {
//Point temp_point = new Point(mouse_x,mouse_y);
Point coords = new Point(e.getX(), e.getY());
setOrigin(coords);
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
}
}
Based on this example, the following program allows the user to drag the axes' intersection to an arbitrary point, origin, which starts at the center of the panel.
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/15576413/230513
* #see https://stackoverflow.com/a/5312702/230513
*/
public class MouseDragTest extends JPanel {
private static final String TITLE = "Drag me!";
private static final int W = 640;
private static final int H = 480;
private Point origin = new Point(W / 2, H / 2);
private Point mousePt;
public MouseDragTest() {
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
mousePt = e.getPoint();
repaint();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int dx = e.getX() - mousePt.x;
int dy = e.getY() - mousePt.y;
origin.setLocation(origin.x + dx, origin.y + dy);
mousePt = e.getPoint();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(0, origin.y, getWidth(), origin.y);
g.drawLine(origin.x, 0, origin.x, getHeight());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame(TITLE);
f.add(new MouseDragTest());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
Related
I am trying to make a chess game in java, by having a class of pieces and a subclass for each piece. However, When I try to draw the pieces, The position doesn't seem to register.
Here is my Piece class:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.net.URL;
public class Piece {
public int Id;
public int color;
public int state;
public Image Sprite;
public AffineTransform tx;
public boolean dragged;
public int x;
public int y;
public Piece(int Id, int color, int position){
dragged = false;
this.Id = Id;
this.color = color;
int x = 100*(position % 8);
int y = 100*(position / 8);
System.out.println(x);
tx = AffineTransform.getTranslateInstance(x, y);
init(x, y);
}
private void init (double a, double b) {
tx.setToTranslation(a, b);
tx.scale(0.1, 0.1);
}
private void update(){
tx.setToTranslation(x*1000, y*1000);
tx.scale(0.1, 0.1);
}
protected Image getImage(String path) {
Image tempImage = null;
try {
URL imageURL = Piece.class.getResource(path);
tempImage = Toolkit.getDefaultToolkit().getImage(imageURL);
} catch (Exception e) {e.printStackTrace();}
return tempImage;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
update();
g2.drawImage(Sprite, tx, null);
}
}
my pawn class:
public class Pawn extends Piece {
public Pawn(int Id, int color, int position) {
super(Id, color, position);
this.state = 0;
String path = "/imgs/Pieces/";
if(color == 0){
path += "W";
} else{
path += "B";
}
path += "_Pawn.png";
Sprite = getImage(path);
}
}
my Board class:
Piece[][] board;
public Board(){
board = new Piece[8][8];
for(int i = 0; i < 8; i++){
board[1][i] = new Pawn(i, 1, 8+i);
}
for(int i = 0; i < 8; i++){
board[6][i] = new Pawn(i, 0, 8+i);
}
}
}
and my main class:
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main extends JPanel implements ActionListener, MouseListener, KeyListener{
Color GREEN = new Color( 41, 176, 59);
Color WHITE = new Color(254, 255, 228);
Board board = new Board();
public static void main(String[] args) {
new Main();
}
public void paint(Graphics g){
super.paintComponent(g);
boolean flag = true;
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
if(flag){
g.setColor(WHITE);
} else{
g.setColor(GREEN);
}
g.fillRect((j*100), (i*100), ((j+1)*100), ((i+1)*100));
flag = !flag;
}
flag = !flag;
}
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
if(board.board[i][j] != null){
board.board[i][j].paint(g);
}
}
}
}
public Main() {
JFrame f = new JFrame("Chess");
f.setSize(new Dimension(800, 800));
f.setBackground(Color.blue);
f.add(this);
f.setResizable(false);
f.setLayout(new GridLayout(1,2));
f.addMouseListener(this);
f.addKeyListener(this);
Timer t = new Timer(16, this);
t.start();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
I had previously written a game that implemented this techqnique, so I'm not sure what could have gone wrong with this one
It's really important to read the documentation, especially for something that is (to my simple brain), complicated.
If you have a read of the documentation for AffineTransform#scale
Concatenates this transform with a scaling transformation
(emphis added by me)
This is important, as it seems to apply that each time you call it, it will apply ANOTHER scaling operation.
Based on your avaliable code, this means that when the Piece is created, a scale is applied and the each time it's painted, a new scale is applied, until you're basically scaled out of existence.
Sooo. I took out your init (applied the scale within the constructor directly instead) and update methods and was able to get a basic result
Scaling from 1.0, 0.75, 0.5, 0.25and0.1` (it's there but I had to reduce the size of the output)
Runnable example...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new Main());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class Main extends JPanel implements ActionListener, MouseListener, KeyListener {
Color GREEN = new Color(41, 176, 59);
Color WHITE = new Color(254, 255, 228);
Board board;
public Main() throws IOException {
board = new Board();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
super.paintComponent(g);
boolean flag = true;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (flag) {
g.setColor(WHITE);
} else {
g.setColor(GREEN);
}
g.fillRect((j * 100), (i * 100), ((j + 1) * 100), ((i + 1) * 100));
flag = !flag;
}
flag = !flag;
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (board.board[i][j] != null) {
board.board[i][j].paint(g);
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
public class Board {
Piece[][] board;
public Board() throws IOException {
board = new Piece[8][8];
for (int i = 0; i < 8; i++) {
board[1][i] = new Pawn(i, 1, 8 + i);
}
for (int i = 0; i < 8; i++) {
board[6][i] = new Pawn(i, 0, 16 + i);
}
}
}
public class Pawn extends Piece {
public Pawn(int Id, int color, int position) throws IOException {
super(Id, color, position);
this.state = 0;
String path = "/imgs/Pieces/";
if (color == 0) {
path += "W";
} else {
path += "B";
}
path += "_Pawn.png";
Sprite = getImage(path);
}
}
public class Piece {
public int Id;
public int color;
public int state;
public Image Sprite;
public AffineTransform tx;
public boolean dragged;
public int x;
public int y;
public Piece(int Id, int color, int position) {
dragged = false;
this.Id = Id;
this.color = color;
x = 100 * (position % 8);
y = 100 * (position / 8);
System.out.println(x + "x" + y);
tx = AffineTransform.getTranslateInstance(x, y);
tx.scale(0.1, 0.1);
}
protected Image getImage(String path) throws IOException {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setFont(new JLabel().getFont().deriveFont(Font.PLAIN, 16));
g2d.setColor(Color.BLACK);
FontMetrics fm = g2d.getFontMetrics();
int cellX = x;
int cellY = y;
String text = x + "x" + y;
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.setStroke(new BasicStroke(16));
g2d.drawRect(0, 0, 99, 99);
g2d.dispose();
return img;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(Sprite, tx, null);
}
}
}
Could get a similar effect to this
https://www.youtube.com/watch?v=ubFq-wV3Eic
in Java? The goal is to have it as fast as possible - my mission is to find out if it's possible to burn pixels in my lcd this way. It may sound ridiculous, but I still need to find out.
The links Spektre shared say that 16 levels of gray are enough, so you can generate a random value in the [0, 15] range, and multiply that to get a pixel value.
Since you’re concerned about speed, you can directly manipulate the DataBuffer of a BufferedImage to update the pixels. TYPE_USHORT_GRAY guarantees an image with a ComponentColorModel and ComponentSampleModel:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsDevice;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferUShort;
import java.awt.image.ComponentSampleModel;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.util.Random;
public class NoiseGenerator
extends JPanel {
private static final long serialVersionUID = 1;
private static final int FPS = Math.max(1, Integer.getInteger("fps", 30));
private final int width;
private final int height;
private final BufferedImage image;
private final Random random = new Random();
public NoiseGenerator(GraphicsDevice screen) {
this(screen.getDefaultConfiguration().getBounds().getSize());
}
public NoiseGenerator(Dimension size) {
this(size.width, size.height);
}
public NoiseGenerator(int width,
int height) {
this.width = width;
this.height = height;
this.image = new BufferedImage(width, height,
BufferedImage.TYPE_USHORT_GRAY);
setFocusable(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
private void generateFrame() {
DataBufferUShort buffer = (DataBufferUShort)
image.getRaster().getDataBuffer();
short[] data = buffer.getData();
ComponentSampleModel sampleModel = (ComponentSampleModel)
image.getSampleModel();
int stride = sampleModel.getScanlineStride();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
data[y * stride + x] = (short) (random.nextInt(16) * 4096);
}
}
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
public void start() {
Timer timer = new Timer(1000 / FPS, e -> generateFrame());
timer.start();
}
public static void main(String[] args) {
int screenNumber = args.length > 0 ? Integer.parseInt(args[0]) : -1;
EventQueue.invokeLater(() -> {
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice screen = env.getDefaultScreenDevice();
if (screenNumber >= 0) {
GraphicsDevice[] screens = env.getScreenDevices();
if (screenNumber >= screens.length) {
System.err.println("Cannot detect screen " + screenNumber);
System.exit(2);
}
screen = screens[screenNumber];
}
NoiseGenerator generator = new NoiseGenerator(screen);
generator.start();
JFrame window = new JFrame("Noise Generator",
screen.getDefaultConfiguration());
window.setUndecorated(true);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
generator.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent event) {
System.exit(0);
}
});
generator.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent event) {
System.exit(0);
}
#Override
public void keyPressed(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
}
});
window.getContentPane().add(generator);
screen.setFullScreenWindow(window);
});
}
}
I am trying to draw two circle on a panel with a line joining them, all after a button is pressed. So far (apart from tweaking locations of the line) this is ok. However, I would like to animate it using a timer. The first circle should appear, then gradually the line will be revealed, and finally the second circle.
I have looked at many examples of timers, but I can't seem to get it to work for me. I must be misunderstanding something.
here is the ball class (for each circle):
package twoBalls;
import java.awt.Color;
import java.awt.Point;
public class Ball {
private int x;
private int y;
private int r;
private Color color;
private Point location;
private Ball parent;
public Ball(int x, int y, int r) {
this.x = x;
this.y = y;
this.r = r;
Point p = new Point(x, y);
setLocation(p);
}
public void setParent(Ball b) {
parent = b;
}
public Ball getParent() {
return parent;
}
public void setx(int x) {
this.x = x;
}
public void sety(int y) {
this.y = y;
}
public int getx() {
return x;
}
public int gety() {
return y;
}
public int getr() {
return r;
}
public void setPreferedSize() {
}
public void setLocation(Point p) {
setx(p.x);
sety(p.y);
location = p;
}
public Point getLocation() {
return location;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
then the class that will store balls in an arrayList. And I think that this is where the actual drawing should take place, along with the timer.
I am trying to set the start and end point of the line to be the same, and increment the end point until it is where it should be, using the timer. I'm probably way of track, but that was the intention!
I have change this class, the if statements in the while loop can now be entered, as I am now comparing different point. But the line doesn't get drawn at all still.
package twoBalls;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;
public class BallsArray extends JPanel implements ActionListener {
private ArrayList<Ball> balls;
private Timer timer;
private final int DELAY = 25;
private int xDest;
private int yDest;
private Point dest;
private Point starts;
private int xStart;
private int yStart;
public BallsArray() {
balls = new ArrayList<Ball>();
timer = new Timer(DELAY, this);
yDest = 0;
xDest = 0;
dest = new Point(xDest, yDest);
starts = new Point(xStart, yStart);
}
public void setDestXY(int x, int y) {
xDest = x;
yDest = y;
dest = new Point(xDest, yDest);
setDest(dest);
}
public void setDest(Point p) {
dest = p;
}
public Point getDest() {
return dest;
}
public void setStartsXY(int x, int y) {
xStart = x;
yStart = y;
starts = new Point(xStart, yStart);
setStarts(starts);
}
public void setStarts(Point p) {
starts = p;
}
public Point getStarts() {
return starts;
}
public void addBall(Ball b) {
balls.add(b);
}
public void addBall(int x, int y, int r) {
balls.add(new Ball(x, y, r));
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
for (int i = 0; i < balls.size(); i++) {
if (i == 0) {
paintBall(balls.get(0), g2);
}
if (i != 0) {
int j = i - 1;
Ball bp = balls.get(j);
Ball bc = balls.get(i);
bc.setParent(bp);
paintLine(bc, g2);
paintBall(bc, g2);
}
}
}
public void paintBall(Ball b, Graphics2D g2d) {
Ellipse2D circ = new Ellipse2D.Float(b.getx(), b.gety(), b.getr(),
b.getr());
g2d.draw(circ);
}
public void paintLine(Ball b, Graphics2D g2d) {
timer.start();
if (b != null && b.getLocation() != null) {
Ball parent = b.getParent();
if (parent != null) {
g2d.setColor(Color.GRAY);
if (parent.getLocation() != null && b.getLocation() != null) {
setDest(parent.getLocation());
setStarts(parent.getLocation());
g2d.draw(new Line2D.Float(starts, dest));
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// Not sure what I need to do here
// increment second location somehow
// Point s = getStarts();
Point p = getDest();
Point t = this.getLocation();
while (p != t) {
if (p.x != t.x && p.y != t.y) {
System.out.println("hello");
int x = dest.x;
int y = dest.y;
x++;
y++;
setDestXY(x, y);
p = getDest();
repaint();
} else if (p.x == t.x && p.y != t.y) {
System.out.println("part 2");
int y = dest.y;
y++;
setDestXY(dest.x, y);
p = getDest();
repaint();
} else if (p.x != t.x && p.y == t.y) {
System.out.println("part 3");
int x = dest.x;
x++;
setDestXY(x, dest.y);
p = getDest();
repaint();
}
repaint();
}
}
}
I have had a lot of help online getting this far, I worry I am just beyond my depth now!. I am unsure about the EventQueue/run part below. Here is the class to set it all up:
package twoBalls;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Display implements ActionListener {
private JFrame frame;
private JButton button;
private BallsArray b;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Display ex = new Display();
}
});
}
public Display() {
b = new BallsArray();
frame = new JFrame();
frame.setSize(800, 500);
frame.setTitle("Show balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
button = new JButton("New Ball");
frame.add(button, BorderLayout.SOUTH);
frame.setVisible(true);
button.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
Ball ball1 = new Ball(100, 100, 50);
b.addBall(ball1);
b.addBall(200, 200, 50);
frame.add(b, BorderLayout.CENTER);
frame.revalidate();
frame.repaint();
}
}
At the moment it draws the two circles, but not the line at all.
When you make an animation, it helps to use the model / view / controller pattern.
Here's the GUI I created from your code.
I simplified your Ball class. This is all you need to define a ball.
package twoBalls;
import java.awt.Color;
import java.awt.Point;
public class Ball {
private final int radius;
private final Color color;
private final Point center;
public Ball(int x, int y, int radius, Color color) {
this(new Point(x, y), radius, color);
}
public Ball(Point center, int radius, Color color) {
this.center = center;
this.radius = radius;
this.color = color;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
I created the GUIModel class to hold all of the information your GUI needs. This separates the model from the view.
package twoBalls;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
public class GUIModel {
private double direction;
private double distance;
private List<Ball> balls;
private Point lineStartPoint;
private Point lineEndPoint;
public GUIModel() {
this.balls = new ArrayList<>();
}
public void addBall(Ball ball) {
this.balls.add(ball);
}
public List<Ball> getBalls() {
return balls;
}
public void calculatePoints() {
this.lineStartPoint = balls.get(0).getCenter();
this.lineEndPoint = balls.get(1).getCenter();
this.distance = Point.distance(lineStartPoint.x, lineStartPoint.y,
lineEndPoint.x, lineEndPoint.y);
this.direction = Math.atan2(lineEndPoint.y - lineStartPoint.y,
lineEndPoint.x - lineStartPoint.x);
}
public Point getCurrentPoint(int pos, int total) {
double increment = distance / total;
double length = increment * pos;
double x = lineStartPoint.x + Math.cos(direction) * length;
double y = lineStartPoint.y - Math.sin(direction) * length;
x = Math.round(x);
y = Math.round(y);
return new Point((int) x, (int) y);
}
public Point getLineStartPoint() {
return lineStartPoint;
}
}
This class holds the two Ball instances, and calculates the length and direction of the line, divided into total increments.
Now that we've defined the model classes, let's look at the view classes. The first is your Display class.
package twoBalls;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Display implements Runnable {
private GUIModel guiModel;
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Display());
}
public Display() {
this.guiModel = new GUIModel();
Ball ball1 = new Ball(150, 200, 50, Color.BLUE);
Ball ball2 = new Ball(450, 200, 50, Color.GREEN);
guiModel.addBall(ball1);
guiModel.addBall(ball2);
guiModel.calculatePoints();
}
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Show Balls Animation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
DrawingPanel drawingPanel = new DrawingPanel(guiModel);
panel.add(drawingPanel, BorderLayout.CENTER);
panel.add(createButtonPanel(drawingPanel), BorderLayout.SOUTH);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
private JPanel createButtonPanel(DrawingPanel drawingPanel) {
JPanel panel = new JPanel();
JButton startButton = new JButton("Start Animation");
startButton.addActionListener(new StartAnimation(drawingPanel));
panel.add(startButton);
return panel;
}
public class StartAnimation implements ActionListener {
private DrawingPanel drawingPanel;
public StartAnimation(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
}
#Override
public void actionPerformed(ActionEvent event) {
LineRunnable runnable = new LineRunnable(drawingPanel);
new Thread(runnable).start();
}
}
}
The constructor of the Display class sets up the GUI model.
The run method of the Display class constructs the GUI, and starts the animation.
See how I've separated the model and view.
The StartAnimation class is your controller. It starts the animation when you left click on the JButton. I'll discuss the LineRunnable class later.
Next, let's take a look at the DrawingPanel class.
package twoBalls;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import javax.swing.JPanel;
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -3709678584255542338L;
private boolean drawLine;
private int pos;
private int total;
private GUIModel guiModel;
public DrawingPanel(GUIModel guiModel) {
this.guiModel = guiModel;
this.drawLine = false;
this.setPreferredSize(new Dimension(600, 400));
}
public boolean isDrawLine() {
return drawLine;
}
public void setDrawLine(boolean drawLine) {
this.drawLine = drawLine;
}
public void setPos(int pos) {
this.pos = pos;
repaint();
}
public void setTotal(int total) {
this.total = total;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Ball ball : guiModel.getBalls()) {
g2d.setColor(ball.getColor());
Point center = ball.getCenter();
int radius = ball.getRadius();
g2d.fillOval(center.x - radius, center.y - radius, radius + radius,
radius + radius);
}
if (isDrawLine()) {
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(5.0F));
Point a = guiModel.getLineStartPoint();
Point b = guiModel.getCurrentPoint(pos, total);
g2d.drawLine(a.x, a.y, b.x, b.y);
}
}
}
The only thing this view class does is draw the balls and the line. The responsibility for calculating the length of the line belongs in the model.
I set the preferred size here, and use the pack method in the Display class to get the size of the JFrame. You usually want to know the dimensions of the drawing area, rather than the entire window.
Finally, let's look at the LineRunnable class. This is the class that controls the animation.
package twoBalls;
import java.awt.EventQueue;
public class LineRunnable implements Runnable {
private int total;
private DrawingPanel drawingPanel;
public LineRunnable(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
this.total = 240;
}
#Override
public void run() {
setDrawLine();
for (int pos = 0; pos <= total; pos++) {
setPos(pos);
sleep(50L);
}
}
private void setDrawLine() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setDrawLine(true);
drawingPanel.setTotal(total);
}
});
}
private void setPos(final int pos) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setPos(pos);
}
});
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
}
}
}
In the run method, we divide the line into 240 segments, and draw a segment every 50 milliseconds. It takes the GUI 12 seconds to draw the line. You can play with these numbers if you wish.
The for loop is a classic animation loop. First you update the model, which I'm doing through the drawing panel. Then you sleep.
This animation loop is running on a different thread from the GUI thread. This keeps the GUI responsive. Since the loop is running on a different thread, we have to use the invokeLater method to draw on the Event Dispatch thread.
I hope this was helpful to you. Divide and conquer. Don't let a class do more than one thing.
I read many other posts regarding this and I learned that the frame is set to BorderLayout by default. I added one shape to the west and one shape to the center of the frame. But still, only one shape comes up on the frame. The shape that comes up on the frame is the one whose location is at center.
Here is the code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class ShapeMover {
private static final int FRAME_WIDTH = 400;
private static final int FRAME_HEIGHT = 400;
private static final int SHAPE_WIDTH = 50;
private static final int INITIAL_X = 30;
private static final int INITIAL_Y = 100;
private static final int rec_x = 200;
private static final int rec_y = 200;
private static final int rec_height = 30;
private static final int rec_width = 50;
private boolean recToggle = true;
private boolean circleToggle = true;
private JFrame frame;
private CircleComponent myShape;
private RectangleComponent recShape;
private JButton circle, rectangle;
private JPanel panel, panel2;
private void initialSetUp() {
frame = new JFrame();
myShape = new CircleComponent(INITIAL_X, INITIAL_Y, SHAPE_WIDTH);
recShape = new RectangleComponent(rec_x, rec_y,rec_height, rec_width);
circle = new JButton("Click for circle");
event c = new event();
circle.addActionListener(c);
rectangle = new JButton("Click for rectangle");
event2 r = new event2();
rectangle.addActionListener(r);
panel = new JPanel();
panel.add(circle);
panel.add(rectangle);
frame.add(panel, BorderLayout.NORTH);
frame.add(myShape, BorderLayout.WEST);
frame.add(recShape,BorderLayout.CENTER);
myShape.setVisible(false);
recShape.setVisible(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setVisible(true);
} //method
public class event implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
if(circleToggle == true){
myShape.setVisible(true);
circleToggle = false;
}
else{
myShape.setVisible(false);
circleToggle = true;
}
}
}
public class event2 implements ActionListener{
#Override
public void actionPerformed(ActionEvent e){
if(recToggle == true){
recShape.setVisible(true);
recToggle = false;
}
else{
recShape.setVisible(false);
recToggle = true;
}
}
}
public static void main(String[] args) {
ShapeMover sm = new ShapeMover();
sm.initialSetUp();
} //main
} //class
I warn you the other code is pretty long.
Here is rectangle component:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JComponent;
public class RectangleComponent extends JComponent{
private CompoundShape shape2;
private Point mousePoint2;
public RectangleComponent(int x, int y, int height, int width){
shape2 = new RectangleShape(x, y, height, width);
addMouseListener(new MouseAdapter(){
#Override
public void mousePressed(MouseEvent event2){
mousePoint2 = event2.getPoint();
if(!shape2.contains(mousePoint2)){
mousePoint2 = null;
}
}
});
addMouseMotionListener(new MouseMotionAdapter(){
#Override
public void mouseDragged(MouseEvent event2){
if(mousePoint2 == null){
return;
}
Point lastMousePoint2 = mousePoint2;
mousePoint2 = event2.getPoint();
double dx = mousePoint2.getX() - lastMousePoint2.getX();
double dy = mousePoint2.getY() - lastMousePoint2.getY();
shape2.translate((int) dx, (int) dy);
repaint();
}
});
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
//shape.draw(g2);
shape2.draw(g2);
} //method
}
Here is the circle Component:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JComponent;
public class CircleComponent extends JComponent{
private Circle shape2;
private Point mousePoint2;
public CircleComponent(int x, int y, int width){
shape2 = new Circle(x, y, width);
addMouseListener(new MouseAdapter(){
#Override
public void mousePressed(MouseEvent event2){
mousePoint2 = event2.getPoint();
if(!shape2.contains(mousePoint2)){
mousePoint2 = null;
}
}
});
addMouseMotionListener(new MouseMotionAdapter(){
#Override
public void mouseDragged(MouseEvent event2){
if(mousePoint2 == null){
return;
}
Point lastMousePoint2 = mousePoint2;
mousePoint2 = event2.getPoint();
double dx = mousePoint2.getX() - lastMousePoint2.getX();
double dy = mousePoint2.getY() - lastMousePoint2.getY();
shape2.translate((int) dx, (int) dy);
repaint();
}
});
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
//shape.draw(g2);
shape2.draw(g2);
} //method
}
Here is circle:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
public class Circle implements CompoundShape {
private GeneralPath path = null;
private int x;
private int y;
private final int width;
public Circle(int x, int y, int width){
this.x = x;
this.y = y;
this.width = width;
}
#Override
public void draw(Graphics2D g2) {
Ellipse2D.Double c = new Ellipse2D.Double(x,y,width,width);
g2.setColor(Color.RED);
g2.fill(c);
g2.draw(c);
path = new GeneralPath();
path.append(c,false);
g2.draw(path);
}
#Override
public void translate(int dx, int dy) {
// TODO Auto-generated method stub
x = x + dx;
y = y + dy;
}
#Override
public boolean contains(Point point) {
// TODO Auto-generated method stub
if(path == null){
return false;
}
return path.contains(point);
}
}
Here is rectangle shape:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
public class RectangleShape implements CompoundShape {
private GeneralPath path = null;
private int x;
private int y;
private final int width;
private final int height;
public RectangleShape(int x, int y, int width, int height){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
#Override
public void draw(Graphics2D g2) {
Rectangle r = new Rectangle(x,y,width,height);
g2.setColor(Color.BLUE);
g2.fill(r);
g2.draw(r);
path = new GeneralPath();
path.append(r,false);
g2.draw(path);
}
#Override
public void translate(int dx, int dy) {
// TODO Auto-generated method stub
x = x + dx;
y = y + dy;
}
#Override
public boolean contains(Point point) {
// TODO Auto-generated method stub
if(path == null){
return false;
}
return path.contains(point);
}
}
Here is compound shape:
import java.awt.Graphics2D;
import java.awt.Point;
public interface CompoundShape {
void draw(Graphics2D g2);
void translate(int dx, int dy);
boolean contains(Point point);
} //interface
Examples of iterating an ArrayList<Shape> can be seen in:
This answer to Get mouse detection with a dynamic shape.
This answer to 'Fill' Unicode characters in labels.
Of course, those examples are painting to an image, but once there is a Graphics (or Graphics2D as I prefer to work with) the principle is much the same. Copy/pasted from one example:
ArrayList<Shape> regions = separateShapeIntoRegions(imageShapeArea);
// ..
for (Shape region : regions) {
// ..
g.fill(region);
// ..
}
So I've got this challenge to make one of those spirograph drawrings in an applet. The problem is, I'm new to Java and have absolutely no idea how to get any components appearing on the screen. I'm hoping someone can just add an extra line of code to fix this, though I'm really grateful for any answers :)
import javax.swing.JApplet;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Toolkit;
public class SpaceCadets3_WB extends JApplet {
int numOfPoints = 1080;
int[] x = new int[numOfPoints];
int[] y = new int[numOfPoints];
int x1, x2, y1, y2, width, height, animationSleep = 0;
int R = 75;
int r = 10;
int O = 75;
JTextField changeNumOfPoints = new JTextField(15);
public SpaceCadets3_WB() {
}
public void start() {
this.setSize(600, 600);
this.setBackground(new Color(100,100,255));
this.getContentPane().setLayout(null);
this.getContentPane().add(changeNumOfPoints);
changeNumOfPoints.setVisible(true);
changeNumOfPoints.setLocation(width - changeNumOfPoints.getSize().width - 25, 25);
}
public void calculatePoints(){
width = SpaceCadets3_WB.this.getWidth();
height = SpaceCadets3_WB.this.getHeight();
for(int t = 0; t<numOfPoints; t++){
x[t] = (int) ((R+r)*Math.cos(t) - (r+O)*Math.cos(((R+r)/r)*t) + width/2);
y[t] = (int) ((R+r)*Math.sin(t) - (r+O)*Math.sin(((R+r)/r)*t) + height/2);
}
}
public void paint(Graphics g){
g.setColor(Color.RED);
calculatePoints();
for(int i = 0; i<numOfPoints;i++){
x1 = x[i];
x2 = x[i+1];
y1 = y[i];
y2 = y[i+1];
g.drawLine(x1, y1, x2, y2);
try {
Thread.sleep(animationSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Your sleeping the paint method, something you should never do. It's bad to call Thread.sleep(...) on the Swing event thread, but it's a sin to do it in any painting method. So now the applet can't draw. Remember that any painting method must do nothing but painting, and must do it blazingly fast. It shouldn't do any program logic, it shouldn't directly do animation, it should just paint. Use a Swing timer as any similar question will show you how to do.
Also, never draw directly in the applet but instead in the paintComponent method of a JPanel that the applet holds.
So create your JPanel, override its paintComponent method, and draw using fields of the class. Then change the state of those fields in your Timer and call repaint() on the JPanel.
Edit
And yes, you should avoid using null layouts as that's preventing you from easily and adequately adding components to your GUI. Instead in this situation, a FlowLayout, the default layout for JPanel, would work great.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SpaceCadets extends JApplet {
#Override
public void init() {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
SpaceCadetsPanel panel = new SpaceCadetsPanel();
getContentPane().add(panel);
setSize(SpaceCadetsPanel.PREF_W, SpaceCadetsPanel.PREF_H);
}
});
} catch (InvocationTargetException | InterruptedException e) {
e.printStackTrace();
}
}
}
#SuppressWarnings("serial")
class SpaceCadetsPanel extends JPanel {
public static final int PREF_W = 600;
public static final int PREF_H = 600;
public final static int TOTAL_POINTS = 1080;
private static final int R = 75;
private static final int R2 = 10;
private static final int O = 75;
private static final Color DRAW_COLOR = Color.RED;
private static final int ANIMATION_DELAY = 20;
private Point[] pts;
private JSpinner pointCountSpinner = new JSpinner(new SpinnerNumberModel(
800, 100, 1080, 1));
private JButton doItButton = new JButton(new DoItBtnAction("Do It!"));
private BufferedImage bufImg = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
private Timer timer;
public int imageIndex;
private CalcWorker calcWorker;
public Graphics2D g2;
public SpaceCadetsPanel() {
System.out.println(pointCountSpinner.getEditor().getClass());
add(pointCountSpinner);
add(doItButton);
setBackground(Color.white);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (bufImg != null) {
g.drawImage(bufImg, 0, 0, this);
}
}
class DoItBtnAction extends AbstractAction {
public DoItBtnAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
imageIndex = 0;
if (timer != null && timer.isRunning()) {
timer.stop();
}
if (g2 != null) {
g2.dispose();
}
pts = new Point[0];
bufImg = new BufferedImage(PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB);
g2 = bufImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(DRAW_COLOR);
int totalPoints = 0;
totalPoints = ((Integer) pointCountSpinner.getValue()).intValue();
timer = new Timer(ANIMATION_DELAY, new TimerListener(totalPoints));
calcWorker = new CalcWorker(totalPoints);
calcWorker.addPropertyChangeListener(new CalcWorkerListener());
calcWorker.execute();
}
}
class CalcWorker extends SwingWorker<Point[], Void> {
private int totalPoints;
public CalcWorker(int totalPoints) {
this.totalPoints = totalPoints;
}
#Override
protected Point[] doInBackground() throws Exception {
Point[] pts2 = new Point[totalPoints];
for (int i = 0; i < pts2.length; i++) {
int x = (int) ((R + R2) * Math.cos(i) - (R2 + O)
* Math.cos(((R + R2) / R2) * i) + PREF_W / 2);
int y = (int) ((R + R2) * Math.sin(i) - (R2 + O)
* Math.sin(((R + R2) / R2) * i) + PREF_H / 2);
pts2[i] = new Point(x, y);
}
return pts2;
}
}
class CalcWorkerListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
try {
pts = calcWorker.get();
timer.start();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
class TimerListener implements ActionListener {
private int totalPoints;
public TimerListener(int totalPoints) {
this.totalPoints = totalPoints;
}
#Override
public void actionPerformed(ActionEvent e) {
int x1 = pts[imageIndex].x;
int y1 = pts[imageIndex].y;
int x2 = pts[imageIndex + 1].x;
int y2 = pts[imageIndex + 1].y;
g2.drawLine(x1, y1, x2, y2);
repaint();
imageIndex++;
if (imageIndex == totalPoints - 1) {
((Timer) e.getSource()).stop();
}
}
}
}
Which displays as,