com.puzzlebazar.client.ui.SquareGridLayoutPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.puzzlebazar.client.ui.SquareGridLayoutPanel.java

Source

/**
 * Copyright 2010 Philippe Beaudoin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.puzzlebazar.client.ui;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.puzzlebazar.client.resources.Resources;
import com.puzzlebazar.shared.util.Has2DSize;
import com.puzzlebazar.shared.util.Vec2i;

/**
 * <b>Important!</b> This layout panel must be added inside a {@link com.google.gwt.user.client.ui.LayoutPanel}.
 * <p />
 * This widget represents a square grid and is often useful to draw puzzles that use such grids.
 * It holds the square grid and the desired border around it.
 *
 * @author Philippe Beaudoin
 */
public class SquareGridLayoutPanel extends AspectRatioLayoutPanel implements Has2DSize {

    private static class CellLayoutPanel extends OverflowLayoutPanel {

        /**
         * Adds a widget that should occupy the top-left vertex.
         *
         * @param widget The widget that should occupy the top-left vertex.
         * @param thickness Its thickness, in pixels.
         */
        private void addVertexWidget(Widget widget, int thickness) {
            add(widget);
            int halfThickness = thickness / 2;
            setWidgetLeftWidth(widget, -halfThickness, Unit.PX, thickness, Unit.PX);
            setWidgetTopHeight(widget, -halfThickness, Unit.PX, thickness, Unit.PX);
        }

        /**
         * Adds a widget that should occupy the left edge.
         *
         * @param widget The widget that should occupy the left edge.
         * @param thickness Its thickness, in pixels.
         */
        private void addLeftEdgeWidget(Widget widget, int thickness) {
            add(widget);
            int halfThickness = thickness / 2;
            int thicknessRemainder = thickness - halfThickness;
            setWidgetLeftWidth(widget, -halfThickness, Unit.PX, thickness, Unit.PX);
            setWidgetTopBottom(widget, -halfThickness, Unit.PX, -thicknessRemainder + 1, Unit.PX);
        }

        /**
         * Adds a widget that should occupy the top edge.
         *
         * @param widget The widget that should occupy the top edge.
         * @param thickness Its thickness, in pixels.
         */
        private void addTopEdgeWidget(Widget widget, int thickness) {
            add(widget);
            int halfThickness = thickness / 2;
            int thicknessRemainder = thickness - halfThickness;
            setWidgetTopHeight(widget, -halfThickness, Unit.PX, thickness, Unit.PX);
            setWidgetLeftRight(widget, -halfThickness, Unit.PX, -thicknessRemainder + 1, Unit.PX);
        }

        /**
         * Adds a widget that should occupy the cell.
         *
         * @param widget The widget that should occupy the cell.
         */
        private void addCellWidget(Widget widget) {
            add(widget);
        }
    }

    private final Resources resources;

    private int border;
    public int width;
    public int height;

    // This container holds the square grid elements themselves, such as
    // grid cells or grid edges.
    private OverflowLayoutPanel squareGridContainer;

    private CellLayoutPanel cells[][];

    @Inject
    public SquareGridLayoutPanel(Resources resources) {
        super();

        this.resources = resources;

        squareGridContainer = GWT.create(OverflowLayoutPanel.class);
        add(squareGridContainer);
        setBorder(border);
    }

    /**
     * Changes the desired border between the edges of the square grid
     * and that of its container. This border will allow the square grid
     * to draw elements slightly outside itself. It's useful for drawing
     * thicker outside lines, for example.
     *
     * @param border The desired border size, in pixels.
     */
    public void setBorder(int border) {
        // The 1 extra pixel on the right and bottom is so that elements positioned at 100%
        // within the puzzleContainer fall inside the mainContainer.
        setWidgetLeftRight(squareGridContainer, border, Unit.PX, border + 1, Unit.PX);
        setWidgetTopBottom(squareGridContainer, border, Unit.PX, border + 1, Unit.PX);
        super.setBorder(border);
    }

    /**
     * Sets the size of the square grid. All the grid content will be lost.
     *
     * @param width The desired number or grid cells along the horizontal direction.
     * @param height The desired number or grid cells along the vertical direction.
     */
    public void setSize(int width, int height) {
        this.width = width;
        this.height = height;
        setAspectRatio(width / (float) height);
        createCellPanels();
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    /**
     * Adds a the widget to draw at a specific vertex.
     * (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     *
     * @param loc The location of this vertex (a {@link Vec2i}).
     * @param widget The {@link Widget} to set.
     * @param thickness The desired thickness for this edge (in pixels).
     */
    public void addVertexWidget(Vec2i loc, Widget widget, int thickness) {
        assert 0 <= loc.x && loc.x <= width : "The x coordinate must be a valid vertex coordinate.";
        assert 0 <= loc.y && loc.y <= height : "The y coordinate must be a valid vertex coordinate.";
        cells[loc.x][loc.y].addVertexWidget(widget, thickness);
    }

    /**
     * Adds a the widget to draw at a specific vertical edge.
     *
     * @param loc The location of this vertical edge. A {@link Vec2i} where the
     *            x coordinate is a vertex coordinate and the y coordinate is a cell coordinate.
     *            (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     * @param widget The {@link Widget} to set.
     * @param thickness The desired thickness for this edge (in pixels).
     */
    public void addVerticalEdgeWidget(Vec2i loc, Widget widget, int thickness) {
        assert 0 <= loc.x && loc.x <= width : "The x coordinate must be a valid vertex coordinate.";
        assert 0 <= loc.y && loc.y < height : "The y coordinate must be a valid cell coordinate.";
        cells[loc.x][loc.y].addLeftEdgeWidget(widget, thickness);
    }

    /**
     * Adds a widget to draw at a specific horizontal edge.
     *
     * @param loc The location of this horizontal edge. A {@link Vec2i} where the
     *            x coordinate is a cell coordinate and the y coordinate is a vertex coordinate.
     *            (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     * @param widget The {@link Widget} to set.
     * @param thickness The desired thickness for this edge (in pixels).
     */
    public void addHorizontalEdgeWidget(Vec2i loc, Widget widget, int thickness) {
        assert 0 <= loc.x && loc.x < width : "The x coordinate must be a valid cell coordinate.";
        assert 0 <= loc.y && loc.y <= height : "The y coordinate must be a valid vertex coordinate.";
        cells[loc.x][loc.y].addTopEdgeWidget(widget, thickness);
    }

    /**
     * Adds a the widget to draw at a specific cell.
     * (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     *
     * @param loc The location of this cell (a {@link Vec2i}).
     * @param widget The {@link Widget} to set.
     */
    public void addCellWidget(Vec2i loc, Widget widget) {
        assert 0 <= loc.x && loc.x < width : "The x coordinate must be a valid cell coordinate.";
        assert 0 <= loc.y && loc.y < height : "The y coordinate must be a valid cell coordinate.";
        cells[loc.x][loc.y].addCellWidget(widget);
    }

    /**
     * Creates a standard vertex widget and adds it at a specific vertex.
     * (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     *
     * @param loc The location of this vertex (a {@link Vec2i}).
     * @param thickness The desired thickness for this edge (in pixels).
     * @param styleNames A list of string containing all the desired style names. (The default edge style is added automatically.)
     * @return The newly created widget.
     */
    public Widget createVertex(Vec2i loc, int thickness, String... styleNames) {
        Widget widget = GWT.create(FlowPanel.class);
        widget.addStyleName(resources.style().vertex());
        for (String style : styleNames) {
            widget.addStyleName(style);
        }
        addVertexWidget(loc, widget, thickness);
        return widget;
    }

    /**
     * Creates a standard edge widget and adds it to the square
     * at a specific vertical edge.
     *
     * @param loc The location of this horizontal edge. A {@link Vec2i} where the
     *            x coordinate is a cell coordinate and the y coordinate is a vertex coordinate.
     *            (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     * @param thickness The desired thickness for this edge (in pixels).
     * @param styleNames A list of string containing all the desired style names. (The default edge style is added automatically.)
     * @return The newly created widget.
     */
    public Widget createHorizontalEdge(Vec2i loc, int thickness, String... styleNames) {
        Widget widget = GWT.create(FlowPanel.class);
        widget.addStyleName(resources.style().edge());
        for (String style : styleNames) {
            widget.addStyleName(style);
        }
        addHorizontalEdgeWidget(loc, widget, thickness);
        return widget;
    }

    /**
     * Creates a standard edge widget and adds it to the square
     * at a specific horizontal edge.
     *
     * @param loc The location of this vertical edge. A {@link Vec2i} where the
     *            x coordinate is a vertex coordinate and the y coordinate is a cell coordinate.
     *            (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     * @param thickness The desired thickness for this edge (in pixels).
     * @param styleNames A list of string containing all the desired style names. (The default edge style is added automatically.)
     * @return The newly created widget.
     */
    public Widget createVerticalEdge(Vec2i loc, int thickness, String... styleNames) {
        Widget widget = GWT.create(FlowPanel.class);
        widget.addStyleName(resources.style().edge());
        for (String style : styleNames) {
            widget.addStyleName(style);
        }
        addVerticalEdgeWidget(loc, widget, thickness);
        return widget;
    }

    /**
     * Creates a standard cell widget and adds it at a specific cell.
     * (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     *
     * @param loc The location of this cell (a {@link Vec2i}).
     * @param styleNames A list of string containing all the desired style names. (The default cell style is added automatically.)
     * @return The newly created widget.
     */
    public Widget createCell(Vec2i loc, String... styleNames) {
        Widget widget = GWT.create(FlowPanel.class);
        widget.addStyleName(resources.style().cell());
        for (String style : styleNames) {
            widget.addStyleName(style);
        }
        addCellWidget(loc, widget);
        return widget;
    }

    /**
     * Creates a standard selected cell widget and adds it at a specific cell.
     * (See {@link com.puzzlebazar.shared.util.Recti} for details on cells and vertices.)
     *
     * @param loc The location of this cell (a {@link Vec2i}).
     * @param styleNames A list of string containing all the desired style names. (The default selected cell style is added automatically.)
     * @return The newly created widget.
     */
    public Widget createSelectedCell(Vec2i loc, String... styleNames) {
        Widget widget = GWT.create(FlowPanel.class);
        widget.addStyleName(resources.style().selectedCell());
        for (String style : styleNames) {
            widget.addStyleName(style);
        }
        addCellWidget(loc, widget);
        return widget;
    }

    /**
     * Creates and adds all the inner horizontal and vertical edges to draw
     * a regular grid.
     *
     * @param thickness The desired thickness of the inner edges.
     * @param styleNames A list of string containing all the desired style names. (The default edge style is added automatically.)
     * @return A list of all newly created widgets.
     */
    public List<Widget> createInnerEdges(int thickness, String... styleNames) {
        Vec2i loc = new Vec2i();
        List<Widget> result = new ArrayList<Widget>();
        for (int y = 0; y < height; ++y) {
            loc.y = y;
            for (int x = 0; x < width; ++x) {
                loc.x = x;
                if (x > 0) {
                    result.add(createVerticalEdge(loc, thickness, styleNames));
                }
                if (y > 0) {
                    result.add(createHorizontalEdge(loc, thickness, styleNames));
                }
            }
        }
        return result;
    }

    /**
     * Creates and adds all the outer horizontal and vertical edges to draw
     * a regular grid.
     *
     * @param thickness The desired thickness of the inner edges.
     * @param styleNames A list of string containing all the desired style names. (The default edge style is added automatically.)
     * @return A list of all newly created widgets.
     */
    public List<Widget> createOuterEdges(int thickness, String... styleNames) {
        Vec2i loc = new Vec2i();
        List<Widget> result = new ArrayList<Widget>();
        for (int y = 0; y <= height; y += height) {
            loc.y = y;
            for (int x = 0; x < width; ++x) {
                loc.x = x;
                result.add(createHorizontalEdge(loc, thickness, styleNames));
            }
        }
        for (int x = 0; x <= width; x += width) {
            loc.x = x;
            for (int y = 0; y < height; ++y) {
                loc.y = y;
                result.add(createVerticalEdge(loc, thickness, styleNames));
            }
        }
        return result;
    }

    /**
     * Clears all the content and creates an {@link OverflowLayoutPanel} for every cell.
     * In addition, similar {@link OverflowLayoutPanel}s are created pass the right-hand
     * column and pass the bottom line. This makes it possible to draw edges and vertices
     * element on the right and bottom edge of the puzzle.
     */
    private void createCellPanels() {
        final int precision = 1024;
        squareGridContainer.clear();
        cells = new CellLayoutPanel[width + 1][height + 1];
        for (int y = 0; y <= height; ++y) {
            int percentY = y * precision / height;
            int percentNextYBottom = precision - (y + 1) * precision / height;
            for (int x = 0; x <= width; ++x) {
                int percentX = x * precision / width;
                int percentNextXRight = precision - (x + 1) * precision / width;
                CellLayoutPanel cell = GWT.create(CellLayoutPanel.class);
                cells[x][y] = cell;
                squareGridContainer.add(cell);
                squareGridContainer.setWidgetLeftRight(cell, percentX * 100.0 / precision, Unit.PCT,
                        percentNextXRight * 100.0 / precision, Unit.PCT);
                squareGridContainer.setWidgetTopBottom(cell, percentY * 100.0 / precision, Unit.PCT,
                        percentNextYBottom * 100.0 / precision, Unit.PCT);
            }
        }
    }

}