Search code examples
javaswingcustom-painting

Use Buttons and mouse to control draw-board


I am doing a GUI that is supposed to work like a Paint application. My current problem is adding proper functionality to my draw line and draw rectangle buttons. They currently don't work as I expected them to work. Help will be greatly appreciated.

I searched many code snippets on learning how to draw shapes but none of them show how to make them work based on if they are activated based on the buttons, and how to alternate from drawing a line to drawing rectangles.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class paintGUI extends JComponent {

    // Image in which we're going to draw
    private Image image;
    // Graphics2D object ==> used to draw on
    private Graphics2D g2;
    // Mouse coordinates
    private int currentX, currentY, oldX, oldY;

    public paintGUI() {
        setDoubleBuffered(false);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                // save coord x,y when mouse is pressed
                oldX = e.getX();
                oldY = e.getY();
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                // coord x,y when drag mouse
                currentX = e.getX();
                currentY = e.getY();

                if (g2 != null) {
                    // draw line if g2 context not null
                    //g2.drawLine(oldX, oldY, currentX, currentY);

                    //Need to implement these to their button
                    //g2.drawRect(oldX, oldY, currentX, currentY);
                    //g2.fillRect(oldX, oldY, currentX, currentY);

                    // refresh draw area to repaint
                    repaint();
                    // store current coords x,y as olds x,y
                    oldX = currentX;
                    oldY = currentY;
                }
            }
        });
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (image == null) {
            // image to draw null ==> we create
            image = createImage(getSize().width, getSize().height);
            g2 = (Graphics2D) image.getGraphics();

            // clear draw area
            clear();
        }

        g.drawImage(image, 0, 0, null);
    }

    // now we create exposed methods
    public void clear() {
        g2.setPaint(Color.white);
        // draw white on entire draw area to clear
        g2.fillRect(0, 0, getSize().width, getSize().height);
        g2.setPaint(Color.black);
        repaint();
    }

    public void thin() {
        g2.setStroke(new BasicStroke(3));
    }

    public void thick() {
        g2.setStroke(new BasicStroke(10));
    }

    public void red() {
        // apply red color on g2 context
        g2.setPaint(Color.red);
    }

    public void black() {
        g2.setPaint(Color.black);
    }

    public void magenta() {
        g2.setPaint(Color.magenta);
    }

    public void drawLine() {
        g2.drawLine(oldX, oldY, currentX, currentY);
    }

    public void drawRectangle() {
        g2.drawRect(oldX, oldY, currentX, currentY);
        g2.fillRect(oldX, oldY, currentX, currentY);

    }
}

class GUIPaint {

    JButton clearBtn, blackBtn, redBtn, magentaBtn, filledRectangleBtn, lineBtn, thinBtn, thickBtn;
    paintGUI paintGUI;
    ActionListener actionListener = e -> {
        if (e.getSource() == clearBtn) {
            paintGUI.clear();
        } else if (e.getSource() == thinBtn) {
            paintGUI.thin();
        } else if (e.getSource() == thickBtn) {
            paintGUI.thick();
        } else if (e.getSource() == blackBtn) {
            paintGUI.black();
        } else if (e.getSource() == redBtn) {
            paintGUI.red();
        } else if (e.getSource() == magentaBtn) {
            paintGUI.magenta();
        } else if (e.getSource() == filledRectangleBtn) {
            paintGUI.drawLine();
        } else if (e.getSource() == lineBtn) {
            paintGUI.drawRectangle();
        }
    };

    public static void main(String[] args) {
        new GUIPaint().show();
    }

    public void show() {
        // create main frame
        JFrame frame = new JFrame("Swing Paint");
        Container content = frame.getContentPane();
        // set layout on content pane
        content.setLayout(new BorderLayout());
        // create draw area
        paintGUI = new paintGUI();

        // add to content pane
        content.add(paintGUI, BorderLayout.CENTER);

        // create controls to apply colors and call clear feature
        JPanel controls = new JPanel();

        clearBtn = new JButton("Clear");
        clearBtn.addActionListener(actionListener);
        blackBtn = new JButton("Black");
        blackBtn.addActionListener(actionListener);
        redBtn = new JButton("Red");
        redBtn.addActionListener(actionListener);
        magentaBtn = new JButton("Magenta");
        magentaBtn.addActionListener(actionListener);

        lineBtn = new JButton("Line");
        lineBtn.addActionListener(actionListener);
        filledRectangleBtn = new JButton("Filled Rectangle");
        filledRectangleBtn.addActionListener(actionListener);

        thickBtn = new JButton("Thick Line");
        thickBtn.addActionListener(actionListener);
        thinBtn = new JButton("Thin Line");
        thinBtn.addActionListener(actionListener);

        controls.add(lineBtn);
        controls.add(filledRectangleBtn);
        controls.add(thinBtn);
        controls.add(thickBtn);
        controls.add(blackBtn);
        controls.add(redBtn);
        controls.add(magentaBtn);
        controls.add(clearBtn);

        // add to content pane
        content.add(controls, BorderLayout.NORTH);

        frame.setSize(800, 800);
        // can close frame
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // show the swing paint result
        frame.setVisible(true);
    }
}

I need the program to properly respond if I click the line button to be able to draw a line or if the rectangle button is pressed than draw rectangle.


Solution

  • The basic idea is to let the controls (buttons, mouse) change the attributes (color, shape, stroke, coordinates) and invoke repaint.
    paintComponent uses those attributes to draw the right shape.
    Note the commented modifications of your code.
    The code is one-file mre: the entire code can be copy pasted into GUIPaint.java and run:

    import java.awt.BasicStroke;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Container;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionListener;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    import swing_tests.PaintGUI.SHAPE;
    
    public class GUIPaint {
    
        private PaintGUI paintGUI;
    
        public void showGui() {
    
            JFrame frame = new JFrame("Swing Paint");
            Container content = frame.getContentPane();
            content.setLayout(new BorderLayout());
            paintGUI = new PaintGUI();
            content.add(paintGUI, BorderLayout.CENTER);
    
            // create controls to apply colors and call clear feature
            JPanel controls = new JPanel();
    
            //todo: reduce duplicate code by having a method that constructs
            //and adds a button
            //todo: use button groups where only one button can be selected 
            JButton clearBtn = new JButton("Clear");
            JButton blackBtn = new JButton("Black");
            JButton redBtn = new JButton("Red");
            JButton magentaBtn = new JButton("Magenta");
            JButton lineBtn = new JButton("Line");
            JButton filledRectangleBtn = new JButton("Filled Rectangle");
            JButton thickBtn = new JButton("Thick Line");
            JButton thinBtn = new JButton("Thin Line");
    
            //todo: register an Action listner to each button by using lambda
            //for example   clearBtn.addActionListener(e-> paintGUI.clear());
            ActionListener actionListener = e -> {
                if (e.getSource() == clearBtn) {
                    paintGUI.clear();
                }else if (e.getSource() == thinBtn) {
                    paintGUI.thin();
                } else if (e.getSource() == thickBtn) {
                    paintGUI.thick();
                } else if (e.getSource() == blackBtn) {
                    paintGUI.black();
                } else if (e.getSource() == redBtn) {
                    paintGUI.red();
                } else if (e.getSource() == magentaBtn) {
                    paintGUI.magenta();
                } else if (e.getSource() == filledRectangleBtn) {
                    paintGUI.setShape(SHAPE.RECTANGLE);
                } else if (e.getSource() == lineBtn) {
                    paintGUI.setShape(SHAPE.LINE);
                }
            };
    
            clearBtn.addActionListener(actionListener);
            blackBtn.addActionListener(actionListener);
            redBtn.addActionListener(actionListener);
            magentaBtn.addActionListener(actionListener);
            lineBtn.addActionListener(actionListener);
            filledRectangleBtn.addActionListener(actionListener);
            thickBtn.addActionListener(actionListener);
            thinBtn.addActionListener(actionListener);
    
            controls.add(lineBtn);
            controls.add(filledRectangleBtn);
            controls.add(thinBtn);
            controls.add(thickBtn);
            controls.add(blackBtn);
            controls.add(redBtn);
            controls.add(magentaBtn);
            controls.add(clearBtn);
    
            content.add(controls, BorderLayout.NORTH);
            frame.setSize(800, 800);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(()-> new GUIPaint().showGui());
        }
    }
    
    class PaintGUI extends JComponent {
    
        //states defined by enum
        enum SHAPE {RECTANGLE, LINE}
    
        private SHAPE shape; // store state
        private static final Color BACKGROUND_COLOR = Color.WHITE;
        private int startX, startY, endX, endY;  //shape coordinates
        private Color color = Color.BLACK;       //draw color
        private BasicStroke stroke = new BasicStroke(3); //draw stroke
        private boolean isClear = false;
    
        public PaintGUI() {
    
            setDoubleBuffered(false);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    //save coord x,y where mouse was pressed
                    startX = e.getX();
                    startY = e.getY();
                    endX = startX; endY = startY;
                    clear(); //clear draw board
                }
    
            });
    
            addMouseMotionListener(new MouseAdapter() {
    
                @Override
                public void mouseDragged(MouseEvent e) {
                        //update end coord as mouse dragged
                        endX = e.getX();
                        endY = e.getY();
                        repaint();  //keep repainting while drag lasts 
                }
            });
        }
    
        @Override
        protected void paintComponent(Graphics g) {
    
            Graphics2D g2 = (Graphics2D) g;
            // draw white on entire draw area to clear
            g2.setColor(BACKGROUND_COLOR);
            g2.fillRect(0, 0, getSize().width, getSize().height);
            if(isClear || shape == null){
                isClear = false;
                return;
            }
    
            //draw using color , stroke and shape attributes 
            g2.setColor(color);   
            g2.setStroke(stroke);
    
            switch (shape){
                case RECTANGLE:
                    drawFilledRectangle(g2);
                    break;
                case LINE:
                    drawLine(g2);
                    break;
                default:
                    break;
            }
        }
    
        public void clear() {
            isClear = true;
            repaint();
        }
    
        public void drawLine(Graphics2D g2) {
            g2.drawLine( startX, startY, endX, endY);
            repaint();
        }
    
        public void drawFilledRectangle(Graphics2D g2) {
            //to allow rectangle dragged from bottom up and left to right
            //use min x and min y as origin
            int x = Math.min(startX, endX);
            int y = Math.min(startY, endY);
            int width = Math.abs(endX-startX);  //to account for negative width
            int height = Math.abs(endY-startY); //or height
            g2.fillRect(x,y,width,height);
        }
    
        public void thin() {
            setStroke(3);
        }
    
        public void thick() {
            setStroke(10);
        }
    
        public void setStroke(int width) {
            stroke  = new BasicStroke(width);
            repaint();
        }
    
        public void red() {
            setColor(Color.red);
        }
    
        public void black() {
            setColor(Color.black);
        }
    
        public void magenta() {
            setColor(Color.magenta);
        }
    
        void setColor(Color color){
            this.color = color;
            repaint();
        }
    
        void setShape(SHAPE shape) {
            this.shape = shape;
            clear();
        }
    }
    

    enter image description here