|
|
- // 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 '<path d="m' + x + ' ' + y + ' ';
- };
-
- this._closePath = function () {
- return 'z" ';
- };
-
- this.text = function (x, y, fontSize, width, alignment, string) {
- this._x = x;
- this._y = y;
- this._checkMinMax();
- this._x = x + width;
- this._y = y - fontSize;
- this._checkMinMax();
-
- // writing-mode:lr';
- switch (alignment) {
- case 'left':
- case 'start':
- var align = 'start';
- break;
- case 'middle':
- case 'center':
- var align = 'middle';
- break;
- case 'right':
- case 'end':
- var align = 'end';
- break;
- }
-
- var yy = y;
- var tspans = string.split('\n');
- var text = '<text style="font-size:' + fontSize + 'px;fill:#000000;font-family:sans-serif;text-anchor:' + align + '">';
- for (var i = 0; i < tspans.length; i++) {
- text += '<tspan x="' + x + '" y="' + yy + '">' + tspans[i] + '</tspan>';
- yy += fontSize;
- }
- text += '</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 '<svg xmlns="http://www.w3.org/2000/svg" width="' + width * 1.1 + '" height="' + this._height * 1.3 + '">' + this._transform(center) + '<filter id="dropshadow" height="130%"> \
- <feGaussianBlur in="SourceAlpha" stdDeviation="3"/> \
- <feOffset dx="2" dy="2" result="offsetblur"/> \
- <feComponentTransfer xmlns="http://www.w3.org/2000/svg"> \
- <feFuncA type="linear" slope="0.2"/> \
- </feComponentTransfer> \
- <feMerge> \
- <feMergeNode/> \
- <feMergeNode in="SourceGraphic"/> \
- </feMerge> \
- </filter>';
- };
-
- this._transform = function (center) {
- if (this._orientation !== 0) {
- var w = this._width / 2.0;
- var h = this._height / 2.0;
- var orientation = '<g transform = "rotate(' + this._orientation + ' ' + w + ' ' + h + ')">';
- } else {
- var orientation = '';
- }
- if (center) {
- var x = -this._minX;
- var y = -this._minY;
- return '<g transform="translate(' + x + ', ' + y + ')">';
- } else {
- return '<g transform="scale(' + this._scale + ', ' + this._scale + ')">' + orientation;
- }
- };
-
- this._footer = function () {
- if (this._orientation !== 0) {
- return '</g></g></svg>';
- } else {
- return '</g></svg>';
- }
- };
-
- 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 = '<g transform="matrix(1,0,0,1,0,-' + yoff + ')"> ';
-
- 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 += '</g>';
-
- 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;
- };
- };
|