How to update Java Swing GUI using Timer and delay? - java

I am trying to make a sorting visualizer using a Java GUI. The problem I'm facing is updating the GUI in real time. For instance, when I shuffle the initial array I will call the following shuffle and swap methods. I expect it to repaint the GUI and then do it again after a delay but instead all it does is finish the shuffleArray method then repaints it all at once showing no "animation". What is a better way of going about this?
public void shuffleArray() {
final int milliDelay = 1000;
for (int i = NUM_BARS - 1; i >= 0; i--) {
int rand = (int) (Math.random() * i);
swap(i,rand, milliDelay);
}
}
public void swap(int firstIndex, int secondIndex, int delay) {
Timer timer = new Timer(delay, e -> {
int temp = data[firstIndex];
data[firstIndex] = data[secondIndex];
data[secondIndex] = temp;
barColors[firstIndex] = Color.RED;
barColors[secondIndex] = Color.RED;
repaint();
barColors[firstIndex] = Color.WHITE;
barColors[secondIndex] = Color.WHITE;
});
timer.setRepeats(false);
timer.start();
}

Related

JFrame defined in one class, JComponent extended in another

Relatively new to Java, coding for a school project.
I'm using JFrame and JComponent, drawing patterns and strings and all that fun stuff.
Currently, I have a class written that extends JComponent. This is the class where I am defining most of my shapes. The issue is that I initialized my Jframe
(Code: JFrame myFrame = new JFrame() ) in the main of one class, but I need to access myFrame.getWidth() in the JComponent class that I'm working in.
How can I access variables getWidth() and getHeight() in "public class MyJComponent extends JComponent" , when I defined myFrame in 'public class Lab2' ??
Edit for code:
public class Lab2 {
public static void main(String[] args) {
System.out.println("Hello Java");
JFrame myFrame = new JFrame();
myFrame.setSize(500, 500);
myFrame.setTitle("Color Test");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyJComponent myComponent = new MyJComponent(500, 500);
myFrame.add(myComponent);
myFrame.getContentPane().setBackground(Color.white); //sets background color.
myFrame.setVisible(true); // setVisible() *after* add() is the norm
//Deciding geometry of hidden shape. paintComponent is called once per run, this is called afterwards.
}
}
/**/
public class MyJComponent extends JComponent {
int[] circleX;
int[] circleY;
int[] circleR;
final int MIN_RADIUS = 5;
final int MAX_RADIUS = 15;
final int MIN_SEPARATION = 1;
final int MAX_ATTEMPTS = 5000;
final int MAX_CIRCLES = 1000;
Random rand;
int initialWidth;
int initialHeight;
int numCircles; // actual number of circles drawn
// are circles at index i and index j separated by *<= tolerance* pixels?
boolean twoCirclesOverlap(int i, int j, int tolerance) {
double distanceBetweenCenters =
Math.sqrt((circleX[i] - circleX[j]) * (circleX[i] - circleX[j]) +
(circleY[i] - circleY[j]) * (circleY[i] - circleY[j]));
return (distanceBetweenCenters <= (circleR[i] + circleR[j] + tolerance));
}
// are any existing circles separated from the proposed one at index i by *<= tolerance* pixels?
boolean anyCirclesOverlap(int i, int tolerance) {
for (int j = 0; j < i; j++) {
if (twoCirclesOverlap(i, j, tolerance)) {
return true;
}
}
return false;
}
// attempt to randomly place the largest-possible circle that does not overlap any existing one
boolean tryToPlaceCircle(int i) {
for (int j = 0; j < MAX_ATTEMPTS; j++) {
// pick a random position, set initial radius to minimum
circleX[i] = rand.nextInt(initialWidth);
circleY[i] = rand.nextInt(initialHeight);
circleR[i] = MIN_RADIUS;
// grow circle until it touches another or reaches max size
while (!anyCirclesOverlap(i, MIN_SEPARATION) && circleR[i] < MAX_RADIUS)
circleR[i]++;
// it was touching from the start -- must try again
if (circleR[i] == MIN_RADIUS) {
continue;
}
// grew to max size -- well done
else if (circleR[i] == MAX_RADIUS) {
return true;
}
// grew some, but then touched
else {
circleR[i]--; // retract to the step before touch
return true;
}
}
// all attempts failed
return false;
}
MyJComponent(int width, int height) {
circleX = new int[MAX_CIRCLES];
circleY = new int[MAX_CIRCLES];
circleR = new int[MAX_CIRCLES];
initialWidth = width;
initialHeight = height;
rand = new Random();
numCircles = 0;
while (numCircles < MAX_CIRCLES && tryToPlaceCircle(numCircles)) {
numCircles++;
}
}
//Override paintComponent
public void paintComponent(Graphics g) {
for (int i = 0; i < numCircles; i++) {
g.drawOval(circleX[i] - circleR[i], circleY[i] - circleR[i], 2 * circleR[i], 2 * circleR[i]);
}
}
//Shape decision
public void shapeDecision() {
double randomShapeDecider = Math.random();
if (randomShapeDecider > .50) {
//shape is circle, define it's properties
hiddenCircleDiameter = myFrame.getWidth();
}
else {
//shape is rectangle
hiddenRectangleWidth = myFrame.getWidth();
}
}
}

Why this Simple Loop is Causing Problematic Behavior of my JFrame

I am doing an assignment from the Java Exposure textbook, which was written in 2007. This book includes some code that I usually update to use some of the more recent features (just basic stuff). However, in this one I am running into a problem. All I tried to do is replace the show with setVisible(true) and change the Frame to a JFrame and add a gfx.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);. However, I noticed that this wouldn't actually cause the window to close. If I clicked many times, maybe 1/30 tries it would close. If I reduced the delay from 10 to 1, it usually closed within 2 tries. This of course led me to believe that the delay method is causing this erratic behavior. I tried Thread.sleep, but of course that didn't work. Is there any simply way to get this code so that the frame will close when I hit the close button? If there isn't, what would be the less simple way of doing it?
Here is the code:
// Lab30st.java
// The Screen Saver Program
// Student Version
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.JOptionPane;
public class Lab30st
{
public static void main(String args[])
{
GfxApp gfx = new GfxApp();
gfx.setSize(800,600);
gfx.addWindowListener(new WindowAdapter() {public void
windowClosing(WindowEvent e) {System.exit(0);}});
gfx.show();
}
}
class GfxApp extends Frame
{
private int circleCount, circleSize;
public GfxApp()
{
circleCount = 50;
circleSize = 30;
}
class Coord
{
private int xPos;
private int yPos;
public Coord(int x, int y)
{
xPos = x;
yPos = y;
}
}
public void paint(Graphics g)
{
int incX = 5;
int incY = 5;
int diameter = 30;
int timeDelay = 10;
Circle c = new Circle(g,diameter,incX,incY,timeDelay);
for (int k = 1; k <= 2000; k++)
{
c.drawCircle(g);
c.hitEdge();
}
}
}
class Circle
{
private int tlX; // top-left X coordinate
private int tlY; // top-left Y coordinate
private int incX; // increment movement of X coordinate
private int incY; // increment movement of Y coordinate
private boolean addX; // flag to determine add/subtract of increment for X
private boolean addY; // flag to determine add/subtract of increment for Y
private int size; // diameter of the circle
private int timeDelay; // time delay until next circle is drawn
public Circle(Graphics g, int s, int x, int y, int td)
{
incX = x;
incY = y;
size = s;
addX = true;
addY = false;
tlX = 400;
tlY = 300;
timeDelay = td;
}
public void delay(int n)
{
long startDelay = System.currentTimeMillis();
long endDelay = 0;
while (endDelay - startDelay < n)
endDelay = System.currentTimeMillis();
}
public void drawCircle(Graphics g)
{
g.setColor(Color.blue);
g.drawOval(tlX,tlY,size,size);
delay(timeDelay);
if (addX)
tlX+=incX;
else
tlX-=incX;
if (addY)
tlY+=incY;
else
tlY-=incY;
}
public void newData()
{
incX = (int) Math.round(Math.random() * 7 + 5);
incY = (int) Math.round(Math.random() * 7 + 5);
}
public void hitEdge()
{
boolean flag = false;
if (tlX < incX)
{
addX = true;
flag = true;
}
if (tlX > 800 - (30 + incX))
{
addX = false;
flag = true;
}
if (tlY < incY + 30) // The +30 is due to the fact that the title bar covers the top 30 pixels of the window
{
addY = true;
flag = true;
}
if (tlY > 600 - (30 + incY))
{
addY = false;
flag = true;
}
if (flag)
newData();
}
}
You are "freezing" the Event Dispatch Thread with
public void delay(int n)
{
long startDelay = System.currentTimeMillis();
long endDelay = 0;
while (endDelay - startDelay < n)
endDelay = System.currentTimeMillis();
}
This means that all the other stuff that is trying to happen (like closing the window) has to wait until the thread comes out of the "sleep".
basically you shouldn't be doing the delay in the EDT, it should be on a different thread and then ask the EDT thread to update.
Your "busy wait" delay may cause other problems too. You can improve the behavior by using Thread.sleep()
See Java Event-Dispatching Thread explanation
That's terrible.
You need to restructure the whole code.
Let's start with the really bad:
delay is (almost) a busy wait, I haven't seen busy waits since BASIC was modern. It basically holds the CPU hostage to the thread, not only does it do nothing, no other thread (almost) can use the time slice. The reason I say almost is that calling the system time function causes a context switch that could allow other threads to run, but it is still bad.
The still pretty bad:
Replacing with Thread.sleep. Better yes, no busy wait, but you are still holding the one and only UI thread. This means no other UI work can happen up to and including closing the main window.
What needs to happen:
Get an external timer (e.g. javax.swing.Timer) to trigger the draw event and do next part of the animation.
Search for "Java smooth animation" there are many examples of how to do this, double buffer and all.

Killing particle systems in processing with a countdown timer

Need some help modifying this code. I was working through some tutorials on particle systems, and I'm currently trying to write seom logic that says:
"If this particle system has been running for 10 seconds, stop adding particles to it. When the last of the particles are dead, and the system is empty,remove it from the systems ArrayList."
What is going on now:
- The timer counts down and particles stop being added to the particle system except it is acting like a timer for every particle system in the array list.
- the timer does not reset when you add a new particle system
What I need help with:
- Where to reset the timer, or re-initialize the timer when you make a new system.
- only having the timer affect the system its in (rather than all of them on screen)
// particle system
class ParticleSystem {
ArrayList<Particle> plist;
PVector origin; // An origin point for where particles are birthed
float c;
int t;
int countdown; // 10 seconds.
boolean end;
ParticleSystem(float col, int num, PVector v){
plist = new ArrayList<Particle>();
origin = v.get();
c = col;
end = false;
countdown = 10;
t = 10;
for(int i = 0; i < num; i++){
plist.add(new Particle(c,origin));
}
}
void applyForce(PVector force){
for (Particle p : plist){
p.applyForce(force);
}
}
void run(){
// iterate through array of single particles backwards
// remove single particles when they are dead.
t = countdown-int(millis()/1000);
print(t);
for (int i = plist.size()-1; i > 0; i--){
Particle p = plist.get(i);
p.run();
if (p.isDead()){
plist.remove(i);
}
}
if(t > 0){
addParticle();
} else {
dead();
}
//print(plist.size());
}
void addParticle(){
//println("AP: "+r);
float r = random(1);
if (r<0.4) {
plist.add(new SquareParticle(c,origin));
}else{
plist.add(new Particle(c,origin));
}
}
boolean dead(){
if(plist.isEmpty() || plist.size() == 1){
t = 10;
return true;
}else{
return false;
}
}
}
// main tab
ArrayList<ParticleSystem> systems;
PVector windRight = new PVector(0.1,0);
PVector sortaSpeed = new PVector(0,0.1);
PVector gravity = new PVector(0,0.05);
boolean wR = false;
boolean sP = false;
void setup() {
size(640,480);
systems = new ArrayList<ParticleSystem>();
noStroke();
}
void draw() {
background(0);
if(!systems.isEmpty()){
for (int i =0; i < systems.size(); i++){
ParticleSystem ps = systems.get(i);
ps.applyForce(gravity);
ps.run();
if(wR){
ps.applyForce(windRight);
}
if(sP){
ps.applyForce(sortaSpeed);
}
if(ps.dead()){
systems.remove(ps);
}
//print(systems.size());
}
} else {
fill(255);
text("'w' controls wind, 'a' controls speed, 's' adds particle systems",1,height-30);
}
}
void keyPressed() {
if(key == 'w'){
wR = true;
} else if(key == 'a'){
//print('a');
sP = true;
}else{
systems.add(new ParticleSystem(random(100,200),10,new PVector(random(10,630),10))); //random(480)
}
}
void keyReleased(){
if(key == 'w'){
wR = false;
} else if(key == 'a'){
sP = false;
}
}
In the future, please try to post an MCVE. Right now we can't run your code because it contains compiler errors. We don't need to see your entire sketch anyway though, just a small example that gets the point across.
But looking at your code, there is a problem here:
for (int i =0; i < systems.size(); i++){
...
if(ps.dead()){
systems.remove(ps);
}
Run through an example using a piece of paper and a pencil. Let's say you have 3 ParticleSystem instances in your systems list, and the loop is on the second one. You then remove the second one, moving the third one into the second index. The next iteration of the loop moves to the third index... but now there's nothing there!
To get around this problem, you could iterate through the ArrayList backwards, or better yet, you could use an Iterator.
From there it's just a matter of keeping track of each instance's startTime and comparing that to millis(), which you aren't doing right now.
Here's an MCVE that demonstrates using millis() and an Iterator to kill off Particle instances after 10 seconds:
import java.util.Iterator;
ArrayList<Particle> particles = new ArrayList<Particle>();
void setup() {
size(500, 500);
}
void draw() {
background(0);
Iterator<Particle> particleIterator = particles.iterator();
while (particleIterator.hasNext()) {
Particle p = particleIterator.next();
p.draw();
if (p.isDead()) {
particleIterator.remove();
}
}
}
void mousePressed() {
particles.add(new Particle(mouseX, mouseY));
}
class Particle {
int startTime;
float x;
float y;
public Particle(float x, float y) {
startTime = millis();
this.x = x;
this.y = y;
}
void draw() {
x += random(-2, 2);
y += random(-2, 2);
ellipse(x, y, 10, 10);
}
boolean isDead() {
return millis() > startTime + 10*1000;
}
}
Note that you'll have to use this logic twice: once for your individual particles, and again for the particle systems themselves. But the logic is the same: record a start time, then compare that to millis(), and use an Iterator to remove stuff when it has timed out.
Also note that Iterator is specific to Java mode. If you want to deploy as JavaScript, you might want to go with the backwards loop approach instead.

How to cause an infinite loop in JPanel

class DrawPane extends JPanel
{
//size is the size of the square, x and y are position coords
double size = 1, x = 0, y = 0;
double start = (-1) * size;
public void paintComponent(Graphics shape)
{
for(x = start; x <= scWidth; x += size)
{
shape.drawRect((int)x, (int)y , (int)size, (int)size);
//When a row is finished drawing, add another
if(x >= scWidth)
{
x = start; y += size;
}
//Redraws the entire grid; makes the for loop infnite
else if(y >= scHeight)
{
x = start; y = start;
}
}
}
}
I'm confused as to why JPanel refuses to work with the loop once I make it infinite. How would I go about allowing it to do so?
When you make the loop "infinite" you effectively tie up and freeze the Swing event thread preventing Swing from doing anything. Instead use a Swing Timer to drive your animation.
e.g.,
class DrawPane extends JPanel {
//size is the size of the square, x and y are position coords
double size = 1, x = 0, y = 0;
double start = (-1) * size;
public DrawPane() {
int timerDelay = 200;
new Timer(timerDelay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
x += size;
if (x >= scWidth) {
x = start;
y += size;
}
if (y >= scHeight) {
x = start;
y = start;
}
repaint();
}
}).start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // Don't forget to call this!
g.drawRect((int)x, (int)y , (int)size, (int)size);
}
}
The paint function is supposed to update the Paint and get out of the way. You really shouldn't be putting in complex logic and definitely not infinite loops there.
Just do what you have (except get rid of the reset stuff that makes your loop infinite) and put repaint() in an infinite loop (preferably with some timer logic) somewhere else in your program.
It will never break out of the paintComponent loop and update the GUI. The GUI will only update once the paintComponent method finishes. If you want to make the loop infinite, you need to take the code out of your event handler and be calling repaint() from elsewhere, possibly using a timer to do so.

BufferedImage, int[] pixels and rendering. How do they work they work together?

There is something in the following code that I am unable to understand. After digging through google for a while, I decided it would be better to ask someone.
I am following a game programming tutorial on youtube, and I feel I understand (to some degree) everything I have written, except for some lines which concern the rendering part of the program.
package com.thomas.game;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import com.thomas.game.graphics.Screen;
import com.thomas.game.input.Keyboard;
public class Game extends Canvas implements Runnable {
private static final int WIDTH = 300;
private static final int HEIGHT = (WIDTH / 16) * 9;
private static final int SCALE = 3;
private static final String TITLE = "Game";
private JFrame frame;
private Thread thread;
private Screen screen;
private BufferedImage image;
private Keyboard key;
private int[] pixels;
private boolean running = false;
private int x = 0, y = 0;
public Game() {
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
screen = new Screen(WIDTH, HEIGHT);
frame = new JFrame();
initializeFrame();
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
this is what I don't understand. I get the image raster, from which I get the databuffer. I typecast that databuffer into a (DatabufferInt), which allows me to retrieve an int[] through the getData() method. After this is done, pixel.length has a value of 48600, and every index contains the int value 0. Operating with this int[] makes the program render like it is supposed to. However, if I don't typecast and retrieve the int[] in the above manner, and instead say pixels = new int[48600], i end up with a black screen.
I guess what I want to know is: what is the difference between these two int[], or rather, what makes the first one work? How does it work?
key = new Keyboard();
addKeyListener(key);
setFocusable(true);
}
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1E9/60;
double delta = 0;
long now;
int ticks = 0;
int frames = 0;
long timer = System.currentTimeMillis();
while(running) {
now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
while(delta >= 1) {
tick();
ticks++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer >= 1000) {
timer += 1000;
frame.setTitle(TITLE + " | ups: " + ticks + " fps: " + frames);
ticks = 0;
frames = 0;
}
}
}
private void render() {
BufferStrategy bs = getBufferStrategy(); // retrieves the bufferstrategy from the current component (the instance of Game that calls this method)
if(bs == null) {
createBufferStrategy(3);
return;
}
screen.clear();
screen.render(x, y);
getPixels();
Graphics g = bs.getDrawGraphics(); // retrieves a graphics object from the next in line buffer in the bufferstrategy, this graphics object draws to that buffer
g.drawImage(image, 0, 0, getWidth(), getHeight(), null); // draws the bufferedimage to the available buffer
g.dispose();
bs.show(); // orders the next in line buffer (which the graphics object g is tied to) to show its contents on the canvas
}
private void tick() {
key.update();
if(key.up)
y--;
if(key.down)
y++;
if(key.left)
x--;
if(key.right)
x++;
}
public void initializeFrame() {
frame.setTitle(TITLE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public synchronized void start() {
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop() {
running = false;
try {
thread.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Game game = new Game();
game.start();
}
public void getPixels() {
for(int i = 0; i < pixels.length; i++)
pixels[i] = screen.pixels[i];
}
}
It seems like the bufferedimage gets values from the pixels array. But I don't understand how these two communicate, or how they are connected. I haven't explicitly told the bufferedimage to get its pixels from the pixels array, so how does it know?
I will also attach the Screen class, which is responsible for updating the pixels array.
package com.thomas.game.graphics;
import java.util.Random;
public class Screen {
private int width, height;
public int[] pixels;
private final int MAP_SIZE = 64;
private final int MAP_SIZE_MASK = MAP_SIZE - 1;
private int[] tiles;
private int tileIndex;
private int xx, yy;
private Random r;
public Screen(int w, int h) {
width = w;
height = h;
pixels = new int[width * height];
tiles = new int[MAP_SIZE * MAP_SIZE];
r = new Random(0xffffff);
for(int i = 0; i < tiles.length; i++) {
tiles[i] = r.nextInt();
}
tiles[0] = 0;
}
public void clear() {
for(int i = 0; i < pixels.length; i++)
pixels[i] = 0;
}
public void render(int xOffset, int yOffset) {
for(int y = 0; y < height; y++) {
yy = y + yOffset;
for(int x = 0; x < width; x++) {
xx = x + xOffset;
tileIndex = (yy >> 4 & MAP_SIZE_MASK) * MAP_SIZE + (xx >> 4 & MAP_SIZE_MASK);
pixels[y * width + x] = tiles[tileIndex];
}
}
}
}
I really hope someone can explain this to me, it would be greatly appreciated. The program is working like it is supposed to, but I don't feel comfortable continuing on the tutorial until I grasp this.
Basic types like short, int, long etc are not Objects.
However, int[] is an array. Arrays are objects in java. Java manipulates objects by reference, not value.
In this line you are not creating a new object. You are storing a reference to the object int[] in your variable pixels. Anything you change in pixels, gets changed inside of the int[] object in image:
pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
I've created an example, try running this code:
public class Data {
private int[] values = {25,14};
public int[] getValues() {
return values;
}
public static void main(String[] args) {
Data d = new Data();
System.out.println(d.getValues()[0]);
int[] values = d.getValues();
values[0] = 15;
System.out.println(d.getValues()[0]);
}
}
Output:
25
15
Note that you have this code...
pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
while it should be like this...
pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
Change image to img.
Hope it works!

Categories

Resources