not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1233 lines
44 KiB

// 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;
};
};