/*
This file contains proprietary software owned by Motorola Mobility, Inc.
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
*/
var ShapeTool = require("js/tools/ShapeTool").ShapeTool;
var ShapesController = require("js/controllers/elements/shapes-controller").ShapesController;
var DrawingToolBase = require("js/tools/drawing-tool-base").DrawingToolBase;
var defaultEventManager = require("montage/core/event/event-manager").defaultEventManager;
var Montage = require("montage/core/core").Montage;
var NJUtils = require("js/lib/NJUtils").NJUtils;
var ElementMediator = require("js/mediators/element-mediator").ElementMediator;
var TagTool = require("js/tools/TagTool").TagTool;
var ElementController = require("js/controllers/elements/element-controller").ElementController;
var snapManager = require("js/helper-classes/3D/snap-manager").SnapManager;
var AnchorPoint = require("js/lib/geom/anchor-point").AnchorPoint;
var SubPath = require("js/lib/geom/sub-path").SubPath;
//todo remove this global var
var g_DoPenToolMouseMove = true;
exports.PenTool = Montage.create(ShapeTool, {
_toolID: { value: "penTool" },
_imageID: { value: "penToolImg" },
_toolImageClass: { value: "penToolUp" },
_selectedToolImageClass: { value: "penToolDown" },
_toolTipText: { value: "Pen Tool" },
_penView: { value: null, writable: true },
_selectedToolClass: { value: "penToolSpecificProperties" },
_penToolProperties: { enumerable: false, value: null, writable: true },
_parentNode: { enumerable: false, value: null, writable: true },
_toolsPropertiesContainer: { enumerable: false, value: null, writable: true },
// Need to keep track of current mouse position for KEY modifiers event which do not have mouse coordinates
_currentX: { value: 0, writable: true },
_currentY: { value: 0, writable: true },
//the subpaths are what is displayed on the screen currently, with _selectedSubpath being the active one currently being edited
_selectedSubpath: { value: null, writable: true },
_makeMultipleSubpaths: { value: true, writable: true }, //set this to true if you want to keep making subpaths after closing current subpath
//whether the user has held down the Alt key
_isAltDown: { value: false, writable: true },
//whether the user has held down the Esc key
_isEscapeDown: {value: false, writable: true },
//whether we have just started a new path (set true in mousedown, and set false in mouse up
_isNewPath: {value: false, writable: true},
//whether we have clicked one of the endpoints after entering the pen tool in ENTRY_SELECT_PATH edit mode
_isPickedEndPointInSelectPathMode: {value: false, writable: true},
//when the user wants to place a selected anchor point on top of another point, this is the target where the point will be placed
_snapTarget: { value: null, writable: true },
//whether or not we're using webgl for drawing
_useWebGL: {value: false, writable: false },
//the canvas created by the pen tool...this is grown or shrunk with the path (if the canvas was not already provided)
_penCanvas: { value: null, writable: true },
//the plane matrix for the first click...so the entire path is on the same plane
_penPlaneMat: { value: null, writable: true },
//index of the anchor point that the user has hovered over
_hoveredAnchorIndex: {value: -1, writable: true},
//constants used for picking points --- NOTE: these should be user-settable parameters
_PICK_POINT_RADIUS: { value: 10, writable: false },
_DISPLAY_ANCHOR_RADIUS: { value: 5, writable: false },
_DISPLAY_SELECTED_ANCHOR_RADIUS: { value: 10, writable: false },
_DISPLAY_SELECTED_ANCHOR_PREV_RADIUS: { value: 2, writable: false },
_DISPLAY_SELECTED_ANCHOR_NEXT_RADIUS: { value: 2, writable: false },
//constants used for editing modes (can be OR-ed)
EDIT_NONE: { value: 0, writable: false },
EDIT_ANCHOR: { value: 1, writable: false },
EDIT_PREV: { value: 2, writable: false },
EDIT_NEXT: { value: 4, writable: false },
EDIT_PREV_NEXT: { value: 8, writable: false },
_editMode: { value: this.EDIT_NONE, writable: true },
//constants used for selection modes on entry to pen tool (mutually exclusive)
ENTRY_SELECT_NONE: { value: 0, writable: false},
ENTRY_SELECT_CANVAS: { value: 1, writable: false},
ENTRY_SELECT_PATH: { value: 2, writable: false},
_entryEditMode: {value: this.ENTRY_SELECT_NONE, writable: true},
_getUnsnappedPosition: {
value: function(x,y){
var elemSnap = snapManager.elementSnapEnabled();
var gridSnap = snapManager.gridSnapEnabled();
var alignSnap = snapManager.snapAlignEnabled();
snapManager.enableElementSnap(false);
snapManager.enableGridSnap(false);
snapManager.enableSnapAlign(false);
var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(x,y));
var unsnappedpos = DrawingToolBase.getHitRecPos(snapManager.snap(point.x, point.y, false));
snapManager.enableElementSnap(elemSnap);
snapManager.enableGridSnap(gridSnap);
snapManager.enableSnapAlign(alignSnap);
return unsnappedpos;
}
},
ShowToolProperties: {
value: function () {
this._penView = PenView.create();
this._penView.element = document.getElementById('topPanelContainer').children[0];
this._penView.needsDraw = true;
this._penView.addEventListener(ToolEvents.TOOL_OPTION_CHANGE, this, false);
}
},
HandleLeftButtonDown:
{
value: function (event) {
//ignore any right or middle clicks
if (event.button !== 0) {
//NOTE: this will work on Webkit only...IE has different codes (left: 1, middle: 4, right: 2)
return;
}
if (this._canDraw) {
this._isDrawing = true;
}
this.startDraw(event);
//assume we are not starting a new path as we will set this to true if we create a new GLSubpath()
this._isNewPath = false;
//add an anchor point by computing position of mouse down
var mouseDownPos = this._getUnsnappedPosition(event.pageX, event.pageY); //this.getMouseDownPos();
if (mouseDownPos) {
//if we had closed the selected subpath previously, or if we have not yet started anything, create a subpath
if (this._selectedSubpath === null) {
this._selectedSubpath = new SubPath();
this._isNewPath = true;
if (this._entryEditMode === this.ENTRY_SELECT_PATH){
//this should not happen, as ENTRY_SELECT_PATH implies there was a selected subpath
this._entryEditMode = this.ENTRY_SELECT_NONE;
console.log("Warning...PenTool handleMouseDown: changing from SELECT_PATH to SELECT_NONE");
}
}
var prevSelectedAnchorIndex = this._selectedSubpath.getSelectedAnchorIndex();
// ************* Add/Select Anchor Point *************
//check if the clicked location is close to an anchor point...if so, make that anchor the selected point and do nothing else
// BUT if the anchor point selected is the first anchor point, check if the previous selected anchor was the last anchor point...in that case, close the path
var selParam = this._selectedSubpath.pickPath(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2], this._PICK_POINT_RADIUS);
var whichPoint = this._selectedSubpath.getSelectedMode();
if (whichPoint & this._selectedSubpath.SEL_ANCHOR) {
//if we're in ENTRY_SELECT_PATH mode AND we have not yet clicked on the endpoint AND if we have now clicked on the endpoint
if (this._entryEditMode === this.ENTRY_SELECT_PATH && this._isPickedEndPointInSelectPathMode === false){
var selAnchorIndex = this._selectedSubpath.getSelectedAnchorIndex();
if (selAnchorIndex===0 || selAnchorIndex===this._selectedSubpath.getNumAnchors()-1){
//we have picked the endpoint of this path...reverse the path if necessary
if (selAnchorIndex ===0){
//reverse this path
this._selectedSubpath.reversePath();
selAnchorIndex = this._selectedSubpath.getSelectedAnchorIndex();
}
this._isPickedEndPointInSelectPathMode = true;
}
}
this._editMode = this.EDIT_ANCHOR;
//if we have selected the first anchor point, and previously had selected the last anchor point, close the path
var numAnchors = this._selectedSubpath.getNumAnchors();
if (numAnchors>1 && !this._selectedSubpath.getIsClosed() && this._selectedSubpath.getSelectedAnchorIndex()===0 && prevSelectedAnchorIndex === numAnchors-1){
//setting the selection mode to NONE will effectively add a new anchor point at the click location and also give us snapping
whichPoint = this._selectedSubpath.SEL_NONE;
//set the snap target in case the mouse move handler doesn't get called
this._snapTarget = this._selectedSubpath.getAnchor(0);
}
}
//check if the clicked location is close to prev and next of the selected anchor point..if so select that anchor, set mode to PREV or NEXT and do nothing else
// but if the selectedAnchor index is not -1 and neither prev nor next are selected, it means click selected a point selParam along bezier segment starting at selectedAnchor
else if (this._selectedSubpath.getSelectedAnchorIndex() !== -1) {
if (whichPoint & this._selectedSubpath.SEL_PREV){
this._editMode = this.EDIT_PREV;
}
else if (whichPoint & this._selectedSubpath.SEL_NEXT){
this._editMode = this.EDIT_NEXT;
}
else if (whichPoint & this._selectedSubpath.SEL_PATH) {
//the click point is close enough to insert point in bezier segment after selected anchor at selParam
if (selParam > 0 && selParam < 1) {
this._selectedSubpath.insertAnchorAtParameter(this._selectedSubpath.getSelectedAnchorIndex(), selParam);
//set the mode so that dragging will update anchor point positions
//this._editMode = this.EDIT_ANCHOR;
}
}
}
//the clicked location is not close to the path or any anchor point
if (whichPoint === this._selectedSubpath.SEL_NONE) {
if (this._entryEditMode !== this.ENTRY_SELECT_PATH) {
//since we're not in ENTRY_SELECT_PATH mode, we don't edit the closed path...we start a new path if we clicked far away from selected path
if (this._selectedSubpath.getIsClosed() && this._makeMultipleSubpaths) {
this._penCanvas = null;
this._penPlaneMat = null;
this._snapTarget = null;
this._selectedSubpath = new SubPath();
this._isNewPath = true;
}
//add an anchor point to end of the subpath, and make it the selected anchor point
if (!this._selectedSubpath.getIsClosed() || this._makeMultipleSubpaths) {
this._selectedSubpath.addAnchor(new AnchorPoint());
var newAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex());
newAnchor.setPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]);
newAnchor.setPrevPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]);
newAnchor.setNextPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]);
//set the mode so that dragging will update the next and previous locations
this._editMode = this.EDIT_PREV_NEXT;
}
} else {
if (this._isPickedEndPointInSelectPathMode){
//TODO clean up this code...very similar to the code block above
if (!this._selectedSubpath.getIsClosed()) {
this._selectedSubpath.addAnchor(new AnchorPoint());
var newAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex());
newAnchor.setPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]);
newAnchor.setPrevPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]);
newAnchor.setNextPos(mouseDownPos[0], mouseDownPos[1], mouseDownPos[2]);
//set the mode so that dragging will update the next and previous locations
this._editMode = this.EDIT_PREV_NEXT;
}
}
}
} //if (whichPoint === this._selectedSubpath.SEL_NONE) (i.e. no anchor point was selected)
//display the curve overlay
this.DrawSubpathAnchors(this._selectedSubpath);
this.DrawSubpathSVG(this._selectedSubpath);
} //if (mouseDownPos) { i.e. if mouse down yielded a valid position
if (!g_DoPenToolMouseMove){
NJevent("enableStageMove");
}
this._hoveredAnchorIndex = -1;
} //value: function (event) {
}, //HandleLeftButtonDown
//need to override this function because the ShapeTool's definition contains a clearDrawingCanvas call - Pushkar
// might not need to override once we draw using OpenGL instead of SVG
// Also took out all the snapping code for now...need to add that back
HandleMouseMove:
{
value: function (event) {
//ignore any right or middle clicks
if (event.button !== 0) {
//NOTE: this will work on Webkit only...IE has different codes (left: 1, middle: 4, right: 2)
return;
}
//clear the canvas before we draw anything else
this.application.ninja.stage.clearDrawingCanvas();
this._hoveredAnchorIndex = -1;
//set the cursor to be the default cursor
this.application.ninja.stage.drawingCanvas.style.cursor = "auto";
if (this._isDrawing) {
var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(event.pageX, event.pageY));
//go through the drawing toolbase to get the position of the mouse
var currMousePos = DrawingToolBase.getHitRecPos(DrawingToolBase.getUpdatedSnapPoint(point.x, point.y, false, this.mouseDownHitRec));
if (currMousePos && this._selectedSubpath && (this._selectedSubpath.getSelectedAnchorIndex() >= 0 && this._selectedSubpath.getSelectedAnchorIndex() < this._selectedSubpath.getNumAnchors())) {
//var scoord = this._getScreenCoord(this._mouseUpHitRec);
var selAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex());
var selX = selAnchor.getPosX();
var selY = selAnchor.getPosY();
var selZ = selAnchor.getPosZ();
if (this._editMode & this.EDIT_ANCHOR) {
selAnchor.translateAll(currMousePos[0] - selX, currMousePos[1] - selY, currMousePos[2] - selZ);
}
else if (this._editMode & this.EDIT_PREV) {
var oldPX = selAnchor.getPrevX();
var oldPY = selAnchor.getPrevY();
var oldPZ = selAnchor.getPrevZ();
selAnchor.translatePrev(currMousePos[0] - oldPX, currMousePos[1] - oldPY, currMousePos[2] - oldPZ);
//move the next point if Alt key is down to ensure relative angle between prev and next
if (this._isAltDown) {
selAnchor.translateNextFromPrev(currMousePos[0] - oldPX, currMousePos[1] - oldPY, currMousePos[2] - oldPZ);
}
}
else if (this._editMode & this.EDIT_NEXT) {
var oldNX = selAnchor.getNextX();
var oldNY = selAnchor.getNextY();
var oldNZ = selAnchor.getNextZ();
selAnchor.translateNext(currMousePos[0] - oldNX, currMousePos[1] - oldNY, currMousePos[2] - oldNZ);
//move the prev point if Alt key is down to ensure relative angle between prev and next
if (this._isAltDown) {
selAnchor.translatePrevFromNext(currMousePos[0] - oldNX, currMousePos[1] - oldNY, currMousePos[2] - oldNZ);
}
}
else if (this._editMode & this.EDIT_PREV_NEXT) {
selAnchor.setNextPos(currMousePos[0], currMousePos[1], currMousePos[2]);
selAnchor.setPrevFromNext();
}
//snapping...check if the new location of the anchor point is close to another anchor point
this._snapTarget = null;
var numAnchors = this._selectedSubpath.getNumAnchors();
for (var i = 0; i < numAnchors; i++) {
//check if the selected anchor is close to any other anchors
if (i === this._selectedSubpath.getSelectedAnchorIndex())
continue;
var currAnchor = this._selectedSubpath.getAnchor(i);
var distSq = currAnchor.getDistanceSq(selX, selY, selZ);
if (distSq < this._PICK_POINT_RADIUS * this._PICK_POINT_RADIUS) {
//set the snap target to the location of the first close-enough anchor
this._snapTarget = currAnchor;
break;
}
}
//make the subpath dirty so it will get re-drawn
this._selectedSubpath.makeDirty();
this.DrawSubpathSVG(this._selectedSubpath);
}
} else { //if mouse is not down:
//this.doSnap(event);
//this.DrawHandles();
var currMousePos = this._getUnsnappedPosition(event.pageX, event.pageY);
if (currMousePos && this._selectedSubpath ){
var selAnchor = this._selectedSubpath.pickAnchor(currMousePos[0], currMousePos[1], currMousePos[2], this._PICK_POINT_RADIUS);
if (selAnchor >=0) {
this._hoveredAnchorIndex = selAnchor;
} else {
//detect if the current mouse position will hit the path
var pathHitTestData = this._selectedSubpath.pathHitTest(currMousePos[0], currMousePos[1], currMousePos[2], this._PICK_POINT_RADIUS);
if (pathHitTestData[0]!==-1){
//change the cursor
var cursor = "url('images/cursors/penAdd.png') 10 10,default";
this.application.ninja.stage.drawingCanvas.style.cursor = cursor;
}
}
}
} //else of if (this._isDrawing) {
//this.drawLastSnap(); // Required cleanup for both Draw/Feedbacks
if (this._selectedSubpath){
this.DrawSubpathAnchors(this._selectedSubpath);
}
}//value: function(event)
},//HandleMouseMove
TranslateSelectedSubpathPerPenCanvas:{
value: function() {
if (this._penCanvas!==null) {
//obtain the 2D translation of the canvas due to the Selection tool...assuming this is called in Configure
var penCanvasLeft = parseInt(ElementMediator.getProperty(this._penCanvas, "left"));//parseFloat(DocumentControllerModule.DocumentController.GetElementStyle(this._penCanvas, "left"));
var penCanvasTop = parseInt(ElementMediator.getProperty(this._penCanvas, "top"));//parseFloat(DocumentControllerModule.DocumentController.GetElementStyle(this._penCanvas, "top"));
var penCanvasWidth = parseInt(ElementMediator.getProperty(this._penCanvas, "width"));//this._penCanvas.width;
var penCanvasHeight = parseInt(ElementMediator.getProperty(this._penCanvas, "height"));//this._penCanvas.height;
var penCanvasOldX = penCanvasLeft + 0.5 * penCanvasWidth;
var penCanvasOldY = penCanvasTop + 0.5 * penCanvasHeight;
var translateCanvasX = penCanvasOldX - this._selectedSubpath.getCanvasX();
var translateCanvasY = penCanvasOldY - this._selectedSubpath.getCanvasY();
//update the canvasX and canvasY parameters for this subpath and also translate the subpath points (since they're stored in stage world space)
this._selectedSubpath.setCanvasX(translateCanvasX + this._selectedSubpath.getCanvasX());
this._selectedSubpath.setCanvasY(translateCanvasY + this._selectedSubpath.getCanvasY());
this._selectedSubpath.translateAnchors(translateCanvasX, translateCanvasY, 0);
this._selectedSubpath.createSamples(); //updates the bounding box
}
}
},
ShowSelectedSubpath:{
value: function() {
if (this._selectedSubpath){
this._selectedSubpath.createSamples(); //dirty bit is checked here
var bboxMin = this._selectedSubpath.getBBoxMin();
var bboxMax = this._selectedSubpath.getBBoxMax();
var bboxWidth = bboxMax[0] - bboxMin[0];
var bboxHeight = bboxMax[1] - bboxMin[1];
var bboxMid = [0.5 * (bboxMax[0] + bboxMin[0]), 0.5 * (bboxMax[1] + bboxMin[1]), 0.5 * (bboxMax[2] + bboxMin[2])];
this._selectedSubpath.setCanvasX(bboxMid[0]);
this._selectedSubpath.setCanvasY(bboxMid[1]);
//call render shape with the bbox width and height
this.RenderShape(bboxWidth, bboxHeight, this._penPlaneMat, bboxMid, this._penCanvas);
}
}
},
HandleLeftButtonUp: {
value: function (event) {
if (this._isAltDown) {
var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas, new WebKitPoint(event.pageX, event.pageY));
this.mouseUpHitRec = DrawingToolBase.getUpdatedSnapPoint(point.x, point.y, false, this.mouseDownHitRec);
}
else if (this._isDrawing) {
this.doDraw(event); //needed to get the mouse up point in case there was no mouse move
}
//snapping...if there was a snapTarget and a selected anchor, move the anchor to the snap target
if (this._snapTarget !== null && this._selectedSubpath && this._selectedSubpath.getSelectedAnchorIndex() !== -1) {
var selAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex());
selAnchor.setPos(this._snapTarget.getPosX(), this._snapTarget.getPosY(), this._snapTarget.getPosZ());
this._selectedSubpath.makeDirty();
//if the first or last anchor point were snapped for an open path, check if the first and last anchor point are at the same position
if (!this._selectedSubpath.getIsClosed()) {
var lastAnchorIndex = this._selectedSubpath.getNumAnchors() - 1;
var firstAnchor = this._selectedSubpath.getAnchor(0);
var lastAnchor = this._selectedSubpath.getAnchor(lastAnchorIndex);
if ((this._selectedSubpath.getSelectedAnchorIndex() === 0 && this._snapTarget === lastAnchor) || (this._selectedSubpath.getSelectedAnchorIndex() === lastAnchorIndex && this._snapTarget === firstAnchor)) {
this._selectedSubpath.setIsClosed(true);
//set the prev of the first anchor to the be prev of the last anchor
firstAnchor.setPrevPos(lastAnchor.getPrevX(), lastAnchor.getPrevY(), lastAnchor.getPrevZ());
//only if we have more than two anchor points, remove the last point
if (lastAnchorIndex > 1) {
//remove the last anchor from the subpath
this._selectedSubpath.removeAnchor(lastAnchorIndex);
} else {
//set the next of the last anchor to the be next of the first anchor
lastAnchor.setPrevPos(firstAnchor.getNextX(), firstAnchor.getNextY(), firstAnchor.getNextZ());
}
}
}
}
this._snapTarget = null;
var drawData = this.getDrawingData();
if (drawData) {
if (!this._penPlaneMat) {
this._penPlaneMat = drawData.planeMat;
}
if (this._isNewPath) {
var strokeSize = 1.0;//default stroke width
if (this.options.strokeSize) {
strokeSize = ShapesController.GetValueInPixels(this.options.strokeSize.value, this.options.strokeSize.units);
}
this._selectedSubpath.setStrokeWidth(strokeSize);
if (this.application.ninja.colorController.colorToolbar.stroke.webGlColor){
this._selectedSubpath.setStrokeColor(this.application.ninja.colorController.colorToolbar.stroke.webGlColor);
}
if (this.application.ninja.colorController.colorToolbar.fill.webGlColor){
this._selectedSubpath.setFillColor(this.application.ninja.colorController.colorToolbar.fill.webGlColor);
}
} //if this is a new path being rendered
this._selectedSubpath.makeDirty();
this._selectedSubpath.createSamples();
//if we have some samples to render...
if (this._selectedSubpath.getNumAnchors() > 1) {
this.ShowSelectedSubpath();
} //if (this._selectedSubpath.getNumPoints() > 0) {
} //if drawData
//always assume that we're not starting a new path anymore
this._isNewPath = false;
this.endDraw(event);
this._isDrawing = false;
this._hasDraw = false;
this._editMode = this.EDIT_NONE;
this.DrawHandles();
//if (this._entryEditMode === this.ENTRY_SELECT_PATH || !this._selectedSubpath.getIsClosed()){
if (this._selectedSubpath){
this.DrawSubpathAnchors(this._selectedSubpath);//render the subpath anchors on canvas (not GL)
}
//}
if (!g_DoPenToolMouseMove){
NJevent("disableStageMove");
}
this._hoveredAnchorIndex = -1;
}
},
//override the Alt key handlers from drawing-tool.js
HandleAltKeyDown: {
value: function (event) {
this._isAltDown = true;
}
},
HandleAltKeyUp: {
value: function (event) {
this._isAltDown = false;
}
},
HandleEscape: {
value: function(event) {
this._isEscapeDown = true;
//close the current subpath and reset the pen tool
this._penCanvas = null;
this._penPlaneMat = null;
this._snapTarget = null;
this._selectedSubpath = null;
this.application.ninja.stage.clearDrawingCanvas();
}
},
HandleKeyUp: {
value: function(event){
if (!(event.target instanceof HTMLInputElement)) {
if(event.altKey || event.keyCode === 18) { //for key up, the event.altKey is false when it should be true, so I also check keyCode
this.HandleAltKeyUp(event);
} else {
console.log("Pen tool Unhandled key up:", event.keyCode);
}
}
}
},
HandleKeyPress: {
value: function(event){
var inc, currentValue, moveCommand;
if (!(event.target instanceof HTMLInputElement)) {
if(event.shiftKey) {
this.HandleShiftKeyDown(event);
} else if(event.altKey) {
this.HandleAltKeyDown(event);
} else if (event.keyCode === Keyboard.SPACE) {
event.preventDefault();
this.HandleSpaceKeyDown(event);
} else if (event.keyCode == Keyboard.BACKSPACE || event.keyCode === Keyboard.DELETE) {
//this is probably unnecessary since we handle delete and backspace via the delete delegate
event.stopImmediatePropagation();
event.preventDefault();
} else if (event.keyCode === Keyboard.ESCAPE){
this.HandleEscape(event);
} else {
console.log("Pen tool Unhandled key press:", event.keyCode);
}
}
}
},
handleScroll: {
value: function(event) {
this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas();
this.DrawSubpathAnchors(this._selectedSubpath);
}
},
RenderShape: {
value: function (w, h, planeMat, midPt, canvas) {
if ((Math.floor(w) === 0) || (Math.floor(h) === 0)) {
return;
}
var left = Math.round(midPt[0] - 0.5 * w);
var top = Math.round(midPt[1] - 0.5 * h);
if (!canvas) {
var newCanvas = null;
newCanvas = NJUtils.makeNJElement("canvas", "Subpath", "shape", {"data-RDGE-id": NJUtils.generateRandom()}, true);
var elementModel = TagTool.makeElement(parseInt(w), parseInt(h), planeMat, midPt, newCanvas, this._useWebGL);
ElementMediator.addElements(newCanvas, elementModel.data, false);
// create all the GL stuff
var world = this.getGLWorld(newCanvas, this._useWebGL);//this.options.use3D);//this.CreateGLWorld(planeMat, midPt, newCanvas, this._useWebGL);//fillMaterial, strokeMaterial);
//store a reference to this newly created canvas
this._penCanvas = newCanvas;
var subpath = this._selectedSubpath; //new GLSubpath();
subpath.setWorld(world);
subpath.setCanvas(newCanvas);
subpath.setPlaneMatrix(planeMat);
var planeMatInv = glmat4.inverse( planeMat, [] );
subpath.setPlaneMatrixInverse(planeMatInv);
subpath.setPlaneCenter(midPt);
world.addObject(subpath);
world.render();
//TODO this will not work if there are multiple shapes in the same canvas
newCanvas.elementModel.shapeModel.GLGeomObj = subpath;
newCanvas.elementModel.shapeModel.shapeCount++;
if(newCanvas.elementModel.shapeModel.shapeCount === 1)
{
newCanvas.elementModel.selection = "Subpath";
newCanvas.elementModel.pi = "SubpathPi";
newCanvas.elementModel.shapeModel.strokeSize = this.options.strokeSize.value + " " + this.options.strokeSize.units;
var strokeColor = subpath.getStrokeColor();
newCanvas.elementModel.shapeModel.stroke = strokeColor;
if(strokeColor) {
newCanvas.elementModel.shapeModel.border = this.application.ninja.colorController.colorToolbar.stroke;
}
newCanvas.elementModel.shapeModel.strokeMaterial = subpath.getStrokeMaterial();
newCanvas.elementModel.shapeModel.GLGeomObj = subpath;
newCanvas.elementModel.shapeModel.useWebGl = this.options.use3D;
}
else
{
// TODO - update the shape's info only. shapeModel will likely need an array of shapes.
}
if(newCanvas.elementModel.isShape)
{
this.application.ninja.selectionController.selectElement(newCanvas);
}
} //if (!canvas) {
else {
var world = null;
if (canvas.elementModel.shapeModel && canvas.elementModel.shapeModel.GLWorld) {
world = canvas.elementModel.shapeModel.GLWorld;
} else {
world = this.getGLWorld(canvas, this._useWebGL);//this.options.use3D);//this.CreateGLWorld(planeMat, midPt, canvas, this._useWebGL);//fillMaterial, strokeMaterial);
}
if (this._entryEditMode !== this.ENTRY_SELECT_CANVAS){
//update the left and top of the canvas element
var canvasArray=[canvas];
ElementMediator.setProperty(canvasArray, "left", [parseInt(left)+"px"],"Changing", "penTool");//DocumentControllerModule.DocumentController.SetElementStyle(canvas, "left", parseInt(left) + "px");
ElementMediator.setProperty(canvasArray, "top", [parseInt(top) + "px"],"Changing", "penTool");//DocumentControllerModule.DocumentController.SetElementStyle(canvas, "top", parseInt(top) + "px");
ElementMediator.setProperty(canvasArray, "width", [w+"px"], "Changing", "penTool");//canvas.width = w;
ElementMediator.setProperty(canvasArray, "height", [h+"px"], "Changing", "penTool");//canvas.height = h;
//update the viewport and projection to reflect the new canvas width and height
world.setViewportFromCanvas(canvas);
if (this._useWebGL){
var cam = world.renderer.cameraManager().getActiveCamera();
cam.setPerspective(world.getFOV(), world.getAspect(), world.getZNear(), world.getZFar());
}
}
var subpath = this._selectedSubpath;
subpath.setDrawingTool(this);
subpath.setPlaneMatrix(planeMat);
var planeMatInv = glmat4.inverse( planeMat, [] );
subpath.setPlaneMatrixInverse(planeMatInv);
subpath.setPlaneCenter(midPt);
subpath.setWorld(world);
world.addIfNewObject(subpath);
world.render();
//TODO this will not work if there are multiple shapes in the same canvas
canvas.elementModel.shapeModel.GLGeomObj = subpath;
} //else of if (!canvas) {
} //value: function (w, h, planeMat, midPt, canvas) {
}, //RenderShape: {
BuildSecondCtrlPoint:{
value: function(p0, p2, p3) {
var baselineOrig = VecUtils.vecSubtract(3, p3, p0);
var baseline = VecUtils.vecNormalize(3, baselineOrig);
var delta = VecUtils.vecSubtract(3, p2, p3);
//component of the delta along baseline
var deltaB = baseline;
VecUtils.vecScale(3, deltaB, VecUtils.vecDot(3, baseline, delta));
//component of the delta orthogonal to baseline
var deltaO = VecUtils.vecSubtract(3, delta, deltaB);
var p1 = VecUtils.vecInterpolate(3, p0, p3, 0.3333);
p1= VecUtils.vecAdd(3, p1,deltaO);
return p1;
} //value: function(p0, p2, p3) {
}, //BuildSecondCtrlPoint:{
HandleDoubleClick: {
value: function () {
//if there is a selected anchor point
if (this._selectedSubpath && this._selectedSubpath.getSelectedAnchorIndex() !== -1) {
var selAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex());
var pos = [selAnchor.getPosX(), selAnchor.getPosY(), selAnchor.getPosZ()];
var distToPrev = selAnchor.getPrevDistanceSq(pos[0], pos[1], pos[2]);
var distToNext = selAnchor.getNextDistanceSq(pos[0], pos[1], pos[2]);
var threshSq = 0; // 4 * this._PICK_POINT_RADIUS * this._PICK_POINT_RADIUS;
//if either prev or next are within threshold distance to anchor position
if (distToPrev <= threshSq || distToNext <= threshSq) {
var haveNext = false;
var havePrev = false;
var numAnchors = this._selectedSubpath.getNumAnchors();
if (numAnchors>1 && (this._selectedSubpath.getSelectedAnchorIndex() < (numAnchors-1) || this._selectedSubpath.getIsClosed())){
//there is an anchor point after this one
var nextAnchor = null;
if (this._selectedSubpath.getSelectedAnchorIndex() < (numAnchors-1))
nextAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()+1);
else
nextAnchor = this._selectedSubpath.getAnchor(0);
var nextAnchorPrev = [nextAnchor.getPrevX(), nextAnchor.getPrevY(), nextAnchor.getPrevZ()];
var nextAnchorPos = [nextAnchor.getPosX(), nextAnchor.getPosY(), nextAnchor.getPosZ()];
var newNext = this.BuildSecondCtrlPoint(pos, nextAnchorPrev, nextAnchorPos);
selAnchor.setNextPos(newNext[0], newNext[1], newNext[2]);
//check if the next is still not over the threshSq..if so, add a constant horizontal amount
if (selAnchor.getNextDistanceSq(pos[0], pos[1], pos[2]) <= threshSq) {
selAnchor.setNextPos(newNext[0]+ (3 * this._PICK_POINT_RADIUS), newNext[1], newNext[2]);
}
haveNext = true;
}
if (numAnchors>1 && (this._selectedSubpath.getSelectedAnchorIndex() > 0 || this._selectedSubpath.getIsClosed())){
//there is an anchor point before this one
var prevAnchor = null;
if (this._selectedSubpath.getSelectedAnchorIndex() > 0)
prevAnchor = this._selectedSubpath.getAnchor(this._selectedSubpath.getSelectedAnchorIndex()-1);
else
prevAnchor = this._selectedSubpath.getAnchor(numAnchors-1);
var prevAnchorNext = [prevAnchor.getNextX(), prevAnchor.getNextY(), prevAnchor.getNextZ()];
var prevAnchorPos = [prevAnchor.getPosX(), prevAnchor.getPosY(), prevAnchor.getPosZ()];
var newPrev = this.BuildSecondCtrlPoint(pos, prevAnchorNext, prevAnchorPos);
selAnchor.setPrevPos(newPrev[0], newPrev[1], newPrev[2]);
//check if the prev is still not over the threshSq..if so, add a constant horizontal amount
if (selAnchor.getPrevDistanceSq(pos[0], pos[1], pos[2]) <= threshSq) {
selAnchor.setPrevPos(newPrev[0]+ (3 * this._PICK_POINT_RADIUS), newPrev[1], newPrev[2]);
}
havePrev = true;
}
if (haveNext && !havePrev){
selAnchor.setPrevFromNext();
} else if (havePrev && !haveNext){
selAnchor.setNextFromPrev();
} else if (!haveNext && !havePrev){
selAnchor.setNextPos(pos[0]+ (3 * this._PICK_POINT_RADIUS), pos[1], pos[2]);
selAnchor.setPrevFromNext();
}
} // if (distToPrev < threshSq) {
else {
//bring points close (to exactly same position)
selAnchor.setNextPos(pos[0], pos[1], pos[2]);
selAnchor.setPrevPos(pos[0], pos[1], pos[2]);
} // else of if (distToPrev < threshSq) {
this._selectedSubpath.makeDirty();
this._selectedSubpath.createSamples();
this.ShowSelectedSubpath();
//clear the canvas before we draw anything else
this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas();
this.DrawSubpathAnchors(this._selectedSubpath);
} //if (this._selectedSubpath && this._selectedSubpath.getSelectedAnchorIndex() !== -1)
} //value: function () {
}, //HandleDoubleClick: {
_getScreenCoord:
{
value: function (hitRec) {
var sRet = hitRec.getScreenPoint();
sRet[2] = 0;
return sRet;
}
}, //_getScreenCoord:
// DrawSubpathSVG
// Draw the subpath using the SVG drawing capability (i.e. not WebGL)
DrawSubpathSVG:
{
value: function (subpath) {
if (subpath === null)
return;
subpath.createSamples(); //dirty bit will be checked inside this function
var numAnchors = subpath.getNumAnchors();
if (numAnchors === 0)
return;
var ctx = this.application.ninja.stage.drawingContext;//stageManagerModule.stageManager.drawingContext;
if (ctx === null)
throw ("null drawing context in Pentool::DrawSubpathSVG");
ctx.save();
var horizontalOffset = this.application.ninja.stage.userContentLeft;
var verticalOffset = this.application.ninja.stage.userContentTop;
//display the subpath as a sequence of cubic beziers
ctx.lineWidth = 1;//TODO replace hardcoded stroke width with some programmatically set value (should not be same as stroke width)
ctx.strokeStyle = "green";
//if (subpath.getStrokeColor())
// ctx.strokeStyle = MathUtils.colorToHex( subpath.getStrokeColor() );
ctx.beginPath();
var p0x = subpath.getAnchor(0).getPosX()+ horizontalOffset;
var p0y = subpath.getAnchor(0).getPosY()+ verticalOffset;
ctx.moveTo(p0x, p0y);
for (var i = 1; i < numAnchors; i++) {
var p1x = subpath.getAnchor(i - 1).getNextX()+ horizontalOffset;
var p1y = subpath.getAnchor(i - 1).getNextY()+ verticalOffset;
var p2x = subpath.getAnchor(i).getPrevX()+ horizontalOffset;
var p2y = subpath.getAnchor(i).getPrevY()+ verticalOffset;
var p3x = subpath.getAnchor(i).getPosX()+ horizontalOffset;
var p3y = subpath.getAnchor(i).getPosY()+ verticalOffset;
ctx.bezierCurveTo(p1x, p1y, p2x, p2y, p3x, p3y);
}
if (subpath.getIsClosed()) {
var i = numAnchors - 1;
var p1x = subpath.getAnchor(i).getNextX()+ horizontalOffset;
var p1y = subpath.getAnchor(i).getNextY()+ verticalOffset;
var p2x = subpath.getAnchor(0).getPrevX()+ horizontalOffset;
var p2y = subpath.getAnchor(0).getPrevY()+ verticalOffset;
var p3x = subpath.getAnchor(0).getPosX()+ horizontalOffset;
var p3y = subpath.getAnchor(0).getPosY()+ verticalOffset;
ctx.bezierCurveTo(p1x, p1y, p2x, p2y, p3x, p3y);
}
ctx.stroke();
ctx.restore();
} //function (subpath)
}, //DrawSubpathSVG
DrawSubpathAnchors:
{
value: function (subpath) {
if (subpath === null)
return;
var numAnchors = subpath.getNumAnchors();
if (numAnchors === 0)
return;
var ctx = this.application.ninja.stage.drawingContext;//stageManagerModule.stageManager.drawingContext;
if (ctx === null)
throw ("null drawing context in Pentool::DrawSelectedSubpathAnchors");
ctx.save();
var horizontalOffset = this.application.ninja.stage.userContentLeft;//stageManagerModule.stageManager.userContentLeft;
var verticalOffset = this.application.ninja.stage.userContentTop;//stageManagerModule.stageManager.userContentTop;
//display circles and squares near all control points
ctx.fillStyle = "#FFFFFF";
ctx.lineWidth = 1;
ctx.strokeStyle = "green";
var anchorDelta = 2;
var selAnchorDelta = 4;
for (var i = 0; i < numAnchors; i++) {
var px = subpath.getAnchor(i).getPosX();
var py = subpath.getAnchor(i).getPosY();
ctx.beginPath();
//ctx.arc(px + horizontalOffset, py + verticalOffset, this._DISPLAY_ANCHOR_RADIUS, 0, 2 * Math.PI, false);
ctx.moveTo(px-anchorDelta+horizontalOffset, py-anchorDelta+verticalOffset);
ctx.lineTo(px+anchorDelta+horizontalOffset, py-anchorDelta+verticalOffset);
ctx.lineTo(px+anchorDelta+horizontalOffset, py+anchorDelta+verticalOffset);
ctx.lineTo(px-anchorDelta+horizontalOffset, py+anchorDelta+verticalOffset);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
//display the hovered over anchor point
ctx.lineWidth = 2;
if (this._hoveredAnchorIndex>=0 && this._hoveredAnchorIndex=0){
this._hoveredAnchorIndex=-1;
this._selectedSubpath.removeAnchor(this._selectedSubpath.getSelectedAnchorIndex());
this._selectedSubpath.createSamples();
//clear the canvas
this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas();
this.DrawSubpathAnchors(this._selectedSubpath);
this.ShowSelectedSubpath();
}
else {
this._selectedSubpath.clearAllAnchors(); //perhaps unnecessary
this._selectedSubpath = null;
//clear the canvas
this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas();
//undo/redo...go through ElementController and NJEvent
var els = [];
ElementController.removeElement(this._penCanvas);
els.push(this._penCanvas);
NJevent( "elementsRemoved", els );
this._penCanvas = null;
}
}
//do nothing if there was no selected subpath and if there was no selection
}
else {
//undo/redo...go through ElementMediator (see ElementMediator.handleDeleting() from where the much of this function is copied)
//clear the canvas
this.application.ninja.stage.clearDrawingCanvas();//stageManagerModule.stageManager.clearDrawingCanvas();
var els = [];
for(var i = 0; i