javaswinghexagonal-tiles

I want to make a hexagonal table with JButtons in Java Swing


I want to make a table filled with hexagonal JButtons and preferably place it over a background image (e.g. Civilization games).

Is there any way to do it? I've tried and searched for many solutions with not much success, any help would be greatly appreciated!

Thank you in advance!


Solution

  • I'm always a bit wary about extending from something like JButton, it itself is a very complex UI component, which a lot of functionality which can come back and byte you.

    I might consider simply starting with JPanel and creating the functionality that's required, but in either case, it kind of comes down to basically the same core concept.

    You need to be able to determine the size of the button which will best fit the content and the border and this might take some tweaking to get right, then painting the desired shape and content and finally responding to appropriate events.

    The following is a VERY basic example, which is based on Too much space between custom Hexagonal JButtons in Swing. Remember, the component is still only rectangular, laying out the buttons in a "honeycomb" structure is a much more complex issue, especially as each button might be a different size. This would probably require a custom layout manager to achieve.

    Button

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagLayout;
    import java.awt.RenderingHints;
    import java.awt.geom.Path2D;
    import javax.swing.Action;
    import javax.swing.Icon;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    public class Main {
    
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            public TestPane() {
                HexagonButton btn = new HexagonButton("Hello");
                setLayout(new GridBagLayout());
                add(btn);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.dispose();
            }
    
        }
    
        public class HexagonButton extends JButton {
    
            private HexagonPath hexagonPath;
    
            public HexagonButton(String text) {
                super(text);
                applyDefaults();
            }
    
            public HexagonButton(Icon icon) {
                super(icon);
                applyDefaults();
            }
    
            public HexagonButton(String text, Icon icon) {
                super(text, icon);
                applyDefaults();
            }
    
            public HexagonButton(Action action) {
                super(action);
                applyDefaults();
            }
    
            @Override
            public void invalidate() {
                hexagonPath = null;
                super.invalidate();
            }
    
            protected int getMaxDimension() {
                Dimension size = super.getPreferredSize();
                return Math.max(size.width, size.height);
            }
    
            @Override
            public Dimension getPreferredSize() {
                int maxDimension = getMaxDimension();
                return new Dimension(maxDimension, maxDimension);
            }
    
            @Override
            public Dimension getMaximumSize() {
                return getPreferredSize();
            }
    
            @Override
            public Dimension getMinimumSize() {
                return getPreferredSize();
            }
    
            protected void applyDefaults() {
                setBorderPainted(false);
                setFocusPainted(false);
            }
    
            protected HexagonPath getHexagonPath() {
                if (hexagonPath == null) {
                    hexagonPath = new HexagonPath(getMaxDimension() - 1);
                }
                return hexagonPath;
            }
    
            @Override
            protected void paintBorder(Graphics g) {
                Graphics2D g2d = (Graphics2D) g.create();
                HexagonPath path = getHexagonPath();
                g2d.setColor(getForeground());
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
                g2d.draw(path);
                g2d.dispose();
            }
    
            @Override
            public Color getBackground() {
                if (getModel().isArmed()) {
                    return Color.BLUE;
                }
                return super.getBackground();
            }
    
            @Override
            public Color getForeground() {
                if (getModel().isArmed()) {
                    return Color.WHITE;
                }
                return super.getForeground();
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
                g2d.setColor(getBackground());
                g2d.fill(getHexagonPath());
                super.paintComponent(g2d);
                g2d.dispose();
            }
    
            protected class HexagonPath extends Path2D.Double {
    
                public HexagonPath(double size) {
                    double centerX = size / 2d;
                    double centerY = size / 2d;
                    for (double i = 0; i < 6; i++) {
                        double angleDegrees = (60d * i) - 30d;
                        double angleRad = ((float) Math.PI / 180.0f) * angleDegrees;
    
                        double x = centerX + ((size / 2f) * (double) Math.cos(angleRad));
                        double y = centerY + ((size / 2f) * (double) Math.sin(angleRad));
    
                        if (i == 0) {
                            moveTo(x, y);
                        } else {
                            lineTo(x, y);
                        }
                    }
                    closePath();
                }
    
            }
        }
    }