package com.kartoflane.superluminal2.mvc.controllers;
import java.util.ArrayList;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import com.kartoflane.superluminal2.components.Tuple;
import com.kartoflane.superluminal2.components.enums.Orientations;
import com.kartoflane.superluminal2.components.interfaces.Action;
import com.kartoflane.superluminal2.components.interfaces.Indexable;
import com.kartoflane.superluminal2.core.Grid;
import com.kartoflane.superluminal2.core.Grid.Snapmodes;
import com.kartoflane.superluminal2.core.LayeredPainter;
import com.kartoflane.superluminal2.core.LayeredPainter.Layers;
import com.kartoflane.superluminal2.core.Manager;
import com.kartoflane.superluminal2.events.SLEvent;
import com.kartoflane.superluminal2.ftl.RoomObject;
import com.kartoflane.superluminal2.mvc.View;
import com.kartoflane.superluminal2.mvc.models.ObjectModel;
import com.kartoflane.superluminal2.mvc.views.RoomView;
import com.kartoflane.superluminal2.tools.ManipulationTool;
import com.kartoflane.superluminal2.tools.Tool.Tools;
import com.kartoflane.superluminal2.ui.OverviewWindow;
import com.kartoflane.superluminal2.ui.ShipContainer;
import com.kartoflane.superluminal2.ui.SystemsMenu;
import com.kartoflane.superluminal2.ui.sidebar.data.DataComposite;
import com.kartoflane.superluminal2.ui.sidebar.data.RoomDataComposite;
import com.kartoflane.superluminal2.undo.UndoableResizeEdit;
import com.kartoflane.superluminal2.utils.Utils;
public class RoomController extends ObjectController implements Indexable, Comparable<RoomController> {
/**
* Tolerance greater than half of CELL_SIZE will result in negative size for 1x1 rooms,
* which will actually cause the collision area to grow.
*
* @see {@link ShipContainer#CELL_SIZE}
* @see {@link AbstractController#collides(int, int, int, int)}
*/
public static final int COLLISION_TOLERANCE = ShipContainer.CELL_SIZE / 2;
protected ShipContainer container = null;
protected boolean resizing = false;
protected boolean canResize = false;
private Point resizeAnchor;
private RoomController(ShipContainer container, ObjectModel model, RoomView view) {
super();
setModel(model);
setView(view);
this.container = container;
resizeAnchor = new Point(0, 0);
setSelectable(true);
setLocModifiable(true);
setBounded(true);
setCollidable(!Manager.allowRoomOverlap);
setTolerance(COLLISION_TOLERANCE);
setParent(container.getShipController());
setPresentedFactor(ShipContainer.CELL_SIZE);
}
/**
* Creates a new object represented by the MVC system, ie.
* a new Controller associated with new Model and View objects
*
* @param shipController
* the ShipController object to which this room belongs
*/
public static RoomController newInstance(ShipContainer container, RoomObject object) {
ObjectModel model = new ObjectModel(object);
RoomView view = new RoomView();
RoomController controller = new RoomController(container, model, view);
OverviewWindow ow = OverviewWindow.getInstance();
controller.addListener(SLEvent.DELETE, ow);
controller.addListener(SLEvent.DISPOSE, ow);
return controller;
}
public RoomObject getGameObject() {
return (RoomObject) getModel().getGameObject();
}
@Override
public void setView(View view) {
super.setView(view);
this.view.addToPainter(Layers.ROOM);
}
protected RoomView getView() {
return (RoomView) view;
}
/**
* Change size of the object to the specified values.<br>
*
* @param w
* width of the room, in pixels
* @param h
* height of the room, in pixels
*/
@Override
public boolean setSize(int w, int h) {
boolean result = super.setSize(w, h);
if (result) {
w = (w + 1) / ShipContainer.CELL_SIZE;
h = (h + 1) / ShipContainer.CELL_SIZE;
setSnapMode(getSnapMode(w, h));
setCollidable(w != 0 && h != 0 && !Manager.allowRoomOverlap);
updateBoundingArea();
return true;
} else
return false;
}
private Snapmodes getSnapMode(int w, int h) {
if (w % 2 == 1 && h % 2 == 1)
return Snapmodes.CELL;
else if (w % 2 == 1)
return Snapmodes.EDGE_H;
else if (h % 2 == 1)
return Snapmodes.EDGE_V;
else if (w % 2 == 0 && h % 2 == 0)
return Snapmodes.CROSS;
else
return Snapmodes.CELL;
}
@Override
public Point getPresentedLocation() {
return new Point((getX() - getW() / 2) / getPresentedFactor(),
(getY() - getH() / 2) / getPresentedFactor());
}
@Override
public Point getPresentedSize() {
return new Point(getW() / getPresentedFactor(), getH() / getPresentedFactor());
}
@Override
public void setPresentedLocation(int x, int y) {
setLocation(x * getPresentedFactor() + getW() / 2, y * getPresentedFactor() + getH() / 2);
}
@Override
public void setPresentedSize(int w, int h) {
Point presLoc = getPresentedLocation();
if (setSize(w * getPresentedFactor(), h * getPresentedFactor())) {
updateBoundingArea();
setPresentedLocation(presLoc.x, presLoc.y);
}
}
public void setId(int id) {
getGameObject().setId(id);
}
public int getId() {
return getGameObject().getId();
}
public boolean isResizing() {
return resizing;
}
protected void setResizing(boolean res) {
this.resizing = res;
if (res)
((ManipulationTool) Manager.getSelectedTool()).setStateRoomResize();
// Tool returns to normal state on its own
}
public boolean canResize() {
return canResize;
}
public Point getResizeAnchor() {
return Utils.copy(resizeAnchor);
}
@Override
public void setHighlighted(boolean high) {
super.setHighlighted(high && !selected);
}
@Override
public void mouseDown(MouseEvent e) {
if (Manager.getSelectedToolId() == Tools.POINTER) {
if (e.button == 1 && isSelected() && !isPinned()) {
for (int i = 0; i < 4; i++) {
if (getView().getResizeHandles()[i].contains(e.x, e.y)) {
resizeAnchor = Grid.getInstance().snapToGrid(getView().getResizeHandles()[3 - i].getCenter(), Snapmodes.CROSS);
canResize = true;
setResizing(true);
break;
}
}
}
super.mouseDown(e);
if (currentEdit != null) {
Action a = new Action() {
public void execute() {
container.getParent().updateSidebarContent();
container.updateBoundingArea();
}
};
currentEdit.setUndoCallback(a);
currentEdit.setRedoCallback(a);
}
}
}
@Override
public void mouseUp(MouseEvent e) {
if (Manager.getSelectedToolId() == Tools.POINTER) {
super.mouseUp(e);
if (e.button == 1) {
if (isResizing()) {
// Confirm resize
setResizing(false);
if (canResize) {
// If the area is clear, resize and reposition the room
Point resLoc = CursorController.getInstance().getLocation();
Point resSize = CursorController.getInstance().getSize();
int w = resSize.x;
int h = resSize.y;
w = (w + 1) / ShipContainer.CELL_SIZE;
h = (h + 1) / ShipContainer.CELL_SIZE;
resLoc = Grid.getInstance().snapToGrid(resLoc, getSnapMode(w, h));
resSize = Grid.getInstance().snapToGrid(resSize, Snapmodes.CROSS);
UndoableResizeEdit edit = new UndoableResizeEdit(this);
edit.setOld(new Tuple<Point, Point>(getLocation(), getSize()));
edit.setCurrent(new Tuple<Point, Point>(resLoc, resSize));
setVisible(false);
setSize(resSize);
setLocation(resLoc);
setVisible(true);
setFollowOffset(getX() - getParent().getX(), getY() - getParent().getY());
container.getParent().updateSidebarContent();
container.updateBoundingArea();
Action a = new Action() {
public void execute() {
container.getParent().updateSidebarContent();
container.updateBoundingArea();
}
};
edit.setUndoCallback(a);
edit.setRedoCallback(a);
container.postEdit(edit);
}
updateView();
}
container.getParent().updateSidebarContent();
} else if (e.button == 3) {
if (!isSelected())
Manager.setSelected(this);
if (resizing) {
setResizing(false);
setMoving(false);
} else if (selected && getBounds().contains(e.x, e.y)) {
// Open system popup menu
SystemsMenu sysMenu = new SystemsMenu(container.getParent().getShell(), this);
sysMenu.open();
}
}
}
}
@Override
public void mouseMove(MouseEvent e) {
// super.mouseMove() only handles box movement -- we're implementing a different
// handling of that functionality here, so we don't call super.mouseMove()
if (Manager.getSelectedToolId() == Tools.POINTER) {
if (Manager.leftMouseDown && selected) {
if (resizing) {
Rectangle resizeBounds = CursorController.getInstance().getDimensions();
canResize = true;
if (resizeBounds.x < getParent().getX() || resizeBounds.y < getParent().getY())
canResize = false;
// calculate collision with other boxes on the same layer
ArrayList<AbstractController> layer = LayeredPainter.getInstance().getLayerMap().get(Layers.ROOM);
for (int i = 0; i < layer.size() && canResize; i++) {
AbstractController c = layer.get(i);
if (c != this && c.isVisible() && c.collides(resizeBounds))
canResize = false;
}
CursorController.getInstance().updateView();
} else if (moving) {
// Decide direction for mono-directional dragging
if (monoDirectionalDrag && lockDragTo == null) {
int d = Utils.distance(getX() + clickOffset.x, getY() + clickOffset.y, e.x, e.y);
// Have the user drag more than 2px away from click point to determine the direction
// Not noticeable to the user, but increases the chance of correct detection
if (d > 2) {
Point click = new Point(getX() + clickOffset.x, getY() + clickOffset.y);
Point cursor = new Point(e.x, e.y);
double angle = (Utils.angle(click, cursor) + 90) % 360;
if ((angle >= 315 || angle < 45) || (angle >= 135 && angle < 225)) {
lockDragTo = Orientations.HORIZONTAL;
} else {
lockDragTo = Orientations.VERTICAL;
}
}
} else {
Point p = Grid.getInstance().snapToGrid(e.x - clickOffset.x - getW() / 2, e.y - clickOffset.y - getH() / 2, Snapmodes.CROSS);
Rectangle checkBounds = new Rectangle(p.x, p.y, getW(), getH());
p = Grid.getInstance().snapToGrid(checkBounds.x + getW() / 2, checkBounds.y + getH() / 2, snapmode);
if (lockDragTo == Orientations.HORIZONTAL) {
p.y = getY();
} else if (lockDragTo == Orientations.VERTICAL) {
p.x = getX();
}
// proceed only if the position actually changed
if (p.x != getX() || p.y != getY()) {
reposition(p.x, p.y);
updateView();
updateFollowOffset();
container.updateBoundingArea();
}
}
}
}
}
}
@Override
public int compareTo(RoomController o) {
return getGameObject().compareTo(o.getGameObject());
}
@Override
public DataComposite getDataComposite(Composite parent) {
return new RoomDataComposite(parent, this);
}
/**
* @param id
* the slot id that is to be checked
* @return true if the room can contain station at the given slot, false otherwise.
*/
public boolean canContainSlotId(int slotId) {
Rectangle bounds = getBounds();
int w = bounds.width / ShipContainer.CELL_SIZE;
int h = bounds.height / ShipContainer.CELL_SIZE;
return w + (h - 1) * w > slotId && slotId >= 0;
}
/**
* Returns the location of the given slot id relative to the room's top left corner.
*
* @param slotId
* id of the slot
* @return location of the slot relative to room's top left corner,
* or null if the slot can't be contained
*/
public Point getSlotLocation(int slotId) {
Point size = getSize();
int w = size.x / ShipContainer.CELL_SIZE;
int h = size.y / ShipContainer.CELL_SIZE;
// can't contain the slot
if (w + (h - 1) * w <= slotId || slotId < 0)
return null;
int x = slotId % w;
int y = slotId / w;
return new Point(x * ShipContainer.CELL_SIZE + ShipContainer.CELL_SIZE / 2 + 1, y * ShipContainer.CELL_SIZE + ShipContainer.CELL_SIZE / 2 + 1);
}
/** Returns the slot id of the tile at the given coordinates (relative to the canvas). */
public int getSlotId(int x, int y) {
Rectangle bounds = getBounds();
if (!bounds.contains(x, y))
return -1;
x = (x - bounds.x) / ShipContainer.CELL_SIZE;
y = (y - bounds.y) / ShipContainer.CELL_SIZE;
return x + (y * (bounds.width / ShipContainer.CELL_SIZE));
}
@Override
public void select() {
super.select();
updateView();
redraw();
}
@Override
public void deselect() {
super.deselect();
setMoving(false);
setResizing(false);
updateView();
redraw();
}
@Override
public void dispose() {
if (model.isDisposed())
return;
super.dispose();
}
@Override
public void updateBoundingArea() {
Point gridSize = Grid.getInstance().getSize();
gridSize.x -= (gridSize.x % ShipContainer.CELL_SIZE) + ShipContainer.CELL_SIZE;
gridSize.y -= (gridSize.y % ShipContainer.CELL_SIZE) + ShipContainer.CELL_SIZE;
// rooms with odd-numbered sizes bigger than 1 are misaligned by 1 pixel, add correction
setBoundingPoints(getParent().getX() + getW() / 2, getParent().getY() + getH() / 2,
gridSize.x - getW() / 2 - (getW() > ShipContainer.CELL_SIZE && getW() % (2 * ShipContainer.CELL_SIZE) != 0 ? 1 : 0),
gridSize.y - getH() / 2 - (getH() > ShipContainer.CELL_SIZE && getH() % (2 * ShipContainer.CELL_SIZE) != 0 ? 1 : 0));
}
@Override
public String toString() {
RoomObject room = getGameObject();
return room.toString();
}
}