0

I have a program where I have to use internal Timer event to rotate a star in circular motion. There is a button in the frame that changes the direction of the star and a slider which changes the speed of the star rotation.

This is my Main Class

import javax.swing.JFrame;

public class Main {
    public static void main(String args[]) {
//      Making an instance of the class that makes the frame
        MainFrame frame = new MainFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);
        frame.setVisible(true);
    }
}

MainFrame Class that makes the frame.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


@SuppressWarnings("serial")
public class MainFrame extends JFrame{
    private StarGraphics frameStar;
    private JButton starToggle;
    public JSlider starSpeed;
    
    
    public MainFrame() {
//      Setting some properties of the frame
        setTitle("Star Moving with Internal Events");
        setLayout(new BorderLayout());
        
//      Initializing the panel which has rotating star
        frameStar = new StarPainter(this);
        
//      Adding the bottom toggle button and slider
        addToggler();   
    }
    
//  Getter for the slider value
    public int sliderSpeed() {
        return starSpeed.getValue();
    }
    
    
//  Adds Button to change the direction of the star
    private void addToggler() {
//      Adding another jpanel which has layout set to null so i can add button of my size
        JPanel panel = new JPanel();
        panel.setLayout(null);
        panel.setPreferredSize(new Dimension(20,80));
        panel.setBackground(Color.WHITE);
        
//      Initializing button and its action listener to change star direction
        starToggle = new JButton("Toggle");
        starToggle.setBounds(190, 0, 80, 20);
        starToggle.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(e.getSource() == starToggle) {
                    frameStar.ChangeDirections();
                }
            }
        });
        
//      Adding button to panel
        panel.add(starToggle);
        
//      Adding slider to the panel
        addSlider(panel);
        
//      Adding panel to the main Panel at the bottom
        add(panel, BorderLayout.SOUTH);
    }
    
    private void addSlider(JPanel panel) {
//      Adding Slider and it's properties
        starSpeed = new JSlider(JSlider.HORIZONTAL, 0, 20, 5);
        starSpeed.setMajorTickSpacing(10);
        starSpeed.setMinorTickSpacing(1);
        starSpeed.setPaintTicks(true);
        starSpeed.setPaintLabels(true);
        starSpeed.setBounds(70,30,400, 45);
        
//      Adding Slider-ChangeListener to change the rotation speed of the star
        starSpeed.addChangeListener(new ChangeListener() { // anonymous inner class  
            // handle change in slider value
            @Override
            public void stateChanged(ChangeEvent e) {
                frameStar.ChangeSpeed(starSpeed.getValue());
            }
         } 
      );
        
//      Adding label besides the slider
        JLabel label = new JLabel("Speed : ");
        label.setBounds(10 , 10, 80, 80);
        panel.add(label);
        panel.add(starSpeed);
    }
}

Class to create the panel that has star rotation

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

@SuppressWarnings("serial")
public class StarGraphics extends JPanel{
    private boolean toggleDir = false;
    private int speed = 10;
    private Timer timer;
    protected double angleOfStarRotation = 0;
    
    public StarGraphics(JFrame frame) {
        
        setPreferredSize(new Dimension(500, 470));
        setBackground(Color.BLACK);
        setLayout(new BorderLayout());
        frame.add(this, BorderLayout.CENTER);
        
        startTimer();
    }
    
    public void startTimer() {
        timer = new Timer(speed, new ActionListener() {
            public void actionPerformed(ActionEvent e){
//              System.out.println(angleOfStarRotation);
                if(!toggleDir)  //rotates clockwise
                    angleOfStarRotation = angleOfStarRotation + 1;
                else        //rotates counterclockwise
                    angleOfStarRotation = angleOfStarRotation - 1;
                  
//                if (angleOfStarRotation == 360 || angleOfStarRotation == -360)  // If there is a full circle, it will reset the angle to zero
//                    angleOfStarRotation = 0;
                  
                repaint();
            }});
        timer.start();
    }
    
    public void ChangeSpeed(int newSpeed) { 
        this.speed = newSpeed;
        timer.setDelay(speed);
    }   
    public void ChangeDirections() {toggleDir = !toggleDir; }

}

And a class that paints the star into the panel

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.security.SecureRandom;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class StarPainter extends StarGraphics{
    private int[] starXPoints = {55, 67, 109, 73, 83, 55, 27, 37, 1, 43};
    private int[] starYPoints = {0, 36, 36, 54, 96, 72, 96, 54, 36, 36};
    GeneralPath starDesign = new GeneralPath();
    
    public StarPainter(JFrame frame) {
        super(frame);
    }
    
    public void drawStar(GeneralPath path) {
        path.moveTo(starXPoints[0], starYPoints[0]);
        
        for(int i=0; i<10; i++)
           path.lineTo(starXPoints[i], starYPoints[i]);
        
        path.closePath();
    }
    
    public void starActions(Graphics2D g) {
        int startAngle = 360;
        // For Random Color
        SecureRandom random = new SecureRandom(); 
           
        // rotate around origin and draw stars in random colors
        for (int count = 1; count <= 1; count++) 
        {
            double angle = startAngle - 90;
            // rotate coordinate system
            g.rotate(angleOfStarRotation * Math.PI / angle); //rotates as per the rotated angle    // 
           // set random drawing color
            g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
          // draw filled star
            g.fill(starDesign);
          // dispose the star
            g.dispose();
        }
    }
    
    @Override
    public void paintComponent(Graphics g)
    {
       super.paintComponent(g);
       
       Graphics2D g2d = (Graphics2D) g;
      
       drawStar(starDesign);
       
       g2d.translate(250,150);
       
       starActions(g2d);
       
    }
}

After running the code, the output will show a frame with coloring star rotating around the panel in circular motion, but after 2 full rotation, the star rotation slow down automatically. Does anyone know why that happens?

  • 2
    I don't see immediately why things slow, but that is a strange for loop, `for (int count = 1; count <= 1; count++)`, and you are disposing of a Graphics object given by the JVM, which is dangerous to do. – Hovercraft Full Of Eels Feb 09 '22 at 23:54
  • 1
    Completely unrelated question. Do you realize you can put all those classes in the same file? Only one should then be `public`. The one with `static main`. The others can just be added at the end. – WJS Feb 10 '22 at 00:31
  • 1
    Also unrelated, but too many code comments can distract those reading the code, reducing the ability of others to quickly read and understand it. It's a fine line between helping and hindering. – Hovercraft Full Of Eels Feb 10 '22 at 00:39
  • 2
    Also, check out this. [should I extend JFrame](https://stackoverflow.com/questions/10442665/extending-a-jframe). One of those who answered should be somewhat familiar. – WJS Feb 10 '22 at 01:14

1 Answers1

3

Your "core" problem is right here...

public void drawStar(GeneralPath path) {
    path.moveTo(starXPoints[0], starYPoints[0]);
    
    for(int i=0; i<10; i++)
       path.lineTo(starXPoints[i], starYPoints[i]);
    
    path.closePath();
}

This gets called each time you re-draw the component, which means, you're adding new points to the shape, making it infinitely more complex on each paint pass.

Instead, just create the shape in the constructor...

public StarPainter() {
    starDesign.moveTo(starXPoints[0], starYPoints[0]);

    for (int i = 0; i < 10; i++) {
        starDesign.lineTo(starXPoints[i], starYPoints[i]);
    }

    starDesign.closePath();
}

As has already been pointed out, you're disposing of a Graphics context which you did not create.

If your going to change the transformation of the context, you should always create a copy of your own, for example...

public void starActions(Graphics2D g) {
    int startAngle = 360;
    // For Random Color
    SecureRandom random = new SecureRandom();

    g = (Graphics2D) g.create();
    // rotate around origin and draw stars in random colors
    //for (int count = 1; count <= 1; count++) {
        double angle = startAngle - 90;
        // rotate coordinate system
        g.rotate(angleOfStarRotation * Math.PI / angle); //rotates as per the rotated angle    // 
        // set random drawing color
        g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
        // draw filled star
        g.fill(starDesign);
        // dispose the star
        g.dispose();
    //}
}

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2d = (Graphics2D) g.create();
    g2d.translate(250, 150);
    starActions(g2d);
    g2d.dispose();

}

Also, passing a reference of the JFrame to the component...

public StarGraphics(JFrame frame) {
    
    setPreferredSize(new Dimension(500, 470));
    setBackground(Color.BLACK);
    setLayout(new BorderLayout());
    frame.add(this, BorderLayout.CENTER);
    
    startTimer();
}

is a bad idea. The component has no need for, nor is it its responsibility too, interact with the frame. You're just exposing implementation detail unnecessarily.

Runnable example

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.GeneralPath;
import java.security.SecureRandom;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                MainFrame frame = new MainFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(500, 500);
                frame.setVisible(true);
            }
        });
    }

    public class MainFrame extends JFrame {

        private StarGraphics frameStar;
        private JButton starToggle;
        public JSlider starSpeed;

        public MainFrame() {
            //      Setting some properties of the frame
            setTitle("Star Moving with Internal Events");
            setLayout(new BorderLayout());

            //      Initializing the panel which has rotating star
            frameStar = new StarPainter();
            add(frameStar);

            //      Adding the bottom toggle button and slider
            addToggler();
        }

        //  Getter for the slider value
        public int sliderSpeed() {
            return starSpeed.getValue();
        }

        //  Adds Button to change the direction of the star
        private void addToggler() {
            //      Adding another jpanel which has layout set to null so i can add button of my size
            JPanel panel = new JPanel();
            panel.setLayout(null);
            panel.setPreferredSize(new Dimension(20, 80));
            panel.setBackground(Color.WHITE);

            //      Initializing button and its action listener to change star direction
            starToggle = new JButton("Toggle");
            starToggle.setBounds(190, 0, 80, 20);
            starToggle.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (e.getSource() == starToggle) {
                        frameStar.ChangeDirections();
                    }
                }
            });

            //      Adding button to panel
            panel.add(starToggle);

            //      Adding slider to the panel
            addSlider(panel);

            //      Adding panel to the main Panel at the bottom
            add(panel, BorderLayout.SOUTH);
        }

        private void addSlider(JPanel panel) {
            //      Adding Slider and it's properties
            starSpeed = new JSlider(JSlider.HORIZONTAL, 0, 20, 5);
            starSpeed.setMajorTickSpacing(10);
            starSpeed.setMinorTickSpacing(1);
            starSpeed.setPaintTicks(true);
            starSpeed.setPaintLabels(true);
            starSpeed.setBounds(70, 30, 400, 45);

            //      Adding Slider-ChangeListener to change the rotation speed of the star
            starSpeed.addChangeListener(new ChangeListener() { // anonymous inner class  
                // handle change in slider value
                @Override
                public void stateChanged(ChangeEvent e) {
                    frameStar.ChangeSpeed(starSpeed.getValue());
                }
            }
            );

            //      Adding label besides the slider
            JLabel label = new JLabel("Speed : ");
            label.setBounds(10, 10, 80, 80);
            panel.add(label);
            panel.add(starSpeed);
        }
    }

    public class StarGraphics extends JPanel {

        private boolean toggleDir = false;
        private int speed = 10;
        private Timer timer;
        protected double angleOfStarRotation = 0;

        public StarGraphics() {
            setPreferredSize(new Dimension(500, 470));
            setBackground(Color.BLACK);
            setLayout(new BorderLayout());
            startTimer();
        }

        public void startTimer() {
            timer = new Timer(speed, new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    //              System.out.println(angleOfStarRotation);
                    if (!toggleDir) //rotates clockwise
                    {
                        angleOfStarRotation = angleOfStarRotation + 1;
                    } else //rotates counterclockwise
                    {
                        angleOfStarRotation = angleOfStarRotation - 1;
                    }

                    System.out.println("tick");
                    //                if (angleOfStarRotation == 360 || angleOfStarRotation == -360)  // If there is a full circle, it will reset the angle to zero
                    //                    angleOfStarRotation = 0;
                    repaint();
                }
            });
            timer.start();
        }

        public void ChangeSpeed(int newSpeed) {
            this.speed = newSpeed;
            timer.setDelay(speed);
        }

        public void ChangeDirections() {
            toggleDir = !toggleDir;
        }

    }

    public class StarPainter extends StarGraphics {

        private int[] starXPoints = {55, 67, 109, 73, 83, 55, 27, 37, 1, 43};
        private int[] starYPoints = {0, 36, 36, 54, 96, 72, 96, 54, 36, 36};
        GeneralPath starDesign = new GeneralPath();

        public StarPainter() {
            starDesign.moveTo(starXPoints[0], starYPoints[0]);

            for (int i = 0; i < 10; i++) {
                starDesign.lineTo(starXPoints[i], starYPoints[i]);
            }

            starDesign.closePath();
        }

        public void starActions(Graphics2D g) {
            int startAngle = 360;
            // For Random Color
            SecureRandom random = new SecureRandom();

            g = (Graphics2D) g.create();
            // rotate around origin and draw stars in random colors
            //for (int count = 1; count <= 1; count++) {
            double angle = startAngle - 90;
            // rotate coordinate system
            g.rotate(angleOfStarRotation * Math.PI / angle); //rotates as per the rotated angle    // 
            // set random drawing color
            g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
            // draw filled star
            g.fill(starDesign);
            // dispose the star
            g.dispose();
            //}
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            g2d.translate(250, 150);
            starActions(g2d);
            g2d.dispose();

        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366