1 /* 2 * Copyright (C) 2008, 2009 Mihai Şucan 3 * 4 * This file is part of PaintWeb. 5 * 6 * PaintWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * PaintWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with PaintWeb. If not, see <http://www.gnu.org/licenses/>. 18 * 19 * $URL: http://code.google.com/p/paintweb $ 20 * $Date: 2009-07-01 18:44:56 +0300 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview Holds the ellipse tool implementation. 26 */ 27 28 /** 29 * @class The ellipse tool. 30 * 31 * @param {PaintWeb} app Reference to the main paint application object. 32 */ 33 pwlib.tools.ellipse = function (app) { 34 var _self = this, 35 clearInterval = app.win.clearInterval, 36 config = app.config, 37 context = app.buffer.context, 38 gui = app.gui, 39 image = app.image, 40 MathMax = Math.max, 41 MathMin = Math.min, 42 mouse = app.mouse, 43 setInterval = app.win.setInterval; 44 45 /** 46 * The interval ID used for invoking the drawing operation every few 47 * milliseconds. 48 * 49 * @private 50 * @see PaintWeb.config.toolDrawDelay 51 */ 52 var timer = null; 53 54 /** 55 * Tells if the <kbd>Shift</kbd> key is down or not. This is used by the 56 * drawing function. 57 * 58 * @private 59 * @type Boolean 60 * @default false 61 */ 62 var shiftKey = false; 63 64 /** 65 * Tells if the drawing canvas needs to be updated or not. 66 * 67 * @private 68 * @type Boolean 69 * @default false 70 */ 71 var needsRedraw = false; 72 73 var K = 4*((Math.SQRT2-1)/3); 74 75 /** 76 * Holds the starting point on the <var>x</var> axis of the image, for the 77 * current drawing operation. 78 * 79 * @private 80 * @type Number 81 */ 82 var x0 = 0; 83 84 /** 85 * Holds the starting point on the <var>y</var> axis of the image, for the 86 * current drawing operation. 87 * 88 * @private 89 * @type Number 90 */ 91 var y0 = 0; 92 93 /** 94 * Tool deactivation event handler. 95 */ 96 this.deactivate = function () { 97 if (timer) { 98 clearInterval(timer); 99 timer = null; 100 } 101 102 if (mouse.buttonDown) { 103 context.clearRect(0, 0, image.width, image.height); 104 } 105 106 needsRedraw = false; 107 108 return true; 109 }; 110 111 /** 112 * Initialize the drawing operation. 113 * 114 * @param {Event} ev The DOM Event object. 115 */ 116 this.mousedown = function (ev) { 117 // The mouse start position 118 x0 = mouse.x; 119 y0 = mouse.y; 120 121 if (!timer) { 122 timer = setInterval(_self.draw, config.toolDrawDelay); 123 } 124 shiftKey = ev.shiftKey; 125 needsRedraw = false; 126 127 gui.statusShow('ellipseMousedown'); 128 129 return true; 130 }; 131 132 /** 133 * Store the <kbd>Shift</kbd> key state which is used by the drawing function. 134 * 135 * @param {Event} ev The DOM Event object. 136 */ 137 this.mousemove = function (ev) { 138 shiftKey = ev.shiftKey; 139 needsRedraw = true; 140 }; 141 142 /** 143 * Perform the drawing operation. This function is called every few 144 * milliseconds. 145 * 146 * <p>Hold down the <kbd>Shift</kbd> key to draw a circle. 147 * <p>Press <kbd>Escape</kbd> to cancel the drawing operation. 148 * 149 * @see PaintWeb.config.toolDrawDelay 150 */ 151 this.draw = function () { 152 if (!needsRedraw) { 153 return; 154 } 155 156 context.clearRect(0, 0, image.width, image.height); 157 158 var rectx0 = MathMin(mouse.x, x0), 159 rectx1 = MathMax(mouse.x, x0), 160 recty0 = MathMin(mouse.y, y0), 161 recty1 = MathMax(mouse.y, y0); 162 163 /* 164 ABCD - rectangle 165 A(rectx0, recty0), B(rectx1, recty0), C(rectx1, recty1), D(rectx0, recty1) 166 */ 167 168 var w = rectx1-rectx0, 169 h = recty1-recty0; 170 171 if (!w || !h) { 172 needsRedraw = false; 173 return; 174 } 175 176 // Constrain the ellipse to be a circle 177 if (shiftKey) { 178 if (w > h) { 179 recty1 = recty0+w; 180 if (recty0 == mouse.y) { 181 recty0 -= w-h; 182 recty1 -= w-h; 183 } 184 h = w; 185 } else { 186 rectx1 = rectx0+h; 187 if (rectx0 == mouse.x) { 188 rectx0 -= h-w; 189 rectx1 -= h-w; 190 } 191 w = h; 192 } 193 } 194 195 // Ellipse radius 196 var rx = w/2, 197 ry = h/2; 198 199 // Ellipse center 200 var cx = rectx0+rx, 201 cy = recty0+ry; 202 203 // Ellipse radius*Kappa, for the Bézier curve control points 204 rx *= K; 205 ry *= K; 206 207 context.beginPath(); 208 209 // startX, startY 210 context.moveTo(cx, recty0); 211 212 // Control points: cp1x, cp1y, cp2x, cp2y, destx, desty 213 // go clockwise: top-middle, right-middle, bottom-middle, then left-middle 214 context.bezierCurveTo(cx + rx, recty0, rectx1, cy - ry, rectx1, cy); 215 context.bezierCurveTo(rectx1, cy + ry, cx + rx, recty1, cx, recty1); 216 context.bezierCurveTo(cx - rx, recty1, rectx0, cy + ry, rectx0, cy); 217 context.bezierCurveTo(rectx0, cy - ry, cx - rx, recty0, cx, recty0); 218 219 if (config.shapeType != 'stroke') { 220 context.fill(); 221 } 222 if (config.shapeType != 'fill') { 223 context.stroke(); 224 } 225 226 context.closePath(); 227 228 needsRedraw = false; 229 }; 230 231 /** 232 * End the drawing operation, once the user releases the mouse button. 233 * 234 * @param {Event} ev The DOM Event object. 235 */ 236 this.mouseup = function (ev) { 237 // Allow click+mousemove, not only mousedown+move+up 238 if (mouse.x == x0 && mouse.y == y0) { 239 mouse.buttonDown = true; 240 return true; 241 } 242 243 if (timer) { 244 clearInterval(timer); 245 timer = null; 246 } 247 248 shiftKey = ev.shiftKey; 249 _self.draw(); 250 app.layerUpdate(); 251 gui.statusShow('ellipseActive'); 252 253 return true; 254 }; 255 256 /** 257 * Allows the user to press <kbd>Escape</kbd> to cancel the drawing operation. 258 * 259 * @param {Event} ev The DOM Event object. 260 * 261 * @returns {Boolean} True if the drawing operation was cancelled, or false if 262 * not. 263 */ 264 this.keydown = function (ev) { 265 if (!mouse.buttonDown || ev.kid_ != 'Escape') { 266 return false; 267 } 268 269 if (timer) { 270 clearInterval(timer); 271 timer = null; 272 } 273 274 context.clearRect(0, 0, image.width, image.height); 275 mouse.buttonDown = false; 276 needsRedraw = false; 277 278 gui.statusShow('ellipseActive'); 279 280 return true; 281 }; 282 }; 283 284 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 285 286