package ch.fusun.baron.swt.isometry.components;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import ch.fusun.baron.swt.isometry.SpriteSelectionListener;
/**
* Widget to display sprites in an isometric world
*/
public class IsometryWidget extends Composite {
private int mapWidth;
private int mapHeight;
private int mapAltitude;
Map<String, Image> map = new HashMap<String, Image>();
Map<Point3D, List<Sprite>> spriteMap = new HashMap<Point3D, List<Sprite>>();
Sprite selectedSprite;
private final int spriteHeight;
private final int spriteWidth;
private final int spriteAltitude;
private int deltaX;
private int deltaY;
private final Map<Sprite, Object> modelMap = new HashMap<Sprite, Object>();
private final List<SpriteSelectionListener> selectionListeners = new LinkedList<SpriteSelectionListener>();
private IsometricRotation rotation = IsometricRotation.ROTATION_0;
/**
* Constructor
*
* @param parent
* The parent composite
* @param style
* The SWT style
* @param width
* The width (x)
* @param height
* The height (y)
* @param altitude
* The altitude (z)
* @param spriteWidth
* The sprite width
* @param spriteHeight
* The sprite height
* @param spriteAltitude
* The sprite altitude
*/
public IsometryWidget(Composite parent, int style, final int width,
final int height, final int altitude, final int spriteWidth,
final int spriteHeight, final int spriteAltitude) {
super(parent, SWT.NO_BACKGROUND);
this.spriteWidth = spriteWidth;
this.spriteHeight = spriteHeight;
this.spriteAltitude = spriteAltitude;
setDimension(width, height, altitude);
this.addMouseListener(new MouseListener() {
@Override
public void mouseDoubleClick(MouseEvent e) {
// Do nothing on double click
}
@Override
public void mouseDown(MouseEvent e) {
int x = e.x;
int y = e.y;
selectedSprite = null;
Point3D size = new Point3D(mapWidth - 1, mapHeight - 1,
mapAltitude - 1); // The -1 is important
for (Entry<Point3D, List<Sprite>> entry : spriteMap.entrySet()) {
Point3D point = rotation.transform(entry.getKey(), size);
for (Sprite sprite : entry.getValue()) {
if (sprite.getMask() != null) {
int width = spriteWidth;
int height = spriteHeight;
int adjustedX = -calcX(point.x(), point.y()) + x;
int adjustedY = -calcY(point.x(), point.y(),
point.z())
+ y;
if ((adjustedX < width) && (adjustedX > 0)
&& (adjustedY < height) && (adjustedY > 0)) {
if (sprite.isSensitive(adjustedX, adjustedY)) {
if (selectedSprite == null) {
selectedSprite = sprite;
} else if (moreInForeground(sprite,
selectedSprite)) {
selectedSprite = sprite;
}
}
}
}
}
}
notifySelectionListeners();
redraw();
}
@Override
public void mouseUp(MouseEvent e) {
// Do nothing
}
});
this.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent event) {
Image image = new Image(getDisplay(), getClientArea());
GC gc = new GC(image);
gc.setBackground(new Color(getDisplay(), 0, 0, 0));
gc.fillRectangle(0, 0, image.getBounds().width,
image.getBounds().height);
Point3D size = new Point3D(mapWidth - 1, mapHeight - 1,
mapAltitude - 1); // The -1 is important :)
for (int k = 0; k < mapAltitude; k++) {
for (int i = 0; i < mapWidth; i++) {
for (int j = 0; j < mapHeight; j++) {
Point3D point = rotation.getInverse().transform(
new Point3D(i, j, k), size);
if (spriteMap.get(point) != null) {
for (Sprite sprite : spriteMap.get(point)) {
gc.drawImage(rotation.getImage(sprite),
calcX(i, j), calcY(i, j, k));
if (selectedSprite == sprite) {
gc.drawImage(
sprite.getHalfTransparentMask(),
calcX(i, j), calcY(i, j, k));
}
}
}
}
}
}
event.gc.drawImage(image, 0, 0);
gc.dispose();
image.dispose();
}
});
}
/**
* @param newSprite
* The sprite
* @param original
* The original
* @return is the new sprite in front of the original?
*/
protected boolean moreInForeground(Sprite newSprite, Sprite original) {
Point3D pos1 = getPosition(original);
Point3D pos2 = getPosition(newSprite);
return (pos1.x() < pos2.x()) || (pos1.y() < pos2.y())
|| original.getPriority() < newSprite.getPriority();
}
private Point3D getPosition(Sprite sprite) {
for (Point3D point : spriteMap.keySet()) {
for (Sprite element : spriteMap.get(point)) {
if (element == sprite) {
return point;
}
}
}
return new Point3D(0, 0, 0);
}
/**
* Notifies selection listeners on a new selection
*/
protected void notifySelectionListeners() {
if (selectedSprite != null) {
for (SpriteSelectionListener listener : selectionListeners) {
listener.itemSelected(getSelection());
}
}
}
/**
* Calculates real x from i and j in model space
*
* @param i
* @param j
* @return The calculated position
*/
protected int calcX(int i, int j) {
return (i - j) * spriteWidth / 2 + deltaX;
}
/**
* Calculates real y from i and j in model space
*
* @param i
* @param j
* @return The calculated position
*/
protected int calcY(int i, int j, int k) {
return spriteHeight / 2 + (j + i) * spriteHeight / 4 + deltaY
- spriteAltitude * k;
}
/**
* @param model
* The model representing the sprite
* @param x
* The x position of the sprite
* @param y
* The y position of the sprite
* @param sprite
*/
public void addItem(Object model, int x, int y, Sprite sprite) {
addItem(model, x, y, 0, sprite);
}
/**
*
* @param model
* @param x
* @param y
* @param z
* height
* @param sprite
*/
public void addItem(Object model, int x, int y, int z, Sprite sprite) {
Point3D point = new Point3D(x, y, z);
if (spriteMap.get(point) == null) {
spriteMap.put(point, new LinkedList<Sprite>());
}
spriteMap.get(point).add(sprite);
modelMap.put(sprite, model);
Collections.sort(spriteMap.get(point), new Comparator<Sprite>() {
@Override
public int compare(Sprite o1, Sprite o2) {
return o1.getPriority() - o2.getPriority();
}
});
}
/**
* Sets the dimensions of the map
*
* @param width
* The width
* @param height
* The height
* @param altitude
* The altitude
*/
public void setDimension(int width, int height, int altitude) {
this.mapWidth = width;
this.mapHeight = height;
this.mapAltitude = altitude;
deltaX = mapHeight * spriteWidth / 2;
deltaY = spriteAltitude * mapAltitude;
}
/**
* Clears all sprites from the map
*/
public void clear() {
this.selectedSprite = null;
this.map.clear();
this.spriteMap.clear();
this.modelMap.clear();
}
/**
* @param spriteSelectionListener
* the listener to add
*/
public void addSelectionListener(
SpriteSelectionListener spriteSelectionListener) {
this.selectionListeners.add(spriteSelectionListener);
}
/**
* @return The current selection
*/
public IStructuredSelection getSelection() {
if (selectedSprite != null) {
if (modelMap.get(selectedSprite) != null) {
return new StructuredSelection(modelMap.get(selectedSprite));
}
}
return new StructuredSelection();
}
/**
* @param selection
* The selection to set
*/
public void setSelection(IStructuredSelection selection) {
this.selectedSprite = null;
Object model = selection.getFirstElement();
if (model != null) {
for (Entry<Sprite, Object> o : modelMap.entrySet()) {
if (model.equals(o.getValue())) {
this.selectedSprite = o.getKey();
return;
}
}
}
}
/**
* Turns the map clockwise
*/
public void turn() {
this.rotation = this.rotation.clockWise();
redraw();
}
/**
* Turns the map counterclockwise
*/
public void turnCounterClockWise() {
this.rotation = this.rotation.counterClockWise();
redraw();
}
}