I am trying to implement a zoom tool for my game editor. I am using a Color[] to store all the "Colors/tiles" and I want to be able to zoom inside my JPanel to view all the tiles upclose or from far away i.e zoomed in or zoomed out. These are some requirements that I would like to have(Any examples are appreciated and they don't need to include all or any of the requirements. They are there to help you understand what I am aiming for):
Zooming should be determined by a zoom variable that can be changed dynamicly(prefferably mouseWheel)
When zoomed(in or out) you should be able to navigate horisontaly and vertically with as little as possible tearing or other graphical glitches(using the JScrollBar s in the example)
The zooming should be based on where the mouse cursor is and zoom towards that point
The zooming can use anything inside normal Java which means no external libraries.
This is a very simple version of my editor. I added some test code for zooming but it doesn't work. I am giving you this code so you have something to start from if neccesary. See the PaintComponent method and the mouseWheel listener.
EDITED: Thanks to #trashgod you can now place colors at the right position. See MouseTest class!
The tearing is gone.
package stuff;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
public class ZoomPane extends JPanel implements MouseWheelListener, Scrollable {
private int width, height, screenWidth, screenHeight, tileSize;
private Color[] colorMap;
private double scale = 1.0;
// Zooming
AffineTransform at;
class MouseTest extends MouseAdapter {
public void mousePressed(MouseEvent e) {
try {
int newX = (int) at.inverseTransform(e.getPoint(), null).getX();
int newY = (int) at.inverseTransform(e.getPoint(), null).getY();
colorMap[(newX / tileSize) + ((newY / tileSize) * width)] = getRandomColor();
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
super.mouseDragged(e);
try {
System.out.println("Dragging");
int newX = (int) at.inverseTransform(e.getPoint(), null).getX();
int newY = (int) at.inverseTransform(e.getPoint(), null).getY();
colorMap[(newX / tileSize) + ((newY / tileSize) * width)] = getRandomColor();
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
repaint();
}
}
public ZoomPane(int width, int height, int tileSize) {
super();
this.width = width;
this.height = height;
this.screenWidth = width * tileSize;
this.screenHeight = height * tileSize;
this.tileSize = tileSize;
this.colorMap = new Color[width * height];
addMouseWheelListener(this);
addMouseListener(new MouseTest());
addMouseMotionListener(new MouseTest());
}
public void paintComponent(Graphics g) {
super.paintComponents(g);
final Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Scale
at = null;
at = g2d.getTransform();
// Translate code here?
at.scale(scale, scale);
g2d.setTransform(at);
final Rectangle clip = g2d.getClipBounds();
g2d.setColor(Color.DARK_GRAY);
g2d.fill(clip);
int topX = clip.x / tileSize;
int topY = clip.y / tileSize;
int bottomX = clip.x + clip.width / tileSize + 1 + (int) (tileSize * scale);
int bottomY = clip.y + clip.height / tileSize + 1;
// Draw colors
for (int y = topY; y < bottomY; y++) {
for (int x = topX; x < bottomX; x++) {
Rectangle r = new Rectangle(width, height);
if (r.contains(x, y) && colorMap[x + y * width] != null) {
g2d.setColor(colorMap[x + y * width]);
g2d.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
g2d.dispose();
}
private Color getRandomColor() {
Random rand = new Random();
return new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int) (screenWidth), (int) (screenHeight));
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
double delta = 0.05 * e.getPreciseWheelRotation();
if (scale + delta > 0)
scale += delta;
revalidate();
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Zoom test");
// Frame settings
frame.setVisible(true);
frame.setPreferredSize(new Dimension(800, 600));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane pane = new JScrollPane(new ZoomPane(50, 50, 32));
frame.add(pane);
frame.pack();
}
});
}
#Override
public Dimension getPreferredScrollableViewportSize() {
repaint();
return null;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
repaint();
return 0;
}
#Override
public boolean getScrollableTracksViewportHeight() {
repaint();
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
repaint();
return false;
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
repaint();
return 0;
}
}
If you try to run the program you will see these problems:
When you move the JScrollBars it seams that the mouse position gets an offset when you try to place a color.
If you zoom/scroll the mouse whell everything shifts up/down and left/right. I would like to zoom at the mouse cursor/point or atleast not shift everything sideways.
This is what works at the moment:
Placing of colors works correctly now.
Moving verticaly or horizontaly doesnt tear anymore.
As you see most of the requirements I mentioned at the top can be found in articles here on SO and most of them are really good and well described but I don't know/ can't implement them so that I can solve the problems above.
My goal with this zoom would be something like the example in here with the exception of drawing my "Colors/tiles" instead of the rectangles he draws and that I am using AffineTransform. Otherwise that example is really what I am aiming for.
It doesn't have to use any of the code he uses. I would preffer to use AffineTransform. If any of you knows how to implement what I am looking for in another way I am all ears. I thought that the example might make it easier for you guys to understand what I am aiming for.
I accept any changes or comments about the code or the implementaion and I will respond as quickly as I can and change the post.
Many thanks,
Towni0
Related
This question has probably been answered but from the ones i have found they don't seem to work, others actually not making any changes.
I'm creating a game, the game has a screen (Canvas) that is inside of the window (JFrame). In this game i would like to use a mouse, as well as allowing the user to maximize the window. The game screen stays the same size no matter what, but if the window is re-sized, so i scale the game image to the size of the window.
My problem is that i cannot find a suitable way to project the coordinates of the the mouse, as when the window is maximized, its coordinates are still being read as if the window were still normal sized, creating an offset.
I've tried:
#Override public void mouseMoved(MouseEvent e){
MOUSE_X = Math.round((float) e.getX() / (float) RenderableCanvas.oldSizeX * (float) f.getWidth());
MOUSE_Y = Math.round((float) e.getY() / (float) RenderableCanvas.oldSizeY * (float) f.getHeight());
}
Where MOUSE_X/MOUSE_Y are static variables that can be referenced anywhere in the program to get the mouse location.
and RenderableCanvas is the game window, containing an embedded canvas object, this class also keeps track of the original size of the window stated as oldSizeX and oldSizeY
and finally f.getHeight() and f.getWidth() are the current size of the frame, as f is a reference to the JFrame inside of the RenderableCanvas class.
but all that does the same as:
#Override public void mouseMoved(MouseEvent e){
MOUSE_X = e.getX();
MOUSE_Y = e.getY();
}
Thank you for any help in advance.
The basic idea is you need to be able to convert between the two coordinate systems, in this case, the "world" which is the space getting scaled and the "view" which is what the user sees (or something like that)
The basic maths is to use the default value and the current value to generate a percentage value, by which you can then multiple the target value by, for example convert from the view to the world might look like...
pointInView * (staticSize / currentSize)
So, given a point in "world" coordinates, you need to scale back to "view" coordinates...
protected Point toView(int x, int y) {
return toView(new Point(x, y));
}
protected Point toView(Point p) {
Point scaled = new Point(p);
scaled.x = Math.round(p.x * ((float) getWidth() / (float) DEFAULT_WIDTH));
scaled.y = Math.round(p.y * ((float) getHeight() / (float) DEFAULT_HEIGHT));
return scaled;
}
And given "view" coordinates, you need to scale up to the "world" coordinates...
protected Point toWorld(int x, int y) {
return toWorld(new Point(x, y));
}
protected Point toWorld(Point p) {
Point scaled = new Point(p);
scaled.x = Math.round(p.x * ((float) DEFAULT_WIDTH) / (float) getWidth());
scaled.y = Math.round(p.y * ((float) DEFAULT_HEIGHT) / (float) getHeight());
return scaled;
}
So, for example, when the mouse is moved or clicked on your "view", you could use
Point world = toWorld(e.getPoint());
to convert the mouse point into world coordinates
... please feel free to rename those to suit your own needs, but basically, view is the physical view that the user sees and is your virtual concept of that view...
The basic concept will work for Dimension and by extension, Rectangle as well...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
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 {
protected static final int DEFAULT_WIDTH = 200;
protected static final int DEFAULT_HEIGHT = 200;
private Dimension preferredSize = new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
private JLabel properties;
private boolean highlighted = false;
private Rectangle hoverRect = new Rectangle(10, 10, 50, 50);
public TestPane() {
setLayout(new GridBagLayout());
properties = new JLabel("...");
add(properties);
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Point world = toWorld(e.getPoint());
highlighted = hoverRect.contains(world);
repaint();
properties.setText("<html>#" + format(e.getPoint())
+ "<br>world = " + format(world)
+ "<br>view = " + format(toView(world)));
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
preferredSize = new Dimension(DEFAULT_WIDTH * 2, DEFAULT_HEIGHT * 2);
SwingUtilities.windowForComponent(TestPane.this).pack();
}
});
}
protected String format(Point p) {
return p.x + "x" + p.y;
}
protected Point toView(int x, int y) {
return toView(new Point(x, y));
}
protected Point toView(Point p) {
Point scaled = new Point(p);
scaled.x = Math.round(p.x * ((float) getWidth() / (float) DEFAULT_WIDTH));
scaled.y = Math.round(p.y * ((float) getHeight() / (float) DEFAULT_HEIGHT));
return scaled;
}
protected Point toWorld(int x, int y) {
return toWorld(new Point(x, y));
}
protected Point toWorld(Point p) {
Point scaled = new Point(p);
scaled.x = Math.round(p.x * ((float) DEFAULT_WIDTH) / (float) getWidth());
scaled.y = Math.round(p.y * ((float) DEFAULT_HEIGHT) / (float) getHeight());
return scaled;
}
protected Rectangle toWorld(Rectangle bounds) {
return toWorld(bounds.x, bounds.y, bounds.width, bounds.height);
}
protected Rectangle toWorld(int x, int y, int width, int height) {
Rectangle scaled = new Rectangle();
scaled.setLocation(toWorld(x, y));
scaled.width = Math.round(width * ((float) DEFAULT_WIDTH / (float) getWidth()));
scaled.height = Math.round(height * ((float) DEFAULT_HEIGHT / (float) getHeight()));
return scaled;
}
protected Rectangle toView(Rectangle bounds) {
return toView(bounds.x, bounds.y, bounds.width, bounds.height);
}
protected Rectangle toView(int x, int y, int width, int height) {
Rectangle scaled = new Rectangle();
scaled.setLocation(toView(x, y));
scaled.width = Math.round(width * ((float) getWidth() / (float) DEFAULT_WIDTH));
scaled.height = Math.round(height * ((float) getHeight() / (float) DEFAULT_HEIGHT));
return scaled;
}
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = toView(hoverRect);
if (highlighted) {
g2d.setColor(Color.BLUE);
g2d.fill(bounds);
}
g2d.setColor(Color.BLACK);
g2d.draw(bounds);
}
}
}
So i think I've got it, i did some old Pen and Paper math and realized that i had part of my equation flipped from what i thought i should have.
This seems to work:
#Override public void mouseMoved(MouseEvent e){
MOUSE_X = (int) ((float) e.getX() / (float) f.getWidth() * (float) RenderableCanvas.oldSizeX);
MOUSE_Y = (int) ((float) e.getY() / (float) f.getHeight() * (float) RenderableCanvas.oldSizeY);
}
How do I make a sprite move in a custom JPanel?
I have looked at the similar questions and although one question is similar, it isn't addressing my problem. I have a sprite in a JPanel and I am unable to get it to move. One of the requirements I have to meet for the program is that it must begin moving when a JButton is pressed (Mouse Click). I have the code set-up in a way I believe should work, but it will spit out a long list of errors when I press the button. I'm also required to have the panel be a custom panel class.
What I need to know is this:
Methods (ha) of programming sprite movement.
Continuing to move the sprite without a trail.
Making the sprite bounce off the edges of the panel. Done (Unable to test due to no moving ball)
Here's the code I have (MainClient).
package clientPackage;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import logicPack.Logic;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class ClientClass
{
Ball mSolo = new Ball();
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ClientClass window = new ClientClass();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public ClientClass()
{
initialize();
}
/**
* Initialize the contents of the frame.
*/
Logic Logical;
Graphics g;
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 590, 520);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
SpriteField panel = new SpriteField();
panel.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
/* int tX = e.getX();
Logical.MoveBallX();
int tY = e.getY();
Logical.MoveBallY();
panel.repaint();*/
Logical.MoveBallX();
Logical.MoveBallY();
panel.repaint();
}
});
panel.setForeground(Color.WHITE);
panel.setBackground(Color.GRAY);
panel.setBounds(64, 92, 434, 355);
frame.getContentPane().add(panel);
JButton btnStart = new JButton("Start");
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
Graphics2D g2 = (Graphics2D)g;
mSolo.DrawSprite(g2 , Logical.MoveBallX(), Logical.MoveBallY());
}
});
btnStart.setBounds(64, 13, 174, 60);
frame.getContentPane().add(btnStart);
}
}
And here are my other Classes (Logic)
package logicPack;
import clientPackage.Ball;
public class Logic
{
Ball mSolo;
public int MoveBallX()
{
int NewX = mSolo.xPos + 50;
return NewX;
}
public int MoveBallY()
{
int NewY = mSolo.yPos + 50;
return NewY;
}
//Motion, force, friction and collision GO HERE ONLY
}
SpriteField
package clientPackage;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class SpriteField extends JPanel
{
Ball mSolo;
SpriteField()
{
mSolo = new Ball();
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
mSolo.DrawSprite(g2 , mSolo.xPos , mSolo.yPos);
}
}
Ball
package clientPackage;
import java.awt.Color;
import java.awt.Graphics2D;
public class Ball
{
Ball()
{
}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2, int xPos, int yPos)
{
g2.setColor(Color.BLACK);
g2.fillOval(xPos - diameter / 2 , yPos - diameter / 2 , diameter , diameter);
}
}
If you do not understand my Java comments, you can just ignore them.
If you need more details to help me, let me know.
EDIT 1:
Andrew, the closest article I could find used arrow keys to move a sprite. The article was "Sprite not moving in JPanel". All the other articles I found either addressed JPanels without sprites, or animating a sprite. However, I need a JButton that is MouseClicked to simply start the movement, and the ball does not change shape or color. I believe I have the collision part working, but I'm unable to test it until the ball starts moving.
EDIT 2:
LuxxMiner, Thanks for the hints. I have refined my collision portion to be a little more accurate using the getHeight and getWidth methods.
EDIT 3:
MadProgrammer, Thanks...? The problem is not the painting of the ball, I cannot get the ball to move in the first place to repaint it. And the example uses arrow keys, not a mouse click or JButton.
First, take a look at Painting in AWT and Swing and Performing Custom Painting to understand how painting works in Swing.
Let's have a look at the code...
You have a Ball class, which has it's own properties, but then your DrawSprite method passes in values which override these properties?
public class Ball {
Ball() {
}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2, int xPos, int yPos) {
g2.setColor(Color.BLACK);
g2.fillOval(xPos - diameter / 2, yPos - diameter / 2, diameter, diameter);
}
}
What's the point of that? The Ball should paint it's own current state. You should get rid of the additional parameters
public class Ball {
Ball() {
}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2) {
g2.setColor(Color.BLACK);
g2.fillOval(xPos - diameter / 2, yPos - diameter / 2, diameter, diameter);
}
}
ClientClass, Logic and SpriteField all have their own Ball references, none of which is shared so if Logic where to update the state of it's Ball, neither ClientClass or SpriteField would actually see those changes.
In reality, only SpriteField needs an instance of Ball, as it's basically the "ball container", it has the information need to determine if the ball moves out of bounds and wants to know when the ball should be repainted, better to isolate the functionality/responsibility for the Ball to SpriteField at this time.
You also need a means to actually move the ball. While you could use other events, I'd be nice if the ball just moved itself, to this end, you can use a Swing Timer, which won't block the Event Dispatching Thread, but which notifies the registered ActionListener within the context of the EDT, making it safe to update the UI from within.
public class SpriteField extends JPanel {
private Ball mSolo;
private Timer timer;
private int xDelta, yDelta;
public SpriteField() {
mSolo = new Ball();
do {
xDelta = (int) ((Math.random() * 8) - 4);
} while (xDelta == 0);
do {
yDelta = (int) ((Math.random() * 8) - 4);
} while (yDelta == 0);
}
public void start() {
if (timer == null || !timer.isRunning()) {
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
mSolo.xPos += xDelta;
mSolo.yPos += yDelta;
if (mSolo.xPos - (mSolo.diameter / 2) < 0) {
mSolo.xPos = mSolo.diameter / 2;
xDelta *= -1;
} else if (mSolo.xPos + (mSolo.diameter / 2) > getWidth()) {
mSolo.xPos = getWidth() - (mSolo.diameter / 2);
xDelta *= -1;
}
if (mSolo.yPos - (mSolo.diameter / 2) < 0) {
mSolo.yPos = (mSolo.diameter / 2);
yDelta *= -1;
} else if (mSolo.yPos + (mSolo.diameter / 2) > getHeight()) {
mSolo.yPos = getHeight() - (mSolo.diameter / 2);
yDelta *= -1;
}
repaint();
}
});
timer.start();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
mSolo.DrawSprite(g2);
}
}
Now, all you need to do, is when the "Start" button is clicked, call the start method
public class ClientClass {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ClientClass window = new ClientClass();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public ClientClass() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
// Logic Logical;
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 590, 520);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SpriteField panel = new SpriteField();
panel.setForeground(Color.WHITE);
panel.setBackground(Color.GRAY);
frame.getContentPane().add(panel);
JButton btnStart = new JButton("Start");
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.start();
}
});
frame.getContentPane().add(btnStart, BorderLayout.SOUTH);
}
}
I have a simple animation in Java that consists of a wheel moving across a window. It is just a plain circle that starts off of the screen from the left, enters and continues to the right until it goes off of the screen. Then it loops and repeats this process.
X is a variable that contains the position of the wheel. It can be between -(wheel width) and the window size + the wheel width.
I would like to simulate rotation by drawing a circle within this wheel, that rotates around the circle as if it were attached.
Imagine a bike wheel in real life with a red flag on the wheel. As the wheel rotates, the red flag would be on the edge on the wheel moving as the wheel progresses. This is the behavior I want.
I am getting a percentage to pass into my wheel class like this:
int percentage = x/windowWidth;
Each frame that the wheel moves, I call wheel.rotate(percentage).
This is the implementation:
private int diameter = 50;
private final int SPOKE_DIAMETER = diameter/5;
public void rotate(double percent){
this.percent = percent;
this.theta = percent*(PI*2);
System.out.println(percent*PI);
}
public void paintComponent(Graphics canvas)
{
// wheel
canvas.setColor(Color.gray);
canvas.fillOval(0, 0, diameter, diameter);
// spinning flag
canvas.setColor(Color.red);
canvas.fillOval((int)(percent*diameter),(int)((sin((percent*(PI*2)))*diameter)), SPOKE_DIAMETER,SPOKE_DIAMETER);
}
The x location works more or less how I wanted, but the y does not. It wiggles like a sin wave, which is expected (I did use sin...), however, I'm not sure how to alter my math to follow the circle around.
What is wrong with my implementation? (I'm not very good with drawing with trigonometric functions)
Basically, you need to calculate the point on the circle, based on an angle that the object should appear...
Like most things, I stole this off the internet somewhere, but it works...
protected Point getPointOnCircle(float degress, float radius) {
int x = Math.round(getWidth() / 2);
int y = Math.round(getHeight() / 2);
double rads = Math.toRadians(degress - 90); // 0 becomes the top
// Calculate the outter point of the line
int xPosy = Math.round((float) (x + Math.cos(rads) * radius));
int yPosy = Math.round((float) (y + Math.sin(rads) * radius));
return new Point(xPosy, yPosy);
}
Based on an angel (in degrees) and the radius of the circle, this will return the x/y position along the circumference of the circle...
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RotateWheel {
public static void main(String[] args) {
new RotateWheel();
}
public RotateWheel() {
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 float degrees = 0;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
degrees += 0.5f;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int diameter = Math.min(getWidth(), getHeight());
int x = (getWidth() - diameter) / 2;
int y = (getHeight() - diameter) / 2;
g2d.setColor(Color.GREEN);
g2d.drawOval(x, y, diameter, diameter);
g2d.setColor(Color.RED);
float innerDiameter = 20;
Point p = getPointOnCircle(degrees, (diameter / 2f) - (innerDiameter / 2));
g2d.drawOval(x + p.x - (int) (innerDiameter / 2), y + p.y - (int) (innerDiameter / 2), (int) innerDiameter, (int) innerDiameter);
g2d.dispose();
}
protected Point getPointOnCircle(float degress, float radius) {
int x = Math.round(getWidth() / 2);
int y = Math.round(getHeight() / 2);
double rads = Math.toRadians(degress - 90); // 0 becomes the top
// Calculate the outter point of the line
int xPosy = Math.round((float) (x + Math.cos(rads) * radius));
int yPosy = Math.round((float) (y + Math.sin(rads) * radius));
return new Point(xPosy, yPosy);
}
}
}
This is my code:
package com.Bench3.simplyMining;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Keying extends JPanel {
private static final long serialVersionUID = 1L;
public Rectangle miningRock;
public Rectangle Rocks;
public int mRockW = 150;
public int mRockH = 100;
public long rocks = 0;
public String rockAmount = rocks + " Rocks";
public Keying(Display f, Images i){
miningRock = new Rectangle(0, 658, mRockW, mRockH);
Rocks = new Rectangle(1024, 0, 0, 0);
f.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e){
}
public void keyReleased(KeyEvent e){
/* if(e.getKeyCode() == KeyEvent.VK_A){
left = false;
} -- Used for setting key combinations and stuff */
}
});
}
public void paintComponent(Graphics g){
if(Main.f.i.imagesLoaded){
super.paintComponent(g);
g.drawImage(Main.f.i.miningRock, miningRock.x, miningRock.y, miningRock.width, miningRock.height, null);
g.drawString(rockAmount, Rocks.x, Rocks.y);
this.setBackground(Color.WHITE); // Sets background color
// g.setColor(Color.WHITE); -- Used for setting colors
repaint();
}
}
}
I've been trying to insert the string "rockAmount" into the rectangle "Rocks" using g.drawString(), but when I try it doesn't output the string inside of the rectangle. What am I doing wrong?
EDIT: Solved, thanks to Paul for providing the answer.
The y position of text represents the base line minus the font ascent. This means that it appears that text is rendered above the y position.
When rendering text, you need to take into consideration the FontMetrics of the text and font.
For example, the following renders the text at the rectangles x/y position on the left and then calculates the x/y position so it can centre the text within the give rectangle.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestTextRect {
public static void main(String[] args) {
new TestTextRect();
}
public TestTextRect() {
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 {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int mRockW = 150;
int mRockH = 100;
int x = ((getWidth() / 2) - mRockW) / 2;
int y = (getHeight() - mRockH) / 2;
badRect(g2d, x, y, mRockW, mRockH);
x = (getWidth() / 2) + (((getWidth() / 2) - mRockW) / 2);
goodRect(g2d, x, y, mRockW, mRockH);
g2d.dispose();
}
protected void badRect(Graphics2D g2d, int x, int y, int mRockW, int mRockH) {
g2d.drawRect(x, y, mRockW, mRockH);
String text = "rockAmount";
g2d.drawString(text, x, y);
}
protected void goodRect(Graphics2D g2d, int x, int y, int mRockW, int mRockH) {
g2d.drawRect(x, y, mRockW, mRockH);
FontMetrics fm = g2d.getFontMetrics();
String text = "rockAmount";
x = x + ((mRockW - fm.stringWidth(text)) / 2);
y = (y + ((mRockH - fm.getHeight()) / 2)) + fm.getAscent();
g2d.drawString(text, x, y);
}
}
}
Take a look at Working with text APIs for more details
You should avoid calling repaint or any method that might call repaint from within your paintXxx methods. Because of the way painting is scheduled in Swing, this will set up an infinite loop that will eventually consume you CPU cycles and make your application unresponsive...
You should, also, always call super.paintComponent, regardless of what ever else you want to do...
I would also recommend Key bindings over KeyListener as it provides better control over the focus level
You need to add to x and y position 1/2 of the width and height of the rectangle.
g.drawString(rockAmount,Rocks.x+rectangleWidth/2,Rocks.y+rectangleHeight/2);
The code that I have here is using a MouseAdapter to listen for the user to "draw" a box around the area of an image that they would like to zoom in on and calculate the ratio of the box to the image. It then resizes the image to the calculated ratio. This part works.
The issue that I am having is making the JScrollPane view appear as if it is still at the same top left position after the image has been resized. I have tried several methods that seem to have gotten close to the result I want but not exactly.
This is the Listener that finds the scale ratio and sets the position:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.JComponent;
public class DynamicZoom extends MouseAdapter {
private Point start;
private Point end;
private double zoom = 1.0;
private JScrollPane pane;
private JViewport port;
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) {
this.pane = (JScrollPane)e.getSource();
this.port = pane.getViewport();
start = e.getPoint();
}
}
public void mouseReleased(MouseEvent e) {
if(this.pane != null) {
Point curr = this.port.getViewPosition();
end = e.getPoint();
ImageComponent canvas = (ImageComponent)this.port.getView();
zoom = canvas.getScale();
double factor = 0.0;
double selectedWidth = Math.abs(end.getX() - start.getX());
double selectedHeight = Math.abs(end.getY() - start.getY());
if(selectedWidth > selectedHeight)
factor = this.port.getWidth() / selectedWidth;
else
factor = this.port.getHeight() / selectedHeight;
zoom *= factor;
int x = (int)((start.x+curr.x)*zoom);
int y = (int)((start.y+curr.y)*zoom);
Point point = new Point(x, y);
((ImageComponent)(this.port.getView())).setScale(zoom);
ResizeViewport.resize(pane);
this.port.setViewPosition(point);
}
}
public void mouseDragged(MouseEvent e) {
if(this.pane != null) {
Graphics g = this.port.getGraphics();
int width = this.start.x - e.getX();
int height = this.start.y - e.getY();
int w = Math.abs( width );
int h = Math.abs( height );
int x = width < 0 ? this.start.x : e.getX();
int y = height < 0 ? this.start.y : e.getY();
g.drawRect(x, y, w, h);
this.port.repaint();
}
}
}
This is the ImageComponent it resizes and displays the image:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
public class ImageComponent extends JComponent {
private static final long serialVersionUID = 1975488835382044371L;
private BufferedImage img = null;
private double scale = 0.0;
public ImageComponent() {}
public ImageComponent(BufferedImage img) {
this.displayPage(img);
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = ((Graphics2D)g);
if(this.img != null) {
int width = (int)(this.img.getWidth() * this.scale);
int height = (int)(this.img.getHeight() * this.scale);
this.setPreferredSize(new Dimension(width, height));
g2.drawImage(this.img, 0, 0, width, height, null, null);
g2.dispose();
}
}
public void displayPage(BufferedImage pic) {
if(img != null) {
this.img = pic;
}
}
public BufferedImage getPage() {
return this.img;
}
public void setScale(double ratio) {
if(ratio > .04) {
this.scale = ratio;
this.repaint();
}
}
public double getScale() {
return this.scale;
}
}
The ResizeViewport forces the viewport to show the entire ImageComponent when it is scaled up because otherwise it will clip the image at the size that it previously was:
import java.awt.Dimension;
import javax.swing.JScrollPane;
public class ResizeViewport {
public static void resize(JScrollPane scroll) {
int vw = scroll.getWidth()-scroll.getVerticalScrollBar().getWidth();
int vh = scroll.getHeight()-scroll.getHorizontalScrollBar().getHeight();
scroll.getViewport().setViewSize(new Dimension(vw, vh));
}
}
It turns out there is nothing wrong with the math used to calculate the position or the way I designed the code. The problem was that the ImageComponent was still painting in another Thread when the position was being calculated; therefore returning "false" values for the getWidth() and getHeight() methods. I solved the issue using the following code:
EventQueue.invokeLater(new Runnable() {
public void run() {
port.setViewPosition(new Point(x,y));
}
});
This allows the painting to finish before trying to calculate the size of the image and setting the position in the JScrollPane. Problem solved. I would still like to throw a thanks out to the people that took the time to at least review this issue.
I had a similar issue but sometimes the panel got painted wrong the first time after resize, after hours of trying to find a workaround I found a solution!
After a resize of the panel in the scrollpane it is best is to destroy viewport by setting to null, and then adding panel back to scrollpane that will recreate a new viewport and everything is working!
// Old viewport has to be replaced
scrollPane.setViewport(null);
scrollPane.setViewportView(zoomablePanel);
/Anders