Reputation: 11
I'm trying to implement zooming and panning feature in my implementation of Convay's game of life in java. The problem it the following:
Each time when we try to pan by draging our mouse, our eye sight appears in the left top corner of the screen. Here is the class that responsible for implementing this feature
package input;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import renderer.Renderer;
public class PanningHandler extends MouseAdapter {
private Renderer renderer;
public PanningHandler(Renderer renderer) {
this.renderer = renderer;
}
@Override
public void mouseDragged(MouseEvent e) {
// Check if the right mouse button is pressed (RMB)
if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
handleRMBDrag(e);
}
}
private void handleRMBDrag(MouseEvent e) {
int deltaX = e.getX() - renderer.getLastMouseX();
int deltaY = e.getY() - renderer.getLastMouseY();
// Calculate new pan offset
int newPanOffsetX = renderer.getPanOffsetX() - deltaX;
int newPanOffsetY = renderer.getPanOffsetY() - deltaY;
// Calculate maximum allowed values based on the size of the display area
int maxPanOffsetX = getMaxPanOffsetX(); // Adjust this based on your requirements
int maxPanOffsetY = getMaxPanOffsetY(); // Adjust this based on your requirements
// Limit the pan offset to avoid going out of bounds
newPanOffsetX = Math.max(0, Math.min(newPanOffsetX, maxPanOffsetX));
newPanOffsetY = Math.max(0, Math.min(newPanOffsetY, maxPanOffsetY));
// Set the new pan offset and repaint
renderer.setPanOffsetX(newPanOffsetX);
renderer.setPanOffsetY(newPanOffsetY);
renderer.getFrame().repaint();
// Update last mouse coordinates for the next iteration
renderer.setLastMouseX(e.getX());
renderer.setLastMouseY(e.getY());
}
// Rest of the code remains unchanged
private int getMaxPanOffsetX() {
return (int) (renderer.getWidth() * 10 * renderer.getZoomFactor() - renderer.getFrame().getWidth());
}
private int getMaxPanOffsetY() {
return (int) (renderer.getHeight() * 10 * renderer.getZoomFactor() - renderer.getFrame().getHeight());
}
}
And here is some bits from renderer that are responsible for drawing our squares and panning.
public class Renderer {
private double zoomFactor = 1.0;
private boolean gameState = false;
private final Color BLACK = Color.BLACK;
private final Color WHITE = Color.WHITE;
private JFrame frame;
private static Renderer instance;
private int height = 100; // TODO - Handle the case when our grid becomes very large (e.g 1000x1000)
private int width = 100;
private boolean[][] grid = new boolean[height][width];
private Logic logic = new Logic(); // instantiate Logic class
private int lastMouseX = -1;
private int lastMouseY = -1;
private int panOffsetX = 0;
private int panOffsetY = 0;
private void configFrame() {
frame.pack();
frame.setSize(width * 10, height * 10);
frame.getContentPane().setBackground(BLACK);
// ...
frame.addMouseMotionListener(new PanningHandler(this)); // TODO - make it work
// ...
frame.add(new MyPanel());
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
/**
* Inner class representing the drawing panel inside the JFrame.
*/
class MyPanel extends JPanel {
public MyPanel() {
setBackground(BLACK); // Set the background color to black
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawSquares(g);
}
}
/**
* Draws squares on the panel based on the current state of the grid.
*
* @param g The Graphics object used for drawing.
*/
public void drawSquares(Graphics g) {
int squareSize = (int) (10 * zoomFactor);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int x = (int) (j * squareSize - panOffsetX);
int y = (int) (i * squareSize - panOffsetY);
if (grid[i][j]) {
g.setColor(WHITE);
g.fillRect(x, y, squareSize, squareSize);
} else {
g.setColor(BLACK);
g.fillRect(x, y, squareSize, squareSize);
}
}
}
}
/**
* Updates the grid based on the Game of Life rules.
* Repaints the frame to reflect the changes.
*/
public void updateGrid() {
if (gameState) {
grid = logic.gridUpdate(grid);
frame.repaint();
}
}
// ... (rest of the code)
}
I know that if we take our newPanOffsetX/Y restriction off,
// Limit the pan offset to avoid going out of bounds
newPanOffsetX = Math.max(0, Math.min(newPanOffsetX, maxPanOffsetX));
newPanOffsetY = Math.max(0, Math.min(newPanOffsetY, maxPanOffsetY));
then it gets in negative numbers very quickly. Each time when we start dragging our mouse in any direction - we make things worse. That is probably the reason why it gets back to the top-left corner.
Would appreciate any help on this problem, cheers!
Upvotes: 1
Views: 40
Reputation: 11
I managed to fix this bug. By changing my MyMouseListener.
Old version of code
package input;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.SwingUtilities;
import renderer.Renderer;
/**
* Custom MouseAdapter for handling mouse events in the Renderer.
* This adapter is responsible for processing mouse clicks, releases, and drag events.
*/
public class MyMouseListener extends MouseAdapter {
private final Renderer renderer;
/**
* Constructs a new MyMouseListener with the specified Renderer.
*
* @param renderer The Renderer associated with this adapter.
*/
public MyMouseListener(Renderer renderer) {
this.renderer = renderer;
}
/**
* Invoked when the mouse is clicked.
* Calls the handleMouseClick method to update the grid based on the mouse click.
*
* @param e The MouseEvent representing the mouse click event.
*/
@Override
public void mouseClicked(MouseEvent e) {
// Check if the left mouse button is pressed (LMB).
// for some reason the never version BUTTON1_DOWN_MASK doesn't work, so I used the older version
if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
handleLMBClick(e);
}
}
/**
* Handles the mouse click event by updating the grid according to the click position.
*
* @param e The MouseEvent representing the mouse click event.
*/
public void handleLMBClick(MouseEvent e) {
int squareSize = (int) (10 * renderer.getZoomFactor());
int x = (int) ((e.getX() + renderer.getPanOffsetX()) / squareSize);
int y = (int) ((e.getY() + renderer.getPanOffsetY() - 40) / squareSize);
// Clamp the indices to valid grid bounds
x = Math.min(Math.max(0, x), renderer.getWidth() - 1);
y = Math.min(Math.max(0, y), renderer.getHeight() - 1);
renderer.reverseElement(y, x);
System.out.println("Mouse Clicked: " + x + "," + y);
renderer.getFrame().repaint(); // Repaint the frame to update the drawing
}
/**
* Invoked when the mouse is released.
* Resets the last mouse coordinates to -1, indicating no ongoing drag.
*
* @param e The MouseEvent representing the mouse release event.
*/
// @Override
// public void mouseReleased(MouseEvent e) {
// renderer.setLastMouseX(-1);
// renderer.setLastMouseY(-1);
// }
}
New version of code
package input;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.SwingUtilities;
import renderer.Renderer;
/**
* Custom MouseAdapter for handling mouse events in the Renderer.
* This adapter is responsible for processing mouse clicks, releases, and drag
* events.
*/
public class MyMouseListener extends MouseAdapter {
private final Renderer renderer;
/**
* Constructs a new MyMouseListener with the specified Renderer.
*
* @param renderer The Renderer associated with this adapter.
*/
public MyMouseListener(Renderer renderer) {
this.renderer = renderer;
}
/**
* Handles the mouse pressed event, updating the grid based on the click
* position.
* If the right mouse button is pressed, it records the initial mouse
* coordinates for panning.
* If the left mouse button is pressed, it calculates the grid indices and
* toggles the cell state.
* @param e The MouseEvent representing the mouse click event.
*/
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
renderer.setLastMouseX(e.getX());
renderer.setLastMouseY(e.getY());
} else {
int squareSize = (int) (10 * renderer.getZoomFactor());
int x = (int) ((e.getX() + renderer.getPanOffsetX()) / squareSize);
int y = (int) ((e.getY() + renderer.getPanOffsetY() - 40) / squareSize);
// Clamp the indices to valid grid bounds
x = Math.min(Math.max(0, x), renderer.getWidth() - 1);
y = Math.min(Math.max(0, y), renderer.getHeight() - 1);
renderer.reverseElement(y, x);
System.out.println("Mouse Clicked: " + x + "," + y);
renderer.getFrame().repaint(); // Repaint the frame to update the drawing
}
}
}
Upvotes: 0