package br.com.etyllica.gui.selection;
import java.awt.BasicStroke;
import java.awt.Color;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import br.com.etyllica.awt.stroke.DashedStroke;
import br.com.etyllica.core.collision.CollisionDetector;
import br.com.etyllica.core.event.KeyEvent;
import br.com.etyllica.core.event.MouseEvent;
import br.com.etyllica.core.event.MouseState;
import br.com.etyllica.core.event.PointerEvent;
import br.com.etyllica.core.graphics.Graphics;
import br.com.etyllica.core.input.mouse.MouseStateChanger;
import br.com.etyllica.layer.GeometricLayer;
import br.com.etyllica.layer.Layer;
public class Resizer<T extends Layer> {
private int count = 0;
ResizerEvent lastEvent = null;
Layer copy = new Layer();
private static final int BUTTON_SIZE = 16;
public static final int UNKNOWN = -1;
private static final Layer NULL_LAYER = new Layer(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN);
protected Map<Integer, T> layers = new HashMap<Integer, T>();
protected int selectedIndex = UNKNOWN;
private Layer selected = NULL_LAYER;
protected Layer overlay = new Layer();
private ResizerPoint selectedArea;
private ResizerPoint[] points;
private ResizerListener listener;
private MouseStateChanger changer;
private final DashedStroke dash = new DashedStroke();
private final BasicStroke resetStroke = new BasicStroke(1);
private boolean dragged = false;
private int offsetX = 0;
private int offsetY = 0;
private int initialX = 0;
private int initialY = 0;
private double initialW = 0;
private double initialH = 0;
private int lastIndex = 0;
private static final int SHIFT_SPEED = 10;
private static final int NORMAL_SPEED = 1;
private int keyboardSpeed = 1;
private int speedFactor = NORMAL_SPEED;
public Resizer(MouseStateChanger context) {
super();
changer = context;
points = new ResizerPoint[9];
for (int i=0;i<8; i++)
points[i] = new ResizerPoint(0, 0, 1, 1);
selectedArea = new ResizerPoint(0, 0, 1, 1);
selectedArea.setState(MouseState.MOVE);
points[8] = selectedArea;
points[0].setState(MouseState.ARROW_NW_SE);
points[1].setState(MouseState.ARROW_VERTICAL);
points[2].setState(MouseState.ARROW_NE_SW);
points[3].setState(MouseState.ARROW_HORIZONTAL);
points[4].setState(MouseState.ARROW_HORIZONTAL);
points[5].setState(MouseState.ARROW_NE_SW);
points[6].setState(MouseState.ARROW_VERTICAL);
points[7].setState(MouseState.ARROW_NW_SE);
points[8].setState(MouseState.MOVE);
}
public void refresh() {
select(selected);
}
public void deselect() {
selected = NULL_LAYER;
changer.changeMouseState(MouseState.NORMAL);
}
public void select(Layer layer) {
if (!isSelected()) {
deselect();
}
this.selected = layer;
selectedArea.copy(layer);
int inc = 0;
//Update 8 points
for (int b=0; b<9; b++) {
int i = b%3;
int j = b/3;
if (i==1 && j==1) {
inc = -1;
continue;
}
int offsetX = (int)(layer.utilWidth()*(1-layer.getScaleX()))/2;
int offsetY = (int)(layer.utilHeight()*(1-layer.getScaleY()))/2;
int bx = (int)(layer.getX()+offsetX+i*(layer.utilWidth()*layer.getScaleX()/2) - BUTTON_SIZE/2);
int by = (int)(layer.getY()+offsetY+j*(layer.utilHeight()*layer.getScaleY()/2) - BUTTON_SIZE/2);
points[b+inc].setBounds(bx, by, BUTTON_SIZE, BUTTON_SIZE);
}
}
public void draw(Graphics g) {
drawOverlay(g);
if (!isSelected())
return;
g.setColor(Color.BLACK);
g.setStroke(dash);
drawScaledRect(g, selected);
for (int b=0; b < 8; b++) {
points[b].draw(g, offsetX, offsetY);
}
g.setStroke(resetStroke);
}
private void drawScaledRect(Graphics g, Layer layer) {
int sw = (int)(layer.utilWidth()*layer.getScaleX());
int sh = (int)(layer.utilHeight()*layer.getScaleY());
int oX = (int)(layer.utilWidth()*(1-layer.getScaleX()))/2;
int oY = (int)(layer.utilHeight()*(1-layer.getScaleY()))/2;
g.drawRect(layer.getX()+oX+offsetX, layer.getY()+oY+offsetY, sw, sh);
}
private void fillScaledRect(Graphics g, Layer layer) {
int sw = (int)(layer.utilWidth()*layer.getScaleX());
int sh = (int)(layer.utilHeight()*layer.getScaleY());
int oX = (int)(layer.utilWidth()*(1-layer.getScaleX()))/2;
int oY = (int)(layer.utilHeight()*(1-layer.getScaleY()))/2;
g.fillRect(layer.getX()+oX+offsetX, layer.getY()+oY+offsetY, sw, sh);
}
private void drawOverlay(Graphics g) {
if (overlay.isVisible() == false) {
return;
}
g.setColor(Color.BLACK);
g.setAlpha(60);
fillScaledRect(g, overlay);
g.resetOpacity();
}
private boolean changed = false;
public void handleEvent(PointerEvent event) {
int mx = event.getX() - offsetX;
int my = event.getY() - offsetY;
if (!isSelected()) {
checkMouseOver(mx, my);
}
if (event.isButtonDown(MouseEvent.MOUSE_BUTTON_LEFT)) {
if (!isSelected()) {
checkSelection(mx, my);
} else if (!isDragged()) {
deselect();
}
}
if (!isSelected()) {
return;
}
changed = false;
if (!dragged) {
for (int b = 0; b < 9; b++) {
if (CollisionDetector.colideRectPoint(points[b], mx, my)) {
lastIndex = b;
changer.changeMouseState(points[b].getState());
changed = true;
handleDragEvent(event);
break;
}
}
}
if (event.isButtonUp(MouseEvent.MOUSE_BUTTON_LEFT)) {
dragged = false;
if (lastEvent != null) {
notifyListener(lastEvent);
lastEvent = null;
}
} else if (dragged && event.isDraggedButton(MouseEvent.MOUSE_BUTTON_LEFT)) {
resizeEvent(lastIndex, event);
refresh();
}
if (!changed && event.isClicked(MouseEvent.MOUSE_BUTTON_LEFT)) {
deselect();
}
if (!changed) {
changer.changeMouseState(MouseState.NORMAL);
}
}
protected boolean checkMouseOver(int mx, int my) {
for (Layer component: layers.values()) {
if (component.onMouse(mx, my)) {
overlay.copy(component);
overlay.setVisible(true);
return true;
}
}
return false;
}
protected void checkSelection(int mx, int my) {
selectedIndex = UNKNOWN;
for (Entry<Integer, T> entry: layers.entrySet()) {
T component = entry.getValue();
if (component.onMouse(mx, my)) {
overlay.copy(component);
selectedIndex = entry.getKey();
select(component);
overlay.setVisible(false);
break;
}
}
}
private void resizeEvent(int index, PointerEvent event) {
lastEvent = ResizerEvent.SCALE;
switch (index) {
case 0:
resizeUp(event);
resizeLeft(event);
break;
case 1:
resizeUp(event);
break;
case 2:
resizeUp(event);
resizeRight(event);
break;
case 3:
resizeLeft(event);
break;
case 4:
resizeRight(event);
break;
case 5:
resizeDown(event);
resizeLeft(event);
break;
case 6:
resizeDown(event);
break;
case 7:
resizeDown(event);
resizeRight(event);
break;
default:
moveSelected(event);
lastEvent = ResizerEvent.MOVE;
break;
}
}
private void handleDragEvent(PointerEvent event) {
if (!dragged && event.isDraggedButton(MouseEvent.MOUSE_BUTTON_LEFT)) {
setInitialValues();
dragged = true;
copy.copy(selected);
}
if (event.isButtonUp(MouseEvent.MOUSE_BUTTON_LEFT)) {
dragged = false;
}
}
private void setInitialValues() {
initialX = selected.getX();
initialY = selected.getY();
initialW = selected.utilWidth()*selected.getScaleX();
initialH = selected.utilHeight()*selected.getScaleY();
}
private void moveSelected(PointerEvent event) {
selected.setX(initialX+event.getAmountX());
selected.setY(initialY+event.getAmountY());
}
private void resizeUp(PointerEvent event) {
selected.setY(initialY+event.getAmountY()/2);
double sy = initialH-event.getAmountY();
selected.setScaleY(sy/selected.utilHeight());
}
private void resizeDown(PointerEvent event) {
selected.setY(initialY+event.getAmountY()/2);
double sy = initialH+event.getAmountY();
selected.setScaleY(sy/selected.utilHeight());
}
private void resizeLeft(PointerEvent event) {
selected.setX(initialX+event.getAmountX()/2);
double sx = initialW-event.getAmountX();
selected.setScaleX(sx/selected.utilWidth());
}
private void resizeRight(PointerEvent event) {
selected.setX(initialX+event.getAmountX()/2);
double sx = initialW+event.getAmountX();
selected.setScaleX(sx/selected.utilWidth());
}
public boolean isDragged() {
return dragged||changed;
}
public boolean isSelected() {
return selected != NULL_LAYER;
}
public GeometricLayer getSelectedLayer() {
return selected;
}
public void handleKeyEvent(KeyEvent event) {
if (event.isAnyKeyDown(KeyEvent.VK_SHIFT_LEFT, KeyEvent.VK_SHIFT_RIGHT)) {
speedFactor = SHIFT_SPEED;
} else if (event.isAnyKeyUp(KeyEvent.VK_SHIFT_LEFT, KeyEvent.VK_SHIFT_RIGHT)) {
speedFactor = NORMAL_SPEED;
}
if (event.isKeyDown(KeyEvent.VK_UP_ARROW)) {
if (selectedIndex != UNKNOWN) {
selected.offsetY(-speed());
notifyListener(ResizerEvent.MOVE);
refresh();
}
} else if (event.isKeyDown(KeyEvent.VK_DOWN_ARROW)) {
if (selectedIndex != UNKNOWN) {
selected.offsetY(+speed());
notifyListener(ResizerEvent.MOVE);
refresh();
}
}
if (event.isKeyDown(KeyEvent.VK_LEFT_ARROW)) {
if (selectedIndex != UNKNOWN) {
selected.offsetX(-speed());
notifyListener(ResizerEvent.MOVE);
refresh();
}
} else if (event.isKeyDown(KeyEvent.VK_RIGHT_ARROW)) {
if (selectedIndex != UNKNOWN) {
selected.offsetX(+speed());
notifyListener(ResizerEvent.MOVE);
refresh();
}
}
}
protected int speed() {
return keyboardSpeed*speedFactor;
}
private void notifyListener(ResizerEvent event) {
if (listener == null)
return;
listener.onResize(event, selectedIndex, selected, copy);
}
public ResizerListener getListener() {
return listener;
}
public void setListener(ResizerListener listener) {
this.listener = listener;
}
public Collection<T> getLayers() {
return layers.values();
}
public void setLayers(List<T> layers) {
for (T layer:layers) {
addLayer(layer);
}
}
private Integer generateId() {
count++;
return count;
}
public int getId(T layer) {
for (Entry<Integer, T> entry:layers.entrySet()) {
if (entry.getValue().equals(layer)) {
return entry.getKey();
}
}
return UNKNOWN;
}
public int addLayer(T layer) {
int id = generateId();
layers.put(id, layer);
return id;
}
public int getSelectedIndex() {
return selectedIndex;
}
public void removeLayer(int index) {
if (selectedIndex == index) {
deselect();
}
layers.remove(index);
}
public int getOffsetX() {
return offsetX;
}
public void setOffsetX(int offsetX) {
this.offsetX = offsetX;
}
public int getOffsetY() {
return offsetY;
}
public void setOffsetY(int offsetY) {
this.offsetY = offsetY;
}
}