I have a Panel in which some 2D objects are moving about. I have overridden paintComponent() as necessary. Now I want to be able to zoom in and zoom out that area. When zooming in, scrollbars will appear by which one can view the entire field by scrolling. While zooming in and out, the 2D objects should increase or decrease in size accordingly. Which Swing component or rather combination of components will help to achieve this?
Easiest way is to modify your panel and introduce a double indicating your zoom level. This double would indicate your scale, where 1 is normal and higher is zoomed in. You can use that double together with Graphics2D in your paintComponent.
Such as:
Graphics2D g2 = (Graphics2D) g;
int w = // real width of canvas
int h = // real height of canvas
// Translate used to make sure scale is centered
g2.translate(w/2, h/2);
g2.scale(scale, scale);
g2.translate(-w/2, -h/2);
For the scrolling, put your panel in a JScrollPane and combine that with a getPreferredSize that also uses your zoom scale. JScrollPane uses the preferred size of the component you put in it. It will show scrollbars if the preferred size exceeds its own size.
If you change the preferred size of your panel so that the width and height it returns is scaled you should be fine. Basically you can just return something like:
return new Dimension(w * scale, h * scale)
I know this question is old, but I thought I could post my solution in case it could be useful for someone in the future.
So, I created a class that extends JPanel which implements the MouseWheelListener in order to detect when the user rolls the mouse. My class also listens for dragging in order to move the contents when the user clicks and drags.
Code Explanation
First, in the constructor you must set this as the MouseWheelListener
addMouseWheelListener(this);
For the zoom in and out I used a boolean zoomer (to indicate when the user rolls with the mouse) and two doubles zoomFactor (to keep the current factor by which the objects' sizes are multiplied) and prevZoomFactor (for the previous zoom factor).
private double zoomFactor = 1;
private double prevZoomFactor = 1;
private boolean zoomer;
I also override the paint() method of the JPanel, in which (before drawing anything) when the user zooms (zoomer=true) I scale the graphics by the zoomFactor. Code:
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
if (zoomer) {
AffineTransform at = new AffineTransform();
at.scale(zoomFactor, zoomFactor);
prevZoomFactor = zoomFactor;
g2.transform(at);
zoomer = false;
}
// All drawings go here
}
Finally, I override the mouseWheelMoved method of the MouseWheelListener, in which I increase the zoomFactor (if the user rolls up) or decrease the zoomFactor (if the user rolls down). Code:
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoomer = true;
//Zoom in
if (e.getWheelRotation() < 0) {
zoomFactor *= 1.1;
repaint();
}
//Zoom out
if (e.getWheelRotation() > 0) {
zoomFactor /= 1.1;
repaint();
}
}
Working Example
If you also want to use the drag function and want to zoom according to the position of the mouse, you can use the class below, which gets a BufferedImage as a parameter in the constructor in order to display something on screen.
I have also uploaded a project on GitHub called Zoomable-Java-Panel in which there is a functional example of what I showed above, which you can test and see how it can be implemented into a project.
package zoomable.panel;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
/**
*
* #author Thanasis1101
* #version 1.0
*/
public class MainPanel extends JPanel implements MouseWheelListener, MouseListener, MouseMotionListener {
private final BufferedImage image;
private double zoomFactor = 1;
private double prevZoomFactor = 1;
private boolean zoomer;
private boolean dragger;
private boolean released;
private double xOffset = 0;
private double yOffset = 0;
private int xDiff;
private int yDiff;
private Point startPoint;
public MainPanel(BufferedImage image) {
this.image = image;
initComponent();
}
private void initComponent() {
addMouseWheelListener(this);
addMouseMotionListener(this);
addMouseListener(this);
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
if (zoomer) {
AffineTransform at = new AffineTransform();
double xRel = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX();
double yRel = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY();
double zoomDiv = zoomFactor / prevZoomFactor;
xOffset = (zoomDiv) * (xOffset) + (1 - zoomDiv) * xRel;
yOffset = (zoomDiv) * (yOffset) + (1 - zoomDiv) * yRel;
at.translate(xOffset, yOffset);
at.scale(zoomFactor, zoomFactor);
prevZoomFactor = zoomFactor;
g2.transform(at);
zoomer = false;
}
if (dragger) {
AffineTransform at = new AffineTransform();
at.translate(xOffset + xDiff, yOffset + yDiff);
at.scale(zoomFactor, zoomFactor);
g2.transform(at);
if (released) {
xOffset += xDiff;
yOffset += yDiff;
dragger = false;
}
}
// All drawings go here
g2.drawImage(image, 0, 0, this);
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoomer = true;
//Zoom in
if (e.getWheelRotation() < 0) {
zoomFactor *= 1.1;
repaint();
}
//Zoom out
if (e.getWheelRotation() > 0) {
zoomFactor /= 1.1;
repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
Point curPoint = e.getLocationOnScreen();
xDiff = curPoint.x - startPoint.x;
yDiff = curPoint.y - startPoint.y;
dragger = true;
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
released = false;
startPoint = MouseInfo.getPointerInfo().getLocation();
}
#Override
public void mouseReleased(MouseEvent e) {
released = true;
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
Related
I am trying to rotate a circle around a separate point in a program. right now I can get the circle to rotate but it slowly starts getting closer and closer to the point it's rotating from. I am trying to do this using JPanel and implementing it as a rectangle.
package WoffindenZone;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.event.*;
import java.lang.Math;
public class Protector extends Rectangle{
double Velocity;
int speed = 3;
Protector(int x, int y, int PROTECTOR_DIAMETER){
super(x,y,PROTECTOR_DIAMETER,PROTECTOR_DIAMETER);
}
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(speed);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(speed);
move();
}
}
public void keyReleased(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(0);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(0);
move();
}
}
public void setDirection(int Direction){
Velocity = Direction*Math.PI/180;
}
public void move(){
x = (int)Math.round(500 + Math.cos(Velocity) * (x-500) - Math.sin(Velocity) * (y-((1000*0.5555)/2)));
y = (int)Math.round(((1000*0.5555)/2) + Math.sin(Velocity) * (x-500) + Math.cos(Velocity) * (y-((1000*0.5555)/2)));
System.out.println(x);
System.out.println(y);
}
public void draw(Graphics g){
g.setColor(Color.blue);
g.fillOval(x,y,width,height);
}
Use a rotation instance of an AffineTransform. See getRotateInstance(theta,anchorx,anchory) for details.
Returns a transform that rotates coordinates around an anchor point. This operation is equivalent to translating the coordinates so that the anchor point is at the origin (S1), then rotating them about the new origin (S2), and finally translating so that the intermediate origin is restored to the coordinates of the original anchor point (S3).
How do you rotate a circle about a point in JPanel?
Here's how I rotate a circle about a point in a JPanel.
I don't know how to make an animated GIF. Just imagine the blue circle rotating clockwise around the center of the drawing JPanel.
So, let's start at the beginning. Basically, I have a circle rotating on the circumference of another circle. So, I create a Circle model class from plain Java.
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
The class consists of basic getters and setters. I make the radius and color final because they don't change value in this Java application.
The calculateCircumferencePoint method is the only interesting method. It takes an int angle in degrees, and calculates the point on the circumference represented by that angle, rounded to the nearest X and Y integer points.
Next, we create two Circle instances, an inner circle and an outer circle. Here's the class constructor setting the preferred size of the drawing area, the inner circle, and the outer circle. We start the outer circle at zero degrees (to the right);
private Circle innerCircle;
private Circle outerCircle;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
Now, we can start coding the GUI. First, we start the Java application by calling the SwingUtilities invokeLater method. This method ensures that we create and execute the Swing components on the Event Dispatch Thread.
Next, we define the JFrame. Here's the code we have so far.
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
The JFrame methods must be called in a certain order. This is the order I use for most of my SWwing applications.
I pack the JFrame. I don't set a JFrame size. I let the Swing layout managers set the size of my JFrame. The default layout of a JFrame content pane is a BorderLayout. I put my drawing JPanel in the center of the BorderLayout.
Next, I create the drawing JPanel.
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
All the drawing JPanel does is paint a Circle object. Pretty straightforward.
The fillOval method paints an oval from the upper left hand corner. We calculate the upper left hand point from the center point.
The responsibility for calculating and updating the outer circle center point falls to my controller class, the Animation class. I use a simple loop to update the theta angle, calculate the new outer circle center point, paint the outer circle, and wait a period of time.
Here's that code.
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
The Animation repaint method makes the call to the drawing JPanel repaint method inside another SwingUtilities invokeLater method. This method ensures that the drawing happens on the Event Dispatch Thread.
Finally, here's the complete, runnable, example. I used inner classes so I could post the code as one block, and you can copy and run this code as one block. Generally, classes should be in separate files and for a more complex GUI, separate packages.
import java.awt.BorderLayout;
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RotateCircle implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
}
I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}
I have a problem. I want to be able to zoom into my Graphics2D screen using the mouse wheel but I want to be able to translate the Graphics2D as well so that it's right on the spot that I scaled. Here's what's happening so far: "http://cdn.makeagif.com/media/6-11-2015/E0kYGY.gif" It's translating to the player's position correctly but I want it to be zoomable in and out with the scrolling of the mouse wheel. Here's the code I have so far to do it:
private AffineTransform getCamTransform()
{
AffineTransform t = new AffineTransform();
float a = 400 - player.getX();
float b = 250 - player.getY();
t.translate(a, b);
/*
* Here's where I want to scale using the zoom
* variable and also translate the g2d according
* to the zoom variable.
*/
return t;
}
private double zoom = 2.0D;
#Override
public void mouseWheelMoved(MouseWheelEvent e)
{
zoom += e.getPreciseWheelRotation();
}
This is just a small segment of the code I have. I have a method that draws everything to the screen but this is just to return the transform that must be applied to the camera. The transform is then set to the previous transform of the Graphics2D.
Let me know if there's an easier way to do this or I'm doing it all wrong. I do also want to be efficient on the CPU as well. Thanks to whoever answers!
The basic idea is, you want to make it appear as if the graphics is remaining centred within the viewable area. I first thought about trying to scale the translation points, and maybe that might work, but I couldn'y get it to work (3 year old wanting to read books didn't give me much time to experiment).
Basically, you need to add another translation in which moves the x/y point the appropriate amount based on the viewable area and the zoom so the image appears to remain stationary within the viewable area, then apply the zoom and the normal translation on-top of it...
double width = getWidth();
double height = getHeight();
double zoomWidth = width * zoom;
double zoomHeight = height * zoom;
double anchorx = (width - zoomWidth) / 2;
double anchory = (height - zoomHeight) / 2;
AffineTransform at = new AffineTransform();
at.translate(anchorx, anchory);
at.scale(zoom, zoom);
at.translate(-100, -100);
So, the anchorx/y represent the amount the resulting graphics would need be offset by, based on the viewable areas width and height, so that the image will remain "centred" within in it...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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 double zoom = 1d;
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("..."));
} catch (IOException ex) {
ex.printStackTrace();
}
addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getPreciseWheelRotation() < 0) {
zoom -= 0.1;
} else {
zoom += 0.1;
}
// zoom += e.getPreciseWheelRotation();
if (zoom < 0.01) {
zoom = 0.01;
}
repaint();
}
});
}
public String format(double value) {
return NumberFormat.getNumberInstance().format(value);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
double width = getWidth();
double height = getHeight();
double zoomWidth = width * zoom;
double zoomHeight = height * zoom;
double anchorx = (width - zoomWidth) / 2;
double anchory = (height - zoomHeight) / 2;
AffineTransform at = new AffineTransform();
at.translate(anchorx, anchory);
at.scale(zoom, zoom);
at.translate(-100, -100);
g2d.setTransform(at);
g2d.drawImage(img, 0, 0, this);
g2d.dispose();
}
}
}
There's probably a really awesome mathematical formula which you could use instead, but me be dumb ;)
i have been working on mouse motion listener in Java couldn't sort it out completely because i want the object to move towards the direction where ever on the screen the mouse is pointed at but unforunately when the mouse is inside the applet window, the object moves only towards a single direction. Here is my code below..
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.applet.*;
import java.awt.event.*;
import javax.swing.*;
public class MouseOver extends Applet implements KeyListener, MouseListener,
MouseMotionListener {
private int[] Xpoints = { 0, -5, 5 };
private int[] Ypoints = { -10, -2, -2 };
private double xpos, ypos;
private Polygon poly;
int polyrot = 0;
private int width; // !! added
private int height; // !! added
public void init() {
poly = new Polygon(Xpoints, Ypoints, Xpoints.length);
addKeyListener(this);
addMouseListener(this);
addMouseMotionListener(this);
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform id = new AffineTransform();
width = getSize().width;
height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.RED);
g2d.draw(poly);
g2d.translate(width / 2, height / 2);
g2d.rotate(Math.toRadians(polyrot));
g2d.scale(5, 5);
}
public void keyReleased(KeyEvent k) {
}
public void keyTyped(KeyEvent k) {
}
public void keyPressed(KeyEvent k) {
switch (k.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (polyrot < 0) {
polyrot = 359;
polyrot++;
}
repaint();
break;
case KeyEvent.VK_RIGHT:
if (polyrot > 360) {
polyrot = 0;
polyrot--;
}
repaint();
break;
}
}
public void mouseEntered(MouseEvent m) {
}
public void mouseExited(MouseEvent m) {
}
public void mouseReleased(MouseEvent m) {
}
public void mouseClicked(MouseEvent m) {
}
public void mousePressed(MouseEvent m) {
switch (m.getButton()) {
case MouseEvent.BUTTON1:
if (polyrot < 0) {
polyrot = 359;
polyrot--;
}
repaint();
break;
case MouseEvent.BUTTON2:
if (polyrot > 360) {
polyrot = 0;
polyrot++;
}
repaint();
break;
}
}
public void mouseMoved(MouseEvent e) {
xpos = getX();
if (xpos < 0) {
polyrot--;
} else if (xpos > 0) {
polyrot++;
}
repaint();
// !! break; // Doesn't belong here
}
#Override
public void mouseDragged(MouseEvent e) {
// You forgot this method
}
}
Your problem is with this line:
public void mouseMoved(MouseEvent e){
xpos=getX(); // ******
if(xpos<0){polyrot--;}
else if(xpos>0){polyrot++;}
repaint();
break;
}
That returns the x position of the applet not the mouse cursor. You need to use your MouseEvent object, e and instead get the mouse's position. Change it to:
xpos = e.getX();
Please don't ignore the comment that I made to your question. Please remember that we're volunteers who help on our free time. Please don't make it any more difficult than it has to be to help you.
I've tried to edit your code so that it compiles, and now is indented. Consider creating a Swing application, not an AWT application, since Swing apps are more flexible, powerful and robust.
This:
if (xpos < 0) {
means "if the cursor is outside of the panel".
This:
xpos = getX();
does not get a mouse coordinate.
Change your event to something like this:
public void mouseMoved(MouseEvent e) {
xpos = e.getX();
if (xpos < getWidth() / 2) {
polyrot--;
} else {
polyrot++;
}
repaint();
}
Now it rotates counter-clockwise if the cursor is on the left side of the panel and clockwise if the cursor is on the right side.
This:
g2d.draw(poly);
g2d.translate(width / 2, height / 2);
g2d.rotate(Math.toRadians(polyrot));
g2d.scale(5, 5);
will not do anything to change the image because you are doing your transforming after drawing it.
This:
Graphics2D g2d = (Graphics2D) g;
is a bad idea because you are applying transforms to the global graphics context which would carry on to subsequent repaints of other components.
Change your paint to something like this:
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g.create();
width = getSize().width;
height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.translate(width / 2, height / 2);
g2d.rotate(Math.toRadians(polyrot));
g2d.scale(5, 5);
g2d.setColor(Color.RED);
g2d.draw(poly);
g2d.dispose();
}
Further reading:
Painting in AWT and Swing
How to Write a Mouse Listener
There are a few things...
In your keyPressed and mousePressed events, are are only ever process the out of bounds conditions, for example...
if (polyrot < 0) {
polyrot = 359;
polyrot++;
}
//...
if (polyrot > 360) {
polyrot = 0;
polyrot--;
}
But you never process what it should do when it's within the acceptable bounds (0-359)...
Instead, you could simply add or subtract the amount from polyrot and allow the API to deal with it (surprisingly, it's capable for dealing with angles < 0 and > 359), for example...
public void mousePressed(MouseEvent m) {
switch (m.getButton()) {
case MouseEvent.BUTTON1:
polyrot--;
repaint();
break;
case MouseEvent.BUTTON2:
polyrot++;
repaint();
break;
}
}
Now, I'm not sure what you mean by "object to move towards the direction where ever on the screen the mouse is pointed". Does this mean that the object should actually change it's x/y coordinates or should it just "look" at the mouse cursor...
Based on the fact that you actually have no movement code and you basically have the object painted in a fixed location, I'm assuming "look at"...
Basically, you need to know where the mouse is and where the object is, then determine the angle between them...
public void mouseMoved(MouseEvent e) {
int x = width / 2;
int y = height / 2;
Point mousePoint = e.getPoint();
int deltaX = mousePoint.x - x;
int deltaY = mousePoint.y - y;
polyrot = -Math.atan2(deltaX, deltaY);
polyrot = Math.toDegrees(polyrot) + 180;
repaint();
}
You should note that I changed 'polyrot' to 'double'
Your paint method is also wrong. Basically, you are painting your object BEFORE you've transformed it, instead, you should be using something more like...
g2d.translate(width / 2, height / 2);
g2d.rotate(Math.toRadians(polyrot));
g2d.draw(poly);
You should also be calling super.paint(g) before you apply you own custom painting...
As a side note, you should avoid overriding paint of top level containers, like JApplet, but instead, create a custom component, extending from something like JPanel and override it's paintComponent method, performing your custom painting there (don't forget to call super.paintComponent). Take a look at Performing Custom Painting for more details
You should also avoid using KeyListener and instead use the Key Bindings API as it doesn't suffer from the same focus issues that KeyListener does...
Updated with runnable example
So I had a play around with code and produced this simple example...
Basically, I tossed out Polygon in favour of Path2D, basically because it provides much greater functionality and is easy to deal with when scaling ;)
import java.applet.Applet;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
public class MouseOver extends Applet implements KeyListener, MouseListener,
MouseMotionListener {
private double xpos, ypos;
private Path2D poly;
private double polyrot = 0;
private int width; // !! added
private int height; // !! added
public void init() {
poly = new Path2D.Double();
poly.moveTo(0, 10);
poly.lineTo(5, 0);
poly.lineTo(10, 10);
poly.lineTo(0, 10);
poly.closePath();
addKeyListener(this);
addMouseListener(this);
addMouseMotionListener(this);
}
public void paint(Graphics g) {
super.paint(g);;
Graphics2D g2d = (Graphics2D) g;
AffineTransform id = new AffineTransform();
width = getSize().width;
height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.RED);
id.scale(5, 5);
Shape scaled = poly.createTransformedShape(id);
Rectangle bounds = scaled.getBounds();
g2d.translate((width - bounds.width) / 2, (height - bounds.height) / 2);
g2d.rotate(Math.toRadians(polyrot), bounds.width / 2, bounds.height / 2);
g2d.setStroke(new BasicStroke(5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(scaled);
}
public void keyReleased(KeyEvent k) {
}
public void keyTyped(KeyEvent k) {
}
public void keyPressed(KeyEvent k) {
switch (k.getKeyCode()) {
case KeyEvent.VK_LEFT:
polyrot++;
repaint();
break;
case KeyEvent.VK_RIGHT:
polyrot--;
repaint();
break;
}
}
public void mouseEntered(MouseEvent m) {
}
public void mouseExited(MouseEvent m) {
}
public void mouseReleased(MouseEvent m) {
}
public void mouseClicked(MouseEvent m) {
}
public void mousePressed(MouseEvent m) {
switch (m.getButton()) {
case MouseEvent.BUTTON1:
polyrot--;
repaint();
break;
case MouseEvent.BUTTON2:
polyrot++;
repaint();
break;
}
}
public void mouseMoved(MouseEvent e) {
int x = width / 2;
int y = height / 2;
Point mousePoint = e.getPoint();
int deltaX = mousePoint.x - x;
int deltaY = mousePoint.y - y;
polyrot = -Math.atan2(deltaX, deltaY);
polyrot = Math.toDegrees(polyrot) + 180;
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
// You forgot this method
}
}
From here:
public void mouseMoved(MouseEvent e){
xpos=getX();
if(xpos<0){polyrot--;}
else if(xpos>0){polyrot++;}
repaint();
break;
}
It seems you update only the xpos. You should update also the variable ypos.
You might want to do it with something like this:
ypos=e.getY();
if (this.ypos<0){
this.polyrot--;
}else if (this.ypos>0) {
this.polyrot++;
}
this.repaint();
I want a point to move towards the mouse position. Currently the point just is the mouse location. But I want it so that the bigger the distance between mouse and center of the screen, the less force get´s added to the point. So if the mouse is enough distance away from the center, the point will just stop moving further outwards because it´s force away from the center is to small. The center should always be the point which is looked at. So no repositioning.
I am really stuck here because I am pretty young and I didn´t have had vector calculation in school (I think this is what you need here). I have no idea how to do what I am trying to archive. The only thing I tried was using the distance between center and mouse (Math.hypot(width / 2 - mouseX , height / 2 - mouseY)). But it didn´t work.
This is my minimal reproducible example:
import javax.swing.*;
import java.awt.*;
public class Example extends JPanel {
private static final int size = 500;
public Example() {
this.setPreferredSize(new Dimension(size, size));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.fillOval(getWidth() / 2 - 3, getHeight() / 2 - 3, 6, 6); // center
Point point = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(point, this);
// Do calculations with the point here
g2d.fillOval(point.x - 5, point.y - 5, 10, 10);
repaint();
}
private static void createFrame() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new Example());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(Example::createFrame);
}
}
It would look like this:
You can declare an attraction ratio value, which will strech the circle to the center of the screen, based on its value.
You need also be able to know for each cursor position how far it is from the center in percentages.
import javax.swing.*;
import java.awt.*;
public class Example extends JPanel {
private static final int SIZE = 500;
private static final int CENTER_CIRCLE_RADIUS = 3;
private static final int CIRCLE_RADIUS = 5;
private static final double ATTRACTION_RATIO = 0.75;
public Example() {
this.setPreferredSize(new Dimension(SIZE, SIZE));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
final int xCenterPoint = getWidth() / 2;
final int yCenterPoint = getHeight() / 2;
g2d.fillOval(xCenterPoint - CENTER_CIRCLE_RADIUS, yCenterPoint - CENTER_CIRCLE_RADIUS,
CENTER_CIRCLE_RADIUS * 2, CENTER_CIRCLE_RADIUS * 2); // center
Point point = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(point, this);
final double xNearToCenter = 1 - ((double) (xCenterPoint - point.x) / xCenterPoint);
final double yNearToCenter = 1 - ((double) (yCenterPoint - point.y) / yCenterPoint);
g2d.fillOval((int) (xCenterPoint * (ATTRACTION_RATIO + xNearToCenter * (1 - ATTRACTION_RATIO))),
(int) (yCenterPoint * (ATTRACTION_RATIO + yNearToCenter * (1 - ATTRACTION_RATIO))),
CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2);
repaint();
}
private static void createFrame() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Example panel = new Example();
f.add(panel);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(Example::createFrame);
}
}
I modified your code somewhat to create the following GUI.
The circle will follow your mouse around the drawing JPanel.
The first thing I did was create a Circle class. This class holds the center point, radius, and color of the circle. I use a Point2D class to hold the center point so I can move the circle in fractional pixel increments. This makes the animation smoother.
Next, I used your code to create a JFrame and a drawing JPanel. I like to separate the JFrame code from the JPanel code. It makes it easier for me to focus on one Swing component at a time.
The drawing JPanel draws the circle. Period. The controller classes are responsible for calculating the new position of the circle and calling the repaint method of the drawing JPanel.
I created a MouseMotionListener to listen for the mouse movement. This class keeps track of the mouse position.
I created a Swing Timer to animate the motion of the circle.
I used polar geometry to calculate the direction that the circle moves. The polar geometry is in the MoveListener class, the actionPerformed method. I use an arc-tangent to calculate the angle to the mouse position in radians, then take the cosine to get the X movement, and the sine to get the Y movement.
Here's the complete runnable code.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
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.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class MouseMoveExample implements Runnable {
public static void main(String[] args) {
EventQueue.invokeLater(new MouseMoveExample());
}
private final int size = 500;
private Circle circle;
private DrawingPanel drawingPanel;
public MouseMoveExample() {
this.circle = new Circle(new Point(size / 2, size / 2), 12,
Color.BLACK);
}
#Override
public void run() {
JFrame f = new JFrame("Mouse Move Example");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.drawingPanel = new DrawingPanel();
f.add(drawingPanel);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(size, size));
this.addMouseMotionListener(new MoveListener());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(circle.getColor());
Point centerPoint = circle.getCenterPoint();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.fillOval(centerPoint.x - radius, centerPoint.y - radius,
diameter, diameter);
}
}
public class MoveListener extends MouseAdapter {
private MoveTimer moveTimer;
private Timer timer;
public MoveListener() {
this.moveTimer = new MoveTimer();
this.timer = new Timer(20, moveTimer);
this.timer.start();
}
#Override
public void mouseMoved(MouseEvent event) {
moveTimer.setEventPoint(event.getPoint());
}
}
public class MoveTimer implements ActionListener {
private Point eventPoint;
public void setEventPoint(Point eventPoint) {
this.eventPoint = eventPoint;
}
#Override
public void actionPerformed(ActionEvent event) {
Point centerPoint = circle.getCenterPoint();
if (eventPoint != null) {
double theta = Math.atan2(eventPoint.y - centerPoint.y,
eventPoint.x - centerPoint.x);
int increment = 1;
double x = Math.cos(theta) * increment;
double y = Math.sin(theta) * increment;
circle.incrementCenterPoint(x, y);
drawingPanel.repaint();
}
}
}
public class Circle {
private final int radius;
private final Color color;
private Point2D centerPoint;
public Circle(Point centerPoint, int radius, Color color) {
setCenterPoint(centerPoint);
this.radius = radius;
this.color = color;
}
public Point getCenterPoint() {
return new Point((int) Math.round(centerPoint.getX()),
(int) Math.round(centerPoint.getY()));
}
public void setCenterPoint(Point centerPoint) {
this.centerPoint = new Point2D.Double(
centerPoint.x, centerPoint.y);
}
public void incrementCenterPoint(double x, double y) {
centerPoint.setLocation(centerPoint.getX() + x,
centerPoint.getY() + y);
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
}
}