// Copyright (c) 2015,16 Walter Bender // // This program is free software; you can redistribute it and/or // modify it under the terms of the The GNU Affero General Public // License as published by the Free Software Foundation; either // version 3 of the License, or (at your option) any later version. // // You should have received a copy of the GNU Affero General Public // License along with this library; if not, write to the Free Software // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA // Borrowing loosely from tasprite_factory.py in the Python version. function SVG() { // Interface to the graphical representation of blocks, turtles, // palettes, etc. on screen. // Terms used here: // docks -- list of connection points of a block to other blocks // innies -- right hand side docks of a block, argument slots // outie -- left hand side dock of a block // slot -- top dock of a block that can be attached to other blocks // cap -- top dock of a block that cannot be attached to other blocks // tab -- bottom dock of a block if other blocks can be attached // tail -- bottom dock of a block if no other blocks can be attached // arm -- connection point of a branching block (if-then, loops) where // inner blocks are attached // else -- optional second `arm' for if-then-else blocks this.init = function() { this._x = 0; this._y = 0; this._minX = 10000; this._minY = 10000; this._maxX = -10000; this._maxY = -10000; this._width = 0; this._height = 0; this.docks = []; this._scale = 1; this._orientation = 0; this._radius = 8; this._strokeWidth = 1; this._innies = []; this._outie = false; this._innieX1 = (9 - this._strokeWidth) / 2; this._innieY1 = 3; this._innieX2 = (9 - this._strokeWidth) / 2; this._innieY2 = (9 - this._strokeWidth) / 2; this._inniesSpacer = 9; this._padding = this._innieY1 + this._strokeWidth; this._slot = true; this._cap = false; this._tab = true; this._bool = false; this._slotX = 10; this._slotY = 2; this._tail = false; this._porch = false; this._porchX = this._innieX1 + this._innieX2 + 4 * this._strokeWidth; this._porchY = this._innieY2; this._expandX = 30; this._expandX2 = 0; this._expandY = 0; this._expandY2 = 0; this._clampCount = 1; this._clampSlots = [1]; this._slotSize = 21; // TODO: Compute this. this._arm = true; this._else = false; this._draw_inniess = true; this._fill = 'fill_color'; this._stroke = 'stroke_color'; this.margins = [0, 0, 0, 0]; this._fontSize = 10; } // Attribute methods this.setFontSize = function (fontSize) { this._fontSize = fontSize; }; this.setDrawInniess = function (flag) { this._draw_inniess = flag; }; this.getWidth = function () { return this._width; }; this.getHeight = function () { return this._height; }; this.clearDocks = function () { this.docks = []; }; this.setScale = function (scale) { this._scale = scale; }; this.setOrientation = function (orientation) { this._orientation = orientation; }; this.setClampCount = function (number) { this._clampCount = number; var n = this._clampSlots.length; if (n < number) { for (var i = 0; i < number - n; i++) { this._clampSlots.push(1); } } }; this.setClampSlots = function (clamp, number) { if (clamp > this._clampCount.length - 1) { this.setClampCount(clamp + 1); } this._clampSlots[clamp] = number; }; this.setExpand = function (w, h, w2, h2) { // TODO: make this an array this._expandX = w; this._expandY = h; this._expandX2 = w2; this._expandY2 = h2; }; this.setstrokeWidth = function (stroke_width) { this._strokeWidth = stroke_width; this._calc_porch_params(); }; this.setColors = function (colors) { this._fill = colors[0]; this._stroke = colors[1]; }; this.setFillColor = function (color) { this._fill = color; }; this.setStrokeColor = function (color) { this._stroke = color; }; this.setInnies = function (inniesArray) { for (var i = 0; i < inniesArray.length; i++) { this._innies.push(inniesArray[i]); } }; this.setOutie = function (flag) { // Only one outie. this._outie = flag; }; this.setSlot = function (flag) { this._slot = flag; if (flag) { this._cap = false; } }; this.setCap = function (flag) { this._cap = flag; if (flag) { this._slot = false; } }; this.setTab = function (flag) { this._tab = flag; if (flag) { this._tail = false; } }; this.setTail = function (flag) { this._tail = flag; if (flag) { this._tab = false; } }; this.setPorch = function (flag) { this._porch = flag; }; this.setBoolean = function (flag) { this._bool = flag; }; this.setElse = function (flag) { this._else = flag; }; this.setArm = function (flag) { this._arm = flag; }; // SVG-related helper methods this._resetMinMax = function () { this._minX = 10000; this._minY = 10000; this._maxX = -10000; this._maxY = -10000; }; this._checkMinMax = function () { if (this._x < this._minX) { this._minX = this._x; } if (this._y < this._minY) { this._minY = this._y; } if (this._x > this._maxX) { this._maxX = this._x; } if (this._y > this._maxY) { this._maxY = this._y; } }; this._calculateXY = function () { var x = this._strokeWidth / 2.0; var y = this._strokeWidth / 2.0 + this._radius; this.margins[0] = x + this._strokeWidth + 0.5; this.margins[1] = this._strokeWidth + 0.5; if (this._outie) { x += this._innieX1 + this._innieX2; this.margins[0] += this._innieX1 + this._innieX2; } if (this._cap) { y += this._slotY * 3.0; this.margins[1] += this._slotY * 3.0; } else if (this._slot) { this.margins[1] += this._slotY; } this.margins[0] *= this._scale; this.margins[1] *= this._scale; return([x, y]); }; this._calculateWH = function (addstrokeWidth) { if (addstrokeWidth) { this._width = (this._maxX - this._minX + this._strokeWidth) * this._scale; } else { this._width = (this._maxX - this._minX) * this._scale; } if (this.margins[2] === 0) { this.margins[2] = (this._strokeWidth + 0.5) * this._scale; } else { this.margins[2] = this._width - this.margins[2]; } if (addstrokeWidth) { this._height = (this._maxY - this._minY + this._strokeWidth) * this._scale; } else { this._height = (this._maxY - this._minY) * this._scale; } if (this.margins[3] === 0) { if (this._tab) { this.margins[3] = (this._slotY + this._strokeWidth + 0.5) * this._scale; } else { this.margins[3] = (this._slotY * 2 + this._strokeWidth + 0.5) * this._scale; } } else { this.margins[3] = this._height - this.margins[3]; } }; this._newPath = function (x, y) { this._x = x; this._y = y; return ''; for (var i = 0; i < tspans.length; i++) { text += '' + tspans[i] + ''; yy += fontSize; } text += ''; return text; }; this._lineTo = function (x, y) { this._checkMinMax(); if (this._x === x && this._y === y) { return ''; } else { this._x = x; this._y = y; this._checkMinMax(); return 'L ' + x + ' ' + y + ' '; } }; this._rLineTo = function (dx, dy) { if (dx === 0 && dy === 0) { return ''; } else { return this._lineTo(this._x + dx, this._y + dy); } }; this._arcTo = function (x, y, r, a, l, s) { this._checkMinMax(); if (r === 0) { return this._lineTo(x, y); } else { this._x = x; this._y = y; this._checkMinMax(); return 'A ' + r + ' ' + r + ' ' + a + ' ' + l + ' ' + s + ' ' + x + ' ' + y + ' '; } }; this._rarcTo = function (signX, signY, a, l, s) { if (this._radius === 0) { return ''; } else { var x = this._x + signX * this._radius; var y = this._y + signY * this._radius; return this._arcTo(x, y, this._radius, a, l, s); } }; this._corner = function (signX, signY, a, l, s, start, end, skip) { var svg_str = ''; if (this._radius > 0) { var r2 = this._radius / 2.0; if (start) { if (signX * signY === 1) { svg_str += this._rLineTo(signX * r2, 0); } else if (!skip) { svg_str += this._rLineTo(0, signY * r2); } } var x = this._x + signX * r2; var y = this._y + signY * r2; svg_str += this._arcTo(x, y, r2, a, l, s); if (end) { if (signX * signY === 1) { svg_str += this._rLineTo(0, signY * r2); } else if (!skip) { svg_str += this._rLineTo(signX * r2, 0); } } } return svg_str; }; this._iCorner = function (signX, signY, a, l, s, start, end) { var r2 = this._strokeWidth + this._radius / 2.0; if (start) { if (signX * signY === -1) { var svg_str = this._rLineTo(signX * (r2 - this._strokeWidth), 0); } else { var svg_str = this._rLineTo(0, signY * (r2 - this._strokeWidth)); } } else { var svg_str = ''; } var x = this._x + signX * r2; var y = this._y + signY * r2; svg_str += this._arcTo(x, y, r2, a, l, s); if (end) { if (signX * signY === -1) { svg_str += this._rLineTo(0, signY * (r2 - this._strokeWidth)); } else { svg_str += this._rLineTo(signX * (r2 - this._strokeWidth), 0); } } return svg_str; }; this._doInnie = function () { this.docks.push([(this._x + this._strokeWidth) * this._scale, (this._y + this._innieY2) * this._scale]); if (this.margins[2] === 0) { this.margins[1] = (this._y - this._innieY1) * this._scale; this.margins[2] = (this._x - this._innieX1 - this._innieX2 - this._strokeWidth * 2) * this._scale; } this.margins[3] = (this._y + this._innieY2 + this._innieY1) * this._scale; return this._rLineTo(-this._innieX1, 0) + this._rLineTo(0, -this._innieY1) + this._rLineTo(-this._innieX2, 0) + this._rLineTo(0, this._innieY2 + 2 * this._innieY1) + this._rLineTo(this._innieX2, 0) + this._rLineTo(0, -this._innieY1) + this._rLineTo(this._innieX1, 0); }; this._doOutie = function () { if (!this._outie) { return this._rLineTo(0, -this._innieY2); } // Outie needs to be the first dock element. this.docks.unshift([(this._x * this._scale), (this._y * this._scale)]); return this._rLineTo(0, -this._strokeWidth) + this._rLineTo(-this._innieX1 - 2 * this._strokeWidth, 0) + this._rLineTo(0, this._innieY1) + this._rLineTo(-this._innieX2 + 2 * this._strokeWidth, 0) + this._rLineTo(0, -this._innieY2 - 2 * this._innieY1 + 2 * this._strokeWidth) + this._rLineTo(this._innieX2 - 2 * this._strokeWidth, 0) + this._rLineTo(0, this._innieY1) + this._rLineTo(this._innieX1 + 2 * this._strokeWidth, 0) + this._rLineTo(0, -this._strokeWidth); }; this._doSlot = function () { if (this._slot) { var x = this._x + this._slotX / 2.0; this.docks.push([(x * this._scale), (this._y * this._scale)]); return this._rLineTo(0, this._slotY) + this._rLineTo(this._slotX, 0) + this._rLineTo(0, -this._slotY); } else if (this._cap) { var x = this._x + this._slotX / 2.0; this.docks.push([(x * this._scale), (this._y * this._scale)]); return this._rLineTo(this._slotX / 2.0, -this._slotY * 3.0) + this._rLineTo(this._slotX / 2.0, this._slotY * 3.0); } else { return this._rLineTo(this._slotX, 0); } }; this._doTail = function () { if (this._outie) { return this._rLineTo(-this._slotX, 0); } else if (this._tail) { var x = this._x + this._slotX / 2.0; this.docks.push([(x * this._scale), (this._y * this._scale)]); return this._rLineTo(-this._slotX / 2.0, this._slotY * 3.0) + this._rLineTo(-this._slotX / 2.0, -this._slotY * 3.0); } else { return this._rLineTo(-this._slotX, 0); } }; this._doTab = function () { if (this._outie) { return this._rLineTo(-this._slotX, 0); } var x = this._x - this._slotX / 2.0; this.docks.push([x * this._scale, (this._y + this._strokeWidth) * this._scale]); return this._rLineTo(-this._strokeWidth, 0) + this._rLineTo(0, this._slotY) + this._rLineTo(-this._slotX + 2 * this._strokeWidth, 0) + this._rLineTo(0, -this._slotY) + this._rLineTo(-this._strokeWidth, 0); }; this._doPorch = function (flag) { if (flag) { return this._rLineTo(0, this._porchY + this._innieY1) + this._rLineTo(this._porchX - this._radius, 0) + this._corner(1, 1, 90, 0, 1, true, true, false); } else { return this._rLineTo(0, this._porchY - this._padding) + this._rLineTo(this._porchX - this._radius, 0) + this._corner(1, 1, 90, 0, 1, true, true, false); } }; this._startBoolean = function (xoffset, yoffset) { var svg = this._newPath(xoffset, yoffset); // - this._radius); this._radius -= this._strokeWidth; this.docks.push([this._x * this._scale, this._y * this._scale]); svg += this._rarcTo(1, -1, 90, 0, 1); this._radius += this._strokeWidth; svg += this._rLineTo(this._strokeWidth, 0); svg += this._rLineTo(0, -this._expandY); return svg; }; this._doBoolean = function () { this.docks.push([(this._x - this._radius + this._strokeWidth) * this._scale, (this._y + this._radius) * this._scale]); this.margins[2] = (this._x - this._radius - this._strokeWidth) * this._scale; var svg = this._rarcTo(-1, 1, 90, 0, 0) + this._rarcTo(1, 1, 90, 0, 0); return svg; }; this._endBoolean = function (notnot) { if (!notnot) { var svg = this._rLineTo(-this._radius * 1.5, 0); } else { var svg = ''; } svg += this._rLineTo(0, -this._strokeWidth); svg += this._rLineTo(-this._strokeWidth, 0); this._radius -= this._strokeWidth; svg += this._rarcTo(-1, -1, 90, 0, 1); this._radius += this._strokeWidth; svg += this._closePath(); this._calculateWH(true); svg += this._style(); return svg; }; this._header = function (center) { // FIXME: Why are our calculations off by 2 x strokeWidth? var width = this._width + 2 * this._strokeWidth; return '' + this._transform(center) + ' \ \ \ \ \ \ \ \ \ \ '; }; this._transform = function (center) { if (this._orientation !== 0) { var w = this._width / 2.0; var h = this._height / 2.0; var orientation = ''; } else { var orientation = ''; } if (center) { var x = -this._minX; var y = -this._minY; return ''; } else { return '' + orientation; } }; this._footer = function () { if (this._orientation !== 0) { return ''; } else { return ''; } }; this._style = function () { return 'style="fill:' + this._fill + ';fill-opacity:1;stroke:' + this._stroke + ';stroke-width:' + this._strokeWidth + ';stroke-linecap:round;stroke-opacity:1;filter:url(#dropshadow);" />'; }; /* The block construction methods typically start on the upper-left side of a block and proceed clockwise around the block, first constructing a corner (1, -1), a slot or hat on along the top, a corner (1, 1), right side connectors ("innies"), possibly a "porch" to suggest an order of arguments, another corner (-1, 1), a tab or tail, and the fourth corner (-1, -1), and finally, a left-side connector ("outie"). In addition: * Minimum and maximum values are calculated for the SVG bounding box; * Docking coordinates are calculated for each innies, outie, tab, and slot. */ this.basicBlock = function() { // The most common block type: used for 0, 1, 2, or 3 // argument commands (forward, setxy, plus, sqrt, etc.) this._resetMinMax(); var obj = this._calculateXY(); var x = obj[0]; var y = obj[1]; this.margins[2] = 0; this.margins[3] = 0; var svg = this._newPath(x, y); svg += this._corner(1, -1 , 90, 0, 1, true, true, false); svg += this._doSlot(); svg += this._rLineTo(this._expandX, 0); xx = this._x; svg += this._corner(1, 1 , 90, 0, 1, true, true, false); if (this._innies.length === 0) { // To maintain standard block height svg += this._rLineTo(0, this._padding); } else { for (var i = 0; i < this._innies.length; i++) { if (this._innies[i]) { svg += this._doInnie(); } if (i === 0) { svg += this._rLineTo(0, this._expandY); } else if (i === 1 && this._expandY2 > 0) { svg += this._rLineTo(0, this._expandY2); } if (i === 0 && this._porch) { svg += this._doPorch(false); } else if (this._innies.length - 1 > i) { svg += this._rLineTo(0, 2 * this._innieY2 + this._inniesSpacer); } } } svg += this._corner(-1, 1 , 90, 0, 1, true, true, false); svg += this._lineTo(xx, this._y); svg += this._rLineTo(-this._expandX, 0); if (this._tab) { svg += this._doTab(); } else { svg += this._doTail(); } svg += this._corner(-1, -1 , 90, 0, 1, true, true, false); svg += this._rLineTo(0, -this._expandY); if (this._innies.indexOf(true) !== -1) { svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0); svg += this._doOutie(); } this._calculateWH(true); svg += this._closePath(); svg += this._style(); // Add a block label var tx = this._width - this._scale * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth; var ty = this._height / 2 + this._fontSize / (5 / this._scale); // If we have an odd number of innie slots, we need to avoid a // collision between the block label and the slot label. var nInnies = this._innies.length; if (nInnies > 2 && Math.round(nInnies / 2) * 2 !== nInnies) { ty -= 2 * this._fontSize; } svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label'); // Add a label for each innies if (this._slot || this._outie) { var di = 1; // Skip the first dock since it is a slot. } else { var di = 0; } var count = 1; for (var i = 0; i < this._innies.length; i++) { if (this._innies[i]) { ty = this.docks[di][1] - (this._fontSize / (8 / this._scale)); svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_' + count); count += 1; di += 1; } } svg += this._footer(); return this._header(false) + svg; }; this.basicBox = function () { // Basic argument style used for numbers, text, media, parameters this._resetMinMax(); this.setOutie(true); var x = this._strokeWidth / 2.0 + this._innieX1 + this._innieX2; this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale; this.margins[1] = (this._strokeWidth + 0.5) * this._scale; this.margins[2] = 0; this.margins[3] = 0; var svg = this._newPath(x, this._strokeWidth / 2.0); svg += this._rLineTo(this._expandX, 0); svg += this._rLineTo(0, 2 * this._radius + this._innieY2 + this._expandY); svg += this._rLineTo(-this._expandX, 0); svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0); svg += this._doOutie(); svg += this._closePath(); this._calculateWH(true); svg += this._style(); // Add a block label var tx = 2 * (this._innieX1 + this._innieX2) + 4 * this._strokeWidth; var ty = this._height / 2 + this._fontSize / 2; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label'); svg += this._footer(); return this._header(false) + svg; }; this.booleanAndOr = function () { // Booleans are in a class of their own this._resetMinMax(); var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 5.5 + this._strokeWidth / 2.0 + this._innieY2 + this._inniesSpacer + this._expandY); svg += this._rLineTo(0, -this._radius * 3.5 - this._innieY2 - this._inniesSpacer - this._strokeWidth); svg += this._rarcTo(1, -1, 90, 0, 1); svg += this._rLineTo(this._radius / 2.0 + this._expandX, 0); var xx = this._x; svg += this._rLineTo(0, this._radius / 2.0); svg += this._doBoolean(); svg += this._rLineTo(0, this._radius * 1.5 + this._innieY2 + this._inniesSpacer); svg += this._rLineTo(0, this._expandY); svg += this._doBoolean(); svg += this._rLineTo(0, this._radius / 2.0); svg += this._lineTo(xx, this._y); svg += this._rLineTo(-this._expandX, 0); svg += this._endBoolean(false); this.margins[0] = (this._radius + this._strokeWidth + 0.5) * this._scale; this.margins[1] = this._strokeWidth * this._scale; this.margins[2] = this._strokeWidth * this._scale; this.margins[3] = this._strokeWidth * this._scale; // Add a block label var tx = this._width - this._scale * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth; var ty = this._height / 2 + this._fontSize / 2; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label'); svg += this._footer(); return this._header(false) + svg; }; this.booleanNot = function (notnot) { // Booleans are in a class of their own: not and not not this._resetMinMax(); if (this._innies[0]) { var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 1.25 + this._strokeWidth / 2.0); } else if (!notnot) { var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 2.0 + this._strokeWidth / 2.0); } else { var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 1.25 + this._strokeWidth / 2.0); } svg += this._rLineTo(0, -this._strokeWidth); if (this._innies[0]) { svg += this._rLineTo(0, -this._radius / 4.0); } else if (!notnot) { svg += this._rarcTo(1, -1, 90, 0, 1); } else { svg += this._rLineTo(0, -this._radius / 4.0); } svg += this._rLineTo(this._radius / 2.0 + this._expandX, 0); var xx = this._x; if (this._innies[0]) { svg += this._rLineTo(0, this._radius); svg += this._doInnie(); svg += this._rLineTo(0, this._radius); } else if (!notnot) { svg += this._rLineTo(0, this._radius / 2.0); svg += this._doBoolean(); svg += this._rLineTo(0, this._radius / 2.0); } else { svg += this._rLineTo(0, this._radius * 2.25); } svg += this._lineTo(xx, this._y); // FIXME: Is this in the correct place? if (this._expandY2 > 0) { svg += this._rLineTo(0, this._expandY2); } if (this._innies[0]) { svg += this._rLineTo(-this._radius / 2.0 - this._expandX, 0); svg += this._rLineTo(0, -this._radius / 4.0); } else if (!notnot) { svg += this._rLineTo(-this._expandX, 0); } else { svg += this._rLineTo(-this._radius / 2.0 - this._expandX, 0); } // FIXME: Is this in the correct place? if (this._expandY2 > 0) { svg += this._rLineTo(0, -this._expandY2); } svg += this._endBoolean(notnot); if (notnot) { this.margins[0] = (this._radius + this._strokeWidth + 0.5) * this._scale; this.margins[2] = (this._radius + this._strokeWidth + 0.5) * this._scale; } else { this.margins[0] = (this._strokeWidth + 0.5) * this._scale; this.margins[2] = (this._strokeWidth + 0.5) * this._scale; } this.margins[1] = this._strokeWidth * this._scale; this.margins[3] = this._strokeWidth * this._scale; // Add a block label var tx = this._width - 2 * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth; var ty = this._height / 2 + this._fontSize / 2; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label'); svg += this._footer(); return this._header(false) + svg; }; this.booleanCompare = function () { // Booleans are in a class of their own (greater than, less than, etc) this._resetMinMax(); var yoffset = this._radius * 2 + 2 * this._innieY2 + this._inniesSpacer + this._strokeWidth / 2.0 + this._expandY; var xoffset = this._strokeWidth / 2.0; var yoff = this._radius * 2; var svg = ' '; svg += this._newPath(xoffset, yoffset + this._radius); this.docks.push([this._x * this._scale, (this._y - 2 * this._radius) * this._scale]); this._radius -= this._strokeWidth; svg += this._rarcTo(1, -1, 90, 0, 1); this._radius += this._strokeWidth; svg += this._rLineTo(this._strokeWidth, 0); svg += this._rLineTo(0, -this._expandY); yoffset = -2 * this._innieY2 - this._inniesSpacer - this._strokeWidth; svg += this._rLineTo(0, yoffset + this._radius); svg += this._rarcTo(1, -1, 90, 0, 1); svg += this._rLineTo(this._radius / 2.0 + this._expandX, 0); svg += this._rLineTo(0, this._radius); var xx = this._x; svg += this._doInnie(); this.docks[1][1] -= this._radius * 2 * this._scale; svg += this._rLineTo(0, this._expandY); if (this._porch) { svg += this._doPorch(false); } else { svg += this._rLineTo(0, 2 * this._innieY2 + this._inniesSpacer); } svg += this._doInnie(); this.docks[2][1] -= this._radius * 2 * this._scale; svg += this._rLineTo(0, this._radius); svg += this._lineTo(xx, this._y); svg += this._rLineTo(-this._expandX, 0); svg += this._rLineTo(-this._radius * 1.5, 0); svg += this._rLineTo(0, -this._radius); svg += this._rLineTo(0, -this._strokeWidth); svg += this._rLineTo(-this._strokeWidth, 0); this._radius -= this._strokeWidth; svg += this._rarcTo(-1, -1, 90, 0, 1); this._radius += this._strokeWidth; svg += this._closePath(); this._calculateWH(true); svg += this._style(); svg += ''; this.margins[0] = (this._radius + this._strokeWidth) * this._scale; this.margins[1] = this._strokeWidth * this._scale; this.margins[2] = this._strokeWidth * this._scale; // Add a block label var tx = this._width - 2 * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth; var ty = this._height / 2 + this._fontSize / 2; // + this._radius * this._scale; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label'); svg += this._footer(); return this._header(false) + svg; }; this.basicClamp = function () { // Special block for collapsible stacks; includes an 'arm' // that extends down the left side of a stack and a bottom jaw // to clamp the blocks. (Used for start, action, repeat, etc.) var save_cap = this._cap; var save_slot = this._slot; this._resetMinMax(); if (this._outie) { var x = this._strokeWidth / 2.0 + this._innieX1 + this._innieX2; } else { var x = this._strokeWidth / 2.0; } if (this._cap) { var y = this._strokeWidth / 2.0 + this._radius + this._slotY * 3.0; } else { var y = this._strokeWidth / 2.0 + this._radius; } this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale; this.margins[1] = (this._strokeWidth + 0.5) * this._scale; this.margins[2] = 0; this.margins[3] = 0; var svg = this._newPath(x, y); svg += this._corner(1, -1 , 90, 0, 1, true, true, false); svg += this._doSlot(); if (this._cap) { this._slot = true; this._cap = false; } svg += this._rLineTo(this._radius + this._strokeWidth, 0); var xx = this._x; svg += this._rLineTo(this._expandX, 0); svg += this._corner(1, 1 , 90, 0, 1, true, true, false); if (this._innies[0]) { // svg += this._doInnie(); for (var i = 0; i < this._innies.length; i++) { if (this._innies[i]) { svg += this._doInnie(); } if (i === 0) { svg += this._rLineTo(0, this._expandY); } else if (i === 1 && this._expandY2 > 0) { svg += this._rLineTo(0, this._expandY2); } if (i === 0 && this._porch) { svg += this._doPorch(false); } else if (this._innies.length - 1 > i) { svg += this._rLineTo(0, 2 * this._innieY2 + this._inniesSpacer); } } } else if (this._bool) { svg += this._rLineTo(0, 2 * this._padding + this._strokeWidth); svg += this._doBoolean(); this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale; } else { svg += this._rLineTo(0, this._padding); this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale; } for (var clamp = 0; clamp < this._clampCount; clamp++) { if (clamp > 0) { svg += this._rLineTo(0, 3 * this._padding); } svg += this._corner(-1, 1, 90, 0, 1, true, true, false); svg += this._lineTo(xx, this._y); var saveOutie = this._outie; this._outie = false; svg += this._doTab(); this._outie = saveOutie; svg += this._iCorner(-1, 1, 90, 0, 0, true, true); svg += this._rLineTo(0, this._padding); if (this._clampSlots[clamp] > 1) { var dy = this._slotSize * (this._clampSlots[clamp] - 1); svg += this._rLineTo(0, dy); } svg += this._rLineTo(0, this._expandY2); svg += this._iCorner(1, 1, 90, 0, 0, true, true); var saveSlot = this._slot; this._slot = true; svg += this._doSlot(); this._slot = saveSlot; this.docks.pop(); // We don't need this dock. svg += this._rLineTo(this._radius, 0); } svg += this._rLineTo(0, this._innieY1 * 2); // Add a bit of padding to make multiple of standard block height. svg += this._rLineTo(0, this._innieY1 + 3 * this._strokeWidth); svg += this._corner(-1, 1, 90, 0, 1, true, true, false); if (this._clampCount === 0) { svg += this._lineTo(xx, this._y); } svg += this._rLineTo(-this._radius - this._strokeWidth, 0); if (this._tail) { svg += this._doTail(); } else { svg += this._doTab(); } this._cap = save_cap; this._slot = save_slot; svg += this._corner(-1, -1, 90, 0, 1, true, true, false); if (this._outie) { svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0); svg += this._doOutie(); } svg += this._closePath(); this._calculateWH(true); svg += this._style(); // Add a block label if (this._outie) { var tx = 10 * this._strokeWidth + this._innieX1 + this._innieX2; } else { var tx = 8 * this._strokeWidth; } if (this._cap) { var ty = (this._strokeWidth / 2.0 + this._radius + this._slotY) * this._scale; } else if (this._innies.length > 1) { var ty = (this._strokeWidth / 2.0 + this._radius) * this._scale / 2; ty += this._fontSize; } else { var ty = (this._strokeWidth / 2.0 + this._radius) * this._scale / 2; } ty += (this._fontSize + 1) * this._scale; if (this._bool) { ty += this._fontSize / 2; } svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label'); // Booleans get an extra label. if (this._bool) { var count = 1; var tx = this._width - this._radius; for (var clamp = 0; clamp < this._clampCount; clamp++) { ty = this.docks[clamp + 2][1] - this._fontSize + 3 * this._strokeWidth; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_' + count); count += 1; } } // Add a label for each innies if (this._slot || this._outie) { var di = 1; // Skip the first dock since it is a slot. } else { var di = 0; } var count = 1; var tx = this._width - this._scale * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth; for (var i = 0; i < this._innies.length; i++) { if (this._innies[i]) { ty = this.docks[di][1] - (this._fontSize / (8 / this._scale)); svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_' + count); count += 1; di += 1; } } svg += this._footer(); return this._header(false) + svg; }; this.argClamp = function () { // A clamp that contains innies rather than flow blocks this._resetMinMax(); if (this._outie) { var x = this._strokeWidth / 2.0 + this._innieX1 + this._innieX2; } else { var x = this._strokeWidth / 2.0; } var y = this._strokeWidth / 2.0 + this._radius; this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale; this.margins[1] = (this._strokeWidth + 0.5) * this._scale; this.margins[2] = 0; this.margins[3] = 0; var svg = this._newPath(x, y); svg += this._corner(1, -1 , 90, 0, 1, true, true, false); svg += this._doSlot(); svg += this._rLineTo(this._radius + this._strokeWidth, 0); var xx = this._x; svg += this._rLineTo(this._expandX, 0); svg += this._corner(1, 1 , 90, 0, 1, true, true, false); if (this._innies[0]) { svg += this._doInnie(); } else { svg += this._rLineTo(0, this._padding); this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale; } svg += this._corner(-1, 1, 90, 0, 1, true, true, false); svg += this._lineTo(xx, this._y); svg += this._iCorner(-1, 1, 90, 0, 0, true, true); var j = 0; svg += this._doInnie(); var dy = this._slotSize * (this._clampSlots[0][j] - 1); if (dy > 0) { svg += this._rLineTo(0, dy); } j += 1; var ddy = (this._slotSize - this._innieY2); for (var i = 1; i < this._clampSlots[0].length; i++) { svg += this._rLineTo(0, ddy); svg += this._doInnie(); var dy = this._slotSize * (this._clampSlots[0][j] - 1); if (dy > 0) { svg += this._rLineTo(0, dy); } j += 1; } svg += this._rLineTo(0, this._expandY2); svg += this._iCorner(1, 1, 90, 0, 0, true, true); svg += this._rLineTo(this._radius, 0); svg += this._rLineTo(0, this._innieY1 * 2); // Add a bit of padding to make multiple of standard block height. svg += this._rLineTo(0, this._innieY1 + 3 * this._strokeWidth); svg += this._corner(-1, 1, 90, 0, 1, true, true, false); svg += this._lineTo(xx, this._y); svg += this._rLineTo(-this._radius - this._strokeWidth, 0); if (this._tail) { svg += this._doTail(); } else { svg += this._doTab(); } svg += this._corner(-1, -1, 90, 0, 1, true, true, false); if (this._outie) { svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0); svg += this._doOutie(); } svg += this._closePath(); this._calculateWH(true); svg += this._style(); // Add a block label if (this._outie) { var tx = 10 * this._strokeWidth + this._innieX1 + this._innieX2; } else { var tx = 8 * this._strokeWidth; } if (this._cap) { var ty = (this._strokeWidth / 2.0 + this._radius + this._slotY) * this._scale; } else { var ty = (this._strokeWidth / 2.0 + this._radius) * this._scale / 2; } ty += (this._fontSize + 1) * this._scale; if (this._bool) { ty += this._fontSize / 2; } svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label'); svg += this._footer(); return this._header(false) + svg; }; this.untilClamp = function () { // Until block is like clamp but docks are flipped this._resetMinMax(); var x = this._strokeWidth / 2.0; var y = this._strokeWidth / 2.0 + this._radius; this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale; this.margins[1] = (this._strokeWidth + 0.5) * this._scale; this.margins[2] = 0; this.margins[3] = 0; var svg = this._newPath(x, y); svg += this._corner(1, -1, 90, 0, 1, true, true, false); svg += this._doSlot(); svg += this._rLineTo(this._radius + this._strokeWidth, 0); svg += this._rLineTo(this._expandX, 0); var xx = this._x; svg += this._corner(1, 1, 90, 0, 1, true, true, true); svg += this._rLineTo(0, 2 * this._innieY1); svg += this._corner(-1, 1, 90, 0, 1, true, true, true); svg += this._lineTo(xx, this._y); svg += this._rLineTo(-this._expandX, 0); svg += this._doTab(); svg += this._iCorner(-1, 1, 90, 0, 0, true, true); svg += this._rLineTo(0, this._expandY); svg += this._iCorner(1, 1, 90, 0, 0, true, true); svg += this._doSlot(); this.docks.pop(); // We don't need this dock. svg += this._rLineTo(this._radius, 0); if (this._innies[0]) { svg += this._doInnie(); } else { this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale; } svg += this._rLineTo(0, this._radius + this._expandY2); if (this._bool) { svg += this._doBoolean(); } svg += this._corner(-1, 1, 90, 0, 1, true, true, false); svg += this._rLineTo(-this._radius - this._strokeWidth, 0); svg += this._doTab(); svg += this._corner(-1, -1, 90, 0, 1, true, true, false); svg += this._closePath(); this._calculateWH(true); svg += this._style(); // Add a block label var tx = 4 * this._strokeWidth; var ty = this.docks[2][1]; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label'); if (this._bool) { // Booleans get an extra label. var tx = this._width - this._radius; ty = this.docks[1][1] - this._fontSize; svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_1'); } if (this._bool) { // Swap bool and tab args so that the docking behaves like the // while block. var tx = this.docks[1][0]; var ty = this.docks[1][1]; this.docks[1][0] = this.docks[2][0]; this.docks[1][1] = this.docks[2][1]; this.docks[2][0] = tx; this.docks[2][1] = ty; } svg += this._footer(); return this._header(false) + svg; }; this.statusBlock = function (graphic) { // Generate a status block this._resetMinMax(); var obj = this._calculateXY(); var x = obj[0]; var y = obj[1]; this.margins[2] = 0; this.margins[3] = 0; var svg = this._newPath(x, y); svg += this._corner(1, -1, 90, 0, 1, true, true, false); svg += this._rLineTo(this._expandX, 0); var xx = this._x; svg += this._corner(1, 1, 90, 0, 1, true, true, false); svg += this._rLineTo(0, this._expandY); svg += this._corner(-1, 1, 90, 0, 1, true, true, false); svg += this._lineTo(xx, this._y); svg += this._rLineTo(-this._expandX, 0); svg += this._corner(-1, -1, 90, 0, 1, true, true, false); svg += this._rLineTo(0, -this._expandY); this._calculateWH(true); svg += this._closePath(); svg += this._style(); svg += this._footer(); return this._header(false) + svg; }; };