/**
* 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);
}
}
}
}