CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 13 of 13
  1. #1
    Join Date
    Apr 2008
    Posts
    40

    Hexagonal Buttons

    Hi

    Im just starting a new project where i need a GUI component composed of interleaved hexagonal buttons (like in a beeswax pattern). Im unsure about how to implement them, and wondered if I could get some input. Here are some thoughts I've had about how to do it:


    Implementing a hexagonal JButton.

    This solution would give me all the "Button" functionality i need, but Im unsure about how practical the graphical portion of it would be. Since the buttons need to be interleaved, im worried about overlapping and such. But I guess that could be managed with a (overly complicated) GridbagLayout. Still, I think it would be a hassle to make it look good.


    Drawing a hexagonal pattern to a panel and then add button-like actions to portions of it.

    This solution would look nice, since I can draw the "buttons" as I wish. The drawback is that it would be a lot of work defining the portions of background that belong to each "button". It would also be a lot of work to change these portions. Say that i wanted a portion to change color. Its doable, but complicated (as opposed to a JButton). And generally, this solution is very hard to change once it is implemented..



    So thats my thoughts. Any input, especialy better alternatives would be greatly appreciated!

  2. #2
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: Hexagonal Buttons

    IMHO implementing your own hexagonal button is the correct approach. You can extend JButton and override methods to paint the button, provide hit detection etc. As for laying out the buttons you will need to write a specific layout manager to handle the overlapping either that or use absolute positioning. Of course, writing your own layout manager is a much better solution than using absolute positioning and it's not too difficult to do. I would recommend looking at the source code for some of the existing layout managers before you start.

  3. #3
    dlorde is offline Elite Member Power Poster
    Join Date
    Aug 1999
    Location
    UK
    Posts
    10,163

    Re: Hexagonal Buttons

    For writing a custom layout manager, have a browse of this.

    Imagination is more important than knowledge...
    A. Einstein
    Please use [CODE]...your code here...[/CODE] tags when posting code. If you get an error, please post the full error message and stack trace, if present.

  4. #4
    Join Date
    Apr 2008
    Posts
    40

    Re: Hexagonal Buttons

    Ok, Thanks!

    Ill give the custom LayoutManager a try after checking that there is none that meet my requirements.

  5. #5
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: Hexagonal Buttons

    Ok, this problem interested me so I've had a play and knocked up this very rough demo showing what you need to do. Please note this is not fully or even partially featured, it is not at all efficient and has not been tested. But it does show you the sort of things you need to do.

    The hexagonal button class

    Code:
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.FontMetrics;
    import java.awt.Graphics;
    import java.awt.Point;
    import java.awt.Polygon;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    import javax.swing.Action;
    import javax.swing.Icon;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JToggleButton;
    import javax.swing.SwingUtilities;
    
    /**
     * A six sided toggle button. This is not guaranteed to be a perfect hexagon, it is just guaranteed to have six sides in
     * the form of a hexagon. To be a perfect hexagon the size of this component must have a height to width ratio of
     * 1 to 0.866
     *
     * @author keang
     * @date 5 Jun 2009
     *
     */
    public class HexButton extends JToggleButton
    {
    private static final long serialVersionUID = 4865976127980106774L;
    
    private Polygon hexagon = new Polygon();
    
    
    /**
     * @param arg0
     */
    public HexButton(String arg0)
        {
        super(arg0);
        }
    
    @Override
    public boolean contains(Point p)
        {
        return hexagon.contains(p);
        }
    
    @Override
    public boolean contains(int x, int y)
        {
        return hexBoundary.contains(x, y);
        }
    
    @Override
    public void setSize(Dimension d)
        {
        super.setSize(d);
        calculateCoords();
        }
    
    @Override
    public void setSize(int w, int h)
        {
        super.setSize(w, h);
        calculateCoords();
        }
    
    @Override
    public void setBounds(int x, int y, int width, int height)
        {
        super.setBounds(x, y, width, height);
        calculateCoords();
        }
    
    @Override
    public void setBounds(Rectangle r)
        {
        super.setBounds(r);
        calculateCoords();
        }
    
    @Override
    protected void processMouseEvent(MouseEvent e)
        {
        if ( contains(e.getPoint()) )
            super.processMouseEvent(e);
        }
    
    private void calculateCoords()
        {
        int w = getWidth()-1;
        int h = getHeight()-1;
    
        int ratio = (int)(h*.25);
        int nPoints = 6;
        int[] hexX = new int[nPoints];
        int[] hexY = new int[nPoints];
    
        hexX[0] = w/2;
        hexY[0] = 0;
        hexX[1] = w;
        hexY[1] = ratio;
        hexX[2] = w;
        hexY[2] = h - ratio;
        hexX[3] = w/2;
        hexY[3] = h;
        hexX[4] = 0;
        hexY[4] = h - ratio;
        hexX[5] = 0;
        hexY[5] = ratio;
    
        hexagon = new Polygon(hexX, hexY, nPoints);
        }
    
    @Override
    protected void paintComponent(Graphics g)
        {
        if ( isSelected() )
            {
            g.setColor(Color.lightGray);
            }
        else
            {
            g.setColor(getBackground());
            }
    
        g.fillPolygon(hexagon);
    
        g.setColor(getForeground());
        g.drawPolygon(hexagon);
    
        FontMetrics fm = getFontMetrics(getFont());
        Rectangle viewR = getBounds();
        Rectangle iconR = new Rectangle();
        Rectangle textR = new Rectangle();
    
    
        SwingUtilities.layoutCompoundLabel(this, fm, getText(), null,
                SwingUtilities.CENTER, SwingUtilities.CENTER, SwingUtilities.BOTTOM, SwingUtilities.CENTER,
                viewR, iconR, textR, 0);
    
        Point loc = getLocation();
        g.drawString(getText(), textR.x-loc.x, textR.y-loc.y+fm.getAscent());
        }
    
    @Override
    protected void paintBorder(Graphics g)
        {
        // do not paint a border
        }
    
    /**
     * Application Entry Point
     */
    public static void main(String[] args)
        {
        JFrame frame = new JFrame("Hex Button Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        JPanel p = new JPanel();
        p.setLayout(new HexLayout(4, 0, 4));
    
        for ( int i = 0; i < 24; i++ )
            {
            HexButton b = new HexButton("B"+(i+1));
            b.setForeground(Color.black);
            b.setBackground(Color.green);
    
            p.add(b);
            }
    
        frame.add(p);
        frame.pack();
        frame.setSize(700, 400);
        frame.setVisible(true);
    
        }
    
    }
    The hexagonal layout manager class

    Code:
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.Insets;
    import java.awt.LayoutManager;
    
    /**
     * This layout manager is based on java.awt.GridLayout
     *
     * The <code>GridLayout</code> class is a layout manager that
     * lays out a container's components in a hexagonal grid.
     * The container is divided into equal-sized hexagons,
     * and one component is placed in each hexagon.
     *
     *
     *
     *
     * @author keang
     * @date 5 Jun 2009
     *
     */
    public class HexLayout implements LayoutManager, java.io.Serializable
    {
    private static final long serialVersionUID = -858342723067286796L;
    
    /**
     * This is the gap (in pixels) which specifies the space
     * between components.  They can be changed at any time.
     * This should be a non-negative integer.
     *
     * @serial
     * @see #getHgap()
     * @see #setHgap(int)
     */
    int cgap;
    /**
     * This is the number of rows specified for the grid.  The number
     * of rows can be changed at any time.
     * This should be a non negative integer, where '0' means
     * 'any number' meaning that the number of Rows in that
     * dimension depends on the other dimension.
     *
     * @serial
     * @see #getRows()
     * @see #setRows(int)
     */
    int rows;
    /**
     * This is the number of columns specified for the grid.  The number
     * of columns can be changed at any time.
     * This should be a non negative integer, where '0' means
     * 'any number' meaning that the number of Columns in that
     * dimension depends on the other dimension.
     *
     * @serial
     * @see #getColumns()
     * @see #setColumns(int)
     */
    int cols;
    
    /**
     * Creates a grid layout with a default of one column per component,
     * in a single row.
     * @since JDK1.1
     */
    public HexLayout() {
    this(1, 0, 0);
    }
    
    /**
     * Creates a grid layout with the specified number of rows and
     * columns. All components in the layout are given equal size.
     * <p>
     * One, but not both, of <code>rows</code> and <code>cols</code> can
     * be zero, which means that any number of objects can be placed in a
     * row or in a column.
     * @param     r   the rows, with the value zero meaning
     *                   any number of rows.
     * @param     c   the columns, with the value zero meaning
     *                   any number of columns.
     */
    public HexLayout(int r, int c) {
    this(r, c, 0);
    }
    
    /**
     * Creates a grid layout with the specified number of rows and
     * columns. All components in the layout are given equal size.
     * <p>
     * In addition, the gap between components is set to the
     * specified value.
     * <p>
     * One, but not both, of <code>rows</code> and <code>cols</code> can
     * be zero, which means that any number of objects can be placed in a
     * row or in a column.
     * <p>
     * All <code>GridLayout</code> constructors defer to this one.
     * @param     r   the rows, with the value zero meaning
     *                   any number of rows
     * @param     c   the columns, with the value zero meaning
     *                   any number of columns
     * @param     hgap   the gap around the component
     * @exception   IllegalArgumentException  if the value of both
     *          <code>rows</code> and <code>cols</code> is
     *          set to zero
     */
    public HexLayout(int r, int c, int hgap) {
    if ((r == 0) && (c == 0)) {
        throw new IllegalArgumentException("rows and cols cannot both be zero");
    }
    this.rows = r;
    this.cols = c;
    this.cgap = hgap;
    }
    
    /**
     * Gets the number of rows in this layout.
     * @return    the number of rows in this layout
     */
    public int getRows() {
    return rows;
    }
    
    /**
     * Sets the number of rows in this layout to the specified value.
     * @param        r   the number of rows in this layout
     * @exception    IllegalArgumentException  if the value of both
     *               <code>rows</code> and <code>cols</code> is set to zero
     */
    public void setRows(int r) {
    if ((r == 0) && (this.cols == 0)) {
        throw new IllegalArgumentException("rows and cols cannot both be zero");
    }
    this.rows = r;
    }
    
    /**
     * Gets the number of columns in this layout.
     * @return     the number of columns in this layout
     */
    public int getColumns() {
    return cols;
    }
    
    /**
     * Sets the number of columns in this layout to the specified value.
     * Setting the number of columns has no affect on the layout
     * if the number of rows specified by a constructor or by
     * the <tt>setRows</tt> method is non-zero. In that case, the number
     * of columns displayed in the layout is determined by the total
     * number of components and the number of rows specified.
     * @param        c   the number of columns in this layout
     * @exception    IllegalArgumentException  if the value of both
     *               <code>rows</code> and <code>cols</code> is set to zero
     */
    public void setColumns(int c) {
    if ((c == 0) && (this.rows == 0)) {
        throw new IllegalArgumentException("rows and cols cannot both be zero");
    }
    this.cols = c;
    }
    
    /**
     * Gets the gap between components.
     * @return       the gap between components
     */
    public int getGap() {
    return cgap;
    }
    
    /**
     * Sets the gap between components to the specified value.
     * @param         gap  the gap between components
     */
    public void setGap(int gap) {
    this.cgap = gap;
    }
    
    /**
     * Adds the specified component with the specified name to the layout.
     * @param name the name of the component
     * @param comp the component to be added
     */
    public void addLayoutComponent(String name, Component comp)
        {
        // do nothing
        }
    
    /**
     * Removes the specified component from the layout.
     * @param comp the component to be removed
     */
    public void removeLayoutComponent(Component comp)
        {
        // do nothing
        }
    
    /**
     * Determines the preferred size of the container argument using
     * this grid layout.
     * <p>
     * The preferred width of a grid layout is the largest preferred width
     * of all of the components in the container times the number of columns,
     * plus the horizontal padding times the number of columns minus one,
     * plus the left and right insets of the target container, plus the width
     * of half a component if there is more than one row.
     * <p>
     * The preferred height of a grid layout is the largest preferred height
     * of all of the components in the container plus three quarters of the
     * largest minimum height of all of the components in the container times
     * the number of rows greater than one,
     * plus the vertical padding times the number of rows minus one, plus
     * the top and bottom insets of the target container.
     *
     * @param     parent   the container in which to do the layout
     * @return    the preferred dimensions to lay out the
     *                      subcomponents of the specified container
     * @see       java.awt.GridLayout#minimumLayoutSize
     * @see       java.awt.Container#getPreferredSize()
     */
    public Dimension preferredLayoutSize(Container parent)
        {
        synchronized ( parent.getTreeLock() )
            {
            Insets insets = parent.getInsets();
            int ncomponents = parent.getComponentCount();
            int nrows = rows;
            int ncols = cols;
    
            if ( nrows > 0 )
                {
                ncols = (ncomponents + nrows - 1) / nrows;
                }
            else
                {
                nrows = (ncomponents + ncols - 1) / ncols;
                }
    
            int w = 0;
            int h = 0;
            for ( int i = 0; i < ncomponents; i++ )
                {
                Component comp = parent.getComponent(i);
                Dimension d = comp.getPreferredSize();
                if ( w < d.width )
                    {
                    w = d.width;
                    }
                if ( h < d.height )
                    {
                    h = d.height;
                    }
                }
    
            int dx = insets.left + insets.right + ncols * w + (ncols - 1) * cgap;
            int dy = insets.top + insets.bottom + nrows * h + (nrows - 1) * cgap;
    
            if ( nrows > 1 )
                {
                dx = dx + (int)(w * 0.5f);
    
                dy /= nrows;
                dy = dy + (int)(dy * (nrows - 1) * 0.75f);
                }
    
            return new Dimension(dx, dy);
            }
        }
    
    /**
     * Determines the minimum size of the container argument using this
     * grid layout.
     * <p>
     * The minimum width of a grid layout is the largest minimum width
     * of all of the components in the container times the number of columns,
     * plus the horizontal padding times the number of columns minus one,
     * plus the left and right insets of the target container, plus the width
     * of half a component if there is more than one row.
     * <p>
     * The minimum height of a grid layout is the largest minimum height
     * of all of the components in the container plus three quarters of the
     * largest minimum height of all of the components in the container times
     * the number of rows greater than one,
     * plus the vertical padding times the number of rows minus one, plus
     * the top and bottom insets of the target container.
     *
     * @param       parent   the container in which to do the layout
     * @return      the minimum dimensions needed to lay out the
     *                      subcomponents of the specified container
     * @see         java.awt.GridLayout#preferredLayoutSize
     * @see         java.awt.Container#doLayout
     */
    public Dimension minimumLayoutSize(Container parent)
        {
        synchronized ( parent.getTreeLock() )
            {
            Insets insets = parent.getInsets();
            int ncomponents = parent.getComponentCount();
            int nrows = rows;
            int ncols = cols;
    
            if ( nrows > 0 )
                {
                ncols = (ncomponents + nrows - 1) / nrows;
                }
            else
                {
                nrows = (ncomponents + ncols - 1) / ncols;
                }
            int w = 0;
            int h = 0;
            for ( int i = 0; i < ncomponents; i++ )
                {
                Component comp = parent.getComponent(i);
                Dimension d = comp.getMinimumSize();
                if ( w < d.width )
                    {
                    w = d.width;
                    }
                if ( h < d.height )
                    {
                    h = d.height;
                    }
                }
    
            int dx = insets.left + insets.right + ncols * w + (ncols - 1) * cgap;
            int dy = insets.top + insets.bottom + nrows * h + (nrows - 1) * cgap;
    
            if ( nrows > 1 )
                {
                dx = dx + (int)(w * 0.5f);
    
                dy /= nrows;
                dy = dy + (int)(dy * (nrows - 1) * 0.75f);
                }
    
            return new Dimension(dx, dy);
            }
        }
    
    /**
     * Lays out the specified container using this layout.
     * <p>
     * This method reshapes the components in the specified target
     * container in order to satisfy the constraints of the
     * <code>GridLayout</code> object.
     * <p>
     * The grid layout manager determines the size of individual
     * components by dividing the free space in the container into
     * equal-sized portions according to the number of rows and columns
     * in the layout. The container's free space equals the container's
     * size minus any insets and any specified horizontal or vertical
     * gap. All components in a grid layout are given the same size.
     *
     * @param      parent   the container in which to do the layout
     * @see        java.awt.Container
     * @see        java.awt.Container#doLayout
     */
    public void layoutContainer(Container parent)
        {
        synchronized (parent.getTreeLock())
            {
            Insets insets = parent.getInsets();
            int ncomponents = parent.getComponentCount();
            int nrows = rows;
            int ncols = cols;
    
            if ( ncomponents == 0 )
                {
                return;
                }
            if ( nrows > 0 )
                {
                ncols = (ncomponents + nrows - 1) / nrows;
                }
            else
                {
                nrows = (ncomponents + ncols - 1) / ncols;
                }
    
            int w = parent.getWidth() - (insets.left + insets.right);
            int h = parent.getHeight() - (insets.top + insets.bottom);
    
            w = (int)((w - (ncols - 1) * cgap) / (ncols + (nrows>1?0.5f:0.0f)));
    
            float effectiveRows = 1 + ((nrows - 1) * 0.75f);
            h = (int)((h - (nrows - 1) * cgap) / effectiveRows);
    
            int xoffset = (w+cgap)/2;
            int yoffset = (int)(h * 0.75f);
            boolean staggeredRow = false;
            for ( int r = 0, y = insets.top; r < nrows; r++, y += yoffset + cgap )
                {
                int offset = 0;
    
                if ( staggeredRow )
                    offset = xoffset;
    
                for ( int c = 0, x = insets.left; c < ncols; c++, x += w + cgap )
                    {
                    int i = r * ncols + c;
    
                    if ( i < ncomponents )
                        {
                        parent.getComponent(i).setBounds(x+offset, y, w, h);
                        }
    
                    }
    
                staggeredRow = !staggeredRow;
                }
            }
    }
    
    /**
     * Returns the string representation of this grid layout's values.
     * @return     a string representation of this grid layout
     */
    public String toString() {
    return getClass().getName() + "[gap=" + cgap +
                           ",rows=" + rows + ",cols=" + cols + "]";
    }
    
    }
    If you run the HexButton class it will display a window with a number of hexagonal buttons. The HexButton class extends JToggleButton so you can select and deselect individual buttons to prove it works properly.

    To modify the demo to display different amounts of buttons, rows, columns and spaces between buttons edit the main method of the HexButton class

    If you want to force the buttons to be proper hexagons then you need to add some code to the layout managers layoutContainer method to fix the ratio between the buttons height and width to 1 : 0.866
    Last edited by keang; June 6th, 2009 at 08:34 AM. Reason: Added overloaded methods to HexButton for contains(int, int) and setBounds(Rectangle)

  6. #6
    dlorde is offline Elite Member Power Poster
    Join Date
    Aug 1999
    Location
    UK
    Posts
    10,163

    Re: Hexagonal Buttons

    Coo, nice work

    Simplicity is the ultimate sophistication...
    L. Da Vinci
    Please use &#91;CODE]...your code here...&#91;/CODE] tags when posting code. If you get an error, please post the full error message and stack trace, if present.

  7. #7
    Join Date
    Apr 2008
    Posts
    40

    Re: Hexagonal Buttons

    Wow, thanks!

    This will be very helpfull when Im making my program! Way more help than I anticipated :P

    Ive just compiled your code, looks good. I'll read through it and make my own version soon. If it looks nice when Im finished I might post it here as well.

    Thanks again!

  8. #8
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: Hexagonal Buttons

    Wow, thanks!
    No problem.
    If it looks nice when Im finished I might post it here as well.
    Please do, it would be nice to see a fully featured version.

  9. #9
    Join Date
    Apr 2008
    Posts
    40

    Re: Hexagonal Buttons

    I finally finished my layout and button inbetween exams and work, so if you're still interested here is the first version:


    Hexagonal Button

    Code:
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Point;
    import java.awt.Polygon;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    
    import javax.swing.JButton;
    
    /**
     * Simple hexagonal button class.
     *
     * @author Kristian Johansen
     *
     */
    public class HexagonalButton extends JButton {
    
        /**
         * autoGenerated serial-version.
         */
        private static final long serialVersionUID = -7142502695252118612L;
        Polygon hexagonalShape;
    
    
        public HexagonalButton() {
    	this.setOpaque(false);
    	hexagonalShape = getHexPolygon();
        }
    
        /**
         * Generates the buttons Hexagonal shape
         *
         * @return Polygon with the buttons hexagonal shape.
         */
        private Polygon getHexPolygon() {
    	Polygon hex = new Polygon();
    	int w = getWidth() - 1;
    	int h = getHeight() - 1;
    	int ratio = (int) (h * .25);
    
    	hex.addPoint(w / 2, 0);
    	hex.addPoint(w, ratio);
    	hex.addPoint(w, h - ratio);
    	hex.addPoint(w / 2, h);
    	hex.addPoint(0, h - ratio);
    	hex.addPoint(0, ratio);
    
    	return hex;
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.Component#contains(java.awt.Point)
         */
        @Override
        public boolean contains(Point p) {
    	return hexagonalShape.contains(p);
        }
    
        /*
         * (non-Javadoc)
         * @see javax.swing.JComponent#contains(int, int)
         */
        @Override
        public boolean contains(int x, int y) {
    	return hexagonalShape.contains(x, y);
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.Component#setSize(java.awt.Dimension)
         */
        @Override
        public void setSize(Dimension d) {
    	super.setSize(d);
    	hexagonalShape = getHexPolygon();
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.Component#setSize(int, int)
         */
        @Override
        public void setSize(int w, int h) {
    	super.setSize(w, h);
    	hexagonalShape = getHexPolygon();
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.Component#setBounds(int, int, int, int)
         */
        @Override
        public void setBounds(int x, int y, int width, int height) {
    	super.setBounds(x, y, width, height);
    	hexagonalShape = getHexPolygon();
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.Component#setBounds(java.awt.Rectangle)
         */
        @Override
        public void setBounds(Rectangle r) {
    	super.setBounds(r);
    	hexagonalShape = getHexPolygon();
        }
    
        /*
         * (non-Javadoc)
         * @see javax.swing.JComponent#processMouseEvent(java.awt.event.MouseEvent)
         */
        @Override
        protected void processMouseEvent(MouseEvent e) {
    	if (contains(e.getPoint()))
    	    super.processMouseEvent(e);
        }
    
        /*
         * (non-Javadoc)
         * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
         */
        @Override
        protected void paintComponent(Graphics g) {
    	g.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
    	g.fillRect(0, 0, getWidth(), getHeight());
    	g.setColor(getBackground());
    	g.drawPolygon(hexagonalShape);
    	g.fillPolygon(hexagonalShape);
    
    
        }
    
        /*
         * (non-Javadoc)
         * @see javax.swing.AbstractButton#paintBorder(java.awt.Graphics)
         */
        @Override
        protected void paintBorder(Graphics g) {
    	// Does not print border
        }
    
    }

    Hexagonal Layout class

    Code:
    
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.Insets;
    import java.awt.LayoutManager;
    
    /**
     * A layoutmanager for interleaved hexagons. This layout is based on GridLayout. It
     * divides the parent component into equal parts and resizes the subcomponents to fit
     * these. Since every other row will hold one element less than than the one before
     * (or after), this LM only register how many columns the user wants, and then calculates
     * the number of rows needed to fit this need according to the number of gui elements
     * that needs to be allocated. The number of columns given by the user is the number of
     * elements in a big row:
     *
     * Example:
     *
     * *** *** *** *** *** ***    big row
     *   *** *** *** *** ***      small row
     * *** *** *** *** *** ***    big row
     *   *** *** *** *** ***      etc.
     * *** *** *** *** *** ***
     *
     * The user can also specify wether or not to begin the layout with a small row, and can
     * specify the insets between gui elements via a Insets object.
     *
     * @author Kristian Johansen
     *
     */
    public class HexagonalLayout implements LayoutManager {
    
        private Insets insets;
        private int rows;
        private int cols;
        private boolean beginWithSmallRow;
    
        private Dimension minSize;
        private Dimension prefSize;
    
    
        /**
         * Generates a HexagonalLayout with the number of columns given. The layout
         * divides the componenets into equal portions of the parent container. The
         * rows are arranged in big rows and small rows (every other). The layout
         * calculates how many rows it need to hold the number of items the parent has.
         * The number of columns represent the number of items in a long row.
         * @param cols Number of items in a big row
         */
        public HexagonalLayout(int cols) {
    	checkColInput(cols);
    	minSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	prefSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	insets = new Insets(0, 0, 0, 0);
    	this.rows = 0;
    	this.cols = cols;
    	beginWithSmallRow = false;
        }
    
        /**
         * Generates a HexagonalLayout with the number of columns given. The layout
         * divides the componenets into equal portions of the parent container. The
         * rows are arranged in big rows and small rows (every other). The layout
         * calculates how many rows it need to hold the number of items the parent has.
         * The number of columns represent the number of items in a big row.
         *
         * This constructor has a flag for wether or not to begin with a small row
         *
         * @param cols Number of items in a big row.
         * @param beginWithSmallRow Wether or not to begin with a small row.
         */
        public HexagonalLayout(int cols, boolean beginWithSmallRow) {
    	checkColInput(cols);
    	minSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	prefSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	insets = new Insets(0, 0, 0, 0);
    	this.rows = 0;
    	this.cols = cols;
    	this.beginWithSmallRow = beginWithSmallRow;
        }
    
        /**
         * Generates a HexagonalLayout with the number of columns given. The layout
         * divides the componenets into equal portions of the parent container. The
         * rows are arranged in big rows and small rows (every other). The layout
         * calculates how many rows it need to hold the number of items the parent has.
         * The number of columns represent the number of items in a big row.
         *
         * This constructor also takes an Insets object that specify insets between
         * elements in the gui.
         *
         * @param cols Number of coulumns in a big row.
         * @param i Insets object that specifies the spacing between gui elements.
         */
        public HexagonalLayout(int cols, Insets i) {
    	checkColInput(cols);
    	insets = i;
    	minSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	prefSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	this.rows = 0;
    	this.cols = cols;
    	beginWithSmallRow = false;
        }
    
        /**
         * Generates a HexagonalLayout with the number of columns given. The layout
         * divides the componenets into equal portions of the parent container. The
         * rows are arranged in big rows and small rows (every other). The layout
         * calculates how many rows it need to hold the number of items the parent has.
         * The number of columns represent the number of items in a big row.
         *
         * This constructor has a flag for wether or not to start with a small row.
         *
         * This constructor also takes an Insets object that specify insets between
         * elements in the gui.
         *
         * @param cols Number of columns in a big row.
         * @param i Insets object that specify the spacing between gui elements
         * @param beginWithSmallRow Flag for wether or not to begin with a small row.
         */
        public HexagonalLayout(int cols, Insets i, boolean beginWithSmallRow) {
    	checkColInput(cols);
    	insets = i;
    	minSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	prefSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
    	this.rows = 0;
    	this.cols = cols;
    	this.beginWithSmallRow = beginWithSmallRow;
        }
    
        /**
         * Checks that the column input is valid: Columns must be set to n > 0;
         * @param cols
         */
        private void checkColInput(int cols) {
    	if (cols <= 0) {
    	    throw new IllegalArgumentException("Columns can't be set to n < 0");
    	}
        }
    
        /**
         * Calculates the numbers of rows needed for the components given the
         * number of columns given.
         * @param componentCount
         * @return
         */
        private int calculateRows(int componentCount) {
    
    	boolean smallRow = beginWithSmallRow;
    
    	int numberOfRows = 0;
    	int bgRow = cols;
    	int smRow = bgRow - 1;
    
    	int placedItems = 0;
    	if (smallRow) {
    	    while (true) {
    		if (placedItems >= componentCount) {
    		    break;
    		}
    		placedItems += smRow;
    		numberOfRows += 1;
    		if (placedItems >= componentCount) {
    		    break;
    		}
    		placedItems += bgRow;
    		numberOfRows += 1;
    	    }
    	} else {
    	    while (true) {
    		if (placedItems >= componentCount) {
    		    break;
    		}
    		placedItems += bgRow;
    		numberOfRows += 1;
    		if (placedItems >= componentCount) {
    		    break;
    		}
    		placedItems += smRow;
    		numberOfRows += 1;
    	    }
    
    	}
    	System.out.println(numberOfRows);
    	return numberOfRows;
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
         */
        @Override
        public void layoutContainer(Container parent) {
    
    	// Get componentCount and check that it is not 0
    	int componentCount = parent.getComponentCount();
    	if (componentCount == 0) {
    	    return;
    	}
    
    	// This indicates wither or not to begin with a small row
    	boolean smallRow = beginWithSmallRow;
    
    	// Calculating the number of rows needed
    	rows = calculateRows(componentCount);
    
    	// insets
    	int leftOffset = insets.left;
    	int rightOffset = insets.right;
    	int topOffset = insets.top;
    	int bottomOffset = insets.bottom;
    
    	// spacing dimensions
    	int boxWidth = parent.getWidth() / cols;
    	int boxHeight = (int) Math
    		.round((parent.getHeight() / (1 + (0.75 * (rows - 1)))));
    	double heightRatio = 0.75;
    
    	// component dimensions
    	int cWidth = (boxWidth - (leftOffset + rightOffset));
    	int cHeight = (boxHeight - (topOffset + bottomOffset));
    
    
    	int x;
    	int y;
    	if(smallRow){
    	    x = (int)Math.round((boxWidth / 2.0));
    	}else{
    	    x = 0;
    	}
    	y = 0;
    
    	// Laying out each of the components in the container
    	for (Component c : parent.getComponents()) {
    	    if (x > parent.getWidth() - boxWidth) {
    		smallRow = !smallRow;
    		if (smallRow) {
    		    x = (int)Math.round(boxWidth / 2.0);
    		    y += Math.round(boxHeight * heightRatio);
    		} else {
    		    x = 0;
    		    y += Math.round(boxHeight * heightRatio);
    		}
    
    	    }
    	    c.setBounds(x + leftOffset, y + topOffset, cWidth, cHeight);
    	    x += boxWidth;
    	}
    
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
         */
        @Override
        public Dimension minimumLayoutSize(Container parent) {
    	return minSize;
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
         */
        @Override
        public Dimension preferredLayoutSize(Container parent) {
    	return prefSize;
        }
    
        /*
         * (non-Javadoc)
         * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
         */
        @Override
        public void removeLayoutComponent(Component comp) {
    	// NOT IMPLEMENTED
    
        }
        /*
         * (non-Javadoc)
         * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String, java.awt.Component)
         */
        @Override
        public void addLayoutComponent(String name, Component comp) {
    	// NOT IMPLEMENTED
    
        }
    
        public Insets getInsets() {
            return insets;
        }
    
        public void setInsets(Insets insets) {
            this.insets = insets;
        }
    
        public int getColumns() {
            return cols;
        }
    
        public void setColumns(int cols) {
            this.cols = cols;
        }
    
        public boolean isBeginWithSmallRow() {
            return beginWithSmallRow;
        }
    
        public void setBeginWithSmallRow(boolean beginWithSmallRow) {
            this.beginWithSmallRow = beginWithSmallRow;
        }
    
        public Dimension getMinimumSize() {
            return minSize;
        }
    
        public void setMinimumSize(Dimension minSize) {
            this.minSize = minSize;
        }
    
        public Dimension getPreferredSize() {
            return prefSize;
        }
    
        public void setPrefferedSize(Dimension prefSize) {
            this.prefSize = prefSize;
        }
    
        public int getRows() {
            return rows;
        }
    
    }

    Simple test class:

    Code:
    
    import java.awt.Color;
    import java.awt.Insets;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    
    /**
     * Simple test class for HexagonalLayout.
     *
     * @author Kristian Johansen
     *
     */
    public class HexTest {
    
        public static void main(String[] args) {
    	JFrame f = new JFrame();
    	f.setExtendedState(JFrame.MAXIMIZED_BOTH);
    	f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    	//Change insets and columns here.
    	//The flag indicates wether or not to begin with a small row.
    	//
    	//Rows are calculated automaticaly, based on number of columns/elements
    	f.setLayout(new HexagonalLayout(6, new Insets(5, 5, 5, 5), false));
    
    	for (int i = 0; i < 44; i++) { // Change the number in the loop to get
    					// more/less buttons
    
    	    HexagonalButton b = new HexagonalButton();
    	    b.setBackground(Color.blue);
    
    	    //"Random" color actionlistener, just for fun.
    	    b.addActionListener(new ActionListener() {
    		@Override
    		public void actionPerformed(ActionEvent e) {
    		    JButton a = (JButton) e.getSource();
    		    a.setBackground(Color.getHSBColor(
    			    (float) Math.random() * 10000, (float) Math
    				    .random(), (float) Math.random()));
    		}
    
    	    });
    	    f.add(b);
    	}
    
    	f.setVisible(true);
    
        }
    }


    All in all it ended up pretty similar to the solution previously posted in this thread, but it suits my needs for now.

    Thanks again for the help

  10. #10
    dlorde is offline Elite Member Power Poster
    Join Date
    Aug 1999
    Location
    UK
    Posts
    10,163

    Re: Hexagonal Buttons

    Just a suggestion - it's a good idea to minimise duplicate code (the DRY or 'Don't Repeat Yourself' principle). The preferred idiom for multiple constructors like HexagonalLayout's is to chain them, like this:
    Code:
    public HexagonalLayout(int cols) {
        this(cols, false);
    }
    
    public HexagonalLayout(int cols, boolean beginWithSmallRow) {
        this(cols, new Insets(0, 0, 0, 0), beginWithSmallRow);
    }
    
    public HexagonalLayout(int cols, Insets i) {
        this(cols, i, false);
    }
    
    public HexagonalLayout(int cols, Insets i, boolean beginWithSmallRow) {
        checkColInput(cols);
        insets = i;
        minSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
        prefSize = new Dimension(800, 600); //Standard size. Can be changed with setter.
        this.rows = 0;
        this.cols = cols;
        this.beginWithSmallRow = beginWithSmallRow;
    }
    The key to performance is elegance, not batallions of special cases...
    J. Bently & D. McIlroy
    Please use &#91;CODE]...your code here...&#91;/CODE] tags when posting code. If you get an error, please post the full error message and stack trace, if present.

  11. #11
    Join Date
    Apr 2008
    Posts
    40

    Re: Hexagonal Buttons

    Yeah, that looks a lot better.

    Ill change it right away!

  12. #12
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: Hexagonal Buttons

    I was thinking more of something like the attached code. I can't attach a jar so I've renamed it as HexButtons.zip. If you don't want to compile the source, you can just download HexButtons.zip, change the extension to .jar and run it .

    This hasn't been fully debugged so please feel free to try and break it and let me know of any problems. Once it's stable I'll add it to the resources section.
    Last edited by keang; June 20th, 2009 at 05:46 AM.

  13. #13
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: Hexagonal Buttons

    Oops whilst viewing my User CP I appear to have deleted the attachments. Anyway here's the latest version. See previous post for details.
    Last edited by keang; March 12th, 2010 at 04:40 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured