package gwt.g2d.client.mouse;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.Context2d.Composite;
import gwt.g2d.client.graphics.ImageData;
import gwt.g2d.client.graphics.Surface;
import gwt.g2d.shared.Color;
import gwt.g2d.shared.math.Rectangle;
import gwt.g2d.shared.math.Vector2;
/**
* A mouse surface is tied to a normal surface, and registers different shapes that can be checked for
* clicks or hovers form the mouse. This system works with any complex shape, and will detect hovers/clicks on
* all non-transparent areas in the drawn shape or image.
* @author Karel
*
*/
public class MouseSurface {
// handlers
private class Handlers {
public SurfaceClickHandler clickHandler;
public SurfaceMouseOutHandler mouseOutHandler;
public SurfaceMouseOverHandler mouseOverHandler;
public SurfaceMouseMoveHandler mouseMoveHandler;
};
// the surface we are tied to
Surface fOriginalSurface;
// original context
Context2d fOriginalContext;
// the temporary surface which is used to draw on during mouse registration
public Surface fTempSurface = null;
// current id
Long fCurrentId = null;
// map of id to handlers
Map<Long, Handlers> fIdToHandlers = new HashMap<Long, Handlers>();
// map of id to surface (used for checking against this specific collision)
Map<Long, Surface> fIdToSurface = new HashMap<Long, Surface>();
// drag handlers
Vector<SurfaceMouseDragHandler> fDragHandlers = new Vector<SurfaceMouseDragHandler>();
// the id where the drag begin
Long fStartDragId = null;
// the position where the drag began
Vector2 fStartDragLoc = null;
// list of id's in the order in which they were drawn - used to make sure that only the topmost object is actually triggered
List<Long> fIds = new LinkedList<Long>();
// last color
Color fCurrentColor = new Color(200, 200, 0);
// last id that was hovered by the mouse
Long fLastId = null;
// does this surface has move handlers - otherwise, don't perform the calculations
boolean fHasMoveHandlers = false;
// create a new mouse surface
public MouseSurface(Surface originalSurface) {
fOriginalSurface = originalSurface;
fOriginalContext = fOriginalSurface.getContext();
}
// start registering a new clickable surface
public void startRegister(Long id) {
// set id
fCurrentId = id;
// surface
Surface surface = null;
// doesn't exist yet - create a new canvas
if (!fIdToSurface.containsKey(id)) {
surface = new Surface(fOriginalSurface.getCoordinateSpaceWidth(), fOriginalSurface.getCoordinateSpaceHeight());
fIdToSurface.put(id, surface);
fIdToHandlers.put(id, new Handlers());
fIds.add(id);
}
// exist - get the existing canvas
else {
// get the surface
surface = fIdToSurface.get(id);
surface.clear();
// find the position of this id, and put it at the end of our draw list
fIds.remove(id);
fIds.add(id);
}
// start drawing on the temp surface now
fTempSurface = surface;
fOriginalSurface.replaceContext(fTempSurface.getContext());
}
// stop registering
public void stopRegister(boolean draw) {
// invalid - surface doesn't exist
if (fTempSurface == null) return;
// switch back to the original context
fOriginalSurface.replaceContext(fOriginalContext);
if (draw) fOriginalSurface.drawImage(fTempSurface.getCanvasElement(), 0, 0, fTempSurface.getCoordinateSpaceWidth(), fTempSurface.getCoordinateSpaceHeight());
// done
fCurrentId = null;
fTempSurface = null;
}
// next color
private void generateNextColor() {
fCurrentColor = new Color(fCurrentColor.red, fCurrentColor.green, fCurrentColor.blue+1);
if (fCurrentColor.blue > 255) {
fCurrentColor = new Color(fCurrentColor.red, fCurrentColor.green+1, 0);
if (fCurrentColor.green > 255) {
fCurrentColor = new Color(fCurrentColor.red+1, 0, 0);
if (fCurrentColor.red > 255) {
fCurrentColor = new Color(0, 0, 0); // reset back to zero
}
}
}
}
// active?
public boolean isActive() {
return fCurrentId != null;
}
// add click handler
public void addClickHandler(SurfaceClickHandler handler) {
if (fCurrentId == null) return;
Handlers handlers = fIdToHandlers.get(fCurrentId);
if (handlers == null) return;
handlers.clickHandler = handler;
}
// add mouse down handler
public void addMouseDragHandler(SurfaceMouseDragHandler handler) {
fDragHandlers.add(handler);
}
// add mouse over handler
public void addMouseOverHandler(SurfaceMouseOverHandler handler) {
if (fCurrentId == null) return;
Handlers handlers = fIdToHandlers.get(fCurrentId);
if (handlers == null) return;
handlers.mouseOverHandler = handler;
fHasMoveHandlers = true;
}
// add mouse out handler
public void addMouseOutHandler(SurfaceMouseOutHandler handler) {
if (fCurrentId == null) return;
Handlers handlers = fIdToHandlers.get(fCurrentId);
if (handlers == null) return;
handlers.mouseOutHandler = handler;
fHasMoveHandlers = true;
}
// add mouse move handler
public void addMouseMoveHandler(SurfaceMouseMoveHandler handler) {
if (fCurrentId == null) return;
Handlers handlers = fIdToHandlers.get(fCurrentId);
if (handlers == null) return;
handlers.mouseMoveHandler = handler;
fHasMoveHandlers = true;
}
/**
* We clicked on the canvas - check where we clicked exactly.
*/
public void onClick(int x, int y) {
// get all different canvases and check for a collision
ListIterator<Long> it = fIds.listIterator(fIds.size());
while (it.hasPrevious()) {
Long id = it.previous();
Surface surface = fIdToSurface.get(id);
// get the color at the given location
ImageData data = surface.getImageData(x, y, 1, 1);
double alpha = data.getAlpha(0, 0);
// hit is not transparent - we got a hit!
if (alpha > Double.MIN_VALUE) {
// look up color
Handlers handlers = fIdToHandlers.get(id);
if (handlers != null && handlers.clickHandler != null) handlers.clickHandler.onClick(new Vector2(x, y), id);
return;
}
}
}
/**
* We put our mouse down on the canvas - check where we clicked exactly.
*/
public void onMouseDown(int x, int y) {
// perform one single onMouseMove call to detect where we are
fStartDragLoc = new Vector2(x, y);
onMouseMove(x, y);
// set the id matched by onMouseMove
fStartDragId = fLastId;
// let all handlers know
for (SurfaceMouseDragHandler handler : fDragHandlers) {
handler.onDragStart(fStartDragLoc, fStartDragId);
}
}
/**
* We lifted up our mouse on the canvas - check where we clicked exactly.
*/
public void onMouseUp(int x, int y) {
// let all handlers know
for (SurfaceMouseDragHandler handler : fDragHandlers) {
handler.onDragStop(fStartDragLoc, new Vector2(x, y), fStartDragId, fLastId);
}
// disable the onMouseMove calls again by setting fStartDragLoc to null
fStartDragId = null;
fStartDragLoc = null;
}
/**
* We're entering the canvas - check if we entered immediately on an object.
*/
public void onMouseOver(int x, int y) {
if (!fHasMoveHandlers) return;
// get all different canvases and check for a collision
ListIterator<Long> it = fIds.listIterator(fIds.size());
while (it.hasPrevious()) {
Long id = it.previous();
Surface surface = fIdToSurface.get(id);
// get the color at the given location
ImageData data = surface.getImageData(x, y, 1, 1);
double alpha = data.getAlpha(0, 0);
// hit is not transparent - we got a hit!
if (alpha > Double.MIN_VALUE) {
// we entered on an object, signal it
Handlers handlers = fIdToHandlers.get(id);
if (handlers != null && handlers.mouseOverHandler != null) handlers.mouseOverHandler.onMouseOver(new Vector2(x, y), id);
fLastId = id;
return;
}
}
}
/**
* We left the surface - make sure we send the last selected object a message.
*/
public void onMouseOut(int x, int y) {
if (!fHasMoveHandlers) return;
// no entity selected when we left the surface - don't do anything
if (fLastId == null) return;
// look up color
Handlers handlers = fIdToHandlers.get(fLastId);
if (handlers != null && handlers.mouseOutHandler != null) handlers.mouseOutHandler.onMouseOut(new Vector2(x, y));
fLastId = null;
}
/**
* We moved the mouse over the canvas - check for collision with a registered object.
*/
public void onMouseMove(int x, int y) {
if (!fHasMoveHandlers && fStartDragLoc == null) return;
// get all different canvases and check for a collision
boolean hit = false;
ListIterator<Long> it = fIds.listIterator(fIds.size());
while (it.hasPrevious()) {
Long id = it.previous();
Surface surface = fIdToSurface.get(id);
// get the color at the given location
ImageData data = surface.getImageData(x, y, 1, 1);
double alpha = data.getAlpha(0, 0);
// hit is not transparent - we got a hit!
if (alpha > Double.MIN_VALUE) {
hit = true;
// we were hovering over another id and now we're hovering over nothing or a new id - send a mouse out event
if (fLastId != null && fLastId != id) {
Handlers handlers = fIdToHandlers.get(fLastId);
if (handlers != null && handlers.mouseOutHandler != null) handlers.mouseOutHandler.onMouseOut(new Vector2(x, y));
fLastId = null;
}
// we enter a new object
if (fLastId == null) {
fLastId = id;
Handlers handlers = fIdToHandlers.get(fLastId);
if (handlers != null && handlers.mouseOverHandler != null) handlers.mouseOverHandler.onMouseOver(new Vector2(x, y), fLastId);
}
// we were already on this object, but we moved the mouse
else {
Handlers handlers = fIdToHandlers.get(fLastId);
if (handlers != null && handlers.mouseMoveHandler != null) handlers.mouseMoveHandler.onMouseMove(new Vector2(x, y), fLastId);
}
// only process one hit
break;
}
}
// update drag
if (fStartDragLoc != null) {
for (SurfaceMouseDragHandler handler : fDragHandlers) {
handler.onDragChange(fStartDragLoc, new Vector2(x, y), fStartDragId, fLastId);
}
}
// we got here - this means there was no hit
// if we were above an object in the previous cycle, let it know we left
if (!hit) {
Handlers handlers = fIdToHandlers.get(fLastId);
if (handlers != null && handlers.mouseOutHandler != null) handlers.mouseOutHandler.onMouseOut(new Vector2(x, y));
fLastId = null;
}
}
}