2

I am trying to design a GUI with triangle shaped buttons. I have create the triangle button class correctly in so far as creating a JButton with my class constructor results in a triangle button on the page, but I fall short when it comes to placement of the button.

Could any direct me or have an example for creating a hexagonal shape from triangle buttons?

Here is my TriangleButton class for reference:

import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

class TriangleButton extends JButton {
    final static double side_len = 52; //Change for variable triangle size
    final static double y_offset = (Math.sqrt(3) * side_len / 2);
    private Shape triangle;

    public TriangleButton(int spot){
        triangle = createTriangle(spot);
    }

    public void paintBorder( Graphics g ) {
        ((Graphics2D)g).draw(triangle);
    }
    public void paintComponent( Graphics g ) {
        ((Graphics2D)g).fill(triangle);
    }
    public Dimension getPreferredSize() {
        return new Dimension((int)side_len, (int)y_offset);
    }
    public boolean contains(int x, int y) {
        return triangle.contains(x, y);
    }

    private Shape createTriangle(int spot) {
        Polygon p = new Polygon();
        p.addPoint( 0   , 0 );
        p.addPoint( (int)side_len , 0   );
        p.addPoint( (int)side_len/2, (int)(y_offset)  );
        return p;
    }
}

The look I had in mind would be something like..

Hexagon Shape

With space between the buttons.. basically just up-pointing and down-pointing triangles lined up. But anything to put me in the right direction would be appreciated!

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
user1091684
  • 101
  • 1
  • 11
  • I could be wrong, but I'm not 100% sure that this is possible by using or extending JButton. You may need to create your own Button class, one that uses or extends JComponent or JPanel, that you draw on, and that using a MouseListener you change the image on on mousePressed and mouseReleased. I hope that I'm wrong, so let's see what others have to say before you start throwing out code. – Hovercraft Full Of Eels Aug 10 '15 at 22:21
  • I also hope not, haha. I would think since the buttons are simply lined up in rows it wouldn't be difficult? But here I am asking so what do I know.. – user1091684 Aug 10 '15 at 22:50
  • What are you using these buttons for? – Hovercraft Full Of Eels Aug 10 '15 at 23:06
  • Teaching myself GUI stuff by making a small game which revolves around (you guessed it!) triangle shaped buttons being interacted with. In the end I want to be able to create variably sized areas of buttons. Right now looking to create 'blank' buttons that don't actually get shown (and won't be interacted with) to fill spots in a larger GridLayout. – user1091684 Aug 12 '15 at 22:13

3 Answers3

3

As an alternative, due to the complexities of generating a suitable layout to allow components to overlap, you could simply create a single button which housed all the triangles and which provided centralised control, for example

Multi button

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            HexagonButton btn = new HexagonButton();
            btn.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(Arrays.toString(btn.getSelectedObjects()));
                    System.out.println(e.getActionCommand());
                }
            });
            add(btn);
        }

    }

    public class HexagonButton extends AbstractButton {

        public static final String TOP_RIGHT_QUAD = "Top.right";
        public static final String TOP_QUAD = "Top";
        public static final String TOP_LEFT_QUAD = "Top.left";
        public static final String BOTTOM_LEFT_QUAD = "Bottom.left";
        public static final String BOTTOM_QUAD = "Bottom";
        public static final String BOTTOM_RIGHT_QUAD = "Bottom.right";

        private Shape top;
        private Shape topRight;
        private Shape topLeft;
        private Shape bottomLeft;
        private Shape bottomRight;
        private Shape bottom;

        private Map<String, Shape> paths;
        private String selectedQuad;

        public HexagonButton() {
            setModel(new DefaultButtonModel());
            createPaths();

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {

                    String previousQuad = selectedQuad;
                    selectedQuad = null;
                    for (String quad : paths.keySet()) {

                        Shape shape = paths.get(quad);
                        if (shape.contains(e.getPoint())) {
                            getModel().setPressed(true);
                            getModel().setArmed(true);
                            selectedQuad = quad;
                            if (!selectedQuad.equals(previousQuad)) {
                                fireActionPerformed(new ActionEvent(HexagonButton.this, ActionEvent.ACTION_PERFORMED, selectedQuad));
                            }
                            break;
                        }

                    }
                    repaint();

                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    getModel().setArmed(false);
                    getModel().setPressed(false);
                }
            });

        }

        @Override
        public Object[] getSelectedObjects() {
            return new Object[]{selectedQuad};
        }

        @Override
        public void invalidate() {
            super.invalidate();
            createPaths();
        }

        protected void createPaths() {
            topRight = create(0d, -60d);
            top = create(-60d, -120d);
            topLeft = create(-120d, -180d);
            bottomLeft = create(-180d, -240d);
            bottom = create(-240d, -300d);
            bottomRight = create(-300d, -360d);

            paths = new HashMap<>(6);
            paths.put(TOP_RIGHT_QUAD, topRight);
            paths.put(TOP_QUAD, top);
            paths.put(TOP_LEFT_QUAD, topLeft);
            paths.put(BOTTOM_LEFT_QUAD, bottomLeft);
            paths.put(BOTTOM_QUAD, bottom);
            paths.put(BOTTOM_RIGHT_QUAD, bottomRight);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(104, 104);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
            Graphics2D g2d = (Graphics2D) g.create();
            if (selectedQuad != null) {
                Shape path = paths.get(selectedQuad);
                g2d.setColor(UIManager.getColor("List.selectionBackground"));
                g2d.fill(path);
            }
            g2d.setColor(getForeground());
            g2d.draw(topRight);
            g2d.draw(top);
            g2d.draw(topLeft);
            g2d.draw(bottomLeft);
            g2d.draw(bottom);
            g2d.draw(bottomRight);
            g2d.dispose();
        }

        public Shape create(double startAngle, double endAngle) {

            double width = getWidth();
            double height = getHeight();

            double radius = Math.min(width, height) / 2;

            double xOffset = width - radius;
            double yOffset = height - radius;

            double startX = xOffset + radius * (Math.cos(Math.toRadians(startAngle)));
            double startY = yOffset + radius * (Math.sin(Math.toRadians(startAngle)));

            double endX = xOffset + radius * (Math.cos(Math.toRadians(endAngle)));
            double endY = yOffset + radius * (Math.sin(Math.toRadians(endAngle)));

            Path2D path = new Path2D.Double();
            path.moveTo(xOffset, yOffset);
            path.lineTo(startX, startY);
            path.lineTo(endX, endY);
            path.closePath();

            return path;

        }

    }

    public static class TriangleButton extends JButton {

        final static double side_len = 52; //Change for variable triangle size
        final static double y_offset = (Math.sqrt(3) * side_len / 2);
        private Shape triangle;

        public TriangleButton(int spot) {
            triangle = createTriangle(spot);
        }

        @Override
        public void paintBorder(Graphics g) {
            super.paintBorder(g);
            ((Graphics2D) g).draw(triangle);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            ((Graphics2D) g).fill(triangle);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension((int) side_len, (int) y_offset);
        }

        @Override
        public boolean contains(int x, int y) {
            return triangle.contains(x, y);
        }

        private Shape createTriangle(int spot) {
            Polygon p = new Polygon();
            p.addPoint(0, 0);
            p.addPoint((int) side_len, 0);
            p.addPoint((int) side_len / 2, (int) (y_offset));
            return p;
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • This is incredible work, my thanks to you and everyone else. – user1091684 Aug 11 '15 at 22:00
  • As a follow up, is it possible to have two hexagon buttons next to each other such that the sides are touching? Simply using add() on another button would obviously put them next to each other but connected by points. Simply knowing how to move components in a JPanel is all I'd need (haven't found anything yet) – user1091684 Aug 11 '15 at 22:14
  • 1
    Yes, that would be simpler the trying to layout individual triangles. FlowLayout can do it simply, but GridBagLayout would give you more flexibly – MadProgrammer Aug 11 '15 at 22:17
2

Using your class I made some changes and came up with the following:

import java.awt.*;
import java.awt.Shape;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.*;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class TriangleButton2 extends JButton {
    final static double side_len = 52; //Change for variable triangle size
    final static double y_offset = (Math.sqrt(3) * side_len / 2);
    private Shape triangle;

    public TriangleButton2(int degrees){
        triangle = createTriangle(degrees);
        setRolloverEnabled( false );
        setContentAreaFilled( false );
        setBorderPainted( false );
    }

    public void paintBorder( Graphics g ) {
        ((Graphics2D)g).draw(triangle);
    }
    public void paintComponent( Graphics g ) {
        super.paintComponent(g);
        ((Graphics2D)g).fill(triangle);
    }
    public Dimension getPreferredSize() {
        return new Dimension((int)side_len, (int)y_offset);
    }
    public boolean contains(int x, int y) {
        return triangle.contains(x, y);
    }

    private Shape createTriangle(int degrees) {
        Polygon p = new Polygon();
        p.addPoint( 0   , 0 );
        p.addPoint( (int)side_len , 0   );
        p.addPoint( (int)side_len/2, (int)(y_offset)  );

        return ShapeUtils.rotate(p, degrees);

//        return p;
    }

    private static void createAndShowGUI()
    {
        JPanel panelNorth = new JPanel( new FlowLayout(FlowLayout.CENTER, -22, 2) );
        panelNorth.add( new TriangleButton2(180) );
        panelNorth.add( new TriangleButton2(0) );
        panelNorth.add( new TriangleButton2(180) );

        JPanel panelSouth = new JPanel( new FlowLayout(FlowLayout.CENTER, -22, 1) );
        panelSouth.add( new TriangleButton2(0) );
        panelSouth.add( new TriangleButton2(180) );
        panelSouth.add( new TriangleButton2(0) );

        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panelNorth, BorderLayout.NORTH);
        frame.add(panelSouth, BorderLayout.SOUTH);
        frame.setLocationByPlatform( true );
        frame.pack();
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

The above code uses the ShapeUtils class found in Playing With Shapes.

Not sure of the exact functionality you want from the button. Your current implantation doesn't have any visual effects when you click on the button or mouse over the button.

In this case you might want to consider just creating an Icon to represent your triangle. you can use the ShapeIcon class found in Playing With Shapes to create your triangle icons. Then you can use the ShapeComponent class also found in Playing With Shapes to create an actual component that you add to the panel.

The FlowLayout shows how you can overlap the buttons to get your desired layout effect.

camickr
  • 321,443
  • 19
  • 166
  • 288
1

Check out this page for some relatable information: Creating custom JButton from images containing transparent pixels

It just might be easier creating JButtons from triangle images.

Community
  • 1
  • 1
Sajay Shah
  • 21
  • 1
  • 7