package com.kartoflane.superluminal2.tools; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import com.kartoflane.superluminal2.core.Database; 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.mvc.controllers.AbstractController; import com.kartoflane.superluminal2.mvc.controllers.DoorController; import com.kartoflane.superluminal2.mvc.controllers.GibController; import com.kartoflane.superluminal2.mvc.controllers.MountController; import com.kartoflane.superluminal2.mvc.controllers.RoomController; import com.kartoflane.superluminal2.ui.EditorWindow; import com.kartoflane.superluminal2.ui.OverviewWindow; import com.kartoflane.superluminal2.ui.ShipContainer; import com.kartoflane.superluminal2.ui.sidebar.ManipulationToolComposite; import com.kartoflane.superluminal2.undo.UndoableDoorLinkEdit; import com.kartoflane.superluminal2.undo.UndoableGibLinkEdit; public class ManipulationTool extends Tool { private enum States { NORMAL, ROOM_RESIZE, DOOR_LINK_LEFT, DOOR_LINK_RIGHT, MOUNT_GIB_LINK } private States state = States.NORMAL; private Point clickPoint = new Point(0, 0); public ManipulationTool(EditorWindow window) { super(window); } @Override public void select() { window.disposeSidebarContent(); ManipulationToolComposite pointerC = getToolComposite(window.getSidebarWidget()); window.setSidebarContent(pointerC); OverviewWindow.staticUpdate(); Manager.setSelected(null); setStateManipulate(); cursor.setSize(ShipContainer.CELL_SIZE, ShipContainer.CELL_SIZE); cursor.setVisible(false); } @Override public void deselect() { Manager.setSelected(null); AbstractController control = LayeredPainter.getInstance().getSelectableControllerAt(cursor.getMouseLocation()); if (control != null) { control.setHighlighted(false); } cursor.setVisible(false); } @Override public ManipulationToolComposite getToolComposite(Composite parent) { return (ManipulationToolComposite) super.getToolComposite(parent); } @Override public ManipulationToolComposite createToolComposite(Composite parent) { if (parent == null) throw new IllegalArgumentException("Parent must not be null."); compositeInstance = new ManipulationToolComposite(parent, true, false); return (ManipulationToolComposite) compositeInstance; } public void setStateManipulate() { state = States.NORMAL; cursor.updateView(); } public boolean isStateManipulate() { return state == States.NORMAL; } public void setStateDoorLinkLeft() { state = States.DOOR_LINK_LEFT; cursor.updateView(); } public void setStateDoorLinkRight() { state = States.DOOR_LINK_RIGHT; cursor.updateView(); } public boolean isStateDoorLinkLeft() { return state == States.DOOR_LINK_LEFT; } public boolean isStateDoorLinkRight() { return state == States.DOOR_LINK_RIGHT; } public void setStateMountGibLink() { state = States.MOUNT_GIB_LINK; cursor.updateView(); } public boolean isStateMountGibLink() { return state == States.MOUNT_GIB_LINK; } public void setStateRoomResize() { state = States.ROOM_RESIZE; cursor.updateView(); } public boolean isStateRoomResize() { return state == States.ROOM_RESIZE; } @Override public void mouseDoubleClick(MouseEvent e) { } @Override public void mouseDown(MouseEvent e) { clickPoint.x = e.x; clickPoint.y = e.y; AbstractController selected = Manager.getSelected(); if (state == States.NORMAL) { // get the controller at the mouse click pos, and if found, notify it about mouseDown event AbstractController controller = getTopmostSelectableController(e.x, e.y); if (controller != null) controller.mouseDown(e); else if (selected != null) selected.mouseDown(e); } // not using else if here, since a state change can occur in the previous step if (state == States.ROOM_RESIZE) { RoomController room = (RoomController) selected; cursor.updateView(); if (e.button == 3) cursor.setVisible(false); cursor.resize(room.getSize()); cursor.reposition(room.getLocation()); } else if (state == States.DOOR_LINK_LEFT || state == States.DOOR_LINK_RIGHT) { // get the controller at the mouse click pos AbstractController control = null; for (int i = selectableLayerIds.length - 1; i >= 0; i--) { if (selectableLayerIds[i] != null) { control = LayeredPainter.getInstance().getSelectableControllerAt(e.x, e.y, selectableLayerIds[i]); if (control != null && control instanceof RoomController) break; } } linkDoor(selected, control); } else if (state == States.MOUNT_GIB_LINK) { // get the controller at the mouse click pos, and if found, notify it about mouseDown event AbstractController control = null; for (int i = selectableLayerIds.length - 1; i >= 0; i--) { if (selectableLayerIds[i] != null) { control = LayeredPainter.getInstance().getSelectableControllerAt(e.x, e.y, selectableLayerIds[i]); if (control != null && control instanceof GibController) break; } } linkGib(selected, control); } // handle cursor if (cursor.isVisible() && e.button == 1) { cursor.setVisible(state == States.ROOM_RESIZE); } } @Override public void mouseUp(MouseEvent e) { AbstractController controller = null; // inform the selected controller about clicks, so that it can be deselected // prevents a bug: // --controllers A and B are on the same layer, but A is "higher" than B // --dragging B under A, and releasing mouse button while cursor is within A's bounds, causes B to become stuck to the mouse pointer if (Manager.getSelected() != null) Manager.getSelected().mouseUp(e); if (state == States.NORMAL) { // find the topmost selectable controller and notify it about mouseUp event for (int i = selectableLayerIds.length - 1; i >= 0; i--) { if (selectableLayerIds[i] != null) { controller = LayeredPainter.getInstance().getSelectableControllerAt(e.x, e.y, selectableLayerIds[i]); if (controller != null && controller != Manager.getSelected()) { controller.mouseUp(e); break; } } } Point p = Grid.getInstance().snapToGrid(e.x, e.y, cursor.getSnapMode()); cursor.updateView(); cursor.reposition(p.x, p.y); } else if (state == States.ROOM_RESIZE) { RoomController room = (RoomController) Manager.getSelected(); Point p = Grid.getInstance().snapToGrid(e.x, e.y, cursor.getSnapMode()); cursor.setVisible(false); cursor.redraw(); cursor.updateView(); cursor.resize(ShipContainer.CELL_SIZE, ShipContainer.CELL_SIZE); cursor.reposition(p.x, p.y); if (!room.isResizing()) setStateManipulate(); } } @Override public void mouseMove(MouseEvent e) { super.mouseMove(e); // faciliate mouseEnter event for controllers AbstractController controller = getTopmostSelectableController(e.x, e.y); if (controller != null) { // mouseEnter event also highlights the controller, so check if its not highlighted already if (!controller.isHighlighted() && (Manager.getSelected() == null || !Manager.getSelected().isMoving())) controller.mouseEnter(e); } if (state == States.NORMAL) { // move the cursor around to follow mouse Point p = Grid.getInstance().snapToGrid(e.x, e.y, cursor.getSnapMode()); // always redraw it - prevents an odd visual bug where the controller sometimes doesn't register that it was moved cursor.reposition(p.x, p.y); cursor.updateView(); } else if (state == States.ROOM_RESIZE) { RoomController room = (RoomController) Manager.getSelected(); Point resizeAnchor = room.getResizeAnchor(); // choose appropriate snapmode depending on the direction in which the user is resizing Snapmodes resizeSnapmode = null; if (e.x >= resizeAnchor.x && e.y >= resizeAnchor.y) resizeSnapmode = Snapmodes.CORNER_BR; else if (e.x >= resizeAnchor.x && e.y < resizeAnchor.y) resizeSnapmode = Snapmodes.CORNER_TR; else if (e.x < resizeAnchor.x && e.y >= resizeAnchor.y) resizeSnapmode = Snapmodes.CORNER_BL; else resizeSnapmode = Snapmodes.CORNER_TL; Point pointer = Grid.getInstance().snapToGrid(e.x, e.y, resizeSnapmode); Point anchor = new Point(0, 0); if (pointer.x < resizeAnchor.x) // if mouse is to the left of the anchor... anchor.x = resizeAnchor.x + (clickPoint.x > resizeAnchor.x ? ShipContainer.CELL_SIZE : 0); else // if mouse is to the right of the anchor... anchor.x = resizeAnchor.x + (clickPoint.x > resizeAnchor.x ? 0 : -ShipContainer.CELL_SIZE); if (pointer.y < resizeAnchor.y) // if mouse is above the anchor... anchor.y = resizeAnchor.y + (clickPoint.y > resizeAnchor.y ? ShipContainer.CELL_SIZE : 0); else // if mouse is below the anchor... anchor.y = resizeAnchor.y + (clickPoint.y > resizeAnchor.y ? 0 : -ShipContainer.CELL_SIZE); anchor = Grid.getInstance().snapToGrid(anchor.x, anchor.y, Snapmodes.CROSS); int x = Math.min(anchor.x, pointer.x); int y = Math.min(anchor.y, pointer.y); int w = Math.max(Math.abs(anchor.x - pointer.x), ShipContainer.CELL_SIZE); int h = Math.max(Math.abs(anchor.y - pointer.y), ShipContainer.CELL_SIZE); if (cursor.getW() != w || cursor.getH() != h) { cursor.updateView(); cursor.resize(w, h); cursor.reposition(x + w / 2, y + h / 2); } if (!room.isResizing()) setStateManipulate(); } else if (state == States.DOOR_LINK_LEFT || state == States.DOOR_LINK_RIGHT || state == States.MOUNT_GIB_LINK) { // move the cursor around to follow mouse Point p = Grid.getInstance().snapToGrid(e.x, e.y, cursor.getSnapMode()); // always redraw it - prevents an odd visual bug where the controller sometimes doesn't register that it was moved cursor.reposition(p.x, p.y); cursor.updateView(); } // faciliate mouseExit event for controllers for (Layers layer : LayeredPainter.getInstance().getLayerMap().descendingKeySet()) { for (AbstractController control : LayeredPainter.getInstance().getLayerMap().get(layer)) { if (control != null && control.isVisible()) { // send mouseExit event to controllers that are obscured by other controllers // if: // - control is selectable AND // --- control doesn't contain the mouse pointer OR // ----- control is not the same object as controller AND // ----- both control and controller contain the mouse pointer if (control.isSelectable() && ((!control.contains(e.x, e.y)) || (controller != null && control != controller && control.contains(e.x, e.y) && controller.contains(e.x, e.y)))) control.mouseExit(e); // also pass on mouseMove event control.mouseMove(e); } } } } @Override public void mouseEnter(MouseEvent e) { cursor.setVisible(!Manager.leftMouseDown); } @Override public void mouseExit(MouseEvent e) { cursor.setVisible(false); for (Layers layer : LayeredPainter.getInstance().getLayerMap().descendingKeySet()) { for (AbstractController control : LayeredPainter.getInstance().getLayerMap().get(layer)) { if (control != null && control.isVisible()) { control.mouseExit(e); } } } } @Override public void mouseHover(MouseEvent e) { } private AbstractController getTopmostSelectableController(int x, int y) { AbstractController controller = null; for (int i = selectableLayerIds.length - 1; i >= 0 && controller == null; i--) { if (selectableLayerIds[i] != null) controller = LayeredPainter.getInstance().getSelectableControllerAt(x, y, selectableLayerIds[i]); } return controller; } public void linkDoor(AbstractController door, AbstractController room) { if (door instanceof DoorController == false) { setStateManipulate(); return; } DoorController doorC = (DoorController) door; RoomController roomC = null; if (room instanceof RoomController) roomC = (RoomController) room; if (state == States.DOOR_LINK_LEFT) { UndoableDoorLinkEdit edit = new UndoableDoorLinkEdit(doorC, true); edit.setOld(doorC.getLeftRoom()); doorC.setLeftRoom(roomC == null ? null : roomC.getGameObject()); edit.setCurrent(doorC.getLeftRoom()); Manager.getCurrentShip().postEdit(edit); } else { UndoableDoorLinkEdit edit = new UndoableDoorLinkEdit(doorC, false); edit.setOld(doorC.getRightRoom()); doorC.setRightRoom(roomC == null ? null : roomC.getGameObject()); edit.setCurrent(doorC.getRightRoom()); Manager.getCurrentShip().postEdit(edit); } EditorWindow.getInstance().updateSidebarContent(); setStateManipulate(); } public void linkGib(AbstractController mount, AbstractController gib) { if (mount instanceof MountController == false) { setStateManipulate(); return; } MountController mountC = (MountController) mount; GibController gibC = null; if (gib instanceof GibController) gibC = (GibController) gib; // Can be null if the the user hides the mount/mount layer during linking if (mountC != null) { UndoableGibLinkEdit edit = new UndoableGibLinkEdit(mountC); edit.setOld(mountC.getGib()); mountC.setGib(gibC == null ? Database.DEFAULT_GIB_OBJ : gibC.getGameObject()); edit.setCurrent(mountC.getGib()); Manager.getCurrentShip().postEdit(edit); } EditorWindow.getInstance().updateSidebarContent(); setStateManipulate(); } }