I'm trying to make a small Mosaic programm, that draws squares randomly all over the JPanel. I want to make it so, that it draws every 0,2 sec 1 square (not all at once), but so far i can make only it to draw all at once with a while loop. I have tried with ActionListener and Timer, but i found out, that i cant pass the same Graphics g to ActionListener. Then i tried with Thread.Sleep(200) but then the app froze. Now i have tried with System.currentTimeMillis(); but its same as with Threads... Searched all over the internet but didnt find anything that works.
Main:
import javax.swing.JApplet;
public class Main extends JApplet{
public void init(){
setSize(500, 300);
Mosaic mosaic = new Mosaic();
setContentPane(mosaic);
}
}
App:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JPanel;
public class Mosaic extends JPanel{
private int width, height;
private int ruut; // square
private int w = width / 2, h = height / 2; // middle of the app
Random rand = new Random();
Color color;
public Mosaic(){
this(500, 300, 10);
}
public Mosaic(int width, int height, int ruut){
this.width = width;
this.height = height;
this.ruut = ruut;
setBackground(Color.BLACK);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
//draws random squares
while (true) {
moveNext(g);
wait(200);
}
}
//delay n millisec
public void wait(int n){
long t0, t1;
t0 = System.currentTimeMillis();
do {
t1 = System.currentTimeMillis();
} while ((t1 - t0) < n);
}
//next square
public void moveNext(Graphics g){
int r = rand.nextInt(4);
switch (r) {
case 1:
h += ruut;
wallTest();
break;
case 2:
h -= ruut;
wallTest();
break;
case 3:
w -= ruut;
wallTest();
break;
case 4:
w -= ruut;
wallTest();
break;
}
color = new Color(0, rand.nextInt(255-50)+50, 0);
g.setColor(color);
g.fillRect(w, h, ruut, ruut);
}
public void wallTest(){
if (h > height){
h = 0;
}
if (h < 0){
h = height;
}
if (w > width){
w = 0;
}
if (w < 0){
w = width;
}
}
}
You need to maintain an off-screen buffer, and draw tiles into that buffer under control of timer events.
Then paintComponent simply copies that buffer to the screen.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Mosaic extends JPanel{
private int width, height;
private int ruut; // square
private int w = width / 2, h = height / 2; // middle of the app
private BufferedImage buffer;
Random rand = new Random();
Color color;
public Mosaic(){
this(500, 300, 10);
}
public Mosaic(int width, int height, int ruut){
this.width = width;
this.height = height;
this.ruut = ruut;
this.buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
setBackground(Color.BLACK);
setPreferredSize(new Dimension(width, height));
setDoubleBuffered(false);
new Timer(200, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
moveNext(buffer.getGraphics());
}
}).start();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(buffer, 0, 0, this);
}
//next square
public void moveNext(Graphics g){
int r = rand.nextInt(4);
switch (r) {
case 1:
h += ruut;
wallTest();
break;
case 2:
h -= ruut;
wallTest();
break;
case 3:
w -= ruut;
wallTest();
break;
case 4:
w -= ruut;
wallTest();
break;
}
color = new Color(0, rand.nextInt(255-50)+50, 0);
g.setColor(color);
g.fillRect(w, h, ruut, ruut);
repaint();
}
public void wallTest(){
if (h > height){
h = 0;
}
if (h < 0){
h = height;
}
if (w > width){
w = 0;
}
if (w < 0){
w = width;
}
}
}
Use the javax.swing.Timer class in conjunction with overriding the paintComponent method. You do not want to execute long-running tasks in the EDT since this will cause the GUI to freeze.
Example -
new javax.swing.Timer(DELAY_IN_MILLIS, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
// do stuff
repaint();
}
});
I have tried with ActionListener and Timer, but i found out, that i cant pass the same Graphics g to ActionListener.
Of course not, the Graphics object is transitory. Instead either do either of:
Add a rectangle to an expandable list and call repaint() (in a loop), drawing every object in the list at once.
Put a BufferedImage in a JLabel and add a 'rectangle at a time' to the BufferedImage.
You can use the timer approach as that is only way to not freeze the app. You can call repaint or some equivalent method when timer fires and then in the paint method draw as per requirements.
Related
I have finished working on a path finding visualizer recently. I was wondering if it was possible to use the graphics package for the colors to start off as white and then fade to their respective color like cyan or black. Right now I have it where the color just appears instantly and would think it would look nicer if the colors were able to fade from one another. Here is the code I have so far and a picture of the output
Path finding Visualizer
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int x = 0; x < cells; x++) { //coloring each node
for (int y = 0; y < cells; y++) {
switch (map[x][y].getType()) {
case 0: //start node
g.setColor(Color.GREEN);
break;
case 1: //end node
g.setColor(Color.RED);
break;
case 2: //wall node
g.setColor(Color.BLACK);
break;
case 3: //empty node
g.setColor(Color.WHITE);
break;
case 4: //visited nodes
g.setColor(Color.CYAN);
break;
case 5: //path
g.setColor(Color.YELLOW);
break;
}
g.fillRect(x * CSIZE, y * CSIZE, CSIZE, CSIZE);
g.setColor(Color.BLACK); //grid color
g.drawRect(x * CSIZE, y * CSIZE, CSIZE, CSIZE);
}
}
}
Here is one way. It uses a timer to periodically decrement the red component in the rgb color scheme.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ColorFadingDemo extends JPanel implements ActionListener {
Color color = new Color(255,0,0);
final static int height = 500;
final static int width = 500;
final static String title = "default title";
JFrame frame = new JFrame(title);
public static void main(String[] args) {
SwingUtilities.invokeLater(
() -> new ColorFadingDemo().start());
}
public void start() {
Timer timer = new Timer(0, this);
timer.setDelay(20);
timer.start();
}
public void actionPerformed(ActionEvent ae) {
int rgb = color.getRGB();
rgb -= 0x10000;
color = new Color(rgb);
repaint();
}
public ColorFadingDemo() {
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
frame.add(this);
setPreferredSize(
new Dimension(width, height));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(color);
g2d.fillRect(100,100,300,300);
g2d.dispose();
}
}
Easy:
int transparency = 0; // Transparency value;
int R = 255, G = 0, B = 0; //Enter your RGB values
Color c; // The color that you can then use in your paint method
Thread t = new Thread() {
public void run() {
while (transparency < 255) {
int transparency = 0;
c = new Color(R,G,B,transparency); // By creeting a custom color you can define the R, G, B and transparency values
transparency++;
try {
int fadeTime = 1000; // How long the fading process should go
Thread.sleep(255/fadeTime);
} catch (Exception e) {}
}
}
};
t.start(); // The thread will continue to run in the background until the transparency reaches 255
i try to make a simple java game . ıt work fine but i draw a background but i didn't stop square's color , it always change
i write another class for that and i call it in main but i didn't work it
#SuppressWarnings("serial")
class BackPan extends JFrame{
private JLayeredPane layers;
private JPanel down;
static int width = Board.boardWidth;
static int height = Board.boardHeight;
Random rnd = new Random();
public BackPan(){
layers = new JLayeredPane();
rnd =new Random();
down = new JPanel(){
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D)g;
//***************************************************************
int low = 50;
int high = 255;
for(int i = 0; i<= width; i+=50){
g2d.setColor(new Color(rnd.nextInt(high-low)+low,rnd.nextInt(high-low)+low,rnd.nextInt(high-low)+low));
g2d.fillRect(i, 50, 50, 50);
for(int j = 0; j<= height; j += 50 ){
g2d.setColor(new Color(rnd.nextInt(high-low)+low,rnd.nextInt(high-low)+low,rnd.nextInt(high-low)+low));
g2d.fillRect(i, j, 50, 50);
}
}
}
};
//****************************************************************
down.setBounds(0, 0, width, height);
layers.add(down, new Integer(1));
getContentPane().add(layers, BorderLayout.CENTER);
}
}
Better than using a set seed for your randomization, simply fix the random image by one of two ways:
Create an array of colors that you randomize when needed and then use this array when drawing the background
Even better, simply draw your random colors to a BufferedImage and draw that in the paintComponent method. If this needs to be re-randomized, then re-create the image.
For an example of the latter, note that the image re-randomizes only when the button is pressed (by calling the createBackground() method):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class ColorSquares extends JPanel {
public static final int SQR_SIDE = 50;
public static final int COLUMNS = 20;
public static final int ROWS = 16;
private int columns;
private int rows;
private int sqrSide;
private Image backgroundImg;
public ColorSquares(int columns, int rows, int sqrSide) {
this.columns = columns;
this.rows = rows;
this.sqrSide = sqrSide;
backgroundImg = createBackground();
add(new JButton(new AbstractAction("New Background") {
#Override
public void actionPerformed(ActionEvent arg0) {
backgroundImg = createBackground();
repaint();
}
}));
}
public Image createBackground() {
int w = columns * sqrSide;
int h = rows * sqrSide;
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.getGraphics();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < columns; c++) {
float hue = (float) Math.random();
float saturation = (float) (Math.random() * 0.5 + 0.5);
float brightness = (float) (Math.random() * 0.5 + 0.5);
Color randColor = Color.getHSBColor(hue, saturation, brightness);
g.setColor(randColor);
int x = c * sqrSide;
int y = r * sqrSide;
g.fillRect(x, y, sqrSide, sqrSide);
}
}
g.dispose();
return img;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(columns * sqrSide, rows * sqrSide);
}
private static void createAndShowGui() {
JFrame frame = new JFrame("Colors");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ColorSquares(COLUMNS, ROWS, SQR_SIDE));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
An additional benefit of using a BufferedImage is that it's quicker to draw this than as you're doing it.
The simplest solution would be to remember a constant random seed and set it via rnd.setSeed() each time paintComponent is called. For example:
private final static int SEED = 1000;
rnd.setSeed(SEED);
I understand the logic behind it, but don't know how to translate that to code. Could someone please show me on this example that I wrote?
All the applet does is that the center rectangle moves up, down, right and left of its own accord. I want to get rid of the annoying flicker with a double buffer, but I don't know what change/add to make that happen.
import java.applet.*;
import java.awt.*;
public class Broadway extends Applet implements Runnable {
Thread animation;
int locx,locy; // location of rectangle
int width, height; // dimensions of rectangle
// direction of motion
static final byte UP = 0;
static final byte DOWN = 1;
static final byte LEFT = 2;
static final byte RIGHT = 3;
byte state; // state the rect is in
// length of pausing interval in ms
static final int REFRESH_RATE = 100;
public void init() {
setBackground(Color.black);
locx = 80; // parameters of center rect
locy = 100;
width = 110;
height = 90;
state = UP;
}
public void start() {
animation = new Thread(this);
if (animation != null) {
animation.start();
}
}
public void paint(Graphics g) {
g.setColor(Color.yellow);
g.fillRect(0,0,90,90);
g.fillRect(250,0,40,190);
g.fillRect(80,110,100,20);
g.setColor(Color.blue);
g.fillRect(80,200,220,90);
g.fillRect(100,10,90,80);
g.setColor(Color.lightGray);
g.fillRect(locx,locy,width,height);
g.setColor(Color.red);
g.fillRect(200,0,45,45);
g.fillRect(0,100,70,200);
g.setColor(Color.magenta);
g.fillRect(200,55,60,135);
}
//update the center rectangle
void updateRectangle() {
switch (state) {
case DOWN:
locy += 2;
if (locy >= 110) {
state = UP;
}
break;
case UP:
locy -= 2;
if (locy <= 90) {
state = RIGHT;
}
break;
case RIGHT:
locx += 2;
if (locx >= 90) {
state = LEFT;
}
break;
case LEFT:
locx -= 2;
if (locx <= 70) {
state = DOWN;
}
break;
}
}
public void run() {
while (true) {
repaint();
updateRectangle();
try {
Thread.sleep (REFRESH_RATE);
} catch (Exception exc) { };
}
}
public void stop() {
if (animation != null) {
animation.stop();
animation = null;
}
}
}
import java.awt.*;
Change that to
import javax.swing.*;
public class Broadway extends Applet ..
Change that to
public class Broadway extends JApplet ..
Move all the custom painting into a JPanel. Override paintComponent(Graphics) rather than paint(Graphics). A JPanel is double buffered by default.
Do animation using a Swing Timer.
As a general tip
Code a frame & launch it from a link using Java Web Start before considering using an applet.
Update
This code implements most of the advice above, and displays the resulting smooth, buffered animation in an option pane. It could instead be displayed in a JFrame or JApplet.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Broadway extends JPanel {
int locx, locy; // location of rectangle
int width, height; // dimensions of rectangle
Timer timer;
// direction of motion
static final byte UP = 0;
static final byte DOWN = 1;
static final byte LEFT = 2;
static final byte RIGHT = 3;
byte state; // state the rect is in
// length of pausing interval in ms
static final int REFRESH_RATE = 100;
public Broadway() {
setBackground(Color.black);
locx = 80; // parameters of center rect
locy = 100;
width = 110;
height = 90;
state = UP;
ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
updateRectangle();
}
};
timer = new Timer(this.REFRESH_RATE, listener);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.yellow);
g.fillRect(0, 0, 90, 90);
g.fillRect(250, 0, 40, 190);
g.fillRect(80, 110, 100, 20);
g.setColor(Color.blue);
g.fillRect(80, 200, 220, 90);
g.fillRect(100, 10, 90, 80);
g.setColor(Color.lightGray);
g.fillRect(locx, locy, width, height);
g.setColor(Color.red);
g.fillRect(200, 0, 45, 45);
g.fillRect(0, 100, 70, 200);
g.setColor(Color.magenta);
g.fillRect(200, 55, 60, 135);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
//update the center rectangle
void updateRectangle() {
switch (state) {
case DOWN:
locy += 2;
if (locy >= 110) {
state = UP;
}
break;
case UP:
locy -= 2;
if (locy <= 90) {
state = RIGHT;
}
break;
case RIGHT:
locx += 2;
if (locx >= 90) {
state = LEFT;
}
break;
case LEFT:
locx -= 2;
if (locx <= 70) {
state = DOWN;
}
break;
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
Broadway bw = new Broadway();
bw.start();
JOptionPane.showMessageDialog(null, bw);
bw.stop();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Basically you replace the paint method so that it paints to an Image the same size as the Applet, then draw that image on Graphics g.
There's plenty of implementations out there.
Related questions:
Java Double Buffering
Double Buffering in Java
How do you double buffer in java for a game?
Java: how to do double-buffering in Swing?
Implementing Double Buffering in Java
stop applet flickering with double buffering Java Applet
I'm trying to improve my understanding of Java, particularly Java GUI, by making a puzzle program. Currently the user selects an image, which is cut up into a specified number of pieces. The pieces are drawn randomly to the screen but they seem to be covered by blank portions of other pieces, and not all of them show up, but I can print out all the coordinates. I am using absolute positioning because a LayoutManager didn't seem to work. I briefly tried layeredPanes but they confused me and didn't seem to solve the problem. I would really appreciate some help.
Here are the 2 relevant classes:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
public class PuzzlePieceDriver extends JFrame
{
private static Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
private static final int HEIGHT = SCREENSIZE.height;
private static final int WIDTH = SCREENSIZE.width;
public static int MY_WIDTH;
public static int MY_HEIGHT;
private static BufferedImage image;
private int xPieces = PuzzleMagicDriver.getXPieces();
private int yPieces = PuzzleMagicDriver.getYPieces();
private PuzzlePiece[] puzzle = new PuzzlePiece[xPieces*yPieces];
public Container pane = this.getContentPane();
private JLayeredPane layeredPane = new JLayeredPane();
public PuzzlePieceDriver(ImageIcon myPuzzleImage)
{
MY_WIDTH = myPuzzleImage.getIconWidth()+(int)myPuzzleImage.getIconHeight()/2;
MY_HEIGHT = myPuzzleImage.getIconHeight()+(int)myPuzzleImage.getIconHeight()/2;
setTitle("Hot Puzz");
setSize(MY_WIDTH,MY_HEIGHT);
setLocationByPlatform(true);
pane.setLayout(null);
image = iconToImage(myPuzzleImage); //pass image into bufferedImage form
puzzle = createClip(image);
//pane.add(layeredPane);
setVisible(true);
}//end constructor
public static BufferedImage iconToImage(ImageIcon icon)
{
Image img = icon.getImage();
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
// Paint the image onto the buffered image
g.drawImage(img, 0, 0, null);
g.dispose();
return image;
}//end BufferedImage
protected int randomNumber(int min, int max)
{
int temp =
min + (int)(Math.random() * ((max - min) + 1));
return temp;
}//end randomNumber
private PuzzlePiece[] createClip(BufferedImage passedImage)
{
int cw, ch;
int w,h;
w = image.getWidth(null);
h = image.getHeight(null);
cw = w/xPieces;
ch = h/yPieces;
int[] cells=new int[xPieces*yPieces];
int dx, dy;
BufferedImage clip = passedImage;
//layeredPane.setPreferredSize(new Dimension(w,h));
for (int x=0; x<xPieces; x++)
{
int sx = x*cw;
for (int y=0; y<yPieces; y++)
{
int sy = y*ch;
int cell = cells[x*xPieces+y];
dx = (cell / xPieces) * cw;
dy = (cell % yPieces) * ch;
clip= passedImage.getSubimage(sx, sy, cw, ch);
int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);
PuzzlePiece piece=new PuzzlePiece(clip,myX,myY);
puzzle[x*xPieces+y]=piece;
piece.setBounds(myX,myY,w,h);
//layeredPane.setBounds(myX,myY,w,h);
//layeredPane.add(piece,new Integer(x*xPieces+y));
pane.add(piece);
piece.repaint();
}//end nested for
}//end for
return puzzle;
}//end createClip
}//end class
Sorry if the spacing is a little messed up!
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
public class PuzzlePiece extends JPanel
{
private Point imageCorner; //the image's top-left corner location
private Point prevPt; //mouse location for previous event
private Boolean insideImage =false;
private BufferedImage image;
public PuzzlePiece(BufferedImage clip, int x, int y)
{
image = clip;
imageCorner = new Point(x,y);
//repaint();
}//end constructor
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, (int)getImageCornerX(),(int)getImageCornerY(), this);
System.out.println("paint "+getImageCornerX()+" "+getImageCornerY());
//repaint();
//g.dispose();
}//end paintComponent
public Point getImageCorner()
{
return imageCorner;
}//end getImageCorner
public double getImageCornerY()
{
return imageCorner.getY();
}//end getImageCornerY
public double getImageCornerX()
{
return imageCorner.getX();
}//end getPoint
}//end class PuzzlePiece
Any help would be appreciated, I've gotten really stuck! Thanks!!
I was really intrigued by this idea, so I made another example, using a custom layout manager.
public class MyPuzzelBoard extends JPanel {
public static final int GRID_X = 4;
public static final int GRID_Y = 4;
private BufferedImage image;
public MyPuzzelBoard(BufferedImage image) {
setLayout(new VirtualLayoutManager());
setImage(image);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
removeAll();
generatePuzzel();
} else {
Component comp = getComponentAt(e.getPoint());
if (comp != null && comp != MyPuzzelBoard.this) {
setComponentZOrder(comp, 0);
invalidate();
revalidate();
repaint();
}
}
}
});
}
public void setImage(BufferedImage value) {
if (value != image) {
image = value;
removeAll();
generatePuzzel();
}
}
public BufferedImage getImage() {
return image;
}
protected float generateRandomNumber() {
return (float) Math.random();
}
protected void generatePuzzel() {
BufferedImage image = getImage();
if (image != null) {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
int clipWidth = imageWidth / GRID_X;
int clipHeight = imageHeight / GRID_Y;
for (int x = 0; x < GRID_X; x++) {
for (int y = 0; y < GRID_Y; y++) {
float xPos = generateRandomNumber();
float yPos = generateRandomNumber();
Rectangle bounds = new Rectangle((x * clipWidth), (y * clipHeight), clipWidth, clipHeight);
MyPiece piece = new MyPiece(image, bounds);
add(piece, new VirtualPoint(xPos, yPos));
}
}
}
invalidate();
revalidate();
repaint();
}
public class VirtualPoint {
private float x;
private float y;
public VirtualPoint(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
}
public class VirtualLayoutManager implements LayoutManager2 {
private Map<Component, VirtualPoint> mapConstraints;
public VirtualLayoutManager() {
mapConstraints = new WeakHashMap<>(25);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof VirtualPoint) {
mapConstraints.put(comp, (VirtualPoint) constraints);
}
}
#Override
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
mapConstraints.remove(comp);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return new Dimension(400, 400);
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
for (Component comp : parent.getComponents()) {
VirtualPoint p = mapConstraints.get(comp);
if (p != null) {
int x = Math.round(width * p.getX());
int y = Math.round(height * p.getY());
Dimension size = comp.getPreferredSize();
x = Math.min(x, width - size.width);
y = Math.min(y, height - size.height);
comp.setBounds(x, y, size.width, size.height);
}
}
}
}
}
Basically, this uses a "virtual" coordinate system, where by rather then supply absolute x/y positions in pixels, you provide them as percentage of the parent container. Now, to be honest, it wouldn't take much to convert back to absolute positioning, just this way, you also get layout scaling.
The example also demonstrates Z-reording (just in case) and the double click simple re-randomizes the puzzel
Oh, I also made the piece transparent (opaque = false)
Oh, one thing I should mention, while going through this example, I found that it was possible to have pieces placed off screen (completely and partially).
You may want to check your positioning code to make sure that the images when they are laid out aren't been moved off screen ;)
Try using setBorder(new LineBorder(Color.RED)) in your puzzle piece constructor to see where the bounds of your puzzle pieces are. If they are where you'd expect them to be, it's likely that your positioning is wrong. Also make your puzzle pieces extend JComponent instead, or use setOpaque(false) if you're extending JPanel.
There are lots of suggestions I'd like to make, but first...
The way you choose a random position is off...
int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);
This allows duplicate position's to be generated (and overlaying cells)
UPDATES (using a layout manager)
Okay, so this is a slight shift in paradigm. Rather then producing a clip and passing it to the piece, I allowed the piece to make chooses about how it was going to render the the piece. Instead, I passed it the Rectangle it was responsible for.
This means, you could simply use something like setCell(Rectangle) to make a piece change (unless you're hell bent on drag'n'drop ;))
I ended up using Board panel due to some interesting behavior under Java 7, but that's another question ;)
package puzzel;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.*;
public class PuzzlePieceDriver extends JFrame {
public PuzzlePieceDriver(ImageIcon myPuzzleImage) {
setTitle("Hot Puzz");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
add(new Board(myPuzzleImage));
pack();
setVisible(true);
}//end constructor
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
ImageIcon image = new ImageIcon(PuzzlePieceDriver.class.getResource("/issue459.jpg"));
PuzzlePieceDriver driver = new PuzzlePieceDriver(image);
driver.setLocationRelativeTo(null);
driver.setVisible(true);
}
});
}
}//end class
A piece panel...
The panel overrides the preferred and minimum size methods...while it works for this example, it's probably better to use setPreferredSize and setMiniumumSize instead ;)
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package puzzel;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
public class PuzzlePiece extends JPanel {
private BufferedImage masterImage;
private Rectangle pieceBounds;
private BufferedImage clip;
public PuzzlePiece(BufferedImage image, Rectangle bounds) {
masterImage = image;
pieceBounds = bounds;
// Make sure the rectangle fits the image
int width = Math.min(pieceBounds.x + pieceBounds.width, image.getWidth() - pieceBounds.x);
int height = Math.min(pieceBounds.y + pieceBounds.height, image.getHeight() - pieceBounds.y);
clip = image.getSubimage(pieceBounds.x, pieceBounds.y, width, height);
}//end constructor
#Override
public Dimension getPreferredSize() {
return pieceBounds.getSize();
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x = 0;
int y = 0;
g.drawImage(clip, x, y, this);
g.setColor(Color.RED);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}//end paintComponent
}//end class PuzzlePiece
The board panel...used mostly because of some interesting issues I was having with Java 7...Implements a MouseListener, when you run the program, click the board, it's fun ;)
package puzzel;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
/**
*
* #author shane
*/
public class Board extends JPanel {
public static final int X_PIECES = 4;
public static final int Y_PIECES = 4;
private PuzzlePiece[] puzzle = new PuzzlePiece[X_PIECES * Y_PIECES];
private static BufferedImage image;
public Board(ImageIcon myPuzzleImage) {
image = iconToImage(myPuzzleImage); //pass image into bufferedImage form
puzzle = createClip();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
removeAll();
invalidate();
createClip();
// doLayout();
invalidate();
revalidate();
repaint();
}
});
}
public static BufferedImage iconToImage(ImageIcon icon) {
Image img = icon.getImage();
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
// Paint the image onto the buffered image
g.drawImage(img, 0, 0, null);
g.dispose();
return image;
}//end BufferedImage
protected int randomNumber(int min, int max) {
int temp = min + (int) (Math.random() * ((max - min) + 1));
return temp;
}//end randomNumber
private PuzzlePiece[] createClip() {
int cw, ch;
int w, h;
w = image.getWidth(null);
h = image.getHeight(null);
cw = w / X_PIECES;
ch = h / Y_PIECES;
// Generate a list of cell bounds
List<Rectangle> lstBounds = new ArrayList<>(25);
for (int y = 0; y < h; y += ch) {
for (int x = 0; x < w; x += cw) {
lstBounds.add(new Rectangle(x, y, cw, ch));
}
}
BufferedImage clip = image;
setLayout(new GridBagLayout());
for (int x = 0; x < X_PIECES; x++) {
for (int y = 0; y < Y_PIECES; y++) {
// Get a random index
int index = randomNumber(0, lstBounds.size() - 1);
// Remove the bounds so we don't duplicate any positions
Rectangle bounds = lstBounds.remove(index);
PuzzlePiece piece = new PuzzlePiece(clip, bounds);
puzzle[x * X_PIECES + y] = piece;
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.fill = GridBagConstraints.BOTH;
add(piece, gbc);
piece.invalidate();
piece.repaint();
}//end nested for
}//end for
invalidate();
repaint();
return puzzle;
}//end createClip
}
Now I know you eventually going to ask about how to move a piece, GridBagLayout has this wonderful method called getConstraints which allows you to retrieve the constraints used to layout the component in question. You could then modify the gridx and gridy values and use setConstraints to update it (don't forget to call invalidate and repaint ;))
I'd recommend having a read of How to Use GridBagLayout for more information ;)
Eventually, you'll end up with something like:
I have a thread which drops a circle in the y direction. I want to now create several circles on screen dropping at the same time with random x positions.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Goo
{
protected GooPanel gooPanel;
private boolean loop = true;
protected int width , height;
private int frameTimeInMillis = 50;
private RenderingHints renderingHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING , RenderingHints.
VALUE_ANTIALIAS_ON);
#SuppressWarnings("serial")
class GooPanel extends JPanel
{
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHints(renderingHints);
draw(g2d);
}
}
public Goo()
{
this (800, 500);
}
public Goo(int w, int h)
{
width = w;
height = h;
JFrame frame = new JFrame ();
frame.setSize(width , height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gooPanel = new GooPanel ();
gooPanel.setPreferredSize(new Dimension(w, h));
frame.getContentPane ().add(gooPanel);
frame.pack();
frame.setVisible(true);
}
public void go()
{
while (loop)
{
gooPanel.repaint();
try
{
Thread.sleep(frameTimeInMillis);
} catch (InterruptedException e) {}
}
}
public void draw(Graphics2D g) {}
public void setFrameTime(int millis)
{
frameTimeInMillis = millis;
}
public Component getGooPanel ()
{
return gooPanel;
}
}
My FallingDrop class:
import java.awt.*;
public class FallingDrops extends Goo
{
double x, y, r;
int red, green, blue = 0;
Color a;
FallingDrops()
{
x = width / 2;
r = 10;
y = -r;
}
FallingDrops(double x)
{
this.x = x;
r = 10;
y = -r;
}
public void draw(Graphics2D g)
{
g.setColor(Color.GRAY);
g.fillRect(0, 0, width , height);
g.setColor(Color.WHITE);
g.fillOval ((int) (x - r), (int) (y - r), (int) (2 * r),
(int) (2 * r));
y++;
if (y - r > height)
y = -r;
}
public static void main(String [] args)
{
int num = 10;
Goo gooDrop [] = new FallingDrops[num];
for(int i = 0; i < gooDrop.length; i++)
{
double x = Math.random()*800;
gooDrop[i] = new FallingDrops(x);
System.out.println(x);
gooDrop[i].go();
}
}
}
At current, the loop fails to complete when the go() method is executed; thus only painting ONE object on screen, and not several as indicated in my loop. This is a simple fix I am sure. Any ideas what I am doing wrong?
The method go() never returns. when it is called on the first object in the array, it continues working infinitely. you should either make the repainting in a separate thread that is constantly repainting. or if you want repainting only when drops are added, then remove the while in your go method
public void go()
{
gooPanel.repaint();
try
{
Thread.sleep(frameTimeInMillis);
} catch (InterruptedException e) {}
}
this way it will returns after it had made a repaining and a pause.
while (loop) .. gooPanel.repaint();
Not the way to do custom painting. Establish a Swing Timer and call repaint() in the actionPerformed() method of the listener.
See the Custom Painting lesson in the tutorial for details and working examples.