From 50cd1871ae114b623ae2097b03c6653e1d8bf027 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 19 Jul 2014 11:16:40 +0000 Subject: [PATCH 001/180] Turtle background should stretch when css size is adjusted. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 75ac8a7..2bb7feb 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7705,7 +7705,7 @@ var shapes = { turtleHull: "-8 -5 -8 6 -2 -13 2 -13 8 6 8 -5 0 9", opacity: 0.67, backgroundImage: 'url(' + turtleGIFUrl + ')', - backgroundSize: 'contain' + backgroundSize: 'cover' } }; }, From 5c7f7accf2bf9d1231464a27ff3712bbb4a8460d Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 8 Aug 2014 01:51:23 -0400 Subject: [PATCH 002/180] Add .clip() method for sprites. Also makes sprites not-perfectly-transparent by default. Also refactors and fixes some small issues with the new hull-computing code (thanks for the code alex), and adds a test. --- jquery-turtle.js | 130 ++++++++++++++++++++++++++++++++++------------- test/clip.html | 45 ++++++++++++++++ 2 files changed, 141 insertions(+), 34 deletions(-) create mode 100644 test/clip.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 3613692..38912ab 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1366,6 +1366,7 @@ function convexHull(points) { function parseTurtleHull(text) { if (!text) return null; + if ($.isArray(text)) return text; var nums = $.map(text.trim().split(/\s+/), parseFloat), points = [], j = 0; while (j + 1 < nums.length) { points.push({ pageX: nums[j], pageY: nums[j + 1] }); @@ -2656,18 +2657,13 @@ function applyLoadedImage(loaded, elem, css) { sel.css(css); } var newOrigin = readTransformOrigin(elem); - if(loaded){ - var hull = cutTransparent(loaded); - var turtleHull = ''; - for(var i = 0; i < hull.length; i++){ - if(i > 0) turtleHull += ' '; - // Scale coordinates to the size of elem - hull[i].pageX = Math.floor(hull[i].pageX * sel.css('height').slice(0, -2) / loaded.height); - hull[i].pageY = Math.floor(hull[i].pageY * sel.css('width').slice(0, -2) / loaded.width); - turtleHull += (hull[i].pageX - newOrigin[0]) + ' ' + (hull[i].pageY - newOrigin[1]); - } - sel.css('turtleHull', turtleHull); - console.log(turtleHull); + if (loaded && !css.turtleHull) { + var hull = transparentHull(loaded); + scalePolygon(hull, + parseFloat(sel.css('width')) / loaded.width, + parseFloat(sel.css('height')) / loaded.height, + -newOrigin[0], -newOrigin[1]); + sel.css('turtleHull', hull); } // If there was a change, then translate the element to keep the origin // in the same location on the screen. @@ -5115,7 +5111,9 @@ function gatherelts(args) { } // Gather elements passed as arguments. for (j = 0; j < argcount; ++j) { - if (args[j].constructor === $) { + if (!args[j]) { + continue; // Skip null args. + } else if (args[j].constructor === $) { elts.push.apply(elts, args[j].toArray()); // Unpack jQuery. } else if ($.isArray(args[j])) { elts.push.apply(elts, args[j]); // Accept an array. @@ -6154,6 +6152,29 @@ var turtlefn = { function pf() { return this.pen('path', continuationArg(arguments, 0)); }, + clip: wrapcommand('clip', 1, + ["Clips tranparent bits out of the image of the sprite, " + + "and sets the hit region."], + function clip(cc, threshold) { + if (threshold == null) { + threshold = 0.125; + } + return this.plan(function(j, elem) { + cc.appear(j); + if (elem.tagName == 'CANVAS') { + var hull = transparentHull(elem, threshold), + sel = $(elem), + origin = readTransformOrigin(elem); + eraseOutsideHull(elem, hull); + scalePolygon(hull, + parseFloat(sel.css('width')) / elem.width, + parseFloat(sel.css('height')) / elem.height, + -origin[0], -origin[1]); + sel.css('turtleHull', hull); + } + cc.resolve(j); + }); + }), play: wrapcommand('play', 1, ["play(notes) Play notes. Notes are specified in " + "" + @@ -7796,7 +7817,10 @@ function createRectangleShape(width, height, subpixels) { return (function(color) { var c = getOffscreenCanvas(width, height); var ctx = c.getContext('2d'); - if (color && color != 'transparent') { + if (!color) { + color = "rgba(128,128,128,0.125)"; + } + if (color != 'transparent') { ctx.fillStyle = color; ctx.fillRect(0, 0, width, height); } @@ -9413,39 +9437,77 @@ function tryinitpanel() { eval("scope('jquery-turtle', " + seejs + ", this)"); -function cutTransparent(image){ - // We transform image into a 2D array of 0s and 1s, with 0 and 1 representing - // transparent and non-transparent pixels in image, respectively. +function transparentHull(image, threshold) { var c = document.createElement('canvas'); + if (!threshold) threshold = 0; c.width = image.width; c.height = image.height; ctx = c.getContext('2d'); ctx.drawImage(image, 0, 0); - data = ctx.getImageData(0, 0, c.width, c.height).data; - alphaData = []; - for(var i = 0; i < data.length; i += 4){ - var row = Math.floor(i / 4 / c.width); - var col = (i / 4) - row * c.width; - if(!alphaData[row]) alphaData[row] = []; - alphaData[row][col] = (data[i + 3] == 0 ? 0 : 1); - } + return transparentCanvasHull(c, threshold); +} + +function transparentCanvasHull(canvas, threshold) { + var ctx = canvas.getContext('2d'); + data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; var hull = []; - for(var i = 0; i < c.height; i++){ + var intthresh = 256 * threshold; + var first, last, prevfirst = Infinity, prevlast = -1; + for (var row = 0; row < canvas.height; ++row) { // We only take the first/last hull in a row to reduce the number of // possible points from O(n^2) to O(n). - var first = -1, last = -1; - for(var j = 0; j < c.width; j++){ - if(alphaData[i][j] == 1){ - if(first < 0) first = j; - last = j; + first = Infinity; + last = -1; + for (var col = 0; col < canvas.width; ++col) { + if (data[row * 4 * canvas.width + col * 4 + 3] > intthresh) { + if (last < 0) first = col; + last = col; } } - if(first >= 0){ - hull.push({ pageX: first, pageY: i}); - hull.push({ pageX: last, pageY: i}); + if (last >= 0 || prevlast >= 0) { + hull.push({ pageX: Math.min(first, prevfirst), pageY: row}); + hull.push({ pageX: Math.max(last, prevlast), pageY: row}); } + prevfirst = first; + prevlast = last; + } + if (prevlast >= 0) { + hull.push({ pageX: prevfirst, pageY: canvas.height}); + hull.push({ pageX: prevlast, pageY: canvas.height}); } return convexHull(hull); } +function eraseOutsideHull(canvas, hull) { + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height + j = 0; + ctx.save(); + // Erase everything outside clipping region. + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(w, 0); + ctx.lineTo(w, h); + ctx.lineTo(0, h); + ctx.closePath(); + if (hull.length) { + ctx.moveTo(hull[0].pageX, hull[0].pageY); + for (; j < hull.length; j += 1) { + ctx.lineTo(hull[j].pageX, hull[j].pageY); + } + } + ctx.closePath(); + ctx.clip(); + ctx.clearRect(0, 0, w, h); + ctx.restore(); +} + +function scalePolygon(poly, sx, sy, tx, ty) { + for (var i = 0; i < poly.length; i++){ + poly[i].pageX = poly[i].pageX * sx + tx; + poly[i].pageY = poly[i].pageY * sy + ty; + } +} + })(jQuery); diff --git a/test/clip.html b/test/clip.html new file mode 100644 index 0000000..c5b2271 --- /dev/null +++ b/test/clip.html @@ -0,0 +1,45 @@ + + + + + +
+ + + + From 2b818acd68dadf3d025ddeda8ca934ad6ece723a Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 8 Aug 2014 19:41:11 -0400 Subject: [PATCH 003/180] Deal with cross-origin restrictions on bit access, and remove openicon urls. --- jquery-turtle.js | 57 ++++++++++++------------------------------------ 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 38912ab..3841875 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2658,12 +2658,16 @@ function applyLoadedImage(loaded, elem, css) { } var newOrigin = readTransformOrigin(elem); if (loaded && !css.turtleHull) { - var hull = transparentHull(loaded); - scalePolygon(hull, - parseFloat(sel.css('width')) / loaded.width, - parseFloat(sel.css('height')) / loaded.height, - -newOrigin[0], -newOrigin[1]); - sel.css('turtleHull', hull); + try { + var hull = transparentHull(loaded); + scalePolygon(hull, + parseFloat(sel.css('width')) / loaded.width, + parseFloat(sel.css('height')) / loaded.height, + -newOrigin[0], -newOrigin[1]); + sel.css('turtleHull', hull); + } catch (e) { + // Do not do this if the image can't be loaded. + } } // If there was a change, then translate the element to keep the origin // in the same location on the screen. @@ -7907,45 +7911,12 @@ function nameToImg(name, defaultshape) { if (shape) { return shape(color); } - // Parse openicon patterns. - // TODO: add more built-in shapes and remove this. - var openicon = - /^openicon:\/?\/?([^@\/][^@]*)(?:@(?:(\d+):)?(\d+))?$/.exec(name); - if (openicon) { - var openiconName = openicon[1], - sourceSize = parseInt(openicon[3]), - targetSize = parseInt(openicon[2]), - dotloc = openiconName.lastIndexOf('.'), - openiconType = 'png'; - if (openiconName.indexOf('/') == -1) { - openiconName = 'others/' + openiconName; - } - if (dotloc > 0 && dotloc <= openiconName.length - 4 && - dotloc >= openiconName.length - 5) { - openiconType = openiconName.substring(dotloc + 1); - openiconName = openiconName.substring(0, dotloc); - } - if (!targetSize) { - targetSize = sourceSize || 24; - } - if (!sourceSize) { - sourceSize = 48; - } - return { - url: 'http://openiconlibrary.sourceforge.net/gallery2/' + - 'open_icon_library-full/icons/' + openiconType + '/' + - sourceSize + 'x' + sourceSize + '/' + - openiconName + '.' + openiconType, - css: { - width: targetSize, - height: targetSize, - transformOrigin: '50% 50%', - opacity: 1 - } - } - } // Parse URLs. if (/^(?:(?:https?|data):)?\//i.exec(name)) { + if (/^https?:/i.test(name) && !/^https?:\/\/[^/]*pencilcode.net/.test(name) + && /(?:^|\.)pencilcode\.net$/.test(window.location.hostname)) { + name = '/proxy/' + name; + } return { url: name, css: { From f8b82cd8e59c78df53e70fec8bdcd07dec09306a Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 8 Aug 2014 19:57:18 -0400 Subject: [PATCH 004/180] Slight optimization. --- jquery-turtle.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 3841875..81dc0f5 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -427,13 +427,15 @@ if (!transform || !hasGetBoundingClientRect()) { // and that first value will be interpreted as defaultProp:value1. // Some rudimentary quoting can be done, e.g., value:"prop", etc. function parseOptionString(str, defaultProp) { - if (str == null) { - return {}; - } - if ($.isPlainObject(str)) { - return str; + if (typeof(str) != 'string') { + if (str == null) { + return {}; + } + if ($.isPlainObject(str)) { + return str; + } + str = '' + str; } - str = '' + str; // Each token is an identifier, a quoted or parenthesized string, // a run of whitespace, or any other non-matching character. var token = str.match(/[-a-zA-Z_][-\w]*|"[^"]*"|'[^']'|\([^()]*\)|\s+|./g), From 8273770e066b733dcdf1ea69349fa0df3461a067 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 11 Aug 2014 12:10:54 -0400 Subject: [PATCH 005/180] Fix clip off-by-one error. --- jquery-turtle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 81dc0f5..2c58de9 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9439,14 +9439,14 @@ function transparentCanvasHull(canvas, threshold) { } if (last >= 0 || prevlast >= 0) { hull.push({ pageX: Math.min(first, prevfirst), pageY: row}); - hull.push({ pageX: Math.max(last, prevlast), pageY: row}); + hull.push({ pageX: Math.max(last, prevlast) + 1, pageY: row}); } prevfirst = first; prevlast = last; } if (prevlast >= 0) { hull.push({ pageX: prevfirst, pageY: canvas.height}); - hull.push({ pageX: prevlast, pageY: canvas.height}); + hull.push({ pageX: prevlast + 1, pageY: canvas.height}); } return convexHull(hull); } From efe5b4d4176772ca838b4edffbd8463f09d4fb27 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 12 Aug 2014 12:24:31 -0400 Subject: [PATCH 006/180] Fix proxy logic. --- jquery-turtle.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index ed421b2..efe610b 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7917,7 +7917,8 @@ function nameToImg(name, defaultshape) { if (/^(?:(?:https?|data):)?\//i.exec(name)) { if (/^https?:/i.test(name) && !/^https?:\/\/[^/]*pencilcode.net/.test(name) && /(?:^|\.)pencilcode\.net$/.test(window.location.hostname)) { - name = '/proxy/' + name; + name = window.location.protocol + '//' + + window.location.host + '/proxy/' + name; } return { url: name, From d886610c5172649f441eca251dbe920677954df5 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 12 Aug 2014 20:14:42 +0000 Subject: [PATCH 007/180] make pressed more robust; and interact with input better. --- jquery-turtle.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index efe610b..a1682f9 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -3369,10 +3369,14 @@ var pressedKey = (function() { // The pressed function just polls the given keyname. function pressed(keyname) { focusWindowIfFirst(); - // Canonical names are lowercase and have no spaces. - keyname = keyname.replace(/\s/g, '').toLowerCase(); - if (pressedState[keyname]) return true; - return false; + if (keyname) { + // Canonical names are lowercase and have no spaces. + keyname = keyname.replace(/\s/g, '').toLowerCase(); + if (pressedState[keyname]) return true; + return false; + } else { + return listPressedKeys(); + } } pressed.enable = enablePressListener; pressed.list = listPressedKeys; @@ -8373,7 +8377,7 @@ function input(name, callback, numeric) { numeric >= 0 && $.isNumeric(val) && ('' + parseFloat(val) == val))) { val = parseFloat(val); } - if (callback) { callback.call(thisval, val); } + if (callback) { setTimeout(function() {callback.call(thisval, val); }, 0); } } function validate() { if (numeric <= 0) return true; From 3184ccdae278886d36a1aa33bd4c2347bab76911 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 12 Aug 2014 20:32:50 +0000 Subject: [PATCH 008/180] Fix loading of images by relative url. --- jquery-turtle.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index a1682f9..243d718 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2513,6 +2513,12 @@ function makeTurtleXYHook(publicname, propx, propy, displace) { }; } +var absoluteUrlAnchor = document.createElement('a'); +function absoluteUrl(url) { + absoluteUrlAnchor.href = url; + return absoluteUrlAnchor.href; +} + // A map of url to {img: Image, queue: [{elem: elem, css: css, cb: cb}]}. var stablyLoadedImages = {}; @@ -2533,7 +2539,7 @@ var stablyLoadedImages = {}; // @param css is a dictionary of css props to set when the image is loaded. // @param cb is an optional callback, called after the loading is done. function setImageWithStableOrigin(elem, url, css, cb) { - var record; + var record, url = absoluteUrl(url); // The data-loading attr will always reflect the last URL requested. elem.setAttribute('data-loading', url); if (url in stablyLoadedImages) { @@ -2587,6 +2593,8 @@ function setImageWithStableOrigin(elem, url, css, cb) { // Only flip the src if the last requested image is the same as // the one we have now finished loading: otherwise, there has been // some subsequent load that has now superceded ours. + elem.getAttribute('data-loading'), loaded.src); + if (elem.getAttribute('data-loading') == loaded.src) { elem.removeAttribute('data-loading'); applyLoadedImage(loaded, elem, css); From c902a13978facdc61ecb66d9bc14e9084af35cf4 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 12 Aug 2014 20:45:48 +0000 Subject: [PATCH 009/180] Fix handling of failed image loads. --- jquery-turtle.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 243d718..952b469 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2593,8 +2593,6 @@ function setImageWithStableOrigin(elem, url, css, cb) { // Only flip the src if the last requested image is the same as // the one we have now finished loading: otherwise, there has been // some subsequent load that has now superceded ours. - elem.getAttribute('data-loading'), loaded.src); - if (elem.getAttribute('data-loading') == loaded.src) { elem.removeAttribute('data-loading'); applyLoadedImage(loaded, elem, css); @@ -2656,10 +2654,12 @@ function applyLoadedImage(loaded, elem, css) { elem.height = loaded.height; if (!isCanvas) { elem.src = loaded.src; - } else { - ctx = elem.getContext('2d'); - ctx.clearRect(0, 0, loaded.width, loaded.height); - ctx.drawImage(loaded, 0, 0); + } else if (loaded.width > 0 && loaded.height > 0) { + try { + ctx = elem.getContext('2d'); + ctx.clearRect(0, 0, loaded.width, loaded.height); + ctx.drawImage(loaded, 0, 0); + } catch (e) { } } } } From 0cb6c4b437d162b3fc978df8817988d4e5a73af7 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 21 Aug 2014 09:18:42 +0000 Subject: [PATCH 010/180] Remove insterted body warning for now. --- jquery-turtle.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 952b469..7ae474d 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7459,8 +7459,10 @@ $.turtle = function turtle(id, options) { } see.init(seeopt); if (wrotebody) { + /* see.html('Turtle script should be inside body ' + '- wrote a <body>'); + */ } // Return an eval loop hook string if 'see' is exported. if (exportedsee) { From 34583b1b2aa8aa5c8b0804a175ca720a9a0f16ad Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 28 Aug 2014 12:37:33 -0400 Subject: [PATCH 011/180] Improve nonrecursive dequeue, and make wear wait for the image to load. --- jquery-turtle.js | 56 +++++++++++++++++++++++++++------------ test/wear.html | 68 +++++++++++++++++++++++++----------------------- 2 files changed, 76 insertions(+), 48 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 7ae474d..ace75d5 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -369,7 +369,23 @@ var undefined = void 0, rootjQuery = jQuery(function() {}), interrupted = false, async_pending = 0, - global_plan_counter = 0; + global_plan_depth = 0, + global_plan_queue = []; + +function nonrecursive_dequeue(elem, qname) { + if (global_plan_depth > 5) { + global_plan_queue.push({elem: elem, qname: qname}); + } else { + global_plan_depth += 1; + $.dequeue(elem, qname); + while (global_plan_queue.length > 0) { + var task = global_plan_queue.shift(); + $.dequeue(task.elem, task.qname); + checkForHungLoop() + } + global_plan_depth -= 1; + } +} function __extends(child, parent) { for (var key in parent) { @@ -2159,13 +2175,14 @@ function touchesPixel(elem, color) { // Functions in direct support of exported methods. ////////////////////////////////////////////////////////////////////////// -function applyImg(sel, img) { +function applyImg(sel, img, cb) { if (img.img) { if (sel[0].tagName == 'CANVAS' || sel[0].tagName == img.img.tagName) { applyLoadedImage(img.img, sel[0], img.css); } } else if (sel[0].tagName == 'IMG' || sel[0].tagName == 'CANVAS') { - setImageWithStableOrigin(sel[0], img.url, img.css); + setImageWithStableOrigin(sel[0], img.url, img.css, cb); + cb = null; } else { var props = { backgroundImage: 'url(' + img.url + ')', @@ -2177,6 +2194,9 @@ function applyImg(sel, img) { } sel.css(props); } + if (cb) { + cb(); + } } function doQuickMove(elem, distance, sideways) { @@ -6258,11 +6278,15 @@ var turtlefn = { "wear(url) Sets the turtle image url: " + "wear 'http://bit.ly/1bgrQ0p'"], function wear(cc, name, css) { - if (typeof(name) == 'object' && typeof(css) == 'string') { + if ((typeof(name) == 'object' || typeof(name) == 'number') && + typeof(css) == 'string') { var t = css; css = name; name = t; } + if (typeof(css) == 'number') { + css = { height: css }; + } var img = nameToImg(name, 'turtle'); if (!img) return this; if (css) { @@ -6276,10 +6300,20 @@ var turtlefn = { this.css({ backgroundImage: 'none', }); - applyImg(this, img); + var loaded = false, waiting = null; + applyImg(this, img, function() { + loaded = true; + var callback = waiting; + if (callback) { waiting = null; callback(); } + }); if (!canMoveInstantly(this)) { this.delay(animTime(elem)); } + if (!loaded) { + this.pause({done: function(cb) { + if (loaded) { cb(); } else { waiting = cb; } + }}); + } this.plan(function() { cc.resolve(j); }); @@ -6684,17 +6718,7 @@ var turtlefn = { // The Array.prototype.push is faster. // $.merge($.queue(elem, qname), saved); Array.prototype.push.apply($.queue(elem, qname), saved); - if (global_plan_counter++ % 64) { - $.dequeue(elem, qname); - } else { - // Insert a timeout after executing a batch of plans, - // to avoid deep recursion. - async_pending += 1; - setTimeout(function() { - async_pending -= 1; - $.dequeue(elem, qname); - }, 0); - } + nonrecursive_dequeue(elem, qname); }); animation.finish = action; $.queue(elem, qname, animation); diff --git a/test/wear.html b/test/wear.html index 37a581c..0f0f0f7 100644 --- a/test/wear.html +++ b/test/wear.html @@ -14,38 +14,42 @@ ok(touches(white)); ok(touches(red)); wear('radius'); - ok(touches(white)); - ok(!touches(red)); - rt(20); - ok(touches(white)); - ok(!touches(red)); - rt(20); - ok(touches(white)); - ok(!touches(red)); - fd(3); - ok(touches(white)); - ok(touches(red)); - bk(3); - ok(touches(white)); - ok(!touches(red)); - dot(white, 27); - wear('pointer'); - ok(touches(white)); - ok(touches(red)); - wear('turtle'); - ok(touches(white)); - ok(!touches(red)); - fd(3); - ok(touches(white)); - ok(touches(red)); - var b = new Turtle(); - ok(!b.touches(blue)); - b.drawon(turtle); - ok(!b.touches(blue)); - wear(blue); - pause(0.1, function() { - ok(b.touches(blue)); - start(); + done(function() { + ok(touches(white)); + ok(!touches(red)); + rt(20); + ok(touches(white)); + ok(!touches(red)); + rt(20); + ok(touches(white)); + ok(!touches(red)); + fd(3); + ok(touches(white)); + ok(touches(red)); + bk(3); + ok(touches(white)); + ok(!touches(red)); + dot(white, 27); + wear('pointer'); + done(function() { + ok(touches(white)); + ok(touches(red)); + wear('turtle'); + ok(touches(white)); + ok(!touches(red)); + fd(3); + ok(touches(white)); + ok(touches(red)); + var b = new Turtle(); + ok(!b.touches(blue)); + b.drawon(turtle); + ok(!b.touches(blue)); + wear(blue); + pause(0.1, function() { + ok(b.touches(blue)); + start(); + }); + }); }); }); From cfffdce4fe54dfbc9fa65e5ccf06e21cc8904e04 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 28 Aug 2014 13:03:41 -0400 Subject: [PATCH 012/180] Add range two-argument form for random. --- jquery-turtle.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index ace75d5..607b9fa 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -231,6 +231,7 @@ timer(secs, fn) // Calls back fn once after secs seconds. tick([perSec,] fn) // Repeatedly calls fn at the given rate (null clears). done(fn) // Calls back fn after all turtle animation is complete. random(n) // Returns a random number [0..n-1]. +random(n,m) // Returns a random number [n..m-1]. random(list) // Returns a random element of the list. random('normal') // Returns a gaussian random (mean 0 stdev 1). random('uniform') // Returns a uniform random [0...1). @@ -8042,8 +8043,15 @@ function hatchone(name, container, defaultshape) { } // Simplify Math.floor(Math.random() * N) and also random choice. -function random(arg) { - if (typeof(arg) == 'number') { return Math.floor(Math.random() * arg); } +function random(arg, arg2) { + if (typeof(arg) == 'number') { + arg = Math.ceil(arg); + if (typeof(arg2) == 'number') { + arg2 = Math.ceil(arg2); + return Math.floor(Math.random() * (arg2 - arg) + arg); + } + return Math.floor(Math.random() * arg); + } if (typeof(arg) == 'object' && arg.length && arg.slice) { return arg[Math.floor(Math.random() * arg.length)]; } From e28b180cae445b95348ea95e212480f088be2099 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 28 Aug 2014 14:43:56 -0400 Subject: [PATCH 013/180] Add cross-origin support to all image requests. --- jquery-turtle.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 607b9fa..d303ec1 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2581,6 +2581,7 @@ function setImageWithStableOrigin(elem, url, css, cb) { img: new Image(), queue: [{elem: elem, css: css, cb: cb}] }; + record.img.crossOrigin = 'Anonymous'; // Pop the element to the right dimensions early if possible. resizeEarlyIfPossible(url, elem, css); // First set up the onload callback, then start loading. @@ -7955,7 +7956,7 @@ function nameToImg(name, defaultshape) { // Parse URLs. if (/^(?:(?:https?|data):)?\//i.exec(name)) { if (/^https?:/i.test(name) && !/^https?:\/\/[^/]*pencilcode.net/.test(name) - && /(?:^|\.)pencilcode\.net$/.test(window.location.hostname)) { + && /(?:^|\.)pencilcode\.net\b/.test(window.location.hostname)) { name = window.location.protocol + '//' + window.location.host + '/proxy/' + name; } From 5ae6b3929cfbd589f75b41de12c57633fe211ca5 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 28 Aug 2014 15:53:12 -0400 Subject: [PATCH 014/180] Add optional key-name argument to keydown, keyup, keypress events. --- jquery-turtle.js | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index d303ec1..0077b70 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -3408,11 +3408,44 @@ var pressedKey = (function() { return listPressedKeys(); } } + // Used to get names for key codes. + function match(choices, code) { + var name = keyCodeName[code]; + if (choices.indexOf(name) >= 0) return true; + if (code >= 160 && code <= 165) { + // For "shift left", also trigger "shift"; same for control and alt. + simplified = name.replace(/(?:left|right)$/, ''); + if (choices.indexOf(simplified) >= 0) return true; + } + return false; + } + // Keyup, keydown, and keypress handlers all can accept an optional + // string which is a name of a key to handle (or a comma-separated + // list of names of keys to handle. + function makeKeyHandler(fn, ch) { + focusWindowIfFirst(); + var t, listener, choices; + if (typeof(fn) == 'string' && typeof(ch) == 'function') { + t = ch; ch = fn; fn = t; + } + if (ch) { + choices = ch.replace(/\s/g, '').toLowerCase().split(','); + return function(e) { + if (match(choices, e.which)) { + fn.call(this, e); + } + } + } else { + return fn; + } + } pressed.enable = enablePressListener; pressed.list = listPressedKeys; + pressed.wrap = makeKeyHandler; return pressed; })(); + ////////////////////////////////////////////////////////////////////////// // WEB AUDIO SUPPORT // Definition of play("ABC") - uses ABC music note syntax. @@ -7143,19 +7176,19 @@ var dollar_turtle_methods = { ["keydown(fn) Calls fn(event) whenever a key is pushed down. " + "keydown (e) -> write 'down ' + e.which"], function(fn) { - $(window).keydown(fn); + $(window).keydown(pressedKey.wrap(fn, arguments[1])); }), keyup: wrapraw('keyup', ["keyup(fn) Calls fn(event) whenever a key is released. " + "keyup (e) -> write 'up ' + e.which"], function(fn) { - $(window).keyup(fn); + $(window).keyup(pressedKey.wrap(fn, arguments[1])); }), keypress: wrapraw('keypress', ["keypress(fn) Calls fn(event) whenever a letter is typed. " + "keypress (e) -> write 'press ' + e.which"], function(fn) { - $(window).keypress(fn); + $(window).keypress(pressedKey.wrap(fn, arguments[1])); }), send: wrapraw('send', ["send(name) Sends a message to be received by recv. " + From ae4c7e291a86e6fee86f76d68021ac0e9837aaa3 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 3 Sep 2014 05:07:23 -0400 Subject: [PATCH 015/180] Default speed within tick is Infinity. --- jquery-turtle.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 0077b70..68c5693 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8128,7 +8128,15 @@ function globaltick(rps, fn) { tickinterval = null; } if (fn && rps) { - tickinterval = window.setInterval(fn, 1000 / rps); + tickinterval = window.setInterval( + function() { + // Set default speed to Infinity within tick(). + var savedturtlespeed = $.fx.speeds.turtle; + $.fx.speeds.turtle = 0; + fn(); + $.fx.speeds.turtle = savedturtlespeed; + }, + 1000 / rps); } } From ed60f78a75b40392125901c153a6a7491f14299d Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 3 Sep 2014 10:07:29 -0400 Subject: [PATCH 016/180] Add load and modify tick-speed-infinity impl. --- jquery-turtle.js | 73 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 68c5693..e99229e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2300,10 +2300,11 @@ function makeTurtleEasingHook() { function animTime(elem) { var state = $.data(elem, 'turtleData'); - if (!state) return 'turtle'; + if (!state) return (insidetick ? 0 : 'turtle'); if ($.isNumeric(state.speed) || state.speed == 'Infinity') { return 1000 / state.speed; } + if (state.speed == 'turtle' && insidetick) return 0; return state.speed; } @@ -2535,9 +2536,29 @@ function makeTurtleXYHook(publicname, propx, propy, displace) { } var absoluteUrlAnchor = document.createElement('a'); -function absoluteUrl(url) { +function absoluteUrlObject(url) { absoluteUrlAnchor.href = url; - return absoluteUrlAnchor.href; + return absoluteUrlAnchor; +} +function absoluteUrl(url) { + return absoluteUrlObject(url).href; +} +function isPencilHost(hostname) { + return /(?:^|\.)pencil(?:\.|code\.)/i.test(hostname); +} +function apiUrl(url, topdir) { + var link = absoluteUrlObject(url), result = link.href; + if (isPencilHost(link.hostname)) { + if (/^\/(?:edit|home|code|load|save)/.test(link.pathname)) { + // Replace a special topdir name. + result = link.protocol + '//' + link.host + '/' + topdir + '/' + + link.pathname.replace(/\/[^\/]*/, '') + link.search + link.hash; + } else { + // Prepend a topdir name if there is no special name already. + result = link.protocol + '//' + link.host + '/' + topdir + '/' + + link.pathname + link.search + link.hash; + } + } } // A map of url to {img: Image, queue: [{elem: elem, css: css, cb: cb}]}. @@ -7055,6 +7076,30 @@ var dollar_turtle_methods = { }); sync = null; }), + load: wrapraw('load', + ["load(url, cb) Loads data from the url and passes it to cb. " + + "load '/intro', (t) -> write 'intro contains', t"], + function(url, cb) { + var retval = null; + $.ajax(apiUrl(url, 'load'), { async: !!cb, complete: function(xhr) { + try { + retval = JSON.parse(e.responseText); + if (typeof(json.data) == 'string' && typeof(json.file) == 'string') { + retval = retval.data; + } else if ($.isArray(json.list) && typeof(json.directory) == 'string') { + retval = retval.list; + } + } catch(e) { + if (retval == null && e && e.responseText) { + retval = e.responseText; + } + } + if (cb) { + cb(retval, xhr); + } + }}); + return retval; + }), append: wrapraw('append', ["append(html) Appends text to the document without a new line. " + "append 'try this twice...'"], @@ -7987,11 +8032,13 @@ function nameToImg(name, defaultshape) { return shape(color); } // Parse URLs. - if (/^(?:(?:https?|data):)?\//i.exec(name)) { - if (/^https?:/i.test(name) && !/^https?:\/\/[^/]*pencilcode.net/.test(name) - && /(?:^|\.)pencilcode\.net\b/.test(window.location.hostname)) { + if (/\//.test(name)) { + var hostname = absoluteUrlObject(name).hostname; + // Use proxy to load image if the image is offdomain but the page is on + // a pencil host (with a proxy). + if (!isPencilHost(hostname) && isPencilHost(window.location.hostname)) { name = window.location.protocol + '//' + - window.location.host + '/proxy/' + name; + window.location.host + '/proxy/' + absoluteUrl(name); } return { url: name, @@ -8117,7 +8164,7 @@ function random(arg, arg2) { } // Simplify setInterval(fn, 1000) to just tick(fn). -var tickinterval = null; +var tickinterval = null, insidetick = 0; function globaltick(rps, fn) { if (fn === undefined && $.isFunction(rps)) { fn = rps; @@ -8131,10 +8178,12 @@ function globaltick(rps, fn) { tickinterval = window.setInterval( function() { // Set default speed to Infinity within tick(). - var savedturtlespeed = $.fx.speeds.turtle; - $.fx.speeds.turtle = 0; - fn(); - $.fx.speeds.turtle = savedturtlespeed; + try { + insidetick++; + fn(); + } finally { + insidetick--; + } }, 1000 / rps); } From 8c58f2846f42a075ca51cc5c0023f6074dfc50a5 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 3 Sep 2014 14:35:09 +0000 Subject: [PATCH 017/180] Add proxying to load and save. --- jquery-turtle.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index e99229e..9f71cf3 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2547,7 +2547,8 @@ function isPencilHost(hostname) { return /(?:^|\.)pencil(?:\.|code\.)/i.test(hostname); } function apiUrl(url, topdir) { - var link = absoluteUrlObject(url), result = link.href; + var link = absoluteUrlObject(url == null ? '' : url), + result = link.href; if (isPencilHost(link.hostname)) { if (/^\/(?:edit|home|code|load|save)/.test(link.pathname)) { // Replace a special topdir name. @@ -2558,7 +2559,11 @@ function apiUrl(url, topdir) { result = link.protocol + '//' + link.host + '/' + topdir + '/' + link.pathname + link.search + link.hash; } + } else if (isPencilHost(window.location.hostname)) { + // Proxy offdomain requests to avoid CORS issues. + result = '/proxy/' + result; } + return result; } // A map of url to {img: Image, queue: [{elem: elem, css: css, cb: cb}]}. @@ -7080,25 +7085,25 @@ var dollar_turtle_methods = { ["load(url, cb) Loads data from the url and passes it to cb. " + "load '/intro', (t) -> write 'intro contains', t"], function(url, cb) { - var retval = null; + var val = null; $.ajax(apiUrl(url, 'load'), { async: !!cb, complete: function(xhr) { try { - retval = JSON.parse(e.responseText); - if (typeof(json.data) == 'string' && typeof(json.file) == 'string') { - retval = retval.data; - } else if ($.isArray(json.list) && typeof(json.directory) == 'string') { - retval = retval.list; + val = JSON.parse(xhr.responseText); + if (typeof(val.data) == 'string' && typeof(val.file) == 'string') { + val = val.data; + } else if ($.isArray(val.list) && typeof(val.directory) == 'string') { + val = val.list; } } catch(e) { - if (retval == null && e && e.responseText) { - retval = e.responseText; + if (val == null && xhr && xhr.responseText) { + val = xhr.responseText; } } if (cb) { - cb(retval, xhr); + cb(val, xhr); } }}); - return retval; + return val; }), append: wrapraw('append', ["append(html) Appends text to the document without a new line. " + From 3aba65eeda8e15ff9fb9504f192e60b6742a4fea Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 3 Sep 2014 12:19:32 -0400 Subject: [PATCH 018/180] Draft of save method. --- jquery-turtle.js | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9f71cf3..c3e6e6d 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2543,9 +2543,20 @@ function absoluteUrlObject(url) { function absoluteUrl(url) { return absoluteUrlObject(url).href; } + +// Pencil-code specific function: detects whether a domain appears to +// be a pencilcode site. function isPencilHost(hostname) { - return /(?:^|\.)pencil(?:\.|code\.)/i.test(hostname); + return /(?:^|\.)pencil(?:code)?\./i.test(hostname); +} +// Returns a pencilcode username from the URL, if any. +function pencilUserFromUrl(url) { + var hostname = absoluteUrlObject(url == null ? '' : url).hostname, + match = /^(\w+)\.pencil(?:code)?\./i.exec(hostname); + if (match) return match[1]; + return null; } +// Rewrites a url to have the top directory name given. function apiUrl(url, topdir) { var link = absoluteUrlObject(url == null ? '' : url), result = link.href; @@ -2565,6 +2576,20 @@ function apiUrl(url, topdir) { } return result; } +// Retrieves the pencil code login cookie, if there is one. +function loginCookie() { + if (!document.cookie) return null; + var cookies = document.cookie.split(/;\s*/); + for (var j = 0; j < cookies.length; ++j) { + if (/^login=/.test(cookies[j])) { + var val = unescape(cookies[j].substr(6)).split(':'); + if (val && val.length == 2) { + return { user: val[0], key: val[1] }; + } + } + } + return null; +} // A map of url to {img: Image, queue: [{elem: elem, css: css, cb: cb}]}. var stablyLoadedImages = {}; @@ -7105,6 +7130,38 @@ var dollar_turtle_methods = { }}); return val; }), + save: wrapraw('save', + ["save(url, data, cb) Posts data to the url and calls when done. " + + "save '/intro', 'pen gold, 20\\nfd 100\\n'"], + function(url, data, cb) { + var payload = data, url = apiUrl(url, 'save'); + if (typeof(payload) == 'string' || typeof(payload) == 'number') { + payload = { data: payload }; + } + if (!payload.key) { + var login = loginCookie(); + if (login && login.key && login.user == pencilUserFromUrl(url)) { + payload.key = login.key; + } + } + $.ajax(apiUrl(url, 'save'), { + type: 'POST', + data: payload, + complete: function(xhr) { + var val + try { + val = JSON.parse(xhr.responseText); + } catch(e) { + if (val == null && xhr && xhr.responseText) { + val = xhr.responseText; + } + } + if (cb) { + cb(val, xhr); + } + } + }); + }), append: wrapraw('append', ["append(html) Appends text to the document without a new line. " + "append 'try this twice...'"], From 249083e5dacfe83d371a31975e3af3d850aa847b Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 3 Sep 2014 16:52:07 +0000 Subject: [PATCH 019/180] Add save. --- jquery-turtle.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index c3e6e6d..8a50d09 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2561,13 +2561,13 @@ function apiUrl(url, topdir) { var link = absoluteUrlObject(url == null ? '' : url), result = link.href; if (isPencilHost(link.hostname)) { - if (/^\/(?:edit|home|code|load|save)/.test(link.pathname)) { + if (/^\/(?:edit|home|code|load|save)(?:\/|$)/.test(link.pathname)) { // Replace a special topdir name. result = link.protocol + '//' + link.host + '/' + topdir + '/' + - link.pathname.replace(/\/[^\/]*/, '') + link.search + link.hash; + link.pathname.replace(/\/[^\/]*(?:\/|$)/, '') + link.search + link.hash; } else { // Prepend a topdir name if there is no special name already. - result = link.protocol + '//' + link.host + '/' + topdir + '/' + + result = link.protocol + '//' + link.host + '/' + topdir + link.pathname + link.search + link.hash; } } else if (isPencilHost(window.location.hostname)) { @@ -7134,11 +7134,12 @@ var dollar_turtle_methods = { ["save(url, data, cb) Posts data to the url and calls when done. " + "save '/intro', 'pen gold, 20\\nfd 100\\n'"], function(url, data, cb) { + if (!url) throw new Error('Missing url for save'); var payload = data, url = apiUrl(url, 'save'); if (typeof(payload) == 'string' || typeof(payload) == 'number') { payload = { data: payload }; } - if (!payload.key) { + if (payload && !payload.key) { var login = loginCookie(); if (login && login.key && login.user == pencilUserFromUrl(url)) { payload.key = login.key; From 59526b0b362e53ac4602fd16779a816d9e0b6f42 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 3 Sep 2014 18:21:00 -0400 Subject: [PATCH 020/180] Improve inside-tick default. --- jquery-turtle.js | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 8a50d09..da9b930 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2298,13 +2298,14 @@ function makeTurtleEasingHook() { } } -function animTime(elem) { +function animTime(elem, intick) { var state = $.data(elem, 'turtleData'); - if (!state) return (insidetick ? 0 : 'turtle'); + intick = intick || insidetick; + if (!state) return (intick ? 0 : 'turtle'); if ($.isNumeric(state.speed) || state.speed == 'Infinity') { return 1000 / state.speed; } - if (state.speed == 'turtle' && insidetick) return 0; + if (state.speed == 'turtle' && intick) return 0; return state.speed; } @@ -5644,7 +5645,7 @@ function rtlt(cc, degrees, radius) { if (degrees == null) { degrees = 90; // zero-argument default. } - var elem, left = (cc.name === 'lt'); + var elem, left = (cc.name === 'lt'), intick = insidetick; if ((elem = canMoveInstantly(this)) && (radius === 0 || (radius == null && getTurningRadius(elem) === 0))) { cc.appear(0); @@ -5657,7 +5658,7 @@ function rtlt(cc, degrees, radius) { this.plan(function(j, elem) { cc.appear(j); this.animate({turtleRotation: operator + cssNum(degrees || 0) + 'deg'}, - animTime(elem), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc.resolver(j)); }); return this; } else { @@ -5666,7 +5667,7 @@ function rtlt(cc, degrees, radius) { var oldRadius = this.css('turtleTurningRadius'); this.css({turtleTurningRadius: (degrees < 0) ? -radius : radius}); this.animate({turtleRotation: operator + cssNum(degrees) + 'deg'}, - animTime(elem), animEasing(elem)); + animTime(elem, intick), animEasing(elem)); this.plan(function() { this.css({turtleTurningRadius: oldRadius}); cc.resolve(j); @@ -5684,7 +5685,7 @@ function fdbk(cc, amount) { if (cc.name === 'bk') { amount = -amount; } - var elem; + var elem, intick = insidetick; if ((elem = canMoveInstantly(this))) { cc.appear(0); doQuickMove(elem, amount, 0); @@ -5694,7 +5695,7 @@ function fdbk(cc, amount) { this.plan(function(j, elem) { cc.appear(0); this.animate({turtleForward: '+=' + cssNum(amount || 0) + 'px'}, - animTime(elem), animEasing(elem), cc.resolver(0)); + animTime(elem, intick), animEasing(elem), cc.resolver(0)); }); return this; } @@ -5705,6 +5706,7 @@ function fdbk(cc, amount) { ////////////////////////////////////////////////////////////////////////// function animatedDotCommand(fillShape) { + var intick = insidetick; return (function(cc, style, diameter) { if ($.isNumeric(style)) { // Allow for parameters in either order. @@ -5732,7 +5734,7 @@ function animatedDotCommand(fillShape) { } else { this.queue(function(next) { $({radius: 0}).animate({radius: animDiam}, { - duration: animTime(elem), + duration: animTime(elem, intick), step: function() { if (!hasAlpha) { fillShape(drawOnCanvas, c, this.radius, ts.rot, ps); @@ -5830,10 +5832,11 @@ var turtlefn = { } if (!y) { y = 0; } if (!x) { x = 0; } + var intick = insidetick; this.plan(function(j, elem) { cc.appear(j); this.animate({turtlePosition: displacedPosition(elem, y, x)}, - animTime(elem), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc.resolver(j)); }); return this; }), @@ -5847,7 +5850,7 @@ var turtlefn = { } if (!y) { y = 0; } if (!x) { x = 0; } - var elem; + var elem, intick = insidetick; if ((elem = canMoveInstantly(this))) { cc.appear(0); doQuickMoveXY(elem, x, y); @@ -5859,7 +5862,7 @@ var turtlefn = { var tr = getElementTranslation(elem); this.animate( { turtlePosition: cssNum(tr[0] + x) + ' ' + cssNum(tr[1] - y) }, - animTime(elem), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc.resolver(j)); }); return this; }), @@ -5870,7 +5873,7 @@ var turtlefn = { "or an object on the page (see pagexy): " + "moveto lastmousemove"], function moveto(cc, x, y) { - var position = x, localx = 0, localy = 0, limit = null; + var position = x, localx = 0, localy = 0, limit = null, intick = insidetick; if ($.isNumeric(position) && $.isNumeric(y)) { // moveto x, y: use local coordinates. localx = parseFloat(position); @@ -5912,7 +5915,7 @@ var turtlefn = { cc.appear(j); this.animate({turtlePosition: computeTargetAsTurtlePosition(elem, pos, limit, localx, localy)}, - animTime(elem), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc.resolver(j)); }); return this; }), @@ -5961,6 +5964,7 @@ var turtlefn = { bearing = [bearing, y]; y = null; } + var intick = insidetick; this.plan(function(j, elem) { cc.appear(j); if ($.isWindow(elem) || elem.nodeType === 9) { @@ -6003,7 +6007,7 @@ var turtlefn = { } dir = ts.rot + normalizeRotation(dir - ts.rot); this.animate({turtleRotation: dir}, - animTime(elem), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc.resolver(j)); }); return this; }), @@ -6060,6 +6064,7 @@ var turtlefn = { } else if ($.isPlainObject(penstyle)) { penstyle = writePenStyle(penstyle); } + var intick = insidetick; this.plan(function(j, elem) { cc.appear(j); var animate = !canMoveInstantly(this) && this.is(':visible'), @@ -6111,7 +6116,7 @@ var turtlefn = { pencil.css({ opacity: 0 }); target.opacity = 1; } - pencil.animate(target, animTime(elem)); + pencil.animate(target, animTime(elem, intick)); this.queue(function(next) { pencil.done(function() { pencil.remove(); @@ -6187,6 +6192,7 @@ var turtlefn = { if (valy === undefined) { valy = valx; } // Disallow scaling to zero using this method. if (!valx || !valy) { valx = valy = 1; } + var intick = insidetick; this.plan(function(j, elem) { cc.appear(j); if ($.isWindow(elem) || elem.nodeType === 9) { @@ -6198,7 +6204,7 @@ var turtlefn = { c[0] *= valx; c[1] *= valy; this.animate({turtleScale: $.map(c, cssNum).join(' ')}, - animTime(elem), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc.resolver(j)); }); return this; }), @@ -6373,7 +6379,7 @@ var turtlefn = { if (typeof(css) == 'number') { css = { height: css }; } - var img = nameToImg(name, 'turtle'); + var img = nameToImg(name, 'turtle'), intick = insidetick; if (!img) return this; if (css) { $.extend(img.css, css); @@ -6393,7 +6399,7 @@ var turtlefn = { if (callback) { waiting = null; callback(); } }); if (!canMoveInstantly(this)) { - this.delay(animTime(elem)); + this.delay(animTime(elem, intick)); } if (!loaded) { this.pause({done: function(cb) { @@ -6433,6 +6439,7 @@ var turtlefn = { if ($.isNumeric(styles)) { styles = { fontSize: styles }; } + var intick = insidetick; return this.plan(function(j, elem) { cc.appear(j); var applyStyles = {}, currentStyles = this.prop('style'); @@ -6470,7 +6477,7 @@ var turtlefn = { out.css(applyStyles); // Add a delay. if (!canMoveInstantly(this)) { - this.delay(animTime(elem)); + this.delay(animTime(elem, intick)); } this.plan(function() { cc.resolve(j); From 51b6bdd42af476be7236b05e47fd0a881a5b2872 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 5 Sep 2014 09:56:30 -0400 Subject: [PATCH 021/180] Add position argument to label. --- jquery-turtle.js | 31 +++++++++++++++++++++++++------ test/label.html | 4 ++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index da9b930..29acb79 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6433,16 +6433,24 @@ var turtlefn = { label: wrapcommand('label', 1, ["label(text) Labels the current position with HTML: " + "label 'remember'", - "label(text, styles) Apply CSS styles to the label: " + - "label 'big', { fontSize: 100 }"], - function label(cc, html, styles) { + "label(text, position, styles) Position is 'top', 'bottom', 'left'," + + "or 'right', and styles is an optional size or CSS object: " + + "label 'big', 'bottom', { color: red:, fontSize: 100 }"], + function label(cc, html, side, styles) { + if (!styles && ($.isNumeric(side) || $.isPlainObject(side))) { + styles = side; + side = null; + } if ($.isNumeric(styles)) { styles = { fontSize: styles }; } + if (!side) { + side = 'rotated-scaled'; + } var intick = insidetick; return this.plan(function(j, elem) { cc.appear(j); - var applyStyles = {}, currentStyles = this.prop('style'); + var applyStyles = {margin: 8}, currentStyles = this.prop('style'); // For defaults, copy inline styles of the turtle itself except for // properties in the following list (these are the properties used to // make the turtle look like a turtle). @@ -6466,13 +6474,24 @@ var turtlefn = { // Place the label on the screen using the figured styles. var out = output(html, 'label').css(applyStyles) .addClass('turtle').appendTo(getTurtleField()); + var rotated = /\brotated\b/.test(side), + scaled = /\bscaled\b/.test(side); // Mimic the current position and rotation and scale of the turtle. out.css({ turtlePosition: computeTargetAsTurtlePosition( out.get(0), this.pagexy(), null, 0, 0), - turtleRotation: this.css('turtleRotation'), - turtleScale: this.css('turtleScale') + turtleRotation: rotated ? this.css('turtleRotation') : 0, + turtleScale: scaled ? this.css('turtleScale') : 1 }); + // Modify top-left to slide to the given corner, if requested. + if (/\b(?:top|bottom)\b/.test(side)) { + applyStyles.top = + (/\btop\b/.test(side) ? -1 : 1) * out.outerHeight(true) / 2; + } + if (/\b(?:left|right)\b/.test(side)) { + applyStyles.left = + (/\bleft\b/.test(side) ? -1 : 1) * out.outerWidth(true) / 2; + } // Then finally apply styles (turtle styles may be overridden here). out.css(applyStyles); // Add a delay. diff --git a/test/label.html b/test/label.html index 66ec7ae..8eb221b 100644 --- a/test/label.html +++ b/test/label.html @@ -40,6 +40,7 @@ fd(150) label('giant', 150); label('upside down', { turtleRotation: 180, fontSize: 8 }); + label('bottomleft', 'bottom-left'); done(function() { ok($('label:contains(small)').width() < $('label:contains(mid)').width()); ok($('label:contains(mid)').width() < $('label:contains(big)').width()); @@ -49,10 +50,13 @@ deepEqual(rounded($('label:contains(big)').getxy()), [0, 50]); deepEqual(rounded($('label:contains(mid)').getxy()), [0, -50]); deepEqual(rounded($('label:contains(giant)').getxy()), [150, -50]); + ok($('label:contains(bottomleft)').getxy()[0] < -50); + ok($('label:contains(bottomleft)').getxy()[1] < -100); equal(rounded($('label:contains(small)').direction()), 0); equal(rounded($('label:contains(big)').direction()), 0); equal(rounded($('label:contains(mid)').direction()), 90); equal(rounded($('label:contains(giant)').direction()), 90); + equal(rounded($('label:contains(bottomleft)').direction()), 0); equal(direction(), 90); equal(rounded($('label:contains(upside down)').direction()), 180); start(); From 3289c750d34d051cd28ebe6f69008db2cedea5eb Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 5 Sep 2014 14:09:19 +0000 Subject: [PATCH 022/180] Withstand shuffled label argument order. --- jquery-turtle.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 29acb79..9ec1658 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6433,13 +6433,17 @@ var turtlefn = { label: wrapcommand('label', 1, ["label(text) Labels the current position with HTML: " + "label 'remember'", - "label(text, position, styles) Position is 'top', 'bottom', 'left'," + - "or 'right', and styles is an optional size or CSS object: " + - "label 'big', 'bottom', { color: red:, fontSize: 100 }"], + "label(text, styles, position) Optional position specifies " + + "'top', 'bottom', 'left', 'right', and optional styles is a size " + + "or CSS object: " + + "label 'big', { color: red, fontSize: 100 }, 'bottom'"], function label(cc, html, side, styles) { - if (!styles && ($.isNumeric(side) || $.isPlainObject(side))) { + if ((!styles || typeof(styles) == 'string') && + ($.isNumeric(side) || $.isPlainObject(side))) { + // Handle switched second and third argument order. + var t = styles; styles = side; - side = null; + side = t; } if ($.isNumeric(styles)) { styles = { fontSize: styles }; From 97f51eef7eb5b3e1eef2f8a5720ee5fd0e9c771e Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 5 Sep 2014 14:52:57 +0000 Subject: [PATCH 023/180] load returns null by default and does not insert topdir if there isn't one. --- jquery-turtle.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9ec1658..18502ed 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2566,10 +2566,6 @@ function apiUrl(url, topdir) { // Replace a special topdir name. result = link.protocol + '//' + link.host + '/' + topdir + '/' + link.pathname.replace(/\/[^\/]*(?:\/|$)/, '') + link.search + link.hash; - } else { - // Prepend a topdir name if there is no special name already. - result = link.protocol + '//' + link.host + '/' + topdir + - link.pathname + link.search + link.hash; } } else if (isPencilHost(window.location.hostname)) { // Proxy offdomain requests to avoid CORS issues. @@ -7138,9 +7134,9 @@ var dollar_turtle_methods = { }), load: wrapraw('load', ["load(url, cb) Loads data from the url and passes it to cb. " + - "load '/intro', (t) -> write 'intro contains', t"], + "load 'intro', (t) -> write 'intro contains', t"], function(url, cb) { - var val = null; + var val; $.ajax(apiUrl(url, 'load'), { async: !!cb, complete: function(xhr) { try { val = JSON.parse(xhr.responseText); @@ -7162,7 +7158,7 @@ var dollar_turtle_methods = { }), save: wrapraw('save', ["save(url, data, cb) Posts data to the url and calls when done. " + - "save '/intro', 'pen gold, 20\\nfd 100\\n'"], + "save 'intro', 'pen gold, 20\\nfd 100\\n'"], function(url, data, cb) { if (!url) throw new Error('Missing url for save'); var payload = data, url = apiUrl(url, 'save'); From d55a2128e504524c42f21364f80b383d534d5432 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 10 Sep 2014 19:32:37 +0000 Subject: [PATCH 024/180] Protect click against button clicks. --- jquery-turtle.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 18502ed..d22ca8a 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7286,7 +7286,13 @@ var dollar_turtle_methods = { ["click(fn) Calls fn(event) whenever the mouse is clicked. " + "click (e) -> moveto e; label 'clicked'"], function(fn) { - $(window).click(fn); + $(window).click(function(e) { + if ($(e.target).closest('input,button').length) { + // Discard clicks on input fields and buttons. + return; + } + this.apply(fn, arguments); + }); }), mouseup: wrapraw('mouseup', ["mouseup(fn) Calls fn(event) whenever the mouse is released. " + From 8a7e3f82269782d590c45e4a797eca2317d442c9 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 10 Sep 2014 20:02:09 +0000 Subject: [PATCH 025/180] Simplify mouse events: filter out buttons, gather lastmouse. --- jquery-turtle.js | 90 ++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index d22ca8a..b11aed6 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5623,6 +5623,17 @@ function wrapglobalcommand(name, helptext, fn) { return wrapraw(name, helptext, wrapper); } +function wrapwindowevent(name, helptext) { + return wrapraw(name, helptext, function(fn) { + var wrapped = /^key/.test(name) ? pressedKey.wrap(fn, arguments[1]) : fn; + var filtered = function(e) { + if ($(e.target).closest('input,button').length) { return; } + wrapped.apply(this, arguments); + } + $(window).on(name, filtered); + }); +} + // Wrapraw sets up help text for a function (such as "sqrt") that does // not need any other setup. function wrapraw(name, helptext, fn) { @@ -7282,54 +7293,27 @@ var dollar_turtle_methods = { (s * 100).toFixed(0) + '%', (l * 100).toFixed(0) + '%', a]); }), - click: wrapraw('click', + click: wrapwindowevent('click', ["click(fn) Calls fn(event) whenever the mouse is clicked. " + - "click (e) -> moveto e; label 'clicked'"], - function(fn) { - $(window).click(function(e) { - if ($(e.target).closest('input,button').length) { - // Discard clicks on input fields and buttons. - return; - } - this.apply(fn, arguments); - }); - }), - mouseup: wrapraw('mouseup', + "click (e) -> moveto e; label 'clicked'"]), + mouseup: wrapwindowevent('mouseup', ["mouseup(fn) Calls fn(event) whenever the mouse is released. " + - "mouseup (e) -> moveto e; label 'up'"], - function(fn) { - $(window).mouseup(fn); - }), - mousedown: wrapraw('mousedown', + "mouseup (e) -> moveto e; label 'up'"]), + mousedown: wrapwindowevent('mousedown', ["mousedown(fn) Calls fn(event) whenever the mouse is pressed. " + - "mousedown (e) -> moveto e; label 'down'"], - function(fn) { - $(window).mousedown(fn); - }), - mousemove: wrapraw('mousemove', + "mousedown (e) -> moveto e; label 'down'"]), + mousemove: wrapwindowevent('mousemove', ["mousedown(fn) Calls fn(event) whenever the mouse is moved. " + - "mousemove (e) -> moveto e"], - function(fn) { - $(window).mousemove(fn); - }), - keydown: wrapraw('keydown', + "mousemove (e) -> moveto e"]), + keydown: wrapwindowevent('keydown', ["keydown(fn) Calls fn(event) whenever a key is pushed down. " + - "keydown (e) -> write 'down ' + e.which"], - function(fn) { - $(window).keydown(pressedKey.wrap(fn, arguments[1])); - }), - keyup: wrapraw('keyup', + "keydown (e) -> write 'down ' + e.which"]), + keyup: wrapwindowevent('keyup', ["keyup(fn) Calls fn(event) whenever a key is released. " + - "keyup (e) -> write 'up ' + e.which"], - function(fn) { - $(window).keyup(pressedKey.wrap(fn, arguments[1])); - }), - keypress: wrapraw('keypress', + "keyup (e) -> write 'up ' + e.which"]), + keypress: wrapwindowevent('keypress', ["keypress(fn) Calls fn(event) whenever a letter is typed. " + - "keypress (e) -> write 'press ' + e.which"], - function(fn) { - $(window).keypress(pressedKey.wrap(fn, arguments[1])); - }), + "keypress (e) -> write 'press ' + e.which"]), send: wrapraw('send', ["send(name) Sends a message to be received by recv. " + "send 'go'; recv 'go', -> fd 100"], @@ -8318,19 +8302,27 @@ function turtleevents(prefix) { if (prefix || prefix === '') { eventsaver = (function(e) { // Keep the old instance if possible. - var old = window[prefix + e.type], prop; - if (old && old.__proto__ === e.__proto__) { - for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } - for (prop in e) { if (e.hasOwnProperty(prop)) old[prop] = e[prop]; } - return; + var names = [prefix + e.type], j, old, name, prop; + if ((e.originalEvent || e) instanceof MouseEvent) { + names.push(prefix + 'mouse'); + } + for (j = 0; j < names.length; ++j) { + name = names[j]; + old = window[name], prop; + if (old && old.__proto__ === e.__proto__) { + for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } + for (prop in e) { if (e.hasOwnProperty(prop)) old[prop] = e[prop]; } + } else { + window[name] = e; + } } - window[prefix + e.type] = e; }); - $(window).on($.map(eventfn, function(x,k) { return k; }).join(' '), - eventsaver); + window[prefix + 'mouse'] = new $.Event(); for (var k in eventfn) { window[prefix + k] = new $.Event(); } + $(window).on($.map(eventfn, function(x,k) { return k; }).join(' '), + eventsaver); } } From c26ac2a306332c401d0828446f59e5d665570169 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 11 Sep 2014 15:26:15 -0400 Subject: [PATCH 026/180] Support save meta. --- jquery-turtle.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index b11aed6..11da6e4 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7172,9 +7172,17 @@ var dollar_turtle_methods = { "save 'intro', 'pen gold, 20\\nfd 100\\n'"], function(url, data, cb) { if (!url) throw new Error('Missing url for save'); - var payload = data, url = apiUrl(url, 'save'); - if (typeof(payload) == 'string' || typeof(payload) == 'number') { - payload = { data: payload }; + var payload = { }, url = apiUrl(url, 'save'), key; + if (typeof(data) == 'string' || typeof(data) == 'number') { + payload.data = data; + } else { + for (key in data) if (data.hasOwnProperty(key)) { + if (typeof data[key] == 'string') { + payload[key] = data[key]; + } else { + payload[key] = JSON.stringify(data[key]); + } + } } if (payload && !payload.key) { var login = loginCookie(); From 289e36c168c74dccbfde5428f6a9c965460a5a7f Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 17 Sep 2014 12:35:26 -0400 Subject: [PATCH 027/180] Fix cross-origin handling for images, piano, etc. --- jquery-turtle.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 11da6e4..82e8256 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2608,7 +2608,7 @@ var stablyLoadedImages = {}; // @param css is a dictionary of css props to set when the image is loaded. // @param cb is an optional callback, called after the loading is done. function setImageWithStableOrigin(elem, url, css, cb) { - var record, url = absoluteUrl(url); + var record, urlobj = absoluteUrlObject(url), url = urlobj.href; // The data-loading attr will always reflect the last URL requested. elem.setAttribute('data-loading', url); if (url in stablyLoadedImages) { @@ -2629,7 +2629,11 @@ function setImageWithStableOrigin(elem, url, css, cb) { img: new Image(), queue: [{elem: elem, css: css, cb: cb}] }; - record.img.crossOrigin = 'Anonymous'; + if (isPencilHost(urlobj.hostname)) { + // When requesting through pencilcode, always make a + // cross-origin request. + record.img.crossOrigin = 'Anonymous'; + } // Pop the element to the right dimensions early if possible. resizeEarlyIfPossible(url, elem, css); // First set up the onload callback, then start loading. From ff9d5a481a90c1b9b95e4e9de71ef99f4f5249bd Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 21 Sep 2014 03:15:46 -0400 Subject: [PATCH 028/180] Make canvas transparent to pointer events. --- jquery-turtle.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 82e8256..66cbfda 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1578,18 +1578,20 @@ function createSurfaceAndField() { // fixes a "center" point in page coordinates that // will not change even if the document resizes. transformOrigin: cw + "px " + ch + "px", + pointerEvents: 'none', overflow: 'hidden' }); $(field).attr('id', 'field') .css({ position: 'absolute', display: 'inline-block', - top: ch, left: cw, width: '100%', height: '100%', + top: ch, left: cw, width: '0', height: '0', font: 'inherit', // Setting transform origin for the turtle field // fixes a "center" point in page coordinates that // will not change even if the document resizes. transformOrigin: "0px 0px", + pointerEvents: 'all' }).appendTo(surface); globalDrawing.surface = surface; globalDrawing.field = field; From 008eff9266a880aaa170609349d26fbcc621fab3 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 22 Sep 2014 18:35:46 -0400 Subject: [PATCH 029/180] Add min-height 100% to html --- jquery-turtle.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 66cbfda..ed17ed2 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1600,6 +1600,10 @@ function createSurfaceAndField() { function attachClipSurface() { if (document.body) { + if ($('html').attr('style') == null) { + // This prevents the body from shrinking. + $('html').css('min-height', '100%'); + } $(globalDrawing.surface).prependTo('body'); // Attach an event handler to forward mouse events from the body // to turtles in the turtle field layer. From 2736fb8f6a1ad864af0df394459673f374f53f66 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 22 Sep 2014 20:30:50 -0400 Subject: [PATCH 030/180] Add turtle option. --- jquery-turtle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index ed17ed2..b5ffbfb 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7563,6 +7563,9 @@ $.turtle = function turtle(id, options) { id = 'turtle'; } options = options || {}; + if ('turtle' in options) { + id = options.turtle; + } // Clear any previous turtle methods. clearGlobalTurtle(); // Expand any From 64a43ef0ce184722d89ed8ef6c1c4600a4e9ba46 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 15 Oct 2014 19:15:16 +0000 Subject: [PATCH 037/180] Fix bugs in jumpxy --- jquery-turtle.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index d03e18c..cab8da0 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5753,11 +5753,11 @@ function movexy(cc, x, y) { return this; } this.plan(function(j, elem) { - cc.appear(j); + cc && cc.appear(j); var tr = getElementTranslation(elem); this.animate( { turtlePosition: cssNum(tr[0] + x) + ' ' + cssNum(tr[1] - y) }, - animTime(elem, intick), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc && cc.resolver(j)); }); return this; } @@ -5795,9 +5795,9 @@ function moveto(cc, x, y) { } if (!pos || !isPageCoordinate(pos)) return; if ($.isWindow(elem)) { - cc.appear(j); + cc && cc.appear(j); scrollWindowToDocumentPosition(pos, limit); - cc.resolve(j); + cc && cc.resolve(j); return; } else if (elem.nodeType === 9) { return; From 702b8df6aa8c0e5a5f724c0db348850ae98653d6 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 21 Oct 2014 15:28:48 -0400 Subject: [PATCH 038/180] Fix two leaking globals. --- jquery-turtle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index cab8da0..52003b8 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -3381,7 +3381,7 @@ var pressedKey = (function() { 254: 'clear' }; // :-@, 0-9, a-z(lowercased) - for (i = 48; i < 91; ++i) { + for (var i = 48; i < 91; ++i) { keyCodeName[i] = String.fromCharCode(i).toLowerCase(); } // num-0-9 numeric keypad @@ -8367,7 +8367,7 @@ function turtleevents(prefix) { names.push(prefix + 'mouse'); } for (j = 0; j < names.length; ++j) { - name = names[j]; + var name = names[j]; old = window[name], prop; if (old && old.__proto__ === e.__proto__) { for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } From 08490693009e4188a877092e8e7f8c383e902d88 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 23 Oct 2014 13:21:08 -0400 Subject: [PATCH 039/180] Add tests for no globals leaks. --- Gruntfile.js | 3 +- test/globals.html | 321 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 test/globals.html diff --git a/Gruntfile.js b/Gruntfile.js index c0e8f94..b2f1735 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -42,7 +42,8 @@ module.exports = function(grunt) { qunit: { all: ["test/*.html"], options: { - timeout: 100000 + timeout: 100000, + noGlobals: true } }, release: { diff --git a/test/globals.html b/test/globals.html new file mode 100644 index 0000000..3178be6 --- /dev/null +++ b/test/globals.html @@ -0,0 +1,321 @@ + + + + + + +
+ From 396829f90176923105b02c60a9aa199fa0720d6f Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 28 Oct 2014 16:02:51 -0400 Subject: [PATCH 040/180] Clear labels on cg, and provide center-up x, y on mouse events. --- jquery-turtle.js | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 52003b8..cec473a 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1580,7 +1580,7 @@ function createSurfaceAndField() { transformOrigin: cw + "px " + ch + "px", pointerEvents: 'none', overflow: 'hidden' - }); + }).addClass('turtlefield'); $(field).attr('id', 'field') .css({ position: 'absolute', @@ -2068,13 +2068,16 @@ function clearField(arg) { sel.remove(); } } + if (!arg || /\blabels\b/.test(arg)) { + if (globalDrawing.surface) { + var sel = $(globalDrawing.surface).find('.turtlelabel'); + sel.remove(); + } + } if (!arg || /\btext\b/.test(arg)) { // "turtlefield" is a CSS class to use to mark top-level // elements that should not be deleted by clearscreen. var keep = $('.turtlefield'); - if (globalDrawing.surface) { - keep = keep.add(globalDrawing.surface); - } $('body').contents().not(keep).remove(); } } @@ -5636,7 +5639,8 @@ function wrapglobalcommand(name, helptext, fn) { function wrapwindowevent(name, helptext) { return wrapraw(name, helptext, function(fn) { - var wrapped = /^key/.test(name) ? pressedKey.wrap(fn, arguments[1]) : fn; + var wrapped = /^key/.test(name) ? pressedKey.wrap(fn, name) : + /^mouse|click$/.test(name) ? wrapmouselistener(fn, name) : fn; var filtered = function(e) { if ($(e.target).closest('input,button').length) { return; } wrapped.apply(this, arguments); @@ -5653,6 +5657,20 @@ function wrapraw(name, helptext, fn) { return fn; } +// Adds center-up x and y coordinates to the mouse event. +function wrapmouselistener(fn, name) { + return function(event) { + if ('pageX' in event && 'pageY' in event) { + var origin = $('#field').offset(); + if (origin) { + event.x = event.pageX - origin.left; + event.y = origin.top - event.pageY; + } + } + return fn.apply(this, arguments); + }; +} + ////////////////////////////////////////////////////////////////////////// // BASIC TURTLE MOTIONS // Generic functions to handle symmetric pairs of motions. @@ -6530,7 +6548,7 @@ var turtlefn = { }, styles); // Place the label on the screen using the figured styles. var out = output(html, 'label').css(applyStyles) - .addClass('turtle').appendTo(getTurtleField()); + .addClass('turtlelabel').appendTo(getTurtleField()); var rotated = /\brotated\b/.test(side), scaled = /\bscaled\b/.test(side); // Mimic the current position and rotation and scale of the turtle. @@ -7079,7 +7097,7 @@ var dollar_turtle_methods = { ["cg() Clear graphics. Does not alter body text: " + "do cg"], function cg() { - clearField('canvas'); + clearField('canvas labels'); }), ct: wrapglobalcommand('ct', ["ct() Clear text. Does not alter graphics canvas: " + From 5a5206d850334ad28a26de1697a423d369c3e1ae Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 28 Oct 2014 16:51:58 -0400 Subject: [PATCH 041/180] Fix key handling after button focus. --- jquery-turtle.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index cec473a..b1c572c 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -3494,11 +3494,15 @@ var pressedKey = (function() { choices = ch.replace(/\s/g, '').toLowerCase().split(','); return function(e) { if (match(choices, e.which)) { - fn.call(this, e); + e.keyname = keyCodeName[e.which]; + return fn.apply(this, arguments); } } } else { - return fn; + return function(e) { + e.keyname = keyCodeName[e.which]; + return fn.apply(this, arguments); + } } } pressed.enable = enablePressListener; @@ -5639,13 +5643,17 @@ function wrapglobalcommand(name, helptext, fn) { function wrapwindowevent(name, helptext) { return wrapraw(name, helptext, function(fn) { - var wrapped = /^key/.test(name) ? pressedKey.wrap(fn, name) : - /^mouse|click$/.test(name) ? wrapmouselistener(fn, name) : fn; - var filtered = function(e) { - if ($(e.target).closest('input,button').length) { return; } - wrapped.apply(this, arguments); - } - $(window).on(name, filtered); + var forKey = /^key/.test(name), + forMouse = /^mouse|click$/.test(name), + wrapped = forKey ? pressedKey.wrap(fn, arguments[1]) + : forMouse ? wrapmouselistener(fn) : fn, + filter = forMouse ? 'input,button' : forKey ? + 'textarea,input:not([type]),input[type=text],input[type=password]' + : null; + $(window).on(name, !filter ? wrapped : function(e) { + if ($(e.target).closest(filter).length) { return; } + return wrapped.apply(this, arguments); + }); }); } @@ -5658,7 +5666,7 @@ function wrapraw(name, helptext, fn) { } // Adds center-up x and y coordinates to the mouse event. -function wrapmouselistener(fn, name) { +function wrapmouselistener(fn) { return function(event) { if ('pageX' in event && 'pageY' in event) { var origin = $('#field').offset(); From 4d38c97709b437af364f30667bdaca8fd06074cd Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 30 Oct 2014 21:21:58 -0400 Subject: [PATCH 042/180] Add imagedata function. --- jquery-turtle.js | 74 +++++++++++++++++++++++++++++++++++++++++++---- test/globals.html | 1 + 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index b1c572c..c33dac9 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2763,6 +2763,12 @@ function applyLoadedImage(loaded, elem, css) { // Do not do this if the image can't be loaded. } } + moveToPreserveOrigin(elem, oldOrigin, newOrigin); +} + +function moveToPreserveOrigin(elem, oldOrigin, newOrigin) { + var sel = $(elem); + if (!sel.hasClass('turtle')) return; // If there was a change, then translate the element to keep the origin // in the same location on the screen. if (newOrigin[0] != oldOrigin[0] || newOrigin[1] != oldOrigin[1]) { @@ -2783,7 +2789,6 @@ function applyLoadedImage(loaded, elem, css) { } } - function withinOrNot(obj, within, distance, x, y) { var sel, elem, gbcr, pos, d2; if (x === undefined && y === undefined) { @@ -6708,6 +6713,57 @@ var turtlefn = { function canvas() { return this.filter('canvas').get(0); }), + imagedata: wrapraw('imagedata', + ["imagedata() Returns the image data for the turtle. " + + "imdat = imagedata(); write imdat.data.length, 'bytes'", + "imagedata(imdat) Sets the image data for the turtle. " + + "imagedata({width: 1, height:1, data:[255,0,0,255]});", + ], + function imagedata(val) { + var canvas = this.canvas(); + if (!canvas) { + if (val) throw new Error( + 'can only set imagedata on a canvas like a Sprite'); + var img = this.filter('img').get(0); + if (!img) return; + canvas = getOffscreenCanvas(img.naturalWidth, img.naturalHeight); + canvas.getContext('2d').drawImage(img, 0, 0); + } + var ctx = canvas.getContext('2d'); + if (!val) { + // The read case: return the image data for the whole canvas. + return ctx.getImageData(0, 0, canvas.width, canvas.height); + } + // The write case: if it's not an ImageData, convert it to one. + if (!(val instanceof ImageData)) { + if (typeof val != 'object' || + !$.isNumeric(val.width) || !$.isNumeric(val.height) || + !($.isArray(val.data) || val.data instanceof Uint8ClampedArray || + val.data instanceof Unit8Array)) { + return; + } + var imdat = ctx.createImageData( + Math.round(val.width), Math.round(val.height)); + var minlen = Math.min(val.data.length, imdat.data.length); + for (var j = 0; j < minlen; ++j) { imdat.data[j] = val.data[j]; } + val = imdat; + } + // If the size must be changed, resize it. + if (val.width != canvas.width || + val.height != canvas.height) { + var oldOrigin = readTransformOrigin(canvas); + canvas.width = val.width; + canvas.height = val.height; + var newOrigin = readTransformOrigin(canvas); + // Preserve the origin if it's a turtle. + moveToPreserveOrigin(canvas, oldOrigin, newOrigin); + // Drop the turtle hull, if any. + $(canvas).css('turtleHull', 'auto'); + ctx = canvas.getContext('2d'); + } + // Finally put the image data into the canvas. + ctx.putImageData(val, 0, 0); + }), cell: wrapraw('cell', ["cell(r, c) Row r and column c in a table. " + "Use together with the table function: " + @@ -8114,14 +8170,20 @@ function createRectangleShape(width, height, subpixels) { ctx.fillRect(0, 0, width, height); } var sw = width / subpixels, sh = height / subpixels; - return { - url: c.toDataURL(), - css: { + var css = { width: sw, height: sh, transformOrigin: (sw / 2) + 'px + ' + (sh / 2) + 'px', opacity: 1 - } + }; + if (subpixels < 1) { + // Requires newer than Chrome 40. + // Avoid smooth interpolation of big pixels. + css.imageRendering = 'pixelated'; + } + return { + url: c.toDataURL(), + css: css }; }); } @@ -8144,7 +8206,7 @@ function lookupShape(shapename) { function specToImage(spec, defaultshape) { var width = spec.width || spec.height || 256; var height = spec.height || spec.width || 256; - var subpixel = spec.subpixel || 1; + var subpixel = spec.subpixel || 1 / (spec.scale || 1); var color = spec.color || 'transparent'; var shape = createRectangleShape(width, height, subpixel); return shape(color); diff --git a/test/globals.html b/test/globals.html index 3178be6..675ade2 100644 --- a/test/globals.html +++ b/test/globals.html @@ -279,6 +279,7 @@ "pd", "pe", "pf", + "imagedata", "clip", "wear", "saveimg", From 98336220d5854b2a8438231f8527ea83b23047a8 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 31 Oct 2014 02:45:10 -0400 Subject: [PATCH 043/180] Remove pause animation. --- jquery-turtle.js | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index c33dac9..6827339 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5467,27 +5467,7 @@ function visiblePause(elem, seconds) { } var thissel = $(elem); if (ms) { - if (thissel.is(':visible')) { - // Generic indication of taking some time for an action - // A visual indicator of a pen color change. - var circle = new Turtle('gray radius'); - circle.css({ - zIndex: 1, - turtlePosition: thissel.css('turtlePosition'), - turtleRotation: thissel.css('turtleRotation') - }); - circle.animate({ - turtleRotation: '+=360' - }, ms, 'linear'); - thissel.queue(function(next) { - circle.done(function() { - circle.remove(); - next(); - }); - }); - } else { - thissel.delay(ms); - } + thissel.delay(ms); } } From 463fee49e0d9d4390c7fa22d9af21268930fb999 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 31 Oct 2014 05:44:26 -0400 Subject: [PATCH 044/180] Fix pause test. --- jquery-turtle.js | 2 +- test/pause.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 6827339..b9999d8 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5466,7 +5466,7 @@ function visiblePause(elem, seconds) { ms = seconds * 1000; } var thissel = $(elem); - if (ms) { + if (ms > 0) { thissel.delay(ms); } } diff --git a/test/pause.html b/test/pause.html index bf7ee3d..fb0f570 100644 --- a/test/pause.html +++ b/test/pause.html @@ -11,7 +11,7 @@ speed(100); var a = new Turtle(red); var b = new Turtle(blue); - a.pause(); + a.pause(0.1); a.plan(function() { equal(a.getxy()[1], 0); equal(round(b.getxy()[1]), 0); From 582720694fc5d973b0c8ab56bc232c7b6564c4ed Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 31 Oct 2014 16:51:20 -0400 Subject: [PATCH 045/180] Add Pencil object. --- jquery-turtle.js | 42 ++++++++++++++++++++++++++++++++++++++++++ test/globals.html | 1 + 2 files changed, 43 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index b9999d8..d43853f 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2890,6 +2890,42 @@ var Sprite = (function(_super) { })(jQuery.fn.init); +// Pencil extends Sprite, and is invisible and fast by default. +var Pencil = (function(_super) { + __extends(Pencil, _super); + + function Pencil(canvas) { + // A pencil draws on a canvas. Allow a selector or element. + if (canvas.jquery && $.isFunction(canvas.canvas)) { + canvas = canvas.canvas(); + } + if (canvas && canvas.tagName != 'CANVAS' || + typeof canvas.getContext != 'function') { + canvas = $(canvas).filter('canvas').get(0); + } + if (!canvas || canvas.tagName != 'CANVAS' || + typeof canvas.getContext != 'function') { + canvas = null; + } + // The pencil is a sprite that just defaults to zero size. + var context = canvas ? canvas.parentElement : null; + var settings = { width: 0, height: 0, color: transparent }; + Pencil.__super__.constructor.call(this, settings, context); + // Set the pencil to hidden, infinite speed, + // and drawing on the specifed canvas. + this.each(function() { + var state = getTurtleData(this); + state.speed = Infinity; + state.drawOnCanvas = canvas; + this.style.display = 'none'; + }); + } + + return Pencil; + +})(Sprite); + + // Turtle extends Sprite, and draws a turtle by default. var Turtle = (function(_super) { __extends(Turtle, _super); @@ -7522,6 +7558,12 @@ var dollar_turtle_methods = { min: wrapraw('min', ["min(x, y, ...) The minimum of a set of values. " + "see min 2, -5, 1"], Math.min), + Pencil: wrapraw('Pencil', + ["new Pencil(canvas) " + + "Make an invisble pencil for drawing on a canvas." + + "s = new Sprite; p = new Pencil(s); " + + "p.pen red; p.fd 100; remove p"], + Pencil), Turtle: wrapraw('Turtle', ["new Turtle(color) Make a new turtle. " + "t = new Turtle; t.fd 100"], Turtle), diff --git a/test/globals.html b/test/globals.html index 675ade2..584833c 100644 --- a/test/globals.html +++ b/test/globals.html @@ -93,6 +93,7 @@ "sqrt", "max", "min", + "Pencil", "Turtle", "Piano", "Webcam", From 881664e8d27a9ab2888ec3c49e0fa2b3db706e20 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 1 Nov 2014 05:44:36 -0400 Subject: [PATCH 046/180] Overhaul event hooking to generalize it. --- jquery-turtle.js | 362 +++++++++++++++++++++++++++++----------------- test/globals.html | 5 +- test/pressed.html | 61 ++++++-- 3 files changed, 279 insertions(+), 149 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index d43853f..547ccde 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -216,6 +216,7 @@ $.turtle() are as follows:
 lastclick             // Event object of the last click event in the doc.
+lastdblclick          // The last double-click event.
 lastmousemove         // The last mousemove event.
 lastmouseup           // The last mouseup event.
 lastmousedown         // The last mousedown event.
@@ -1470,7 +1471,7 @@ function readTurtleTransform(elem, computed) {
 
 function cssNum(n) {
   var r = n.toString();
-  if (r.indexOf('e') >= 0) {
+  if (~r.indexOf('e')) {
     r = Number(n).toFixed(17);
   }
   return r;
@@ -1541,6 +1542,8 @@ var globalDrawing = {
   ctx: null,
   canvas: null,
   timer: null,
+  fieldMouse: false,  // if a child of the field listens to mouse events.
+  fieldHook: false,   // once the body-event-forwarding logic is set up.
   subpixel: 1
 };
 
@@ -1597,6 +1600,9 @@ function createSurfaceAndField() {
   globalDrawing.surface = surface;
   globalDrawing.field = field;
   attachClipSurface();
+  // Now that we have a surface, the upward-center cartesian coordinate
+  // system based on that exists, so we can hook mouse events to add x, y.
+  addMouseEventHooks();
 }
 
 function attachClipSurface() {
@@ -1608,27 +1614,7 @@ function attachClipSurface() {
     $(globalDrawing.surface).prependTo('body');
     // Attach an event handler to forward mouse events from the body
     // to turtles in the turtle field layer.
-    $('body').on('click.turtle ' +
-      'mouseup.turtle mousedown.turtle mousemove.turtle', function(e) {
-      if (e.target === this && !e.isTrigger) {
-        // Only forward events directly on the body that (geometrically)
-        // touch a turtle directly within the turtlefield.
-        var warn = $.turtle.nowarn;
-        $.turtle.nowarn = true;
-        var sel = $(globalDrawing.surface)
-            .find('.turtle').within('touch', e).eq(0);
-        $.turtle.nowarn = warn;
-        if (sel.length === 1) {
-          // Erase portions of the event that are wrong for the turtle.
-          e.target = null;
-          e.relatedTarget = null;
-          e.fromElement = null;
-          e.toElement = null;
-          sel.trigger(e);
-          return false;
-        }
-      }
-    });
+    forwardBodyMouseEventsIfNeeded();
   } else {
     $(document).ready(attachClipSurface);
   }
@@ -3297,19 +3283,20 @@ var Piano = (function(_super) {
 // Implementation of the "pressed" function
 //////////////////////////////////////////////////////////////////////////
 
-// The implementation of the "pressed" function is captured in a closure.
-var pressedKey = (function() {
-  var focusTakenOnce = false;
-  function focusWindowIfFirst() {
-    if (focusTakenOnce) return;
-    focusTakenOnce = true;
-    try {
-      // If we are in a frame with access to a parent with an activeElement,
-      // then try to blur it (as is common inside the pencilcode IDE).
-      window.parent.document.activeElement.blur();
-    } catch (e) {}
-    window.focus();
-  }
+var focusTakenOnce = false;
+function focusWindowIfFirst() {
+  if (focusTakenOnce) return;
+  focusTakenOnce = true;
+  try {
+    // If we are in a frame with access to a parent with an activeElement,
+    // then try to blur it (as is common inside the pencilcode IDE).
+    window.parent.document.activeElement.blur();
+  } catch (e) {}
+  window.focus();
+}
+
+// Construction of keyCode names.
+var keyCodeName = (function() {
   var ua = typeof window !== 'undefined' ? window.navigator.userAgent : '',
       isOSX = /OS X/.test(ua),
       isOpera = /Opera/.test(ua),
@@ -3318,6 +3305,7 @@ var pressedKey = (function() {
       preventable = 'contextmenu',
       events = 'mousedown mouseup keydown keyup blur ' + preventable,
       keyCodeName = {
+    0:  'null',
     1:  'mouse1',
     2:  'mouse2',
     3:  'break',
@@ -3430,43 +3418,45 @@ var pressedKey = (function() {
   }
   // num-0-9 numeric keypad
   for (i = 96; i < 106; ++i) {
-    keyCodeName[i] = 'num ' +  (i - 96);
+    keyCodeName[i] = 'numpad' +  (i - 96);
   }
   // f1-f24
   for (i = 112; i < 136; ++i) {
     keyCodeName[i] = 'f' + (i-111);
   }
+  return keyCodeName;
+})();
+
+var pressedKey = (function() {
   // Listener for keyboard, mouse, and focus events that updates pressedState.
-  function pressListener(event) {
-    var name, simplified, down;
-    if (event.type == 'mousedown' || event.type == 'mouseup') {
-      name = 'mouse ' + event.which;
-      down = (event.type == 'mousedown');
-    } else if (event.type == 'keydown' || event.type == 'keyup') {
-      name = keyCodeName[event.which];
-      down = (event.type == 'keydown');
-      if (event.which >= 160 && event.which <= 165) {
-        // For "shift left", also trigger "shift"; same for control and alt.
-        simplified = name.replace(/(?:left|right)$/, '');
-      }
-    } else if (event.type == 'blur' || event.type == 'contextmenu') {
-      // When losing focus, clear all keyboard state.
-      if (!event.isDefaultPrevented() || preventable != event.type) {
-        resetPressedState();
+  function makeEventListener(mouse, down) {
+    return (function(event) {
+      var name, simplified, which = event.which;
+      if (mouse) {
+        name = 'mouse' + which;
+      } else {
+        // For testability, support whichSynth when which is zero, because
+        // it is impossible to simulate .which on phantom.
+        if (!which && event.whichSynth) { which = event.whichSynth; }
+        name = keyCodeName[which];
+        if (which >= 160 && which <= 165) {
+          // For "shift left", also trigger "shift"; same for control and alt.
+          updatePressedState(name.replace(/(?:left|right)$/, ''), down);
+        }
       }
-      return;
-    }
-    updatePressedState(name, down);
-    updatePressedState(simplified, down);
-    if (down) {
-      // After any down event, unlisten and relisten to contextmenu,
-      // to put oursleves last.  This allows us to test isDefaultPrevented.
-      $(window).off(preventable, pressListener);
-      $(window).on(preventable, pressListener);
-    }
-  }
+      updatePressedState(name, down);
+    });
+  };
+  var eventMap = {
+    'mousedown': makeEventListener(1, 1),
+    'mouseup': makeEventListener(1, 0),
+    'keydown': makeEventListener(0, 1),
+    'keyup': makeEventListener(0, 0),
+    'blur': resetPressedState
+  };
   // The pressedState map just has an entry for each pressed key.
   // Unpressing a key will delete the actual key from the map.
+  var pressedState = {};
   function updatePressedState(name, down) {
     if (name != null) {
       if (!down) {
@@ -3485,10 +3475,12 @@ var pressedKey = (function() {
   // The pressed listener can be turned on and off using pressed.enable(flag).
   function enablePressListener(turnon) {
     resetPressedState();
-    if (turnon) {
-      $(window).on(events, pressListener);
-    } else {
-      $(window).off(events, pressListener);
+    for (var name in eventMap) {
+      if (turnon) {
+        window.addEventListener(name, eventMap[name], true);
+      } else {
+        window.removeEventListener(name, eventMap[name]);
+      }
     }
   }
   // All pressed keys known can be listed using pressed.list().
@@ -3511,48 +3503,164 @@ var pressedKey = (function() {
       return listPressedKeys();
     }
   }
-  // Used to get names for key codes.
-  function match(choices, code) {
-    var name = keyCodeName[code];
-    if (choices.indexOf(name) >= 0) return true;
-    if (code >= 160 && code <= 165) {
-      // For "shift left", also trigger "shift"; same for control and alt.
-      simplified = name.replace(/(?:left|right)$/, '');
-      if (choices.indexOf(simplified) >= 0) return true;
-    }
-    return false;
-  }
-  // Keyup, keydown, and keypress handlers all can accept an optional
-  // string which is a name of a key to handle (or a comma-separated
-  // list of names of keys to handle.
-  function makeKeyHandler(fn, ch) {
-    focusWindowIfFirst();
-    var t, listener, choices;
-    if (typeof(fn) == 'string' && typeof(ch) == 'function') {
-      t = ch; ch = fn; fn = t;
-    }
-    if (ch) {
-      choices = ch.replace(/\s/g, '').toLowerCase().split(',');
-      return function(e) {
-        if (match(choices, e.which)) {
-          e.keyname = keyCodeName[e.which];
-          return fn.apply(this, arguments);
-        }
-      }
-    } else {
-      return function(e) {
-        e.keyname = keyCodeName[e.which];
-        return fn.apply(this, arguments);
-      }
-    }
-  }
   pressed.enable = enablePressListener;
   pressed.list = listPressedKeys;
-  pressed.wrap = makeKeyHandler;
   return pressed;
 })();
 
 
+//////////////////////////////////////////////////////////////////////////
+// JQUERY EVENT ENHANCEMENT
+//  - Keyboard events get the .keyname property.
+//  - Keyboard event listening with a string first (data) arg
+//    automatically filter out events that don't match the keyname.
+//  - Mouse events get .x and .y (center-up) if there is a turtle field.
+//  - If a turtle in the field is listening to mouse events, unhandled
+//    body mouse events are manually forwarded to turtles.
+//////////////////////////////////////////////////////////////////////////
+
+function addEventHook(hookobj, field, defobj, name, fn) {
+  var names = name.split(/\s+/);
+  for (var j = 0; j < names.length; ++j) {
+    name = names[j];
+    var hooks = hookobj[name];
+    if (!hooks) {
+      hooks = hookobj[name] = $.extend({}, defobj);
+    }
+    if (typeof hooks[field] != 'function') {
+      hooks[field] = fn;
+    } else if (hooks[field] != fn) {
+      // Multiple event hooks just listed in an array.
+      if (hooks[field].hooklist) {
+        if (hooks[field].hooklist.indexOf(fn) < 0) {
+          hooks[field].hooklist.push(fn);
+        }
+      } else {
+        (function() {
+          var hooklist = [hooks[field], fn];
+          (hooks[field] = function(event, original) {
+            var current = event;
+            for (var j = 0; j < hooklist.length; ++j) {
+              current = hooklist[j](current, original) || current;
+            }
+            return current;
+          }).hooklist = hooklist;
+        })();
+      }
+    }
+  }
+}
+
+function mouseFilterHook(event, original) {
+  if (globalDrawing.field && 'pageX' in event && 'pageY' in event) {
+    var origin = $(globalDrawing.field).offset();
+    if (origin) {
+      event.x = event.pageX - origin.left;
+      event.y = origin.top - event.pageY;
+    }
+  }
+  return event;
+}
+
+function mouseSetupHook(data, ns, fn) {
+  if (globalDrawing.field && !globalDrawing.fieldMouse &&
+      this.parentElement === globalDrawing.field ||
+      /(?:^|\s)turtle(?:\s|$)/.test(this.class)) {
+    globalDrawing.fieldMouse = true;
+    forwardBodyMouseEventsIfNeeded();
+  }
+  return false;
+}
+
+function forwardBodyMouseEventsIfNeeded() {
+  if (globalDrawing.fieldHook) return;
+  if (globalDrawing.surface && globalDrawing.fieldMouse) {
+    globalDrawing.fieldHook = true;
+    setTimeout(function() {
+      // TODO: check both globalDrawing.surface and
+      // globalDrawing.turtleMouseListener
+      $('body').on('click.turtle dblclick.turtle ' +
+        'mouseup.turtle mousedown.turtle mousemove.turtle', function(e) {
+        if (e.target === this && !e.isTrigger) {
+          // Only forward events directly on the body that (geometrically)
+          // touch a turtle directly within the turtlefield.
+          var warn = $.turtle.nowarn;
+          $.turtle.nowarn = true;
+          var sel = $(globalDrawing.surface)
+              .find('.turtle').within('touch', e).eq(0);
+          $.turtle.nowarn = warn;
+          if (sel.length === 1) {
+            // Erase portions of the event that are wrong for the turtle.
+            e.target = null;
+            e.relatedTarget = null;
+            e.fromElement = null;
+            e.toElement = null;
+            sel.trigger(e);
+            return false;
+          }
+        }
+      });
+    }, 0);
+  }
+}
+
+function addMouseEventHooks() {
+  var hookedEvents = 'mousedown mouseup mousemove click dblclick';
+  addEventHook($.event.fixHooks, 'filter', $.event.mouseHooks,
+       hookedEvents, mouseFilterHook);
+  addEventHook($.event.special, 'setup', {}, hookedEvents, mouseSetupHook);
+}
+
+function keyFilterHook(event, original) {
+  var which = event.which;
+  if (!which) {
+    which = (original || event.originalEvent).whichSynth;
+  }
+  var name = keyCodeName[which];
+  if (!name && which) {
+    name = String.fromCharCode(which);
+  }
+  event.keyname = name;
+  return event;
+}
+
+// Add .keyname to each keyboard event.
+function keypressFilterHook(event, original) {
+  if (event.charCode != null) {
+    event.keyname = String.fromCharCode(event.charCode);
+  }
+}
+
+// Intercept on('keydown/keyup/keypress')
+function keyAddHook(handleObj) {
+  focusWindowIfFirst();
+  if (typeof(handleObj.data) != 'string') return;
+  var choices = handleObj.data.replace(/\s/g, '').toLowerCase().split(',');
+  var original = handleObj.handler;
+  var wrapped = function(event) {
+    if (choices.indexOf(event.keyname) < 0) return;
+    return original.apply(this, arguments);
+  }
+  if (original.guid) { wrapped.guid = original.guid; }
+  handleObj.handler = wrapped;
+}
+
+function addKeyEventHooks() {
+  // Add the "keyname" field to keydown and keyup events - this uses
+  // the lowercase key names listed in the pressedKey utility.
+  addEventHook($.event.fixHooks, 'filter', $.event.keyHooks,
+      'keydown keyup', keyFilterHook);
+  // Add "keyname" to keypress also.  This is just the unicode character
+  // corresponding to event.charCode.
+  addEventHook($.event.fixHooks, 'filter', $.event.keyHooks,
+      'keypress', keypressFilterHook);
+  // Finally, add special forms for the keyup/keydown/keypress events
+  // where the first argument can be the comma-separated name of keys
+  // to target (instead of just data)
+  addEventHook($.event.special, 'add', {},
+      'keydown keyup keypress', keyAddHook);
+}
+
 //////////////////////////////////////////////////////////////////////////
 // WEB AUDIO SUPPORT
 // Definition of play("ABC") - uses ABC music note syntax.
@@ -4528,7 +4636,7 @@ var Instrument = (function() {
     for (j = 0; j < parts.length; ++j) {
       // It could be reversed, like "66=1/4", or just "120", so
       // determine what is going on by looking for a slash etc.
-      if (parts[j].indexOf('/') >= 0 || /^[1-4]$/.test(parts[j])) {
+      if (~parts[j].indexOf('/') || /^[1-4]$/.test(parts[j])) {
         // The note-unit (e.g., 1/4).
         unit = unit || durationToTime(parts[j]);
       } else {
@@ -5663,17 +5771,16 @@ function wrapglobalcommand(name, helptext, fn) {
 }
 
 function wrapwindowevent(name, helptext) {
-  return wrapraw(name, helptext, function(fn) {
+  return wrapraw(name, helptext, function(d, fn) {
     var forKey = /^key/.test(name),
         forMouse = /^mouse|click$/.test(name),
-        wrapped = forKey ? pressedKey.wrap(fn, arguments[1])
-            : forMouse ? wrapmouselistener(fn) : fn,
         filter = forMouse ? 'input,button' : forKey ?
             'textarea,input:not([type]),input[type=text],input[type=password]'
             : null;
-    $(window).on(name, !filter ? wrapped : function(e) {
-          if ($(e.target).closest(filter).length) { return; }
-          return wrapped.apply(this, arguments);
+    if (fn == null && typeof(d) == 'function') { fn = d; d = null; }
+    $(window).on(name, null, d, !filter ? fn : function(e) {
+      if ($(e.target).closest(filter).length) { return; }
+      return fn.apply(this, arguments);
     });
   });
 }
@@ -5686,20 +5793,6 @@ function wrapraw(name, helptext, fn) {
   return fn;
 }
 
-// Adds center-up x and y coordinates to the mouse event.
-function wrapmouselistener(fn) {
-  return function(event) {
-    if ('pageX' in event && 'pageY' in event) {
-      var origin = $('#field').offset();
-      if (origin) {
-        event.x = event.pageX - origin.left;
-        event.y = origin.top - event.pageY;
-      }
-    }
-    return fn.apply(this, arguments);
-  };
-}
-
 //////////////////////////////////////////////////////////////////////////
 // BASIC TURTLE MOTIONS
 // Generic functions to handle symmetric pairs of motions.
@@ -7116,8 +7209,7 @@ $.fn.extend(turtlefn);
 
 var turtleGIFUrl = "data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP///wAAAAAAACH5BAlkAAYAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAKAAwAAAD72i6zATEgBCAebHpzUnxhDAMAvhxKOoV3ziuZyo3RO26dTbvgXj/gsCO9ysOhENZz+gKJmcUkmA6PSKfSqrWieVtuU+KGNXbXofLEZgR/VHCgdua4isGz9mbmM6U7/94BmlyfUZ1fhqDhYuGgYqMkCOBgo+RfWsNlZZ3ewIpcZaIYaF6XaCkR6aokqqrk0qrqVinpK+fsbZkuK2ouRy0ob4bwJbCibthh6GYebGcY7/EsWqTbdNG1dd9jnXPyk2d38y0Z9Yub2yA6AvWPYk+zEnkv6xdCoPuw/X2gLqy9vJIGAN4b8pAgpQOIlzI8EkCACH5BAlkAAYALAAAAAAoADAAAAPuaLrMBMSAEIB5senNSfGEMAwC+HEo6hXfOK5nKjdE7bp1Nu+BeP+CwI73Kw6EQ1nP6AomZxSSYDo9Ip9KqtaJ5W25Xej3qqGYsdEfZbMcgZXtYpActzLMeLOP6c7f3nVNfEZ7TXSFg4lyZAYBio+LZYiQfHMbc3iTlG9ilGpdjp4ujESiI6RQpqegqkesqqhKrbEpoaa0KLaiuBy6nrxss6+3w7tomo+cDXmBnsoLza2nsb7SN2tl1nyozVOZTJhxysxnd9XYCrrAtT7KQaPruavBo2HQ8xrvffaN+GV5/JbE45fOG8Ek5Q4qXHgwAQA7"
 
-var eventfn = { click:1, mouseup:1, mousedown:1, mousemove:1,
-    keydown:1, keypress:1, keyup:1 };
+var eventfn = { click:1, dblclick:1, mouseup:1, mousedown:1, mousemove:1 };
 
 var global_turtle = null;
 var global_turtle_methods = [];
@@ -7449,6 +7541,9 @@ var dollar_turtle_methods = {
   click: wrapwindowevent('click',
   ["click(fn) Calls fn(event) whenever the mouse is clicked. " +
       "click (e) -> moveto e; label 'clicked'"]),
+  dblclick: wrapwindowevent('dblclick',
+  ["dblclick(fn) Calls fn(event) whenever the mouse is double-clicked. " +
+      "dblclick (e) -> moveto e; label 'double'"]),
   mouseup: wrapwindowevent('mouseup',
   ["mouseup(fn) Calls fn(event) whenever the mouse is released. " +
       "mouseup (e) -> moveto e; label 'up'"]),
@@ -7456,17 +7551,17 @@ var dollar_turtle_methods = {
   ["mousedown(fn) Calls fn(event) whenever the mouse is pressed. " +
       "mousedown (e) -> moveto e; label 'down'"]),
   mousemove: wrapwindowevent('mousemove',
-  ["mousedown(fn) Calls fn(event) whenever the mouse is moved. " +
-      "mousemove (e) -> moveto e"]),
+  ["mousemove(fn) Calls fn(event) whenever the mouse is moved. " +
+      "mousemove (e) -> write 'at ', e.x, ',', e.y"]),
   keydown: wrapwindowevent('keydown',
   ["keydown(fn) Calls fn(event) whenever a key is pushed down. " +
-      "keydown (e) -> write 'down ' + e.which"]),
+      "keydown (e) -> write 'down ' + e.keyname"]),
   keyup: wrapwindowevent('keyup',
   ["keyup(fn) Calls fn(event) whenever a key is released. " +
-      "keyup (e) -> write 'up ' + e.which"]),
+      "keyup (e) -> write 'up ' + e.keyname"]),
   keypress: wrapwindowevent('keypress',
-  ["keypress(fn) Calls fn(event) whenever a letter is typed. " +
-      "keypress (e) -> write 'press ' + e.which"]),
+  ["keypress(fn) Calls fn(event) whenever a character key is pressed. " +
+      "keypress (e) -> write 'press ' + e.keyname"]),
   send: wrapraw('send',
   ["send(name) Sends a message to be received by recv. " +
       "send 'go'; recv 'go', -> fd 100"],
@@ -7730,6 +7825,7 @@ $.turtle = function turtle(id, options) {
     turtleevents(options.eventprefix);
   }
   if (!('pressed' in options) || options.pressed) {
+    addKeyEventHooks();
     pressedKey.enable(true);
   }
   // Set up global log function.
@@ -8788,7 +8884,7 @@ function input(name, callback, numeric) {
     }
     if (numeric > 0 && (e.which >= 32 && e.which <= 127) &&
         (e.which < '0'.charCodeAt(0) || e.which > '9'.charCodeAt(0)) &&
-        (e.which != '.'.charCodeAt(0) || textbox.val().indexOf('.') >= 0)) {
+        (e.which != '.'.charCodeAt(0) || ~textbox.val().indexOf('.'))) {
       return false;
     }
   }
diff --git a/test/globals.html b/test/globals.html
index 584833c..9a17f2b 100644
--- a/test/globals.html
+++ b/test/globals.html
@@ -26,12 +26,10 @@
   var expected = [
     "lastmouse",
     "lastclick",
+    "lastdblclick",
     "lastmouseup",
     "lastmousedown",
     "lastmousemove",
-    "lastkeydown",
-    "lastkeypress",
-    "lastkeyup",
     "see",
     "log",
     "printpage",
@@ -67,6 +65,7 @@
     "hsl",
     "hsla",
     "click",
+    "dblclick",
     "mouseup",
     "mousedown",
     "mousemove",
diff --git a/test/pressed.html b/test/pressed.html
index e523b54..02206be 100644
--- a/test/pressed.html
+++ b/test/pressed.html
@@ -7,15 +7,50 @@
 
+
+
+
+
+
+
+ From 3057160554a8b0e7834819641f89224be5c5ff64 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 13 Nov 2014 12:41:37 -0500 Subject: [PATCH 066/180] Reduce work done on flushPenState. --- jquery-turtle.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index c2cca9d..95dcb93 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1839,10 +1839,11 @@ function makePenStyleHook() { return writePenStyle(getTurtleData(elem).style); }, set: function(elem, value) { - var style = parsePenStyle(value, 'strokeStyle'); - getTurtleData(elem).style = style; + var style = parsePenStyle(value, 'strokeStyle'), + state = getTurtleData(elem); + state.style = style; elem.style.turtlePenStyle = writePenStyle(style); - flushPenState(elem); + flushPenState(elem, state); } }; } @@ -1861,7 +1862,7 @@ function makePenDownHook() { state.quickpagexy = null; state.quickhomeorigin = null; elem.style.turtlePenDown = writePenDown(style); - flushPenState(elem); + flushPenState(elem, state); } } }; @@ -2022,8 +2023,11 @@ function addBezierToPath(path, start, triples) { } } -function flushPenState(elem) { - var state = getTurtleData(elem); +function flushPenState(elem, state) { + if (!state) { + // Default is no pen and no path, so nothing to do. + return; + } if (!state.style || (!state.down && !state.style.savePath)) { if (state.path.length > 1) { state.path.length = 1; } if (state.path[0].length) { state.path[0].length = 0; } @@ -2239,7 +2243,7 @@ function doQuickMove(elem, distance, sideways) { ts.tx += dx; ts.ty += dy; elem.style[transform] = writeTurtleTransform(ts); - flushPenState(elem); + flushPenState(elem, state); } function doQuickMoveXY(elem, dx, dy) { @@ -2256,7 +2260,7 @@ function doQuickMoveXY(elem, dx, dy) { ts.tx += dx; ts.ty -= dy; elem.style[transform] = writeTurtleTransform(ts); - flushPenState(elem); + flushPenState(elem, state); } function doQuickRotate(elem, degrees) { @@ -2371,7 +2375,7 @@ function makeTurtleForwardHook() { ts.tx = ntx; ts.ty = nty; elem.style[transform] = writeTurtleTransform(ts); - flushPenState(elem); + flushPenState(elem, state); } }; } @@ -2398,7 +2402,7 @@ function makeTurtleHook(prop, normalize, unit, displace) { pageY: qpxy.pageY + (ts.ty - oty) }; } - flushPenState(elem); + flushPenState(elem, state); } } }; @@ -2590,7 +2594,7 @@ function makeTurtleXYHook(publicname, propx, propy, displace) { pageY: qpxy.pageY + (ts.ty - oty) }; } - flushPenState(elem); + flushPenState(elem, state); } } }; From cfb7b8d591ad648f7ac35adcea87074b28be5099 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 14 Nov 2014 00:28:22 -0500 Subject: [PATCH 067/180] Improved usability for fill command. --- jquery-turtle.js | 162 +++++++++++++++++++++++++++++++---------------- test/fern.html | 2 +- 2 files changed, 108 insertions(+), 56 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 95dcb93..9f2c52d 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1786,6 +1786,7 @@ function getTurtleData(elem) { if (!state) { state = $.data(elem, 'turtleData', { style: null, + corners: [[]], path: [[]], down: true, speed: 'turtle', @@ -1843,7 +1844,7 @@ function makePenStyleHook() { state = getTurtleData(elem); state.style = style; elem.style.turtlePenStyle = writePenStyle(style); - flushPenState(elem, state); + flushPenState(elem, state, true); } }; } @@ -1862,7 +1863,7 @@ function makePenDownHook() { state.quickpagexy = null; state.quickhomeorigin = null; elem.style.turtlePenDown = writePenDown(style); - flushPenState(elem, state); + flushPenState(elem, state, true); } } }; @@ -1873,6 +1874,11 @@ function isPointNearby(a, b) { Math.round(a.pageY - b.pageY) === 0; } +function isPointVeryNearby(a, b) { + return Math.round(1000 * (a.pageX - b.pageX)) === 0 && + Math.round(1000 * (a.pageY - b.pageY)) === 0; +} + function isBezierTiny(a, b) { return isPointNearby(a, b) && Math.round(a.pageX - b.pageX1) === 0 && @@ -1893,8 +1899,11 @@ function applyPenStyle(ctx, ps, scale) { scale = scale || 1; var extraWidth = ps.eraseMode ? 1 : 0; if (!ps || !('strokeStyle' in ps)) { ctx.strokeStyle = 'black'; } - if (!ps || !('lineWidth' in ps)) { ctx.lineWidth = 1.62 * scale + extraWidth; } + if (!ps || !('lineWidth' in ps)) { + ctx.lineWidth = 1.62 * scale + extraWidth; + } if (!ps || !('lineCap' in ps)) { ctx.lineCap = 'round'; } + if (!ps || !('lineJoin' in ps)) { ctx.lineJoin = 'round'; } if (ps) { for (var a in ps) { if (a === 'savePath' || a === 'eraseMode') { continue; } @@ -1959,7 +1968,7 @@ function setCanvasPageTransform(ctx, canvas) { var buttOverlap = 0.67; -function drawAndClearPath(drawOnCanvas, path, style, scale) { +function drawAndClearPath(drawOnCanvas, path, style, scale, truncateTo) { var ctx = drawOnCanvas.getContext('2d'), isClosed, skipLast, j = path.length, @@ -1972,23 +1981,12 @@ function drawAndClearPath(drawOnCanvas, path, style, scale) { while (j--) { if (path[j].length > 1) { segment = path[j]; - isClosed = segment.length > 2 && isPointNearby( - segment[0], segment[segment.length - 1]); + isClosed = segment.length > 2 && + isPointNearby(segment[0], segment[segment.length - 1]) && + !isPointNearby(segment[0], segment[Math.floor(segment.length / 2)]); skipLast = isClosed && (!('pageX2' in segment[segment.length - 1])); var startx = segment[0].pageX; var starty = segment[0].pageY; - if (ctx.lineCap == 'butt' && segment.length > 0) { - var dx = segment[1].pageX - startx, - dy = segment[1].pageY - starty; - if (dx || dy) { - // Increase the distance of the starting point if using - // butt ends, so that they definitely overlap when animating. - var adjust = Math.min(1, buttOverlap / - Math.max(Math.abs(dx), Math.abs(dy))); - startx -= dx * adjust; - starty -= dy * adjust; - } - } ctx.moveTo(startx, starty); for (var k = 1; k < segment.length - (skipLast ? 1 : 0); ++k) { if ('pageX2' in segment[k] && @@ -2008,7 +2006,7 @@ function drawAndClearPath(drawOnCanvas, path, style, scale) { if ('strokeStyle' in style) { ctx.stroke(); } ctx.restore(); path.length = 1; - path[0].splice(0, path[0].length - 1); + path[0].splice(0, Math.max(0, path[0].length - truncateTo)); } function addBezierToPath(path, start, triples) { @@ -2023,38 +2021,62 @@ function addBezierToPath(path, start, triples) { } } -function flushPenState(elem, state) { - if (!state) { - // Default is no pen and no path, so nothing to do. +function addToPathList(pathList, point) { + if (pathList.length && + (point.corner ? isPointVeryNearby(point, pathList[pathList.length - 1]) : + isPointNearby(point, pathList[pathList.length - 1]))) { return; } - if (!state.style || (!state.down && !state.style.savePath)) { - if (state.path.length > 1) { state.path.length = 1; } - if (state.path[0].length) { state.path[0].length = 0; } + pathList.push(point); +} + +function flushPenState(elem, state, corner) { + if (!state) { + // Default is no pen and no path, so nothing to do. return; } - if (!state.down) { - // Penup when saving path will start a new segment if one isn't started. - if (state.path.length && state.path[0].length) { - state.path.unshift([]); + var path = state.path, style = state.style, corners = state.corners; + if (!style || !state.down) { + if (corner) { + if (style && style.savePath) { + // Penup when saving path will create a new path if needed. + if (corners.length && corners[0].length) { + if (corners[0].length == 1) { + corners[0].length = 0; + } else { + corners.unshift([]); + } + } + } else { + if (corners.length > 1) corners.length = 1; + if (corners[0].length) corners[0].length = 0; + } } + // Penup when not saving path will clear the saved path. + if (path.length > 1) { path.length = 1; } + if (path[0].length) { path[0].length = 0; } return; } + if (!corner && style.savePath) return; var center = getCenterInPageCoordinates(elem); - if (!state.path[0].length || - !isPointNearby(center, state.path[0][state.path[0].length - 1])) { - state.path[0].push(center); - } - if (!state.style.savePath) { - var ts = readTurtleTransform(elem, true); - drawAndClearPath(getDrawOnCanvas(state), state.path, state.style, ts.sx); + if (corner) { + center.corner = true; + addToPathList(corners[0], center); } + if (style.savePath) return; + addToPathList(path[0], center); + var ts = readTurtleTransform(elem, true); + drawAndClearPath(getDrawOnCanvas(state), state.path, style, ts.sx, 2); } function endAndFillPenPath(elem, style) { var ts = readTurtleTransform(elem, true), state = getTurtleData(elem); - drawAndClearPath(getDrawOnCanvas(state), state.path, style); + if (state.style) { + // Apply a default style. + style = $.extend({}, state.style, style); + } + drawAndClearPath(getDrawOnCanvas(state), state.corners, style, ts.sx, 1); if (state.style && state.style.savePath) { $.style(elem, 'turtlePenStyle', 'none'); } @@ -2243,7 +2265,7 @@ function doQuickMove(elem, distance, sideways) { ts.tx += dx; ts.ty += dy; elem.style[transform] = writeTurtleTransform(ts); - flushPenState(elem, state); + flushPenState(elem, state, true); } function doQuickMoveXY(elem, dx, dy) { @@ -2260,7 +2282,7 @@ function doQuickMoveXY(elem, dx, dy) { ts.tx += dx; ts.ty -= dy; elem.style[transform] = writeTurtleTransform(ts); - flushPenState(elem, state); + flushPenState(elem, state, true); } function doQuickRotate(elem, degrees) { @@ -5741,6 +5763,7 @@ function continuationArg(args, argcount) { // as each of the elements' animations completes; when the jth // element completes, resolve(j) should be called. The last time // it is called, it will trigger the continuation callback, if any. +// Call resolve(j, true) if a corner pen state should be marked. // resolver: a function that returns a closure that calls resolve(j). // start: a function to be called once to enable triggering of the callback. // the last argument in an argument list if it is a function, and if the @@ -5753,10 +5776,13 @@ function setupContinuation(thissel, name, args, argcount) { countdown = length + 1, sync = true, debugId = debug.nextId(); - function resolve(j) { + function resolve(j, corner) { if (j != null) { - debug.reportEvent('resolve', - [name, debugId, length, j, thissel && thissel[j]]); + var elem = thissel && thissel[j]; + if (corner && elem) { + flushPenState(elem, $.data(elem, 'turtleData'), true); + } + debug.reportEvent('resolve', [name, debugId, length, j, elem]); } if ((--countdown) == 0) { // A subtlety: if we still have not yet finished setting things up @@ -5789,7 +5815,7 @@ function setupContinuation(thissel, name, args, argcount) { args: mainargs, appear: appear, resolve: resolve, - resolver: function(j) { return function() { resolve(j); }; }, + resolver: function(j, c) { return function() { resolve(j, c); }; }, exit: function exit() { debug.reportEvent('exit', [name, debugId, length, mainargs]); // Resolve one extra countdown; this is needed for a done callback @@ -5918,13 +5944,33 @@ function rtlt(cc, degrees, radius) { } else { this.plan(function(j, elem) { cc.appear(j); - var oldRadius = this.css('turtleTurningRadius'); - this.css({turtleTurningRadius: (degrees < 0) ? -radius : radius}); + var state = getTurtleData(elem), + oldRadius = state.turningRadius, + newRadius = (degrees < 0) ? -radius : radius, + addCorner = null; + if (state.style && state.down) { + addCorner = (function() { + var oldPos = getCenterInPageCoordinates(elem), + oldTs = readTurtleTransform(elem, true), + oldTransform = totalTransform2x2(elem.parentElement); + return (function() { + addArcBezierPaths( + state.corners[0], + oldPos, + oldTs.rot, + oldTs.rot + degrees, + newRadius * oldTs.sy, + oldTransform); + }); + })(); + } + state.turningRadius = newRadius; this.animate({turtleRotation: operator + cssNum(degrees) + 'deg'}, animTime(elem, intick), animEasing(elem)); this.plan(function() { - this.css({turtleTurningRadius: oldRadius}); - cc.resolve(j); + if (addCorner) addCorner(); + state.turningRadius = oldRadius; + cc.resolve(j, true); }); }); return this; @@ -5943,13 +5989,13 @@ function fdbk(cc, amount) { if ((elem = canMoveInstantly(this))) { cc.appear(0); doQuickMove(elem, amount, 0); - cc.resolve(0); + cc.resolve(0, true); return this; } this.plan(function(j, elem) { - cc.appear(0); + cc.appear(j); this.animate({turtleForward: '+=' + cssNum(amount || 0) + 'px'}, - animTime(elem, intick), animEasing(elem), cc.resolver(0)); + animTime(elem, intick), animEasing(elem), cc.resolver(j, true)); }); return this; } @@ -5969,7 +6015,7 @@ function move(cc, x, y) { this.plan(function(j, elem) { cc && cc.appear(j); this.animate({turtlePosition: displacedPosition(elem, y, x)}, - animTime(elem, intick), animEasing(elem), cc && cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc && cc.resolver(j, true)); }); return this; } @@ -5993,7 +6039,7 @@ function movexy(cc, x, y) { var tr = getElementTranslation(elem); this.animate( { turtlePosition: cssNum(tr[0] + x) + ' ' + cssNum(tr[1] - y) }, - animTime(elem, intick), animEasing(elem), cc && cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc && cc.resolver(j, true)); }); return this; } @@ -6041,7 +6087,7 @@ function moveto(cc, x, y) { cc && cc.appear(j); this.animate({turtlePosition: computeTargetAsTurtlePosition(elem, pos, limit, localx, localy)}, - animTime(elem, intick), animEasing(elem), cc && cc.resolver(j)); + animTime(elem, intick), animEasing(elem), cc && cc.resolver(j, true)); }); return this; } @@ -6056,7 +6102,7 @@ function makejump(move) { move.call(this, null, x, y); this.plan(function() { this.css({turtlePenDown: down}); - cc.resolve(j); + cc.resolve(j, true); }); }); return this; @@ -6261,8 +6307,14 @@ var turtlefn = { dir = limitRotation(ts.rot, dir, limit === null ? 360 : limit); } dir = ts.rot + normalizeRotation(dir - ts.rot); + var oldRadius = this.css('turtleTurningRadius'); + this.css({turtleTurningRadius: 0}); this.animate({turtleRotation: dir}, - animTime(elem, intick), animEasing(elem), cc.resolver(j)); + animTime(elem, intick), animEasing(elem)); + this.plan(function() { + this.css({turtleTurningRadius: oldRadius}); + cc.resolve(j); + }); }); return this; }), diff --git a/test/fern.html b/test/fern.html index 00c3b6a..003ea01 100644 --- a/test/fern.html +++ b/test/fern.html @@ -8,7 +8,7 @@ eval($.turtle()); $.turtle.hungtimeout = Infinity; // Allow for slow CPUs. module("Fern test."); -asyncTest("Draws a circle and verifies its presence.", function() { +asyncTest("Draws a fractal and verifies its shape.", function() { speed(Infinity); function fern(x) { if (x > 1) { From 605acd0ff7884dbcbe3a18e9babdeea3fbedffc9 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 14 Nov 2014 00:51:33 -0500 Subject: [PATCH 068/180] End paths when changing pens. --- jquery-turtle.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9f2c52d..1d6e3a3 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1842,6 +1842,11 @@ function makePenStyleHook() { set: function(elem, value) { var style = parsePenStyle(value, 'strokeStyle'), state = getTurtleData(elem); + if (state.style) { + // Switch to an empty pen first, to terminate paths. + state.style = null; + flushPenState(elem, state, true); + } state.style = style; elem.style.turtlePenStyle = writePenStyle(style); flushPenState(elem, state, true); From cf147ceb33d1967d6fe677c2bb19572c15884205 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 14 Nov 2014 07:40:38 -0500 Subject: [PATCH 069/180] Fix bugs in new arc code. --- jquery-turtle.js | 10 ++++++---- test/revolve.html | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 test/revolve.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 1d6e3a3..eefa865 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1785,7 +1785,7 @@ function getTurtleData(elem) { var state = $.data(elem, 'turtleData'); if (!state) { state = $.data(elem, 'turtleData', { - style: null, + styte: null, corners: [[]], path: [[]], down: true, @@ -2529,7 +2529,7 @@ function maybeArcRotation(end, elem, ts, opt) { if (tradius === 0 || ts.rot == end) { // Avoid drawing a line if zero turning radius. opt.displace = false; - return normalizeRotation(end); + return tradius === 0 ? normalizeRotation(end) : end; } var tracing = (state && state.style && state.down), turnradius = tradius * ts.sy, a; @@ -2544,7 +2544,7 @@ function maybeArcRotation(end, elem, ts, opt) { } else { a = setupArc( ts.rot, // starting direction - delta, // degrees change + end, // degrees change turnradius); // scaled turning radius } ts.tx += a.dx; @@ -5929,6 +5929,8 @@ function wrapraw(name, helptext, fn) { function rtlt(cc, degrees, radius) { if (degrees == null) { degrees = 90; // zero-argument default. + } else { + degrees = normalizeRotationDelta(degrees); } var elem, left = (cc.name === 'lt'), intick = insidetick; if ((elem = canMoveInstantly(this)) && @@ -5963,7 +5965,7 @@ function rtlt(cc, degrees, radius) { state.corners[0], oldPos, oldTs.rot, - oldTs.rot + degrees, + oldTs.rot + (left ? -degrees : degrees), newRadius * oldTs.sy, oldTransform); }); diff --git a/test/revolve.html b/test/revolve.html new file mode 100644 index 0000000..1d1fff9 --- /dev/null +++ b/test/revolve.html @@ -0,0 +1,43 @@ + + + + + +
+ From 61e5321f3eae2248172cea74974bb01f41b13f91 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 14 Nov 2014 16:33:50 +0000 Subject: [PATCH 070/180] Default fill is none. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index eefa865..905a086 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6454,7 +6454,7 @@ var turtlefn = { "pen path: " + "pen path; rt 100, 90; fill blue"], function fill(cc, style) { - if (!style) { style = 'black'; } + if (!style) { style = 'none'; } else if ($.isPlainObject(style)) { style = writePenStyle(style); } From 94b5e8cbb89e6dfbdca788d4e16f5698268c2764 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 16 Nov 2014 17:01:06 +0000 Subject: [PATCH 071/180] Fix sync. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 905a086..7ad317a 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5549,7 +5549,7 @@ function sync() { } for (j = 0; j < elts.length; ++j) { queueWaitIfLoadingImg(elts[j]); - $.queue(elts[j], 'fx', function(next) { + $(elts[j]).queue(function(next) { if (ready) { ready.push(next); if (ready.length == elts.length) { From 50ea6d9b99105fee4964f17d5d0aacdc3f3141ce Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 16 Nov 2014 23:08:54 -0500 Subject: [PATCH 072/180] All pen styles save the path. --- jquery-turtle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 7ad317a..ebcdc8a 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2043,8 +2043,8 @@ function flushPenState(elem, state, corner) { var path = state.path, style = state.style, corners = state.corners; if (!style || !state.down) { if (corner) { - if (style && style.savePath) { - // Penup when saving path will create a new path if needed. + if (style) { + // Penup when saving path will create a new segment if needed. if (corners.length && corners[0].length) { if (corners[0].length == 1) { corners[0].length = 0; From e80b3693a6b6e922fa97ecac2145a8671f3f4fe2 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 17 Nov 2014 00:47:37 -0500 Subject: [PATCH 073/180] Remove top-level eval. --- jquery-turtle.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index ebcdc8a..2962e60 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -10138,7 +10138,8 @@ function tryinitpanel() { } } -eval("scope('jquery-turtle', " + seejs + ", this)"); +// Removing this debugging line saves 20kb in minification. +// eval("scope('jquery-turtle', " + seejs + ", this)"); function transparentHull(image, threshold) { var c = document.createElement('canvas'); From 20512b50ae6955c90f8c5747a2339c380e25a71e Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 17 Nov 2014 01:45:42 -0500 Subject: [PATCH 074/180] slide->move --- test/boxfill.html | 6 ++-- test/clip.html | 6 ++-- test/continuations.html | 2 +- test/dotfill.html | 4 +-- test/inside.html | 2 +- test/pause.html | 2 +- test/pen.html | 74 ++++++++++++++++++++++++++++++++++++----- test/twist.html | 6 ++-- 8 files changed, 80 insertions(+), 22 deletions(-) diff --git a/test/boxfill.html b/test/boxfill.html index cc07f56..9d8f488 100644 --- a/test/boxfill.html +++ b/test/boxfill.html @@ -41,15 +41,15 @@ ok(!touches(red)); ok(!touches(green)); ok(touches(blue)); - slide(-20); + move(-20); ok(!touches(red)); ok(!touches(green)); ok(!touches(blue)); - slide(-80,-20); + move(-80,-20); ok(!touches(red)); ok(touches(green)); ok(!touches(blue)); - slide(200); + move(200); ok(!touches(red)); ok(touches(green)); ok(!touches(blue)); diff --git a/test/clip.html b/test/clip.html index c5b2271..ce00fb9 100644 --- a/test/clip.html +++ b/test/clip.html @@ -19,11 +19,11 @@ ok(a.touches(blue)); drawon(a); dot(red, 20); - slide(20); + move(20); dot(red, 20); fd(20); dot(red, 20); - a.slide(20, 20); + a.move(20, 20); ok(a.touches(blue)); a.clip() ok(!a.touches(blue)); @@ -35,7 +35,7 @@ ok(a.touches(blue)); a.lt(30); ok(!a.touches(blue)); - a.slide(-20); + a.move(-20); ok(a.touches(blue)); start(); }); diff --git a/test/continuations.html b/test/continuations.html index 5f2d440..2dbf83d 100644 --- a/test/continuations.html +++ b/test/continuations.html @@ -28,7 +28,7 @@ ok(!touches(red)); turnto(0, function() { ok(!touches(red)); - slide(0, -100, function() { + move(0, -100, function() { ok(touches(red)); moveto(0, 0, function() { ok(touches(red)); diff --git a/test/dotfill.html b/test/dotfill.html index 44e40d2..6f4a89b 100644 --- a/test/dotfill.html +++ b/test/dotfill.html @@ -52,7 +52,7 @@ ok(!touches(green)); ok(touches(red)); ok(touches(transparent)); - slide(50); + move(50); ok(!touches('tan')); ok(!touches(green)); ok(touches(red)); @@ -72,7 +72,7 @@ ok(touches(green)); ok(!touches(red)); ok(!touches(transparent)); - slide(50); + move(50); ok(!touches('tan')); ok(touches(green)); ok(!touches(red)); diff --git a/test/inside.html b/test/inside.html index ff1df20..513402d 100644 --- a/test/inside.html +++ b/test/inside.html @@ -12,7 +12,7 @@ ok(inside(window)); var p = write('hello'); ok(p.inside(window)); - p.slide(-25, 0); + p.move(-25, 0); ok(!p.inside(window)); fd(2000); ok(!inside(window)); diff --git a/test/pause.html b/test/pause.html index fb0f570..a1db124 100644 --- a/test/pause.html +++ b/test/pause.html @@ -30,7 +30,7 @@ equal(b.getxy()[1], 0); equal(getxy()[0], 0); pause(b); - slide(100); + move(100); plan(function() { equal(round(a.getxy()[1]), 100); equal(round(b.getxy()[1]), -100); diff --git a/test/pen.html b/test/pen.html index 46540c6..b3302b2 100644 --- a/test/pen.html +++ b/test/pen.html @@ -3,12 +3,14 @@ -
+
diff --git a/test/twist.html b/test/twist.html index f62ff23..f7960ec 100644 --- a/test/twist.html +++ b/test/twist.html @@ -9,11 +9,11 @@ module("Twist test."); asyncTest("Test of twisting a skinny shape.", function() { speed(Infinity); - slide(100); + move(100); dot(yellow); - slide(-200); + move(-200); dot(pink); - slide(100); + move(100); fd(100); dot(red); bk(200) From 0d8ba8795269dd9b7958c42d4c89c81024ef195c Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 17 Nov 2014 02:25:47 -0500 Subject: [PATCH 075/180] pen color automatically does pen down --- jquery-turtle.js | 34 +++++++++++++++-------- test/pen.html | 72 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 2962e60..77e5396 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1788,7 +1788,7 @@ function getTurtleData(elem) { styte: null, corners: [[]], path: [[]], - down: true, + down: false, speed: 'turtle', easing: 'swing', turningRadius: 0, @@ -2040,11 +2040,21 @@ function flushPenState(elem, state, corner) { // Default is no pen and no path, so nothing to do. return; } - var path = state.path, style = state.style, corners = state.corners; + var path = state.path, + style = state.style, + corners = state.corners; if (!style || !state.down) { + // pen up or pen null will clear the tracing path. + if (path.length > 1) { path.length = 1; } + if (path[0].length) { path[0].length = 0; } if (corner) { - if (style) { - // Penup when saving path will create a new segment if needed. + if (!style) { + if (window.buggy) console.trace('clearing the retracing path'); + // pen null will clear the retracing path too. + if (corners.length > 1) corners.length = 1; + if (corners[0].length) corners[0].length = 0; + } else { + // pen up with a non-null pen will start a new discontinuous segment. if (corners.length && corners[0].length) { if (corners[0].length == 1) { corners[0].length = 0; @@ -2052,25 +2062,23 @@ function flushPenState(elem, state, corner) { corners.unshift([]); } } - } else { - if (corners.length > 1) corners.length = 1; - if (corners[0].length) corners[0].length = 0; } } - // Penup when not saving path will clear the saved path. - if (path.length > 1) { path.length = 1; } - if (path[0].length) { path[0].length = 0; } return; } if (!corner && style.savePath) return; + // Accumulate retracing path using only corners. var center = getCenterInPageCoordinates(elem); if (corner) { center.corner = true; addToPathList(corners[0], center); } if (style.savePath) return; + // Add to tracing path, and trace it righ away. addToPathList(path[0], center); var ts = readTurtleTransform(elem, true); + // Last argument 2 means that the last two points are saved, which + // allows us to draw corner miters and also avoid 'butt' lineCap gaps. drawAndClearPath(getDrawOnCanvas(state), state.path, style, ts.sx, 2); } @@ -6398,14 +6406,16 @@ var turtlefn = { penstyle += ";lineJoin:" + args.lineJoin; } this.css('turtlePenStyle', penstyle); + this.css('turtlePenDown', penstyle == 'none' ? 'up' : 'down'); } if (animate) { // A visual indicator of a pen color change. var style = parsePenStyle(this.css('turtlePenStyle')), - color = (style && style.strokeStyle) || + color = (style && (style.strokeStyle || + (style.savePath && 'gray'))) || (oldstyle && oldstyle.strokeStyle) || 'gray', target = {}, - newdown = this.css('turtlePenDown'), + newdown = this.css('turtlePenDown') && !style, pencil = new Turtle(color + ' pencil', this.parent()), distance = this.height(); pencil.css({ diff --git a/test/pen.html b/test/pen.html index b3302b2..837f014 100644 --- a/test/pen.html +++ b/test/pen.html @@ -67,11 +67,13 @@ }); asyncTest("Draws a series of lines with different style pens.", function() { - speed(100); + speed(Infinity); cs(); home(); - move(25, -25); - pen(blue, 20); + move(25, 25); + // Pen color after up automatically puts pen down. + pen(up); + pen(blue, 15); rt(180, 50); pen(up); fd(50); @@ -81,11 +83,13 @@ pen(up); move(-50, 50); pen(path); + window.buggy = true; lt(180, 50); pen(up); fd(50); pen(down) lt(180, 50); + window.buggy = false; fill(pink); speed(Infinity); done(function() { @@ -94,31 +98,81 @@ ok(!touches(lime)); ok(!touches(pink)); ok(touches(transparent)); - moveto(50, 0); + dot(); + moveto(75, 0); ok(!touches(blue)); ok(!touches(lime)); ok(!touches(pink)); ok(touches(transparent)); - moveto(50, 25); + dot(); + moveto(125, 0); ok(!touches(blue)); - ok(touches(lime)); + ok(!touches(lime)); ok(!touches(pink)); ok(touches(transparent)); - moveto(50, 75); + dot(); + moveto(75, 25); ok(!touches(blue)); ok(touches(lime)); ok(!touches(pink)); ok(touches(transparent)); - moveto(50, -25); + dot(); + moveto(75, 75); + ok(touches(blue)); + ok(touches(lime)); + ok(!touches(pink)); + ok(touches(transparent)); + dot(); + moveto(75, -25); ok(!touches(blue)); ok(touches(lime)); ok(!touches(pink)); ok(touches(transparent)); - moveto(-50, 0); + dot(); + moveto(75, -75); + ok(touches(blue)); + ok(touches(lime)); + ok(!touches(pink)); + // TODO: debug why this fails on phantomjs. + // ok(touches(transparent)); + dot(); + moveto(-75, 0); + ok(!touches(blue)); + ok(!touches(lime)); + ok(!touches(pink)); + ok(touches(transparent)); + dot(); + moveto(-125, 0); ok(!touches(blue)); ok(!touches(lime)); ok(!touches(pink)); ok(touches(transparent)); + dot(); + moveto(-75, 25); + ok(!touches(blue)); + ok(!touches(lime)); + ok(touches(pink)); + ok(touches(transparent)); + dot(); + moveto(-75, 75); + ok(!touches(blue)); + ok(!touches(lime)); + ok(touches(pink)); + ok(touches(transparent)); + dot(); + moveto(-75, -25); + ok(!touches(blue)); + ok(!touches(lime)); + ok(touches(pink)); + ok(touches(transparent)); + dot(); + moveto(-75, -75); + ok(!touches(blue)); + ok(!touches(lime)); + ok(touches(pink)); + ok(touches(transparent)); + dot(); + start(); }); }); From 0d1309af416765fa9e4535f8ba5288f9b1549fbf Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 17 Nov 2014 12:51:45 -0500 Subject: [PATCH 076/180] Write now waits for the turtle before showing text. --- jquery-turtle.js | 216 ++++++++++++++++++++++++++--------------------- test/write.html | 76 +++++++++++++++++ 2 files changed, 197 insertions(+), 95 deletions(-) create mode 100644 test/write.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 77e5396..cec916d 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9,6 +9,7 @@ version 2.0.8 jQuery-turtle is a jQuery plugin for turtle graphics. With jQuery-turtle, every DOM element is a turtle that can be + moved using turtle graphics methods like fd (forward), bk (back), rt (right turn), and lt (left turn). The pen function allows a turtle to draw on a full-document canvas as it moves. @@ -5881,25 +5882,36 @@ function wrappredicate(name, helptext, fn) { // Wrapglobalcommand does boilerplate setup for global commands that should // queue on the main turtle queue when there is a main turtle, but that // should execute immediately otherwise. -function wrapglobalcommand(name, helptext, fn) { +function wrapglobalcommand(name, helptext, fn, fnfilter) { var wrapper = function globalcommandwrapper() { checkForHungLoop(name); if (interrupted) { throw new Error(name + ' interrupted'); } - if (global_turtle) { + var early = null; + var argcount = 0; + var animate = global_turtle; + if (fnfilter) { + early = fnfilter.apply(null, arguments); + argcount = arguments.length; + animate = global_turtle_animating(); + } + if (animate) { var thissel = $(global_turtle).eq(0), args = arguments, - cc = setupContinuation(thissel, name, arguments, 0); + cc = setupContinuation(thissel, name, arguments, argcount); thissel.plan(function(j, elem) { cc.appear(j); - fn.apply(null, args); + fn.apply(early, args); this.plan(cc.resolver(j)); }); cc.exit(); } else { - cc = setupContinuation(null, name, arguments, 0); - fn.apply(null, arguments); + cc = setupContinuation(null, name, arguments, argcount); + fn.apply(early, arguments); cc.exit(); } + if (early) { + return early.result; + } }; return wrapraw(name, helptext, wrapper); } @@ -6843,7 +6855,7 @@ var turtlefn = { left: 0 }, styles); // Place the label on the screen using the figured styles. - var out = output(html, 'label').css(applyStyles) + var out = prepareOutput(html, 'label').result.css(applyStyles) .addClass('turtlelabel').appendTo(getTurtleField()); var rotated = /\brotated\b/.test(side), scaled = /\bscaled\b/.test(side); @@ -7405,6 +7417,10 @@ var turtleGIFUrl = "data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP var eventfn = { click:1, dblclick:1, mouseup:1, mousedown:1, mousemove:1 }; +function global_turtle_animating() { + return (global_turtle && $.queue(global_turtle).length > 0); +} + var global_turtle = null; var global_turtle_methods = []; var attaching_ids = false; @@ -7427,7 +7443,7 @@ var dollar_turtle_methods = { if (tickinterval) return true; if ($.timers.length) return true; if (async_pending) return true; - if (global_turtle && $.queue(global_turtle).length > 0) return true; + if (global_turtle_animating()) return true; if ($(':animated').length) return true; return ($('.turtle').filter(function() { return $.queue(this).length > 0; @@ -7487,7 +7503,7 @@ var dollar_turtle_methods = { "tick is called again: " + "c = 10; tick 1, -> c and write(c--) or tick()"], function tick(tps, fn) { - if (global_turtle) { + if (global_turtle_animating()) { var sel = $(global_turtle); sel.plan(function() { globaltick(tps, fn); @@ -7639,61 +7655,46 @@ var dollar_turtle_methods = { } }); }), - append: wrapraw('append', + append: wrapglobalcommand('append', ["append(html) Appends text to the document without a new line. " + "append 'try this twice...'"], function append(html) { $.fn.append.apply($('body'), arguments); }), - write: wrapraw('write', - ["write(html) Writes a line of text. Arbitrary HTML may be written: " + - "write 'Hello, world!'"], - function write(html) { - return output(Array.prototype.join.call(arguments, ' '), 'div'); - }), - type: wrapraw('type', + type: wrapglobalcommand('type', ["type(text) Types preformatted text like a typewriter. " + "type 'Hello!\n'"], plainTextPrint), - read: wrapraw('read', + write: wrapglobalcommand('write', + ["write(html) Writes a line of text. Arbitrary HTML may be written: " + + "write 'Hello, world!'"], doOutput, function() { + return prepareOutput(Array.prototype.join.call(arguments, ' '), 'div'); + }), + read: wrapglobalcommand('read', ["read(fn) Reads text or numeric input. " + "Calls fn once: " + "read (x) -> write x", "read(html, fn) Prompts for input: " + "read 'Your name?', (v) -> write 'Hello ' + v"], - function read(a, b) { return input(a, b, 0); }), - readnum: wrapraw('readnum', + doOutput, function read(a, b) { return prepareInput(a, b, 0); }), + readnum: wrapglobalcommand('readnum', ["readnum(html, fn) Reads numeric input. Only numbers allowed: " + "readnum 'Amount?', (v) -> write 'Tip: ' + (0.15 * v)"], - function readnum(a, b) { return input(a, b, 1); }), - readstr: wrapraw('readstr', + doOutput, function readnum(a, b) { return prepareInput(a, b, 1); }), + readstr: wrapglobalcommand('readstr', ["readstr(html, fn) Reads text input. Never " + "converts input to a number: " + "readstr 'Enter code', (v) -> write v.length + ' long'"], - function readstr(a, b) { return input(a, b, -1); }), - menu: wrapraw('menu', + doOutput, function readstr(a, b) { return prepareInput(a, b, -1); }), + menu: wrapglobalcommand('menu', ["menu(map) shows a menu of choices and calls a function " + "based on the user's choice: " + "menu {A: (-> write 'chose A'), B: (-> write 'chose B')}"], - menu), - random: wrapraw('random', - ["random(n) Random non-negative integer less than n: " + - "write random 10", - "random(list) Random member of the list: " + - "write random ['a', 'b', 'c']", - "random('position') Random page position: " + - "moveto random 'position'", - "random('color') Random color: " + - "pen random 'color'"], - random), - hatch: - function hatch(count, spec) { - return $(document).hatch(count, spec); - }, + doOutput, prepareMenu), button: wrapraw('button', ["button(text, fn) Writes a button. Calls " + "fn whenever the button is clicked: " + "button 'GO', -> fd 100"], - button), + doOutput, prepareButton), table: wrapraw('table', ["table(m, n) Writes m rows and c columns. " + "Access cells using cell: " + @@ -7701,7 +7702,17 @@ var dollar_turtle_methods = { "table(array) Writes tabular data. " + "Each nested array is a row: " + "table [[1,2,3],[4,5,6]]"], - table), + doOutput, prepareTable), + random: wrapraw('random', + ["random(n) Random non-negative integer less than n: " + + "write random 10", + "random(list) Random member of the list: " + + "write random ['a', 'b', 'c']", + "random('position') Random page position: " + + "moveto random 'position'", + "random('color') Random color: " + + "pen random 'color'"], + random), rgb: wrapraw('rgb', ["rgb(r,g,b) Makes a color out of red, green, and blue parts. " + "pen rgb(150,88,255)"], @@ -7709,6 +7720,10 @@ var dollar_turtle_methods = { Math.max(0, Math.min(255, Math.floor(r))), Math.max(0, Math.min(255, Math.floor(g))), Math.max(0, Math.min(255, Math.floor(b))) ]); }), + hatch: // Deprecated - no docs. + function hatch(count, spec) { + return $(document).hatch(count, spec); + }, rgba: wrapraw('rgba', ["rgba(r,g,b,a) Makes a color out of red, green, blue, and alpha. " + "pen rgba(150,88,255,0.5)"], @@ -8841,30 +8856,6 @@ function undoScrollAfter(f) { // and controls for reading input. ////////////////////////////////////////////////////////////////////////// -function output(html, defaulttag) { - if (html === undefined || html === null) { - // Print a turtle shell when no arguments. - return $('').wear('turtle').css({background: 'none'}).appendTo('body'); - } - var wrapped = false, result = null; - html = '' + html; - while ((result === null || result.length != 1) && !wrapped) { - // Wrap if obviously not surrounded by a tag already, or if we tried - // to trust a surrounding tag but found multiple bits. - if (html.charAt(0) != '<' || html.charAt(html.length - 1) != '>' || - (result !== null && result.length != 1)) { - html = '<' + defaulttag + ' style="display:table;">' + - html + ''; - wrapped = true; - } - result = $(html); - } - autoScrollAfter(function() { - result.appendTo('body'); - }); - return result; -} - // Simplify output of preformatted text inside a
.
 function plainTextPrint() {
   var args = arguments;
@@ -8880,6 +8871,40 @@ function plainTextPrint() {
   });
 }
 
+// Put this output on the screen.  Called some time after prepareOutput
+// if the turtle is animating.
+function doOutput() {
+  var early = this;
+  autoScrollAfter(function() {
+    early.result.appendTo('body');
+    if (early.setup) {
+      early.setup();
+    }
+  });
+}
+
+// Prepares some output to create, but doesn't put it on the screen yet.
+function prepareOutput(html, tag) {
+  var prefix = '<' + tag + ' style="display:table">',
+      suffix = '';
+  if (html === undefined || html === null) {
+    // Make empty line when no arguments.
+    return {result: $(prefix + '
' + suffix)}; + } else { + var wrapped = false, result = null; + html = '' + html; + // Try parsing a tag if possible. + if (/^\s*<.*>\s*$/.test(html)) { + result = $(html); + } + // If it wasn't a single element, then try to wrap it in an element. + if (result == null || result.length != 1 || result[0].nodeType != 1) { + result = $(prefix + html + suffix); + } + return {result: result}; + } +} + // Creates and displays a one-shot input menu as a set of. // radio buttons, each with a specified label. // @@ -8898,7 +8923,7 @@ function plainTextPrint() { // invokes the outcome value if it is a function. That way, the // first argument can be a list of functions or a map from // text labels to functions. -function menu(choices, fn) { +function prepareMenu(choices, fn) { var result = $('
') .css({display:'table',marginLeft:'20px'}) .submit(function(){return false;}), @@ -8984,9 +9009,6 @@ function menu(choices, fn) { } else { addChoice(choices, choices); } - autoScrollAfter(function() { - result.appendTo('body'); - }); // Accessibility support: deal with arrow keys. result.on('keydown', function(e) { if (e.which >= 37 && e.which <= 40 || e.which == 32) { @@ -9014,12 +9036,17 @@ function menu(choices, fn) { focusCursor(); } }); - // Focus, but don't cause autoscroll to occur due to focus. - undoScrollAfter(function() { focusCursor(true); }); + return { + result: result, + setup: function() { + // Focus, but don't cause autoscroll to occur due to focus. + undoScrollAfter(function() { focusCursor(true); }); + } + } } // Simplify $('body'>.append('').click(fn). -function button(name, callback) { +function prepareButton(name, callback) { if ($.isFunction(name) && callback === undefined) { callback = name; name = null; @@ -9028,11 +9055,10 @@ function button(name, callback) { name = '\u25CE'; } var result = $(''); - result.appendTo('body'); if (callback) { result.click(callback); } - return result; + return {result: result}; } ////////////////////////////////////////////////////////////////////////// @@ -9041,7 +9067,7 @@ function button(name, callback) { ////////////////////////////////////////////////////////////////////////// // Simplify $('body').append('' + label) and onchange hookup. -function input(name, callback, numeric) { +function prepareInput(name, callback, numeric) { if ($.isFunction(name) && !callback) { callback = name; name = null; @@ -9093,24 +9119,25 @@ function input(name, callback, numeric) { return false; } } - dodebounce(); textbox.on('keypress keydown', key); textbox.on('change', newval); - autoScrollAfter(function() { - $('body').append(label); - if (numeric < 0) { - // Widen a "readstr" textbox to make it fill the line. - var availwidth = label.parent().width(), - freewidth = availwidth + label.offset().left - textbox.offset().left, - bigwidth = Math.max(256, availwidth / 2), - desiredwidth = freewidth < bigwidth ? availwidth : freewidth, - marginwidth = textbox.outerWidth(true) - textbox.width(); - textbox.width(desiredwidth - marginwidth); + return { + result: label, + setup: function() { + dodebounce(); + if (numeric < 0) { + // Widen a "readstr" textbox to make it fill the line. + var availwidth = label.parent().width(), + freewidth = availwidth + label.offset().left - textbox.offset().left, + bigwidth = Math.max(256, availwidth / 2), + desiredwidth = freewidth < bigwidth ? availwidth : freewidth, + marginwidth = textbox.outerWidth(true) - textbox.width(); + textbox.width(desiredwidth - marginwidth); + } + // Focus, but don't cause autoscroll to occur due to focus. + undoScrollAfter(function() { textbox.focus(); }); } - }); - // Focus, but don't cause autoscroll to occur due to focus. - undoScrollAfter(function() { textbox.focus(); }); - return label; + }; } ////////////////////////////////////////////////////////////////////////// @@ -9118,7 +9145,7 @@ function input(name, callback, numeric) { ////////////////////////////////////////////////////////////////////////// // Simplify creation of tables with cells. -function table(height, width, cellCss, tableCss) { +function prepareTable(height, width, cellCss, tableCss) { var contents = null, row, col; if ($.isArray(height)) { tableCss = cellCss; @@ -9168,10 +9195,9 @@ function table(height, width, cellCss, tableCss) { { width: 'auto', height: 'auto', maxWidth: 'auto', border: 'none'}, tableCss)); result.find('td').css($.extend({}, defaultCss, cellCss)); - autoScrollAfter(function() { - result.appendTo('body'); - }); - return result; + return { + result: result + }; } diff --git a/test/write.html b/test/write.html new file mode 100644 index 0000000..248e0f5 --- /dev/null +++ b/test/write.html @@ -0,0 +1,76 @@ + + + + + +
+ From 0354cae4ca10cdaf60bc825ccd9712952b78800c Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 17 Nov 2014 13:08:41 -0500 Subject: [PATCH 077/180] Fix pen animation. --- jquery-turtle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index cec916d..bc1aa9c 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6401,7 +6401,7 @@ var turtlefn = { cc.appear(j); var animate = !invisible(elem) && !canMoveInstantly(this), oldstyle = animate && parsePenStyle(this.css('turtlePenStyle')), - olddown = animate && this.css('turtlePenDown'), + olddown = oldstyle && ('down' == this.css('turtlePenDown')), moved = false; if (penstyle === false || penstyle === true || penstyle == 'down' || penstyle == 'up') { @@ -6427,7 +6427,7 @@ var turtlefn = { (style.savePath && 'gray'))) || (oldstyle && oldstyle.strokeStyle) || 'gray', target = {}, - newdown = this.css('turtlePenDown') && !style, + newdown = style && 'down' == this.css('turtlePenDown'), pencil = new Turtle(color + ' pencil', this.parent()), distance = this.height(); pencil.css({ @@ -6437,14 +6437,14 @@ var turtlefn = { turtleRotation: this.css('turtleRotation'), turtleSpeed: Infinity }); - if (olddown == "up") { + if (!olddown) { pencil.css({ turtleForward: "+=" + distance, opacity: 0 }); - if (newdown == "down") { + if (newdown) { target.turtleForward = "-=" + distance; target.opacity = 1; } } else { - if (newdown == "up") { + if (!newdown) { target.turtleForward = "+=" + distance; target.opacity = 0; } From 5243eba67bdcc5ead6f4ca91521bb373a73e3fb8 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 18 Nov 2014 22:56:09 +0000 Subject: [PATCH 078/180] Drawon implies sync. --- jquery-turtle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index bc1aa9c..83cbf33 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6792,6 +6792,7 @@ var turtlefn = { "A = new Sprite('100x100'); " + "drawon A; pen red; fd 50; done -> A.rt 360"], function drawon(cc, canvas) { + sync(canvas, this); return this.plan(function(j, elem) { cc.appear(j); var state = getTurtleData(elem); From 7ad0142a196b110a646b1bd1b6adab8f2da82b09 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 17 Nov 2014 19:44:15 -0500 Subject: [PATCH 079/180] Main turtle zindex 1 --- jquery-turtle.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 83cbf33..4274641 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8089,6 +8089,8 @@ $.turtle = function turtle(id, options) { global_turtle_methods.push.apply(global_turtle_methods, globalizeMethods(selector, globalfn)); global_turtle = selector[0]; + // Make sure the main turtle is visible over other normal sprites. + selector.css({zIndex: 1}); } // Set up global objects by id. if (!('ids' in options) || options.ids) { From 142f2d4eb7596d990eab9b9639f3667fd5425220 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 19 Nov 2014 03:31:11 +0000 Subject: [PATCH 080/180] Drawon syncs with old canvas too. --- jquery-turtle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 4274641..cfa7c91 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6796,6 +6796,9 @@ var turtlefn = { return this.plan(function(j, elem) { cc.appear(j); var state = getTurtleData(elem); + if (state.drawOnCanvas) { + sync(elem, state.drawOnCanvas); + } if (!canvas) { state.drawOnCanvas = null; } else if (canvas.jquery && $.isFunction(canvas.canvas)) { From 4b4fc36b3f026d51bc96a87e9d78c0195ef0d58a Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 19 Nov 2014 04:15:29 +0000 Subject: [PATCH 081/180] Add more synchronization to drawon, and support drawon document. --- jquery-turtle.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index cfa7c91..d402743 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5556,7 +5556,7 @@ function sync() { // Unblock all animation queues. for (j = 0; j < cb.length; ++j) { cb[j](); } } - for (j = 0; j < elts.length; ++j) { + if (elts.length > 1) for (j = 0; j < elts.length; ++j) { queueWaitIfLoadingImg(elts[j]); $(elts[j]).queue(function(next) { if (ready) { @@ -6792,6 +6792,11 @@ var turtlefn = { "A = new Sprite('100x100'); " + "drawon A; pen red; fd 50; done -> A.rt 360"], function drawon(cc, canvas) { + this.each(function() { + var state = getTurtleData(this); + if (state.drawOnCanvasSync) sync(this, state.drawOnCanvasSync); + state.drawOnCanvasSync = canvas; + }); sync(canvas, this); return this.plan(function(j, elem) { cc.appear(j); @@ -6805,6 +6810,8 @@ var turtlefn = { state.drawOnCanvas = canvas.canvas(); } else if (canvas.tagName && canvas.tagName == 'CANVAS') { state.drawOnCanvas = canvas; + } else if (canvas.nodeType == 1 || canvas.nodeType == 9) { + state.drawOnCanvas = $(canvas).canvas(); } cc.resolve(j); }); @@ -7011,7 +7018,7 @@ var turtlefn = { "c = turtle.canvas().getContext('2d'); c.fillStyle = red; " + "c.fillRect(10, 10, 30, 30)"], function canvas() { - return this.filter('canvas').get(0); + return this.filter('canvas').get(0) || this.find('canvas').get(0); }), imagedata: wrapraw('imagedata', ["imagedata() Returns the image data for the turtle. " + From 9e47a917807227194a538bc6eee14bd38ee8ce39 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 19 Nov 2014 14:38:56 -0500 Subject: [PATCH 082/180] Fix bugs in button and table, and add tests. --- jquery-turtle.js | 6 ++--- test/inputoutput.html | 55 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 test/inputoutput.html diff --git a/jquery-turtle.js b/jquery-turtle.js index d402743..796ffad 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7701,12 +7701,12 @@ var dollar_turtle_methods = { "based on the user's choice: " + "menu {A: (-> write 'chose A'), B: (-> write 'chose B')}"], doOutput, prepareMenu), - button: wrapraw('button', + button: wrapglobalcommand('button', ["button(text, fn) Writes a button. Calls " + "fn whenever the button is clicked: " + "button 'GO', -> fd 100"], doOutput, prepareButton), - table: wrapraw('table', + table: wrapglobalcommand('table', ["table(m, n) Writes m rows and c columns. " + "Access cells using cell: " + "g = table 8, 8; g.cell(2,3).text 'hello'", @@ -9065,7 +9065,7 @@ function prepareButton(name, callback) { name = null; } if (name === null || name === undefined) { - name = '\u25CE'; + name = 'button'; } var result = $(''); if (callback) { diff --git a/test/inputoutput.html b/test/inputoutput.html new file mode 100644 index 0000000..74035f4 --- /dev/null +++ b/test/inputoutput.html @@ -0,0 +1,55 @@ + + + + + +
+ From 99d9159679cf30dbcd874967031a5f888463cec2 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 22 Nov 2014 06:49:11 -0500 Subject: [PATCH 083/180] Add lineDash, and let pen path persist over fill. --- jquery-turtle.js | 5 +-- test/fill.html | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 test/fill.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 796ffad..b7a6057 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1915,6 +1915,8 @@ function applyPenStyle(ctx, ps, scale) { if (a === 'savePath' || a === 'eraseMode') { continue; } if (scale && a === 'lineWidth') { ctx[a] = scale * ps[a] + extraWidth; + } else if (a === 'lineDash') { + ctx.setLineDash(('' + ps[a]).split(/[,\s]/g)); } else { ctx[a] = ps[a]; } @@ -2091,9 +2093,6 @@ function endAndFillPenPath(elem, style) { style = $.extend({}, state.style, style); } drawAndClearPath(getDrawOnCanvas(state), state.corners, style, ts.sx, 1); - if (state.style && state.style.savePath) { - $.style(elem, 'turtlePenStyle', 'none'); - } } function clearField(arg) { diff --git a/test/fill.html b/test/fill.html new file mode 100644 index 0000000..4a22917 --- /dev/null +++ b/test/fill.html @@ -0,0 +1,110 @@ + + + + + +
+ From c4f4c510f1f3d13cee66afc185b3cc1010546fd8 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 14 Dec 2014 02:27:25 +0000 Subject: [PATCH 084/180] Support id and class properties on label and new Sprite. --- jquery-turtle.js | 14 ++++++++++++++ test/label.html | 9 ++++++--- test/twist.html | 4 +++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index b7a6057..2b399c7 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6867,6 +6867,12 @@ var turtlefn = { // Place the label on the screen using the figured styles. var out = prepareOutput(html, 'label').result.css(applyStyles) .addClass('turtlelabel').appendTo(getTurtleField()); + if (styles && 'id' in styles) { + out.attr('id', styles.id); + } + if (styles && 'class' in styles) { + out.addClass(styles.class); + } var rotated = /\brotated\b/.test(side), scaled = /\bscaled\b/.test(side); // Mimic the current position and rotation and scale of the turtle. @@ -8650,6 +8656,14 @@ function hatchone(name, container, defaultshape) { } else { result = $('
' + escapeHtml(name) + '
'); } + if (name && 'object' == typeof(name)) { + if ('id' in name) { + result.attr('id', name.id); + } + if ('class' in name) { + result.addClass(name.class); + } + } // Position the turtle inside the container. result.css({ position: 'absolute', diff --git a/test/label.html b/test/label.html index 8eb221b..08983f4 100644 --- a/test/label.html +++ b/test/label.html @@ -36,16 +36,19 @@ label('big'); bk(100) rt(90); - label('mid', { fontSize: 50 }); + label('mid', { fontSize: 50, class: "mid" }); fd(150) label('giant', 150); - label('upside down', { turtleRotation: 180, fontSize: 8 }); + label('upside down', { turtleRotation: 180, fontSize: 8, id: 'upside' }); label('bottomleft', 'bottom-left'); done(function() { ok($('label:contains(small)').width() < $('label:contains(mid)').width()); ok($('label:contains(mid)').width() < $('label:contains(big)').width()); ok($('label:contains(big)').width() < $('label:contains(giant)').width()); - ok($('label:contains(upside)').width() < $('label:contains(mid)').width()); + ok($('label:contains(upside)').width() <$('label:contains(mid)').width()); + equal($('.mid').length, 1); + equal($('.mid').text(), 'mid'); + equal($('#upside').text(), 'upside down'); deepEqual(rounded($('label:contains(small)').getxy()), [0, 0]); deepEqual(rounded($('label:contains(big)').getxy()), [0, 50]); deepEqual(rounded($('label:contains(mid)').getxy()), [0, -50]); diff --git a/test/twist.html b/test/twist.html index f7960ec..b3e3f21 100644 --- a/test/twist.html +++ b/test/twist.html @@ -18,7 +18,9 @@ dot(red); bk(200) dot(blue); - var s = new Sprite({ width: 10, height: 200, color: green }); + var s = new Sprite({ width: 10, height: 200, color: green, id: "s" }); + equal($('#s').length, 1); + equal(s.filter('#s').length, 1); ok(s.touches(red)); ok(s.touches(blue)); ok(!s.touches(pink)); From fe1e3a80ee19989c277e1d0ae4ce48f625215e5a Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 14 Dec 2014 02:29:41 +0000 Subject: [PATCH 085/180] Trailing space. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 2b399c7..7e39f07 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6867,7 +6867,7 @@ var turtlefn = { // Place the label on the screen using the figured styles. var out = prepareOutput(html, 'label').result.css(applyStyles) .addClass('turtlelabel').appendTo(getTurtleField()); - if (styles && 'id' in styles) { + if (styles && 'id' in styles) { out.attr('id', styles.id); } if (styles && 'class' in styles) { From 345bdc6a3eff445ac2b0798bb7b0229023888612 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 16 Dec 2014 10:01:08 -0500 Subject: [PATCH 086/180] Implement stop button for input and event handlers. --- jquery-turtle.js | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 7e39f07..435e828 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5924,13 +5924,26 @@ function wrapwindowevent(name, helptext) { : null; if (forKey) { focusWindowIfFirst(); } if (fn == null && typeof(d) == 'function') { fn = d; d = null; } - $(window).on(name, null, d, !filter ? fn : function(e) { + $(window).on(name + '.turtleevent', null, d, !filter ? fn : function(e) { + if (interrupted) return; if ($(e.target).closest(filter).length) { return; } return fn.apply(this, arguments); }); }); } +function windowhasturtleevent() { + var events = $._data(window, 'events'); + if (!events) return false; + for (var type in events) { + var entries = events[type]; + for (var j = 0; j < entries.length; ++j) { + if (entries[j].namespace == 'turtleevent') return true; + } + } + return false; +} + // Wrapraw sets up help text for a function (such as "sqrt") that does // not need any other setup. function wrapraw(name, helptext, fn) { @@ -7461,14 +7474,21 @@ var dollar_turtle_methods = { if (async_pending) return true; if (global_turtle_animating()) return true; if ($(':animated').length) return true; - return ($('.turtle').filter(function() { - return $.queue(this).length > 0; - }).length > 0); + if ($('.turtle').filter(function() { + return $.queue(this).length > 0; }).length > 0) return true; + if ($('.turtleinput').filter(function() { + return !$(this).prop('disabled')}).length > 0) return true; + if (windowhasturtleevent()) return true; + return false; } // Stop all animations. $(':animated,.turtle').clearQueue().stop(); // Stop our audio. resetAudio(); + // Disable all input. + $('.turtleinput').prop('disabled', true); + // Detach all event handlers on the window. + $(window).off('.turtleevent'); // Set a flag that will cause all commands to throw. interrupted = true; // Turn off the global tick interval timer. @@ -9009,7 +9029,7 @@ function prepareMenu(choices, fn) { text = (count + 1).toString(); } var value = $.isFunction(outcome) || outcome == null ? text: outcome, - radio = $('') + radio = $('') .attr('value', value) .on('change click', triggerOnce(outcome)), label = $('').append(textbox).append(button), debounce = null, lastseen = textbox.val(), recognition = null; @@ -9670,6 +9671,7 @@ function prepareInput(name, callback, type) { dodebounce(); lastseen = val; textbox.remove(); + button.remove(); label.append(val).css({display: 'table'}); if (type == 'number' || (type == 'auto' && $.isNumeric(val) && ('' + parseFloat(val) == val))) { @@ -9718,7 +9720,11 @@ function prepareInput(name, callback, type) { marginwidth = textbox.outerWidth(true) - textbox.width(); textbox.width(desiredwidth - marginwidth); } + if (type == 'number') { + textbox.attr('type', 'number'); + } if (type == 'voice') { + button.css({display:none}); var SR = global.SpeechRecognition || global.webkitSpeechRecognition; if ('function' == typeof(SR)) { try { From cf4d9317f89b805dfa8d6a40e843330e4367b828 Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Wed, 28 Oct 2015 11:12:01 -0700 Subject: [PATCH 167/180] Added newval handler to button instead of text box blur. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index cc262a1..ac528b6 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9706,7 +9706,7 @@ function prepareInput(name, callback, type) { } } textbox.on('keypress keydown', key); - textbox.on('change', newval); + button.on('click', newval); return { result: label, setup: function() { From cf03900eb29f746ee61f4d5a232f5eff9f30b2de Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Wed, 28 Oct 2015 21:34:18 -0700 Subject: [PATCH 168/180] Replaced the readvoice command with the listen command. --- jquery-turtle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9f4064d..c44f712 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8046,9 +8046,9 @@ var dollar_turtle_methods = { "converts input to a number: " + "readstr 'Enter code', (v) -> write v.length + ' long'"], doOutput, function readstr(a, b) { return prepareInput(a, b, 'text'); }), - readvoice: wrapglobalcommand('readvoice', - ["readvoice(html, fn) Reads voice input, if the browser supports it:" + - "readvoice 'Say something', (v) -> write v"], + listen: wrapglobalcommand('listen', + ["listen(html, fn) Reads voice input, if the browser supports it:" + + "listen 'Say something', (v) -> write v"], doOutput, function readstr(a, b) { return prepareInput(a, b, 'voice'); }), menu: wrapglobalcommand('menu', ["menu(map) shows a menu of choices and calls a function " + From 3a71978450e101f033b7c74e21e3ae2164583461 Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Wed, 28 Oct 2015 22:40:46 -0700 Subject: [PATCH 169/180] Fixed broken test. --- test/globals.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/globals.html b/test/globals.html index f208663..a19cb14 100644 --- a/test/globals.html +++ b/test/globals.html @@ -60,7 +60,7 @@ "typeline", "read", "readnum", - "readvoice", + "listen", "readstr", "menu", "random", From de2c752ebc0908693e84a4fa6d83ca039bdd233f Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 29 Oct 2015 02:47:21 -0400 Subject: [PATCH 170/180] Fix test. --- test/inputoutput.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/inputoutput.html b/test/inputoutput.html index a29c622..b3b33b4 100644 --- a/test/inputoutput.html +++ b/test/inputoutput.html @@ -20,14 +20,22 @@ equal(td.eq(2).text(), "3"); var clicked = 0; setTimeout(function() { - $('input').val('Amy').change(); + $('input').val('Amy'); + $('button').click(); }, 1); - var b; + var b, r2; var r = read('name?', function(t) { equal(t, 'Amy'); setTimeout(function() { + $('input').val('34'); + var e = jQuery.Event("keydown"); + e.which = 13; // Enter. + $('input').trigger(e); $('button').click(); }, 1); + r2 = readnum('count?', function(c) { + equal(c, 34); + }); b = button('click me!', function() { equal(equal($('button').last().text(), 'click me!')); equal(clicked++, 0); From 06dad3b5ea0a13b87768711dc32e36da44bdbc6b Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 06:50:19 -0500 Subject: [PATCH 171/180] Do not require /img/ for wear, and also add sync for newly written objects. --- jquery-turtle.js | 11 +++++++++-- test/wear.html | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index de99da4..506354e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2689,7 +2689,7 @@ function imgUrl(url) { if (/\//.test(url)) { return url; } url = '/img/' + url; if (isPencilHost(global.location.hostname)) { return url; } - return '//pencil.io' + url; + return '//pencilcode.net' + url; } // Retrieves the pencil code login cookie, if there is one. function loginCookie() { @@ -5944,6 +5944,9 @@ function wrapglobalcommand(name, helptext, fn, fnfilter) { this.plan(cc.resolver(j)); }); cc.exit(); + if (early && early.result && early.result.constructor === jQuery) { + sync(animate, early.result); + } } else { cc = setupContinuation(null, name, arguments, argcount); fn.apply(early, arguments); @@ -9026,7 +9029,7 @@ function nameToImg(name, defaultshape) { // Deal with unquoted "tan" and "dot". name = name.helpname || name.name; } - if (name.constructor === $) { + if (name.constructor === jQuery) { // Unwrap jquery objects. if (!name.length) { return null; } name = name.get(0); @@ -9057,6 +9060,10 @@ function nameToImg(name, defaultshape) { if (shape) { return shape(color); } + // Default to '/img/' URLs if it doesn't match a well-known name. + if (!/\//.test(name)) { + name = imgUrl(name); + } // Parse URLs. if (/\//.test(name)) { var hostname = absoluteUrlObject(name).hostname; diff --git a/test/wear.html b/test/wear.html index eed8ddc..12a9d3f 100644 --- a/test/wear.html +++ b/test/wear.html @@ -24,6 +24,20 @@ }); }); +asyncTest("Wears an creative commons image.", function() { + var r = new Turtle(red); + r.speed(100); + tick(60, function() { + ok(r.width() != 20 || r.getxy()[1] == 0); + }); + r.wear('t-watermelon'); // no /img/ needed. + r.done(function() { + ok(r.width() != 20); + tick(null); + setTimeout(start, 0); + }); +}); + asyncTest("Wears and hittests different shapes.", function() { speed(Infinity); dot(red, 30); From f4ba15971301c6b11cbb0c71e61be99c14436912 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 07:36:23 -0500 Subject: [PATCH 172/180] Allow 'say' when there is no global turtle. --- jquery-turtle.js | 73 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 506354e..bcf03f1 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6427,6 +6427,39 @@ function drawArrowLine(c, w, x0, y0, x1, y1) { drawArrowHead(c, m); } +////////////////////////////////////////////////////////////////////////// +// VOICE SYNTHESIS +// Method for uttering words. +////////////////////////////////////////////////////////////////////////// +function utterSpeech(words, cb) { + var pollTimer = null; + function complete() { + if (pollTimer) { clearInterval(pollTimer); } + if (cb) { cb(); } + } + if (!global.speechSynthesis) { + console.log('No speech synthesis: ' + words); + complete(); + return; + } + try { + var msg = new global.SpeechSynthesisUtterance(words); + msg.addEventListener('end', complete); + msg.addEventListener('error', complete); + msg.lang = navigator.language || 'en-GB'; + global.speechSynthesis.speak(msg); + pollTimer = setInterval(function() { + // Chrome speech synthesis fails to deliver an 'end' event + // sometimes, so we also poll every 250ms. + if (global.speechSynthesis.pending || global.speechSynthesis.speaking) return; + complete(); + }, 250); + } catch (e) { + if (global.console) { global.console.log(e); } + complete(); + } +} + ////////////////////////////////////////////////////////////////////////// // TURTLE FUNCTIONS // Turtle methods to be registered as jquery instance methods. @@ -6876,35 +6909,13 @@ var turtlefn = { this.plan(function(j, elem) { cc.appear(j); this.queue(function(next) { - var finished = false, - pollTimer = null, - complete = function() { - if (finished) return; - clearInterval(pollTimer); - finished = true; + utterSpeech(words, function() { cc.resolve(j); next(); - }; - try { - var msg = new SpeechSynthesisUtterance(words); - msg.addEventListener('end', complete); - msg.addEventListener('error', complete); - msg.lang = 'en-GB'; - speechSynthesis.speak(msg); - pollTimer = setInterval(function() { - // Chrome speech synthesis fails to deliver an 'end' event - // sometimes, so we also poll every 250ms. - if (speechSynthesis.pending || speechSynthesis.speaking) return; - complete(); - }, 250); - } catch (e) { - if (global.console) { global.console.log(e); } - complete(); - } + }); }); }); return this; - }), play: wrapcommand('play', 1, ["play(notes) Play notes. Notes are specified in " + @@ -7863,6 +7874,20 @@ var dollar_turtle_methods = { function globalspeed(mps) { globaldefaultspeed(mps); }), + say: wrapraw('say', + ["say(words) Say something. Use English words." + + "say \"Let's go!\""], + function say(words) { + if (global_turtle) { + var sel = $(global_turtle); + sel.say.call(sel, words); + } else { + var cc = setupContinuation(null, 'say', arguments, 0); + cc.appear(null); + utterSpeech(words, function() { cc.resolve(null); }); + cc.exit(); + } + }), play: wrapraw('play', ["play(notes) Play notes. Notes are specified in " + "
" + From 2a0dcce08464b3b6e729411b1c11d696e1cbb91c Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 09:01:52 -0500 Subject: [PATCH 173/180] Add test for speech synthesis. --- test/say.html | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/say.html diff --git a/test/say.html b/test/say.html new file mode 100644 index 0000000..a8aed1c --- /dev/null +++ b/test/say.html @@ -0,0 +1,25 @@ + + + + + +
+ + + + From 5565321dc105e6949e7483b46f3134d188fd7ca8 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 09:07:55 -0500 Subject: [PATCH 174/180] Fix global_turtle sync when writing new objects. --- jquery-turtle.js | 6 +++--- test/sync.html | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index bcf03f1..8465c7e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5944,15 +5944,15 @@ function wrapglobalcommand(name, helptext, fn, fnfilter) { this.plan(cc.resolver(j)); }); cc.exit(); - if (early && early.result && early.result.constructor === jQuery) { - sync(animate, early.result); - } } else { cc = setupContinuation(null, name, arguments, argcount); fn.apply(early, arguments); cc.exit(); } if (early) { + if (early.result && early.result.constructor === jQuery && global_turtle) { + sync(global_turtle, early.result); + } return early.result; } }; diff --git a/test/sync.html b/test/sync.html index 87e4d1a..4808c51 100644 --- a/test/sync.html +++ b/test/sync.html @@ -46,4 +46,22 @@ start(); }); }); + +asyncTest("Moves a written object and verifies that sync works.", function() { + speed(10); + var w = write('hello'); + w.bk(100); + sync(w, turtle); + w.plan(function() { + // When w is done moving, w2 should not have moved yet. + var xy2 = w2.getxy(); + var xy = w.getxy(); + ok(xy2[1] > xy[1]); + }); + var w2 = write('hello2'); + w2.bk(100); + done(function() { + start(); + }); +}); From 89e724b6054d3aac6eb2b142144a3b54f133f23a Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 30 Nov 2015 07:39:54 -0500 Subject: [PATCH 175/180] Add $.turtle.colors. --- jquery-turtle.js | 1 + test/random.html | 1 + 2 files changed, 2 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 8465c7e..da2d879 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8626,6 +8626,7 @@ $.turtle = function turtle(id, options) { }; $.extend($.turtle, dollar_turtle_methods); +$.turtle.colors = colors; function seehelphook(text, result) { // Also, check the command to look for (non-CoffeeScript) help requests. diff --git a/test/random.html b/test/random.html index 0d03405..4e6e289 100644 --- a/test/random.html +++ b/test/random.html @@ -20,6 +20,7 @@ equal(random([1,2,3]), 3); equal(random(), 0.9520654011140274); equal(random(null), 0.006122341186296597); + equal($.turtle.colors.indexOf('red'), 120); start(); }); From 157acf35a1166b9fc2df8adf840a62175c2230a3 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Sat, 9 Jan 2016 16:12:56 +0100 Subject: [PATCH 176/180] Fix and clean up the code to avoid compiler errors and warnings. --- jquery-turtle.js | 75 +++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index da2d879..033294b 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -772,7 +772,7 @@ function readTransformMatrix(elem) { // Reads out the css transformOrigin property, if present. function readTransformOrigin(elem, wh) { var hidden = ($.css(elem, 'display') === 'none'), - swapout, old, name, gcs, origin; + swapout, old, name; if (hidden) { // IE GetComputedStyle doesn't give pixel values for transformOrigin // unless the element is unhidden. @@ -783,13 +783,13 @@ function readTransformOrigin(elem, wh) { elem.style[name] = swapout[name]; } } - var gcs = (global.getComputedStyle ? global.getComputedStyle(elem) : null), - origin = (gcs && gcs[transformOrigin] || $.css(elem, 'transformOrigin')); + var gcs = (global.getComputedStyle ? global.getComputedStyle(elem) : null); if (hidden) { for (name in swapout) { elem.style[name] = old[name]; } } + var origin = (gcs && gcs[transformOrigin] || $.css(elem, 'transformOrigin')); if (origin && origin.indexOf('%') < 0) { return $.map(origin.split(' '), parseFloat); } @@ -960,7 +960,7 @@ function getTurtleOrigin(elem, inverseParent, extra) { { position: "absolute", visibility: "hidden", display: "block" } : {}, substTransform = swapout[transform] = (inverseParent ? 'matrix(' + $.map(inverseParent, cssNum).join(', ') + ', 0, 0)' : 'none'), - old = {}, name, gbcr, transformOrigin, result; + old = {}, name, gbcr, transformOrigin; for (name in swapout) { old[name] = elem.style[name]; elem.style[name] = swapout[name]; @@ -1250,7 +1250,7 @@ function scrollWindowToDocumentPosition(pos, limit) { if (tx < ww2) { tx = ww2; } if (ty > dh - wh2) { ty = dh - wh2; } if (ty < wh2) { ty = wh2; } - targ = { pageX: tx, pageY: ty }; + var targ = { pageX: tx, pageY: ty }; if ($.isNumeric(limit)) { targ = limitMovement(w.origin(), targ, limit); } @@ -2178,8 +2178,7 @@ function touchesPixel(elem, color) { h = (bb.bottom - bb.top), osc = getOffscreenCanvas(w, h), octx = osc.getContext('2d'), - rgba = rgbaForColor(color), - j = 1, k, data; + j = 1, k; if (!c || c.length < 3 || !w || !h) { return false; } octx.drawImage(canvas, bb.left, bb.top, w, h, 0, 0, w, h); @@ -2726,7 +2725,8 @@ var stablyLoadedImages = {}; // @param css is a dictionary of css props to set when the image is loaded. // @param cb is an optional callback, called after the loading is done. function setImageWithStableOrigin(elem, url, css, cb) { - var record, urlobj = absoluteUrlObject(url), url = urlobj.href; + var record, urlobj = absoluteUrlObject(url); + url = urlobj.href; // The data-loading attr will always reflect the last URL requested. elem.setAttribute('data-loading', url); if (url in stablyLoadedImages) { @@ -3032,7 +3032,7 @@ var Pencil = (function(_super) { } // The pencil is a sprite that just defaults to zero size. var context = canvas ? canvas.parentElement : null; - var settings = { width: 0, height: 0, color: transparent }; + var settings = { width: 0, height: 0, color: 'transparent' }; Pencil.__super__.constructor.call(this, settings, context); // Set the pencil to hidden, infinite speed, // and drawing on the specifed canvas. @@ -3072,7 +3072,7 @@ var Webcam = (function(_super) { function Webcam(opts, context) { var attrs = "", hassrc = false, hasautoplay = false, hasdims = false; if ($.isPlainObject(opts)) { - for (key in opts) { + for (var key in opts) { attrs += ' ' + key + '="' + escapeHtml(opts[key]) + '"'; } hassrc = ('src' in opts); @@ -3317,12 +3317,12 @@ var Piano = (function(_super) { // Converts a midi number to a white key position (black keys round left). function wcp(n) { - return floor((n + 7) / 12 * 7); + return Math.floor((n + 7) / 12 * 7); }; // Converts from a white key position to a midi number. function mcp(n) { - return ceil(n / 7 * 12) - 7; + return Math.ceil(n / 7 * 12) - 7; }; // True if midi #n is a black key. @@ -4517,7 +4517,7 @@ var Instrument = (function() { if (key in given) { timbre[key] = given[key]; } else { - timbre[key] = defaulTimbre[key]; + timbre[key] = defaultTimbre[key]; } } } @@ -5206,7 +5206,6 @@ function parseABCFile(str) { function syncopateStem(stem, t) { var j, note, stemtime = stem.time, newtime = stemtime + t; stem.time = newtime; - syncopateStem for (j = 0; j < stem.notes.length; ++j) { note = stem.notes[j]; // Only adjust a note's duration if it matched the stem's duration. @@ -7116,8 +7115,8 @@ var turtlefn = { // For defaults, copy inline styles of the turtle itself except for // properties in the following list (these are the properties used to // make the turtle look like a turtle). - for (var j = 0; j < currentStyles.length; ++j) { - var styleProperty = currentStyles[j]; + for (var j2 = 0; j2 < currentStyles.length; ++j2) { + var styleProperty = currentStyles[j2]; if (/^(?:width|height|opacity|background-image|background-size)$/.test( styleProperty) || /transform/.test(styleProperty)) { continue; @@ -7535,7 +7534,6 @@ var turtlefn = { callback.apply(this, arguments); } }); - sync = null; }), plan: wrapraw('plan', ["plan(fn) Runs fn in the animation queue. For planning logic: " + @@ -7958,7 +7956,6 @@ var dollar_turtle_methods = { callback.apply(this, arguments); } }); - sync = null; }), load: wrapraw('load', ["load(url, cb) Loads data from the url and passes it to cb. " + @@ -7994,7 +7991,8 @@ var dollar_turtle_methods = { "save 'intro', 'pen gold, 20\\nfd 100\\n'"], function(url, data, cb) { if (!url) throw new Error('Missing url for save'); - var payload = { }, url = apiUrl(url, 'save'), key; + var payload = { }, key; + url = apiUrl(url, 'save'); if (/\.json(?:$|\?|\#)/.test(url)) { data = JSON.stringify(data, null, 2); } @@ -8254,7 +8252,7 @@ var dollar_turtle_methods = { return (x == 0) ? NaN : ((x > 0) ? 0 : 180); } else if (x == 0) { return (y > 0) ? Infinity : -Infinity; - } else if (abs(y) == abs(x)) { + } else if (Math.abs(y) == Math.abs(x)) { return (y > 0) ? ((x > 0) ? 45 : 135) : ((x > 0) ? -45 : -135); } @@ -8608,12 +8606,6 @@ $.turtle = function turtle(id, options) { seeopt.height = options.panelheight; } see.init(seeopt); - if (wrotebody) { - /* - see.html('Turtle script should be inside body ' + - '- wrote a <body>'); - */ - } // Return an eval loop hook string if 'see' is exported. if (exportedsee) { if (global.CoffeeScript) { @@ -9355,13 +9347,12 @@ function turtleevents(prefix) { if (prefix || prefix === '') { eventsaver = (function(e) { // Keep the old instance if possible. - var names = [prefix + e.type], j, old, name, prop; + var names = [prefix + e.type], j; if ((e.originalEvent || e) instanceof MouseEvent) { names.push(prefix + 'mouse'); } for (j = 0; j < names.length; ++j) { - var name = names[j]; - old = global[name], prop; + var name = names[j], old = global[name], prop; if (old && old.__proto__ === e.__proto__) { for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } for (prop in e) { if (e.hasOwnProperty(prop)) old[prop] = e[prop]; } @@ -9757,7 +9748,7 @@ function prepareInput(name, callback, type) { textbox.attr('type', 'number'); } if (type == 'voice') { - button.css({display:none}); + button.css({display: 'none'}); var SR = global.SpeechRecognition || global.webkitSpeechRecognition; if ('function' == typeof(SR)) { try { @@ -9892,40 +9883,41 @@ function componentColor(t, args) { return t + '(' + Array.prototype.join.call(args, ',') + ')'; } -function autoArgs(arguments, start, map) { +function autoArgs(args, start, map) { var j = 0; var taken = []; var result = {}; for (var key in map) { var pattern = map[key]; - for (j = start; j < arguments.length; ++j) { + for (j = start; j < args.length; ++j) { if (~taken.indexOf(j)) continue; if (pattern == '*') { break; - } else if (pattern instanceof RegExp && pattern.test(arguments[j])) { + } else if (pattern instanceof RegExp && pattern.test(args[j])) { break; - } else if (pattern instanceof Function && pattern(arguments[j])) { + } else if (pattern instanceof Function && pattern(args[j])) { break; - } else if (pattern == typeof arguments[j]) { + } else if (pattern == typeof args[j]) { break; } } - if (j < arguments.length) { + if (j < args.length) { taken.push(j); - result[key] = arguments[j]; + result[key] = args[j]; } } - if (taken.length + start < arguments.length) { + if (taken.length + start < args.length) { var extra = []; - for (j = start; j < arguments.length; ++j) { + for (j = start; j < args.length; ++j) { if (~taken.indexOf(j)) continue; - extra.push(arguments[j]); + extra.push(args[j]); } result.extra = extra; } return result; } + ////////////////////////////////////////////////////////////////////////// // DEBUGGING SUPPORT ////////////////////////////////////////////////////////////////////////// @@ -10036,7 +10028,7 @@ debug.init(); c = cnv.getContext('2d'), relative = false, p = lineend || e, - s, html, dx, dy, dd, dir, ang; + html, dx, dy, dd, dir, ang; if (linestart && 'function' == typeof(linestart.pagexy)) { var xy = linestart.getxy(), s = linestart.pagexy(); s.x = xy[0]; @@ -10763,7 +10755,6 @@ function aselement(s, def) { default: return s; } - return null; } function stickscroll() { var stick = false, a = aselement(autoscroll, null); From 04503f35404c3a709d13b3e8b3e2bb7a5f4a4c4c Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Wed, 13 Jan 2016 21:11:19 +0100 Subject: [PATCH 177/180] Fixed smaller issues and removed jquery-turtle.min.js from .gitignore. --- .gitignore | 1 - LICENSE.txt | 2 +- README.md | 2 +- jquery-turtle.js | 24 ++++++++++-------------- jquery-turtle.min.js | 5 +++++ 5 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 jquery-turtle.min.js diff --git a/.gitignore b/.gitignore index c40cd56..a349cbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .svn node_modules -jquery-turtle.min.js diff --git a/LICENSE.txt b/LICENSE.txt index fca7187..f997494 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -2,7 +2,7 @@ jQuery-turtle version 2.0 LICENSE (MIT): -Copyright (c) 2013 Pencil Code Foundation, Google, and other contributors. +Copyright (c) 2013 Pencil Code Foundation, Google Inc., and other contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 91bace6..8af96ab 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ element. License (MIT) ------------- -Copyright (c) 2014 Pencil Code, Google, and other contributors +Copyright (c) 2014 Pencil Code, Google Inc., and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/jquery-turtle.js b/jquery-turtle.js index 033294b..b09f1ad 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -4709,16 +4709,13 @@ var Instrument = (function() { return result; } - var whiteNoiseBuf = null; function getWhiteNoiseBuf() { - if (whiteNoiseBuf == null) { - var ac = getAudioTop().ac, - bufferSize = 2 * ac.sampleRate, - whiteNoiseBuf = ac.createBuffer(1, bufferSize, ac.sampleRate), - output = whiteNoiseBuf.getChannelData(0); - for (var i = 0; i < bufferSize; i++) { - output[i] = Math.random() * 2 - 1; - } + var ac = getAudioTop().ac, + bufferSize = 2 * ac.sampleRate, + whiteNoiseBuf = ac.createBuffer(1, bufferSize, ac.sampleRate), + output = whiteNoiseBuf.getChannelData(0); + for (var i = 0; i < bufferSize; i++) { + output[i] = Math.random() * 2 - 1; } return whiteNoiseBuf; } @@ -7323,7 +7320,7 @@ var turtlefn = { if (typeof val != 'object' || !$.isNumeric(val.width) || !$.isNumeric(val.height) || !($.isArray(val.data) || val.data instanceof Uint8ClampedArray || - val.data instanceof Unit8Array)) { + val.data instanceof Uint8Array)) { return; } var imdat = ctx.createImageData( @@ -7917,7 +7914,7 @@ var dollar_turtle_methods = { sel.tone.apply(sel, arguments); } else { var instrument = getGlobalInstrument(); - instrument.play.apply(instrument, args); + instrument.play.apply(instrument); } }), silence: wrapraw('silence', @@ -8489,14 +8486,12 @@ var colors = [ $.turtle = function turtle(id, options) { var exportedsee = false; - if (!arguments.length) { - id = 'turtle'; - } if (arguments.length == 1 && typeof(id) == 'object' && id && !id.hasOwnProperty('length')) { options = id; id = 'turtle'; } + id = id || 'turtle'; options = options || {}; if ('turtle' in options) { id = options.turtle; @@ -8615,6 +8610,7 @@ $.turtle = function turtle(id, options) { } } } + return $('#' + id); }; $.extend($.turtle, dollar_turtle_methods); diff --git a/jquery-turtle.min.js b/jquery-turtle.min.js new file mode 100644 index 0000000..2718667 --- /dev/null +++ b/jquery-turtle.min.js @@ -0,0 +1,5 @@ +(function(a){function b(b,c){if(Ne>5)Oe.push({elem:b,qname:c});else{for(Ne+=1,a.dequeue(b,c);Oe.length>0;){var d=Oe.shift();a.dequeue(d.elem,d.qname),Vc()}Ne-=1}}function c(a,b){function c(){this.constructor=a}for(var d in b)Je.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a}function d(b){var c,d,e=b.charAt(0).toUpperCase()+b.slice(1),f=["Moz","Webkit","O","ms"],g=document.createElement("div");if(b in g.style)d=b;else for(var h=0;h<0?'"'+a+'"':"'"+a+"'":a}var c=[];for(var d in a)a.hasOwnProperty(d)&&c.push(d+":"+b(a[d])+";");return c.join(" ")}function h(a){return a}function i(a,b){var c=[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1]];return 6==a.length&&(c[0]+=a[4],c[1]+=a[5]),c}function j(a,b){var c=[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1],a[0]*b[2]+a[2]*b[3],a[1]*b[2]+a[3]*b[3]],d=6==a.length;return 6==b.length?(c.push(a[0]*b[4]+a[2]*b[5]+(d?a[4]:0)),c.push(a[1]*b[4]+a[3]*b[5]+(d?a[5]:0))):d&&(c.push(a[4]),c.push(a[5])),c}function k(a){return Math.abs(a)>1e-12}function l(a){return!(k(a[1])||k(a[2])||k(1-a[0])||k(1-a[3]))}function m(a){if(l(a))return[1,0,0,1];var b=u(a);return k(b[2])?j(o(-b[3]),j(p(1/b[1],1/b[2]),o(-b[0]))):null}function n(a){var b=m(a);if(4==a.length)return b;var c=i(b,[-a[4],-a[5]]);return b.push(c[0]),b.push(c[1]),b}function o(a){var b=Math.cos(a),c=Math.sin(a);return[b,c,-c,b]}function p(a,b){return 1==arguments.length&&(a=b),[a,0,0,b]}function q(a,b){return[a[0]+b[0],a[1]+b[1]]}function r(a,b){return[a[0]-b[0],a[1]-b[1]]}function s(a,b){return[a[0]*b,a[1]*b]}function t(a,b,c){return q(i(a,r(b,c)),c)}function u(a){var b,c,d=a[0]*a[0]+a[1]*a[1],e=a[0]*a[2]+a[1]*a[3],f=a[2]*a[2]+a[3]*a[3],g=-.5*Math.atan2(2*e,d-f),h=Math.cos(g),i=Math.sin(g),j=a[0]*h-a[2]*i,k=a[1]*h-a[3]*i,l=Math.atan2(k,j),m=Math.cos(l),n=Math.sin(l),o=(a[1]*i+a[3]*h)*m-(a[0]*i+a[2]*h)*n,p=(a[0]*h-a[2]*i)*m+(a[1]*h-a[3]*i)*n;return g<-Math.PI/4?(g+=Math.PI/2,c=p,b=o,l-=Math.PI/2):(b=p,c=o),l>Math.PI&&(l-=2*Math.PI),[l,b,c,g]}function v(a,b){var c=(b-a)/2,d=Math.cos(c),e=Math.sin(c),f=d,g=-e,h=1+f*d+g*e,i=f*e-g*d,j=i&&4/3*(Math.sqrt(2*h)-h)/i,k=f-j*g,l=g+j*f,m=k,n=-l,o=c+a,p=Math.cos(o),q=Math.sin(o);return[[k*p-l*q,k*q+l*p],[m*p-n*q,m*q+n*p],[Math.cos(b),Math.sin(b)]]}function w(a){var b=ra(a,!1);if(b)return[b.tx,b.ty];var c=x(a);return c?[c[4],c[5]]:[0,0]}function x(b){var c=Ie.getComputedStyle?Ie.getComputedStyle(b)[Pe]:a.css(b,"transform");if(!c||"none"===c)return null;var d=/^matrix\(([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+)(?:px)?,\s*([\-+.\de]+)(?:px)?\)$/.exec(c);return d?[parseFloat(d[1]),parseFloat(d[2]),parseFloat(d[3]),parseFloat(d[4]),parseFloat(d[5]),parseFloat(d[6])]:A(c)}function y(b,c){var d,e,f,g="none"===a.css(b,"display");if(g){d={position:"absolute",visibility:"hidden",display:"block"},e={};for(f in d)e[f]=b.style[f],b.style[f]=d[f]}var h=Ie.getComputedStyle?Ie.getComputedStyle(b):null;if(g)for(f in d)b.style[f]=e[f];var i=h&&h[Qe]||a.css(b,"transformOrigin");if(i&&i.indexOf("%")<0)return a.map(i.split(" "),parseFloat);if(c)return[c[0]/2,c[1]/2];var j=a(b);return[j.width()/2,j.height()/2]}function z(a){for(var b,c=[1,0,0,1];null!==a;)b=x(a),b&&!l(b)&&(c=j(b,c)),a=a.parentElement;return c.slice(0,4)}function A(b){var c=[1,0,0,1],d=[],e=[],f=/(?:^\s*|)(\w*)\s*\(([^)]*)\)\s*/g,g=b.replace(f,function(b){return d.push(b[1].toLowerCase()),e.push(a.map(b[2].split(","),function(a){var b=a.trim().toLowerCase();return{num:parseFloat(b),unit:b.replace(/^[+-.\de]*/,"")}})),""});if(g)return null;for(var h=d.length-1;h>=0;--h){var i,k,l,m=null,n=d[h],o=e[h];if("matrix"==n)o.length>=6&&(m=[o[0].num,o[1].num,o[2].num,o[3].num,o[4].num,o[5].num]);else if("rotate"==n)1==o.length&&(i=wa(o[0]),k=Math.cos(i),l=Math.sin(i),m=[k,-l,k,l]);else if("translate"==n||"translatex"==n||"translatey"==n){var p=0,q=0;if(o.length>=1){if(o[0].unit&&"px"!=o[0].unit)return null;if("translate"==n||"translatex"==n?p=o[0].num:"translatey"==n&&(q=o[0].num),"translate"==n&&o.length>=2){if(o[1].unit&&"px"!=o[1].unit)return null;q=o[1].num}m=[0,0,0,0,p,q]}}else if("scale"==n||"scalex"==n||"scaley"==n){var r=1,s=1;o.length>=1&&("scale"==n||"scalex"==n?r=o[0].num:"scaley"==n&&(s=o[0].num),"scale"==n&&o.length>=2&&(s=o[1].num),m=[r,0,0,s,0,0])}else{if("skew"!=n&&"skewx"!=n&&"skewy"!=n)return null;var t=0,u=0;o.length>=1&&("skew"==n||"skewx"==n?t=Math.tan(wa(o[0])):"skewy"==n&&(u=Math.tan(wa(o[0]))),"skew"==n&&o.length>=2&&(u=Math.tan(wa(o[0]))),m=[1,u,t,1,0,0])}c=j(c,m)}return c}function B(a,b,c){if(0>=c)return a;var d=b.pageX-a.pageX,e=b.pageY-a.pageY,f=d*d+e*e;if(c*c>=f)return b;var g=c/Math.sqrt(f);return{pageX:a.pageX+g*d,pageY:a.pageY+g*e}}function C(a,b,c){if(0>=c)b=a;else if(180>c){var d=xa(b-a);d>c?b=a+c:-c>d&&(b=a-c)}return xa(b)}function D(a,b,c,d){return{pageX:Math.floor(a+c/2),pageY:Math.floor(b+d/2)}}function E(a,b,c,d){var e=a+c,f=b+d;return[{pageX:a,pageY:b},{pageX:a,pageY:f},{pageX:e,pageY:f},{pageX:e,pageY:b}]}function F(a){if(!/e[\-+]/.exec(a))return a;var b=a.replace(/(?:\d+(?:\.\d*)?|\.\d+)e[\-+]\d+/g,function(a){return sa(parseFloat(a))});return b}function G(b,c,d){var e=a.data(b,"turtleData");if(e&&e.quickhomeorigin&&e.down&&e.style&&!d&&b.classList&&b.classList.contains("turtle"))return e.quickhomeorigin;var f,g,h,i="none"===a.css(b,"display"),j=i?{position:"absolute",visibility:"hidden",display:"block"}:{},k=(j[Pe]=c?"matrix("+a.map(c,sa).join(", ")+", 0, 0)":"none",{});for(f in j)k[f]=b.style[f],b.style[f]=j[f];g=N(b),h=y(b,[g.width,g.height]);for(f in j)b.style[f]=F(k[f]);d&&(d.gbcr=g,d.localorigin=h);var l=q([g.left,g.top],h);return e&&e.down&&e.style&&(e.quickhomeorigin=l),l}function H(){return Ie.innerHeight||a(Ie).height()}function I(){return Ie.innerWidth||a(Ie).width()}function J(){return document.body?a(document).height():document.height}function K(){return document.body?a(document).width():document.width}function L(a){return a.offsetHeight<=0&&a.offsetWidth<=0}function M(a,b,c,d){return{left:a,top:b,right:a+c,bottom:b+d,width:c,height:d}}function N(b){return mb(b)?M(b.pageX,b.pageY,0,0):a.isWindow(b)?M(a(Ie).scrollLeft(),a(Ie).scrollTop(),I(),H()):9===b.nodeType?M(0,0,K(),J()):"getBoundingClientRect"in b?T.apply(b):M(0,0,0,0)}function O(a,b,c){var d=Math.max(0,Math.max(c.top-a.pageY,a.pageY-c.bottom)),e=Math.max(0,Math.max(c.left-a.pageX,a.pageX-c.right));return e*e+d*d>b}function P(a,b,c){var d=Math.max(c.bottom-a.pageY,a.pageY-c.top),e=Math.max(c.right-a.pageX,a.pageX-c.left);return b>e*e+d*d}function Q(a,b){return b.right=a.top&&b.bottom<=a.bottom&&b.left>=a.left&&b.right<=a.right}function S(a,b){return 4===a.length&&a[0].pageX===b.left&&a[0].pageY===b.top&&a[1].pageX===b.left&&a[1].pageY===b.bottom&&a[2].pageX===b.right&&a[2].pageY===b.bottom&&a[3].pageX===b.right&&a[3].pageY===b.top}function T(){var a=this.getBoundingClientRect();return{top:a.top+Ie.pageYOffset,bottom:a.bottom+Ie.pageYOffset,left:a.left+Ie.pageXOffset,right:a.right+Ie.pageXOffset,width:a.width,height:a.height}}function U(b,c,d,e,f){var g,h,j,k,l=z(b.parentElement),n=m(l),o=G(b,n);if(n){if(a.isNumeric(d)&&(j=w(b),g=q(i(l,j),o),h={pageX:g[0],pageY:g[1]},c=B(h,c,d)),k=i(n,r([c.pageX,c.pageY],o)),e||f){var p=Gc(b);k[0]+=e*p,k[1]-=f*p}return sa(k[0])+" "+sa(k[1])}}function V(a){var b=a.offsetParent;return b?b:document}function W(b,c){c||(c=a(V(b)).pagexy());var d=z(b.parentElement),e=m(d),f=G(b,e),g=ra(b,!0),h=e&&i(e,r([c.pageX,c.pageY],f)),j=1/Gc(b);if(e)return[(g.tx-h[0])*j,(h[1]-g.ty)*j]}function X(b,c){var d,e,f=z(b.parentElement),g=(ra(b,!0),a(V(b)).pagexy()),h=Gc(b),j=[];for(e=0;ei-f&&(d=i-f),f>d&&(d=f),e>j-g&&(e=j-g),g>e&&(e=g);var l={pageX:d,pageY:e};a.isNumeric(c)&&(l=B(k.origin(),l,c)),k.scrollLeft(l.pageX-f),k.scrollTop(l.pageY-g)}function ca(a,b,c){var d=b.pageX-a.pageX,e=b.pageY-a.pageY,f=c.pageX-a.pageX,g=c.pageY-a.pageY;return f*e-d*g}function da(a,b,c){var d=c.pageX-a.pageX,e=c.pageY-a.pageY;return d*b.pageY-b.pageX*e}function ea(a,b){if(b.length<=0)return!1;if(1==b.length)return b[0].pageX==a.pageX&&b[0].pageY==a.pageY;var c=ca(a,b[b.length-1],b[0]);if(0===c)return!0;var d=c>0;if(2==b.length)return!1;for(var e=1;e0!=d)return!1}return!0}function fa(a,b){return{pageX:a.pageX-b.pageX,pageY:a.pageY-b.pageY}}function ga(a,b,c,d){var e,f,g=fa(c,b);for(e=0;e0?1:0>a?-1:0}function ia(a){if(a.length<=2)return 0;var b=ca(a[a.length-1],a[0],a[1]);if(0!==b)return ha(b);for(var c=1;c1&&1!=ha(ca(a[a.length-2],a[a.length-1],b));)a.pop();return a.length&&d(a[a.length-1],b)||a.push(b),a}function c(a,b,c){for(var d=0;db.pageX?1:a.pageYb.pageY?1:0}a.sort(e);var f=c(a,[],b),g=c(a.reverse(),[],b);return f.concat(g.slice(1,-1))}function ma(b){if(!b)return null;if(a.isArray(b))return b;for(var c=a.map(b.trim().split(/\s+/),parseFloat),d=[],e=0;e+1180&&(b-=360),b}function wa(a){return a/180*Math.PI}function xa(a){return Math.abs(a)>180&&(a%=360,a>180?a-=360:-180>=a&&(a+=360)),a}function ya(a){return Math.abs(a)>=720&&(a=a%360+(a>0?360:-360)),a}function za(){return Re.field||Ba(),Re.field}function Aa(){return Re.surface||Ba(),Re.surface}function Ba(){var b=document.createElement("samp"),c=document.createElement("samp"),d=Math.floor(I()/2),e=Math.floor(H()/2);a(b).css({position:"absolute",display:"inline-block",top:0,left:0,width:"100%",height:"100%",font:"inherit",zIndex:-1,transformOrigin:d+"px "+e+"px",pointerEvents:"none",overflow:"hidden"}).addClass("turtlefield"),a(c).attr("id","origin").css({position:"absolute",display:"inline-block",top:e,left:d,width:"100%",height:"0",font:"inherit",transformOrigin:"0px 0px",pointerEvents:"all",turtleSpeed:1/0}).appendTo(b),Re.surface=b,Re.field=c,Ca(),Sb()}function Ca(){document.body?(null==a("html").attr("style")&&a("html").css("min-height","100%"),a(Re.surface).prependTo("body"),Rb()):a(document).ready(Ca)}function Da(a){return a.drawOnCanvas||(a.drawOnCanvas=Fa()),a.drawOnCanvas}function Ea(b){var c=a.data(b,"turtleData");return c?c.drawOnCanvas?c.drawOnCanvas:Re.canvas:null}function Fa(){if(Re.canvas)return Re.canvas;var b=Aa();return Re.canvas=document.createElement("canvas"),a(Re.canvas).css({"z-index":-1}),b.insertBefore(Re.canvas,b.firstChild),Ja(),Ha(Ja),a(Ie).resize(Ja),Re.canvas}function Ga(a,b){return Re.offscreen&&Re.offscreen.width===a&&Re.offscreen.height===b?(Re.offscreen.getContext("2d").clearRect(0,0,a,b),Re.offscreen):(Re.offscreen||(Re.offscreen=document.createElement("canvas")),Re.offscreen.width=a,Re.offscreen.height=b,Re.offscreen)}function Ha(b){var c=a("body"),d=c.width(),e=c.height(),f=function(){(c.width()!=d||c.height()!=e)&&(b(),d=c.width(),e=c.height())};Re.timer&&clearInterval(Re.timer),Re.timer=setInterval(f,250)}function Ia(){var b=a("body");return[Math.max(b.outerWidth(!0),Ie.innerWidth||a(Ie).width()),Math.max(b.outerHeight(!0),Ie.innerHeight||a(Ie).height())]}function Ja(){if(Re.canvas){var b,c=Ia(),d=c[0],e=c[1],f=Re.canvas.width,g=Re.canvas.height,h=Math.max(Math.min(2e3,Math.max(200,f)),100*Math.ceil(d/100))*Re.subpixel,i=Math.max(Math.min(2e3,Math.max(200,f)),100*Math.ceil(e/100))*Re.subpixel;a(Re.surface).css({width:d+"px",height:e+"px"}),(f!=h||g!=i)&&(b=document.createElement("canvas"),b.width=Math.min(f,h),b.height=Math.min(g,i),b.getContext("2d").drawImage(Re.canvas,0,0),Re.canvas.width=h,Re.canvas.height=i,Re.canvas.getContext("2d").drawImage(b,0,0),a(Re.canvas).css({width:h/Re.subpixel,height:i/Re.subpixel}))}}function Ka(a,b){if(!a)return null;if(a&&"function"==typeof a&&(a.helpname||a.name)&&(a=a.helpname||a.name),a=String(a),a.trim&&(a=a.trim()),!a||"none"===a)return null;if("path"===a||"fill"===a)return{savePath:!0};var c=!1;/^erase\b/.test(a)&&(a=a.replace(/^erase\b/,"white; globalCompositeOperation:destination-out"),c=!0);var d=f(a,b);return c&&(d.eraseMode=!0),d}function La(a){return a?g(a):"none"}function Ma(a){return"down"==a||a===!0?!0:"up"==a||a===!1?!1:He}function Na(a){return a?"down":"up"}function Oa(b){var c=a.data(b,"turtleData");return c||(c=a.data(b,"turtleData",{style:null,corners:[[]],path:[[]],down:!1,speed:"turtle",easing:"swing",turningRadius:0,drawOnCanvas:null,quickpagexy:null,quickhomeorigin:null,oldscale:1,instrument:null,stream:null})),c}function Pa(b){var c=a.data(b,"turtleData");return c?c.turningRadius:0}function Qa(){return{get:function(a,b,c){return sa(Pa(a))+"px"},set:function(a,b){var c=parseFloat(b);if(!isNaN(c)&&(Oa(a).turningRadius=c,a.style.turtleTurningRadius=""+sa(c)+"px",0===c)){var d=ra(a,!1);d&&(d.rot>180||d.rot<=-180)&&(d.rot=xa(d.rot),a.style[Pe]=ta(d))}}}}function Ra(){return{get:function(a,b,c){return La(Oa(a).style)},set:function(a,b){var c=Ka(b,"strokeStyle"),d=Oa(a);d.style&&(d.style=null,bb(a,d,!0)),d.style=c,a.style.turtlePenStyle=La(c),bb(a,d,!0)}}}function Sa(){return{get:function(a,b,c){return Na(Oa(a).down)},set:function(a,b){var c=Ma(b);if(c!==He){var d=Oa(a);c!=d.down&&(d.down=c,d.quickpagexy=null,d.quickhomeorigin=null,a.style.turtlePenDown=Na(c),bb(a,d,!0))}}}}function Ta(a,b){return 0===Math.round(a.pageX-b.pageX)&&0===Math.round(a.pageY-b.pageY)}function Ua(a,b){return 0===Math.round(1e3*(a.pageX-b.pageX))&&0===Math.round(1e3*(a.pageY-b.pageY))}function Va(a,b){return Ta(a,b)&&0===Math.round(a.pageX-b.pageX1)&&0===Math.round(a.pageY-b.pageY1)&&0===Math.round(b.pageX2-b.pageX)&&0===Math.round(b.pageY2-b.pageY)}function Wa(a){var b=1e3*a,c=Math.round(b);return Math.abs(c-b)1){h=b[j],f=h.length>2&&Ta(h[0],h[h.length-1])&&!Ta(h[0],h[Math.floor(h.length/2)]),g=f&&!("pageX2"in h[h.length-1]);var k=h[0].pageX,l=h[0].pageY;i.moveTo(k,l);for(var m=1;m1&&(d.length=1),d[0].length&&(d[0].length=0),void(c&&(e?f.length&&f[0].length&&(1==f[0].length?f[0].length=0:f.unshift([])):(f.length>1&&(f.length=1),f[0].length&&(f[0].length=0))));if(c||!e.savePath){var g=Y(a);if(c&&(g.corner=!0,ab(f[0],g)),!e.savePath){ab(d[0],g);var h=Kc(a);$a(Da(b),b.path,e,h,2)}}}}function cb(b,c){var d=Oa(b);d.style&&(c=a.extend({},d.style,c));var e=Kc(b);$a(Da(d),d.corners,c,e,1)}function db(b){if((!b||/\bcanvas\b/.test(b))&&Re.canvas){var c=Re.canvas.getContext("2d");c.save(),c.setTransform(1,0,0,1,0,0),c.clearRect(0,0,Re.canvas.width,Re.canvas.height),c.restore()}if((!b||/\bturtles\b/.test(b))&&Re.surface){var d=a(Re.surface).find(".turtle").not(".turtlefield");pf&&(d=d.not(pf)),d.remove()}if((!b||/\blabels\b/.test(b))&&Re.surface){var d=a(Re.surface).find(".turtlelabel").not(".turtlefield");d.remove()}(!b||/\btext\b/.test(b))&&a("body").contents().not(".turtlefield").remove()}function eb(a,b){if(!a||a.length<1)return null;for(var c=1,d={left:Math.floor(a[0].pageX),top:Math.floor(a[0].pageY),right:Math.ceil(a[0].pageX),bottom:Math.ceil(a[0].pageY)};c=c[3]/2)return!0}else{var o=!c;for(m=0;m0==o)return!0}return!1}function hb(a,b,c){if(b.img)("CANVAS"==a[0].tagName||a[0].tagName==b.img.tagName)&&Kb(b.img,a[0],b.css);else if("IMG"==a[0].tagName||"CANVAS"==a[0].tagName)Hb(a[0],b.url,b.css,c),c=null;else{var d={backgroundImage:"url("+b.url+")",backgroundRepeat:"no-repeat",backgroundPosition:"center"};b.css.width&&b.css.height&&(d.backgroundSize=b.css.width+"px "+b.css.height+"px"),a.css(d)}c&&c()}function ib(b,c,d){var e,f=ra(b,!0),g=f&&wa(f.rot),h=Gc(b),i=f&&c*h,j=f&&(d||0)*h,k=-Math.cos(g)*i,l=Math.sin(g)*i,m=a.data(b,"turtleData");f&&(d&&(k+=Math.sin(g)*j,l+=Math.cos(g)*j),m&&(e=m.quickpagexy)&&(m.quickpagexy={pageX:e.pageX+l,pageY:e.pageY+k}),f.tx+=l,f.ty+=k,b.style[Pe]=ta(f),bb(b,m,!0))}function jb(b,c,d){var e,f=ra(b,!0),g=a.data(b,"turtleData");f&&(g&&(e=g.quickpagexy)&&(g.quickpagexy={pageX:e.pageX+c,pageY:e.pageY-d}),f.tx+=c,f.ty-=d,b.style[Pe]=ta(f),bb(b,g,!0))}function kb(a,b){var c=ra(a,!0);c&&(c.rot+=b,a.style[Pe]=ta(c))}function lb(a,b,c){var d=ra(a,!0);if(d){var e=Gc(a),f=wa(d.rot),g=b*e,h=(c||0)*e,i=-Math.cos(f)*g,j=Math.sin(f)*g;return h&&(i+=Math.sin(f)*h,j+=Math.cos(f)*h),sa(d.tx+j)+" "+sa(d.ty+i)}}function mb(b){return b&&a.isNumeric(b.pageX)&&a.isNumeric(b.pageY)}function nb(){return{get:function(a,b,c){return Oa(a).speed},set:function(b,c){(a.isNumeric(c)&&!(0>=c)||c in a.fx.speeds||""+c=="Infinity")&&(Oa(b).speed=""+c)}}}function ob(){return{get:function(a,b,c){return Oa(a).easing},set:function(b,c){c in a.easing&&(Oa(b).easing=c)}}}function pb(b,c){var d=a.data(b,"turtleData");return c=c||Df,d?a.isNumeric(d.speed)||"Infinity"==d.speed?1e3/d.speed:"turtle"==d.speed&&c?0:d.speed:c?0:"turtle"}function qb(b){var c=a.data(b,"turtleData");return c?c.easing:null}function rb(){return{get:function(a,b,c){var d=ra(a,b),e=y(a);if(d){var f=wa(d.rot),g=Math.cos(f),h=Math.sin(f),i=Gc(a);return sa(((d.tx+e[0])*h-(d.ty+e[1])*g)/i)+"px"}},set:function(b,c){var d,e=ra(b,!0)||{tx:0,ty:0,rot:0,sx:1,sy:1,twi:0},f=y(b),g=Gc(b),h=parseFloat(c)*g,i=wa(e.rot),j=Math.cos(i),k=Math.sin(i),l=(e.tx+f[0])*j+(e.ty+f[1])*k,m=l*j+h*k-f[0],n=l*k-h*j-f[1],o=a.data(b,"turtleData");o&&(d=o.quickpagexy)&&(o.quickpagexy={pageX:d.pageX+(m-e.tx),pageY:d.pageY+(n-e.ty)}),e.tx=m,e.ty=n,b.style[Pe]=ta(e),bb(b,o)}}}function sb(b,c,d,e){return{get:function(a,c,e){var f=ra(a,c);return f?f[b]+d:void 0},set:function(d,f){var g,h=ra(d,!0)||{tx:0,ty:0,rot:0,sx:1,sy:1,twi:0},i={displace:e},j=a.data(d,"turtleData"),k=h.tx,l=h.ty;h[b]=c(f,d,h,i),d.style[Pe]=ta(h),i.displace?(j&&(g=j.quickpagexy)&&(j.quickpagexy={pageX:g.pageX+(h.tx-k),pageY:g.pageY+(h.ty-l)}),bb(d,j)):Z(d)}}}function tb(a,b,c){var d=ya(b-a),e=d>0?c:-c,f=wa(a),g=[Math.cos(f)*e,Math.sin(f)*e],h=wa(b);return{delta:d,sradius:e,dc:g,dx:g[0]-Math.cos(h)*e,dy:g[1]-Math.sin(h)*e}}function ub(a,b,c,d,e,f){var g,h,j,k,l,m,n,o,p,r,t,u=tb(c,d,e),w=u.sradius,x=u.dc;for(n=1,o=u.delta,p=Math.abs(u.delta),p>45&&(n=Math.ceil(p/45),o=u.delta/n),r=[];--n>=0;)g=0===n?d:c+o,h=wa(c+180),j=wa(g+180),r.push.apply(r,v(h,j)),c=g;for(t=[],k=0;k2||(j.length>=1&&(i[c]=parseFloat(j[0])),j.length>=2?i[d]=parseFloat(j[1]):e?i[d]=0:i[d]=i[c],b.style[Pe]=ta(i),e?(k&&(h=k.quickpagexy)&&(k.quickpagexy={pageX:h.pageX+(i.tx-l),pageY:h.pageY+(i.ty-m)}),bb(b,k)):Z(b))}}}function Ab(a){return Se.href=a,Se}function Bb(a){return Ab(a).href}function Cb(a){return/(?:^|\.)pencil(?:code)?\./i.test(a)}function Db(a){var b=Ab(null==a?"":a).hostname,c=/^(\w+)\.pencil(?:code)?\./i.exec(b);return c?c[1]:null}function Eb(a,b){var c=Ab(null==a?"":a),d=c.href;return Cb(c.hostname)?/^\/(?:edit|home|code|load|save)(?:\/|$)/.test(c.pathname)&&(d=c.protocol+"//"+c.host+"/"+b+"/"+c.pathname.replace(/\/[^\/]*(?:\/|$)/,"")+c.search+c.hash):Cb(Ie.location.hostname)&&(d="/proxy/"+d),d}function Fb(a){return/\//.test(a)?a:(a="/img/"+a,Cb(Ie.location.hostname)?a:"//pencilcode.net"+a)}function Gb(){if(!document.cookie)return null;for(var a=document.cookie.split(/;\s*/),b=0;b0&&b.height>0)try{e=c.getContext("2d"),e.clearRect(0,0,b.width,b.height),e.drawImage(b,0,0)}catch(i){}}else c.src=b.src;d&&g.css(d);var j=y(c);if(b&&!d.turtleHull)try{var k=De(b);Ge(k,parseFloat(g.css("width"))/b.width,parseFloat(g.css("height"))/b.height,-j[0],-j[1]),g.css("turtleHull",k)}catch(i){}Lb(c,f,j)}function Lb(b,c,d){var e=a(b);if(e.hasClass("turtle")&&(d[0]!=c[0]||d[1]!=c[1]))if("absolute"==e.css("position")&&/px$/.test(e.css("left"))&&/px$/.test(e.css("top")))e.css("left",parseFloat(e.css("left"))+c[0]-d[0]),e.css("top",parseFloat(e.css("top"))+c[1]-d[1]);else{var f=ra(b,!0);f.tx+=c[0]-d[0],f.ty+=c[1]-d[1],b.style[Pe]=ta(f)}}function Mb(b,c,d,e,f){var g,h,i,j,k;return e===He&&f===He?(g=a(d),g.length?(h=g[0],i=N(h),S(_(h),i)?b.filter(function(){var b=N(this);return c===(R(i,b)||!Q(i,b)&&a(this).inside(h))}):b.filter(function(){return c===a(this).inside(h)})):[]):(j=a.isNumeric(e)&&a.isNumeric(f)?[e,f]:e,a.isArray(j)&&(j=X(b[0]||document.body,[j])[0]),"touch"===d?mb(j)?b.filter(function(){return c===a(this).touches(j)}):(g=a(j),i=N(g[0]),S(_(g[0]),i)?b.filter(function(){var a=N(this);return c===(!Q(i,a)&&(R(i,a)||g.touches(this)))}):b.filter(function(){return c===g.touches(this)})):(k=d*d,b.filter(function(){var a=N(this);if(O(j,k,a))return!c;if(P(j,k,a))return c;var b=Y(this),d=j.pageX-b.pageX,e=j.pageY-b.pageY;return c===k>=d*d+e*e})))}function Nb(){if(!Ze){Ze=!0;try{Ie.parent.document.activeElement.blur()}catch(a){}Ie.focus()}}function Ob(b,c,d,e,f){for(var g=e.split(/\s+/),h=0;h<0?void 0:c.apply(this,arguments)};c.guid&&(d.guid=c.guid),a.handler=d}}function Wb(){Ob(a.event.fixHooks,"filter",a.event.keyHooks,"keydown keyup",Tb),Ob(a.event.fixHooks,"filter",a.event.keyHooks,"keypress",Ub),Ob(a.event.special,"add",{},"keydown keyup keypress",Vb)}function Xb(){return{get:function(a,b,c){return g(Zb(a).getTimbre())},set:function(a,b){Zb(a).setTimbre(f(b,"wave"))}}}function Yb(){return{get:function(a,b,c){return Zb(a).getVolume()},set:function(a,b){Zb(a).setVolume(parseFloat(b))}}}function Zb(b){var c=Oa(b);if(c.instrument)return c.instrument;c.instrument=new ef("piano");var d=a(b);return c.instrument.on("noteon",function(b){var c=a.Event("noteon");c.midi=b.midi,d.trigger(c)}),c.instrument.on("noteoff",function(b){var c=a.Event("noteoff");c.midi=b.midi,d.trigger(c)}),c.instrument}function $b(){return af||(af=new ef),af}function _b(){return!(!Ie.AudioContext&&!Ie.webkitAudioContext)}function ac(){if(ac.audioTop)return ac.audioTop;if(!_b())return null;var a=new(Ie.AudioContext||Ie.webkitAudioContext);return ac.audioTop={ac:a,wavetable:jc(a),out:null,currentStart:null},bc(),ac.audioTop}function bc(){if(ac.audioTop){var a=ac.audioTop;a.out&&(a.out.disconnect(),a.out=null,a.currentStart=null);try{var b=a.ac.createDynamicsCompressor();b.ratio=16,b.attack=5e-4,b.connect(a.ac.destination),a.out=b}catch(c){ac.audioTop=null}}}function cc(){var a=ac();return null!=a.currentStart?a.currentStart:(a.currentStart=Math.max(.25,a.ac.currentTime),setTimeout(function(){a.currentStart=null},0),a.currentStart)}function dc(a){return 440*Math.pow(2,(a-69)/12)}function ec(a){return Math.round(69+12*Math.log(a/440)/Math.LN2)}function fc(a){var b=/^(\^+|_+|=|)([A-Ga-g])([,']*)$/.exec(a);if(!b)return null;var c=b[3].replace(/,/g,"").length-b[3].replace(/'/g,"").length,d=bf[b[2]]+cf[b[1].charAt(0)]*b[1].length+12*c;return d+60}function gc(a){var b=(a-72)%12;(a>60||0!=b)&&(b+=12);for(var c=Math.round((a-b-60)/12),d=df[b];0!=c;)d+=c>0?"'":",",c+=c>0?-1:1;return d}function hc(a){return dc(fc(a))}function ic(a){function b(a,b){switch(a){case"V":A!==z&&c(b.split(" ")[0]);break;case"M":f(b,A);break;case"L":g(b,A);break;case"Q":h(b,A)}A.hasOwnProperty(a)?A[a]+="\n"+b:A[a]=b,"K"==a&&(B=k(b),A===z&&c(d()))}function c(a){a=a||"",(a||A===z)&&(z.voice||(z.voice={}),z.voice.hasOwnProperty(a)?(A=z.voice[a],C=A.accent):(A={id:a,accent:{slurred:0}},z.voice[a]=A,C=A.accent))}function d(){return z.V?z.V.split(/\s+/)[0]:""}function e(a){var e,f=a.match(gf),g=null,h=0,i=0,j=null;if(!f)return null;for(;h/.test(f[h]))i=f[h++].length;else if(/^\(\d+(?::\d+)*/.test(f[h]))j=o(f[h++]);else if(/^[!+].*[!+]$/.test(f[h]))p(f[h++],C);else if(/^.?".*"$/.test(f[h]))h++;else if(/^[()]$/.test(f[h]))"("==f[h++]?C.slurred+=1:(C.slurred-=1,C.slurred<=0&&(C.slurred=0,A.stems&&A.stems.length>=1&&m(A.stems[A.stems.length-1],!1)));else if(/\|/.test(f[h])){for(e in C)1==e.length&&delete C[e];h++}else g=q(f,h,B,C),null!==g?(j&&(n(g.stem,j.time),j.count-=1,j.count||(j=null)),i&&A.stems&&A.stems.length&&(e=i>0?(1-Math.pow(.5,i))*g.stem.time:(Math.pow(.5,-i)-1)*A.stems[A.stems.length-1].time,l(A.stems[A.stems.length-1],e),l(g.stem,-e)),i=0,C.slurred&&m(g.stem,!0),A===z&&c(d()),"stems"in A||(A.stems=[]),A.stems.push(g.stem),h=g.index):h++}function f(a,b){var c=/^C/.test(a)?1:t(a);c&&(b.unitnote||(.75>c?b.unitnote=1/16:b.unitnote=1/8))}function g(a,b){var c=t(a);c&&(b.unitnote=c)}function h(a,b){var c,d=a.split(/\s+|=/),e=null,f=null;for(c=0;c=0||/^[1-4]$/.test(d[c])?e=e||t(d[c]):f=f||Number(d[c]);e&&(b.unitbeat=e),f&&(b.tempo=f)}function i(a){var b,c,d,e,f,g={};for(c=0;c0)for(b=0;a>b&&7>b;++b)d[c.charAt(b)]="^";else for(b=0;b>a&&b>-7;--b)d[c.charAt(6+b)]="_";return d}function k(a){if(!a)return{};var b,c={"c#":7,"f#":6,b:5,e:4,a:3,d:2,g:1,c:0,f:-1,bb:-2,eb:-3,ab:-4,db:-5,gb:-6,cb:-7,"a#m":7,"d#m":6,"g#m":5,"c#m":4,"f#m":3,bm:2,em:1,am:0,dm:-1,gm:-2,cm:-3,fm:-4,bbm:-5,ebm:-6,abm:-7,"g#mix":7,"c#mix":6,"f#mix":5,bmix:4,emix:3,amix:2,dmix:1,gmix:0,cmix:-1,fmix:-2,bbmix:-3,ebmix:-4,abmix:-5,dbmix:-6,gbmix:-7,"d#dor":7,"g#dor":6,"c#dor":5,"f#dor":4,bdor:3,edor:2,ador:1,ddor:0,gdor:-1,cdor:-2,fdor:-3,bbdor:-4,ebdor:-5,abdor:-6,dbdor:-7,"e#phr":7,"a#phr":6,"d#phr":5,"g#phr":4,"c#phr":3,"f#phr":2,bphr:1,ephr:0,aphr:-1,dphr:-2,gphr:-3,cphr:-4,fphr:-5,bbphr:-6,ebphr:-7,"f#lyd":7,blyd:6,elyd:5,alyd:4,dlyd:3,glyd:2,clyd:1,flyd:0,bblyd:-1,eblyd:-2,ablyd:-3,dblyd:-4,gblyd:-5,cblyd:-6,fblyd:-7,"b#loc":7,"e#loc":6,"a#loc":5,"d#loc":4,"g#loc":3,"c#loc":2,"f#loc":1,bloc:0,eloc:-1,aloc:-2,dloc:-3,gloc:-4,cloc:-5,floc:-6,bbloc:-7},d=a.replace(/\s+/g,"").toLowerCase().substr(0,5),e=d.match(/maj|min|mix|dor|phr|lyd|loc|m/);b=e?"maj"==e?d.substr(0,e.index):"min"==e?d.substr(0,e.index+1):d.substr(0,e.index+e[0].length):/^[a-g][#b]?/.exec(d)||"";var f=j(c[b]),g=a.substr(b.length).match(/(_+|=|\^+)[a-g]/gi);if(g)for(var h=0;h<2))switch(a=a.substring(1,a.length-1)){case"pppp":case"ppp":b.dynamics=.2;break;case"pp":b.dynamics=.4;break;case"p":b.dynamics=.6;break;case"mp":b.dynamics=.8;break;case"mf":b.dynamics=1;break;case"f":b.dynamics=1.2;break;case"ff":b.dynamics=1.4;break;case"fff":case"ffff":b.dynamics=1.5}}function q(a,b,c,d){var e,f,g,h,i=[],j="",k=!1,l=null,m=1/0;if(bf&&(j=e,m=f),b0&&"="==a.charAt(0)?a.substr(1):a}function s(a,b,c){var d,e=/^(\^+|_+|=|)([A-Ga-g])(.*)$/.exec(a);return e?(d=e[2].toUpperCase(),e[1].length>0?(c[d]=e[1],r(a)):r(c.hasOwnProperty(d)?c[d]+e[2]+e[3]:b.hasOwnProperty(d)?b[d]+e[2]+e[3]:a)):a}function t(a){var b,c,d,e=/^(\d*)(?:\/(\d*))?$|^(\/+)$/.exec(a),f=0;if(e){if(e[3])return Math.pow(.5,e[3].length);if(c=e[2]?parseFloat(e[2]):/\//.test(a)?2:1,d=0,b=e[1]?parseFloat(e[1]):1,e[2])for(;d+1c;)d+=1,f=parseFloat(e[1].substring(0,d)),b=parseFloat(e[1].substring(d));return f+b/c}}var u,v,w,x,y=a.split("\n"),z={},A=z,B={},C={slurred:0};for(u=0;uc;++c)e[c]=b.real[c],f[c]=b.imag[c];try{return a.createPeriodicWave(e,f)}catch(g){}try{return a.createWaveTable(e,f)}catch(g){}return null}function d(a,b,c){var d,e,f={real:[],imag:[]},g=a.real.length;for(d=0;g>d;++d)e=Math.log(b[Math.min(d,b.length-1)]),f.real.push(a.real[d]*Math.exp(c*e)),f.imag.push(a.imag[d]*Math.exp(c*e));return f}var e,f,g,h,i,j,k={};for(e in b)if(f=b[e],j=c(f)){if(i={wave:j},f.mult)for(h=b[e].freq,i.freq={},g=0;gc;++c)b[c]&&(b[c].constructor===a?e.push.apply(e,b[c].toArray()):a.isArray(b[c])?e.push.apply(e,b[c]):e.push(b[c]));return{elts:a.unique(e),completion:d}}function lc(){function b(){var a,b=g;for(g=null,f&&f(),a=0;a1)for(c=0;c")}function oc(b){var c,d,e=a.extend({},sf,hf,tf);if(b&&!a.isArray(b.helptext)&&b in e&&(b=e[b]),b&&a.isArray(b.helptext)&&b.helptext.length){for(d=0;d/g,'<$1 style="border:1px solid black;text-decoration:none;word-break:keep-all;white-space:nowrap">').replace(/<(mark)>/g,'<$1 style="border:1px solid blue;color:blue;text-decoration:none;word-break:keep-all;white-space:nowrap;cursor:pointer;" onclick="see.enter($(this).text())">'))}return vf}if("number"==typeof b)return nc("Equal to the number "+b+"."),vf;if("boolean"==typeof b)return nc("Equal to the boolean value "+b+"."),vf;if(null===b)return nc("The special null value represents the absence of a value."),vf;if(b===He)return nc("This is an unassigned value."),vf;if(b===Ie)return nc("The global window object represents the browser window."),vf;if(b===document)return nc("The HTML document running the program."),vf;if(b===jQuery)return nc('The jQuery function. Read about it at
jquery.com.'),vf;if(b&&b!=oc)return nc("No help available for "+b),vf;c=[];for(var g in e)!e[g].helptext||!e[g].helptext.length||g in Ie&&"function"!=typeof Ie[g]||c.push(g);return c.sort(function(a,b){return a.length!=b.length?a.length-b.length:b>a?-1:a>b?1:0}),nc("help available for: "+c.map(function(a){return''+a+""}).join(" ")),vf}function pc(a){return 1==a.length&&qc(a[0])&&a[0]}function qc(b){var c;return b&&0==a.queue(b).length&&(!b.parentElement||!b.parentElement.style.transform)&&(0===(c=pb(b))||0===a.fx.speeds[c])}function rc(b,c){var d;if(null==c){if(qc(b))return;d=pb(b)}else d=1e3*c;var e=a(b);d>0&&e.delay(d)}function sc(a,b){if(b=b||0,a.length<=b)return null;var c=a[a.length-1];return"function"!=typeof c||c.helpname?null:c}function tc(b,c,d,e){function f(d,e){if(null!=d){var f=b&&b[d];e&&f&&bb(f,a.data(f,"turtleData"),!0),Hf.reportEvent("resolve",[c,m,j,d,f])}0==--k&&h&&(l?(Me+=1,setTimeout(function(){Me-=1,h()},0)):h())}function g(a){null!=a&&Hf.reportEvent("appear",[c,m,j,a,b&&b[a],i])}var h=sc(d,e),i=h?Array.prototype.slice.call(d,0,d.length-1):d,j=b?b.length||0:0,k=j+1,l=!0,m=Hf.nextId();return Hf.reportEvent("enter",[c,m,j,i]),{name:c,args:i,appear:g,resolve:f,resolver:function(a,b){return function(){f(a,b)}},exit:function(){Hf.reportEvent("exit",[c,m,j,i]),f(null),l=!1}}}function uc(b,c,d,e){var f=function(){if(Vc(b),Le)throw new Error(b+" interrupted");var d,f=tc(this,b,arguments,c),g=[f].concat(a.makeArray(f.args));try{d=e.apply(this,g)}finally{f.exit()}return d};return zc(b,d,f)}function vc(a,b,c){var d=function(){if(Vc(a),Le)throw new Error(a+" interrupted");return Wc(a,this),c.apply(this,arguments)};return zc(a,b,d)}function wc(b,c,d,e){var f=function(){if(Vc(b),Le)throw new Error(b+" interrupted");var c=null,f=0,g=pf;if(e&&(c=e.apply(null,arguments),f=arguments.length,g=Yc()),g){var h=a(pf).eq(0),i=arguments,j=tc(h,b,arguments,f);h.plan(function(a,b){j.appear(a),d.apply(c,i),this.plan(j.resolver(a))}),j.exit()}else j=tc(null,b,arguments,f),d.apply(c,arguments),j.exit();return c?(c.result&&c.result.constructor===jQuery&&pf&&lc(pf,c.result),c.result):void 0};return zc(b,c,f)}function xc(b,c){return zc(b,c,function(c,d){var e=/^key/.test(b),f=/^mouse|click$/.test(b),g=f?"input,button":e?"textarea,input:not([type]),input[type=text],input[type=password]":null;e&&Nb(),null==d&&"function"==typeof c&&(d=c,c=null),a(Ie).on(b+".turtleevent",null,c,g?function(b){return Le||a(b.target).closest(g).length?void 0:d.apply(this,arguments)}:d)})}function yc(){var b=a._data(Ie,"events");if(!b)return!1;for(var c in b)for(var d=b[c],e=0;eb?-c:c,l=null;i.style&&i.down&&(l=function(){var a=Y(h),c=ra(h,!0),d=z(h.parentElement);return function(){ub(i.corners[0],a,c.rot,c.rot+(e?-b:b),k*(i.oldscale?c.sy:1),d)}}()),i.turningRadius=k,this.animate({turtleRotation:g+sa(b)+"deg"},pb(h,f),qb(h)),this.plan(function(){l&&l(),i.turningRadius=j,a.resolve(d,!0)})}),this)}function Bc(a,b){null==b&&(b=100),"bk"===a.name&&(b=-b);var c,d=Df;return(c=pc(this))?(a.appear(0),ib(c,b,0),a.resolve(0,!0),this):(this.plan(function(c,e){a.appear(c),this.animate({turtleForward:"+="+sa(b||0)+"px"},pb(e,d),qb(e),a.resolver(c,!0))}),this)}function Cc(b,c,d){a.isArray(c)&&(d=c[1],c=c[0]),d||(d=0),c||(c=0);var e=Df;return this.plan(function(a,f){b&&b.appear(a),this.animate({turtlePosition:lb(f,d,c)},pb(f,e),qb(f),b&&b.resolver(a,!0))}),this}function Dc(b,c,d){a.isArray(c)&&(d=c[1],c=c[0]),d||(d=0),c||(c=0);var e,f=Df;return(e=pc(this))?(b&&b.appear(0),jb(e,c,d),b&&b.resolve(0),this):(this.plan(function(a,e){b&&b.appear(a);var g=w(e);this.animate({turtlePosition:sa(g[0]+c)+" "+sa(g[1]-d)},pb(e,f),qb(e),b&&b.resolver(a,!0))}),this)}function Ec(b,c,d){var e=c,f=0,g=0,h=null,i=Df;return a.isNumeric(e)&&a.isNumeric(d)?(f=parseFloat(e),g=parseFloat(d),e=null,h=null):a.isArray(e)?(f=e[0],g=e[1],e=null,h=d):a.isNumeric(d)&&(h=d),this.plan(function(c,d){var j=e;if(null===j&&(j=a(V(d)).pagexy()),j&&!mb(j))try{j=a(j).pagexy()}catch(k){return}return j&&mb(j)?a.isWindow(d)?(b&&b.appear(c),ba(j,h),void(b&&b.resolve(c))):void(9!==d.nodeType&&(b&&b.appear(c),this.animate({turtlePosition:U(d,j,h,f,g)},pb(d,i),qb(d),b&&b.resolver(c,!0)))):void 0}),this}function Fc(a){return function(b,c,d){return this.plan(function(e,f){b.appear(e);var g=this.css("turtlePenDown");this.css({turtlePenDown:"up"}),a.call(this,null,c,d),this.plan(function(){this.css({turtlePenDown:g}),b.resolve(e,!0)})}),this}}function Gc(b){var c=a.data(b,"turtleData");return c&&null!=c.oldscale?c.oldscale:1}function Hc(a,b,c){Jc.call(this,!0,a,b,c)}function Ic(a,b,c){Jc.call(this,!1,a,b,c)}function Jc(b,c,d,e){e===He&&(e=d),d&&e||(d=e=1);var f=Df;return this.plan(function(g,h){if(b&&(Oa(h).oldscale*=e),c.appear(g),a.isWindow(h)||9===h.nodeType)return void c.resolve(g);var i=a.map(a.css(h,"turtleScale").split(" "),parseFloat);1===i.length&&i.push(i[0]),i[0]*=d,i[1]*=e,this.animate({turtleScale:a.map(i,sa).join(" ")},pb(h,f),qb(h),c.resolver(g))}),this}function Kc(a,b){var c=z(a.parentElement),d=l(c),e=d?1:u(c)[1];return e*Gc(a)}function Lc(b){var c=Df;return function(d,e,f){if(a.isNumeric(e)){var g=e;e=f,f=g}return null==f&&(f=8.8),this.plan(function(g,h){var i=Oa(h),j=i.style;e||(e=j&&(j.fillStyle||j.strokeStyle)||"black"),d.appear(g);var k=this.pagexy(),l=ra(h,!0),m=Ka(e,"fillStyle"),n=Da(i),o=Kc(h),p=f*o,q=Math.max(0,p-2),r=p+(m.eraseMode?2:0),s=/rgba|hsla/.test(m.fillStyle);null==m.lineWidth&&j&&j.lineWidth&&(m.lineWidth=j.lineWidth),pc(this)?(b(n,k,r,l.rot,m,!0),d.resolve(g)):this.queue(function(e){a({radius:0}).animate({radius:q},{duration:pb(h,c),step:function(){s||b(n,k,this.radius,l.rot,m,!1)},complete:function(){b(n,k,r,l.rot,m,!0),d.resolve(g),e()}})})}),this}}function Mc(a,b,c,d,e){var f=a.getContext("2d");f.save(),Xa(f,e),c===1/0?(f.setTransform(1,0,0,1,0,0),f.fillRect(0,0,a.width,a.height)):(Za(f,a),f.beginPath(),f.arc(b.pageX,b.pageY,c/2,0,2*Math.PI,!1),f.closePath(),f.fill(),e.strokeStyle&&f.stroke()),f.restore()}function Nc(a,b,c,d,e){var f=a.getContext("2d");if(f.save(),Xa(f,e),c===1/0)f.setTransform(1,0,0,1,0,0),f.fillRect(0,0,a.width,a.height);else{var g=Math.sin((d+45)/180*Math.PI),h=Math.cos((d+45)/180*Math.PI),i=c*h/Math.SQRT2,j=c*g/Math.SQRT2;Za(f,a),f.beginPath(),f.moveTo(b.pageX-i,b.pageY-j),f.lineTo(b.pageX-j,b.pageY+i),f.lineTo(b.pageX+i,b.pageY+j),f.lineTo(b.pageX+j,b.pageY-i),f.closePath(),f.fill(),e.strokeStyle&&f.stroke()}f.restore()}function Oc(a,b,c,d,e,f){var g=a.getContext("2d");if(g.save(),Xa(g,e),!e.strokeStyle&&e.fillStyle&&(g.strokeStyle=e.fillStyle),c!==1/0){var h=Math.sin(d/180*Math.PI),i=-Math.cos(d/180*Math.PI),j=e.lineWidth||1.62,k=b.pageX+c*h,l=b.pageY+c*i,m=Pc(j,k,l,h,i),n=c-m.hs,o=n*h,p=n*i;Za(g,a),n>0&&(g.beginPath(),g.moveTo(b.pageX,b.pageY),g.lineTo(b.pageX+o,b.pageY+p),g.stroke()),f&&Qc(g,m)}g.restore()}function Pc(a,b,c,d,e){var f=Math.max(1.25*a,a+2),g=2*f,h=g-f/2;return{hs:h,x1:b,y1:c,xm:b-d*h,ym:c-e*h,x2:b-e*f-d*g,y2:c+d*f-e*g,x3:b+e*f-d*g,y3:c-d*f-e*g}}function Qc(a,b){a.beginPath(),a.moveTo(b.x2,b.y2),a.lineTo(b.x1,b.y1),a.lineTo(b.x3,b.y3),a.quadraticCurveTo(b.xm,b.ym,b.x2,b.y2),a.closePath(),a.fill()}function Rc(a,b,c,d,e,f){var g=e-c,h=f-d,i=Math.sqrt(g*g+h*h),j=g/i,k=h/i,l=Pc(b,e,f,j,k);i>l.hs&&(a.beginPath(),a.moveTo(c,d),a.lineTo(l.xm,l.ym),a.lineWidth=b,a.stroke()),Qc(a,l)}function Sc(a,b){function c(){d&&clearInterval(d),b&&b()}var d=null;if(!Ie.speechSynthesis)return console.log("No speech synthesis: "+a),void c();try{var e=new Ie.SpeechSynthesisUtterance(a);e.addEventListener("end",c),e.addEventListener("error",c),e.lang=navigator.language||"en-GB",Ie.speechSynthesis.speak(e),d=setInterval(function(){Ie.speechSynthesis.pending||Ie.speechSynthesis.speaking||c()},250)}catch(f){Ie.console&&Ie.console.log(f),c()}}function Tc(){a.each(["toggle","show","hide"],function(b,c){var d=a.fn[c];a.fn[c]=function(b,c,e){var f=arguments;!f.length&&this.hasClass("turtle")&&(this.length>1||!a._data(this[0],"fxshow"))&&(f=[0]),d.apply(this,f)}})}function Uc(c,d){if(d||(d="fx"),"IMG"==c.tagName&&c.src&&!c.complete){var e=a.queue(c,d);0==e.length&&(a.queue(c,d,function(a){Ib(c,null,a)}),b(c,d))}}function Vc(b){if(!(a.turtle.hangtime==1/0||kf++<100)){kf=0;var c=(new Date).getTime();return mf?void(c-mf>a.turtle.hangtime&&(Sf.visible()&&Sf.html('Oops: program interrupted because it was hanging the browser. Try reducing the number of repetitions. Or try using await done defer() or tick to make an animation.'),a.turtle.interrupt("hung"))):(mf=c,clearTimeout(lf),void(lf=setTimeout(function(){clearTimeout(lf),lf=null,mf=null},0)))}}function Wc(b,c){if(!a.turtle.nowarn){var d,e=!0;for(d=0;e&&d=100&&(e=!1);e||(jf[b]||(jf[b]=1,Sf.visible()?Sf.html('Oops: '+b+' may not return useful results when motion is queued. Try speed Infinity or await done defer() first.'):console.warn(b+' may not return useful results when motion is queued. Try "speed Infinity" or "await done defer()".')),c.finish())}}function Xc(a,b,d){a[b]=function(){return b in jf||(Sf.html(''+b+" deprecated. Use "+d+"."),jf[b]=1),a[d].apply(this,arguments)},a[d].__super__&&c(a[b],a[d])}function Yc(){return pf&&a.queue(pf).length>0}function Zc(a){if(null===uf.pollTimer){var b=uf.sent[a],c=uf.waiting[a];c&&c.length&&b&&b.length&&(uf.pollTimer=setTimeout(function(){uf.pollTimer=null,c&&c.length&&b&&b.length&&(c.shift().apply(null,b.shift()),Zc(a))},0))}}function $c(a,b){if("function"!=typeof b&&"undefined"!=typeof b||!/^\w+\s*$/.test(a)){if("undefined"==typeof b&&/^help\s+\S+$/.test(a))return oc(/^help\s+(\S+)$/.exec(a)[1]),!0}else{if(b&&b.helptext)return oc(b),!0;if(a in tf)return oc(a),!0}return!1}function _c(a,b,c,d){return a.helptext?d.helptext=a.helptext:b in c&&(d.helptext=c[b].helptext),d.method=a,d.helpname=b,d}function ad(a,b){var c=[];for(var d in b)!b.hasOwnProperty(d)||d in Ie||(c.push(d),Ie[d]=function(b){var c=a[b],d=a;return _c(c,b,tf,function(){return c.apply(d,arguments)})}(d));return c}function bd(){pf=null;for(var a=0;a=0;i--)null===h[i]?(i--,c.moveTo(d-h[i][0],e+h[i][1])):c.lineTo(d-h[i][0],e+h[i][1])}return c.lineWidth=1.1,c.strokeStyle="rgba(255,255,255,0.75)",c.stroke(),c.beginPath(),c.arc(d,e,15.5,0,2*Math.PI,!1),c.closePath(),c.strokeStyle="rgba(0,0,0,0.4)",c.stroke(),b.toDataURL()}function gd(a){var b=Ga(40,48),c=b.getContext("2d");return c.beginPath(),c.moveTo(0,48),c.lineTo(20,0),c.lineTo(40,48),c.lineTo(20,36),c.closePath(),c.fillStyle=a,c.fill(),b.toDataURL()}function hd(a){var b=Ga(40,40),c=b.getContext("2d");return c.beginPath(),c.arc(20,20,18,-5*Math.PI/2,-Math.PI/2),c.closePath(),c.lineTo(20,20),c.stroke(),c.strokeStyle=a,c.lineWidth=4,c.stroke(),b.toDataURL()}function id(a,b){b=b||12;var c=Ga(b,b),d=c.getContext("2d"),e=b/2;return d.beginPath(),d.arc(e,e,e,0,2*Math.PI),d.closePath(),d.fillStyle=a,d.fill(),c.toDataURL()}function jd(a){function b(){e.moveTo(19.5,43),e.lineTo(20.5,43),e.lineTo(21.5,43.5),e.lineTo(25.5,36.2),e.lineTo(24,35.5),e.lineTo(23,35.5),e.lineTo(20.5,36.5),e.lineTo(19.5,36.5),e.lineTo(17,35.5),e.lineTo(16,35.5),e.lineTo(14.5,36.2),e.lineTo(18.5,43.5),e.closePath()}function c(){e.moveTo(25.5,12),e.lineTo(25.5,8),e.lineTo(14.5,8),e.lineTo(14.5,12),e.closePath()}var d=Ga(40,48),e=d.getContext("2d");return e.beginPath(),b(),e.fillStyle="#ffcb6b",e.fill(),e.beginPath(),c(),e.fillStyle="#d1ebff",e.fill(),e.beginPath(),e.moveTo(19.5,48),e.lineTo(13,36),e.lineTo(13,1),e.lineTo(14,0),e.lineTo(26,0),e.lineTo(27,1),e.lineTo(27,36),e.lineTo(20.5,48),e.closePath(),b(),e.moveTo(25.5,12),e.lineTo(25.5,8),e.lineTo(14.5,8),e.lineTo(14.5,12),e.closePath(),e.fillStyle=a,e.fill(),d.toDataURL()}function kd(a,b,c){return c||(c=1),function(d){var e=document.createElement("canvas");e.width=a,e.height=b;var f=e.getContext("2d");d||(d="rgba(128,128,128,0.125)"),"transparent"!=d&&(f.fillStyle=d,f.fillRect(0,0,a,b));var g=a/c,h=b/c,i={width:g,height:h,transformOrigin:g/2+"px + "+h/2+"px",opacity:1};return 1>c&&(i.imageRendering="pixelated"),{img:e,css:i}}}function ld(a){if(!a)return null;if(a in yf)return yf[a];var b=a.match(/^(\d+)x(\d+)(?:\/(\d+))?$/);return b?kd(parseFloat(b[1]),parseFloat(b[2]),b[3]&&parseFloat(b[3])):null}function md(a,b){var c=a.width||a.height||256,d=a.height||a.width||256,e=a.subpixel||1/(a.scale||1),f=a.color||"transparent",g=kd(c,d,e);return g(f)}function nd(b,c){if(!b)return null;if(a.isPlainObject(b))return md(b,c);if(a.isFunction(b)&&(b.helpname||b.name)&&(b=b.helpname||b.name),b.constructor===jQuery){if(!b.length)return null;b=b.get(0)}if(b.tagName)return"CANVAS"!=b.tagName&&"IMG"!=b.tagName&&"VIDEO"!=b.tagName?null:{img:b,css:{opacity:1}};var d=b.toString().trim().split(/\s+/),e=null,f=null;if(d.length&&(f=ld(d[d.length-1]),f&&d.pop()),d.length&&cd(d.join(" "))&&(e=d.join(" "),d.length=0),!f&&e&&(f=ld(c)),f)return f(e);if(/\//.test(b)||(b=Fb(b)),/\//.test(b)){var g=Ab(b).hostname;return!Cb(g)&&Cb(Ie.location.hostname)&&(b=Ie.location.protocol+"//"+Ie.location.host+"/proxy/"+Bb(b)),{url:b,css:{transformOrigin:"50% 50%",opacity:1}}}return null}function od(a){return null==a?"":String(a).replace(/[&<>"]/g,function(a){return zf[a]})}function pd(b,c,d){var e=b&&/^[a-zA-Z]\w*$/.exec(b),f=b&&/^<.*>$/.exec(b),g=nd(b,d)||null==b&&nd(d);e&&a("#"+b).length&&(e=!1);var h;g?(h=a(""),hb(h,g)):h=a(f?b:"
"+od(b)+"
"),b&&"object"==typeof b&&("id"in b&&h.attr("id",b.id),"class"in b&&h.addClass(b["class"])),h.css({position:"absolute",display:"table",top:0,left:0}),(!c||9==c.nodeType||a.isWindow(c))&&(c=za()),h.appendTo(c);var i=y(h[0]);return h.css({top:-i[1],left:-i[0]}),h.css({turtlePosition:U(h[0],a(c).pagexy(),null,0,0),turtleRotation:0,turtleScale:1}),h.addClass("turtle"),e&&(h.attr("id",b),rf&&!Ie.hasOwnProperty(b)&&(Ie[b]=h)),h}function qd(a,b){if("number"==typeof a)return a=Math.ceil(a),"number"==typeof b?(b=Math.ceil(b),Math.floor(Math.random()*(b-a)+a)):Math.floor(Math.random()*a);if("object"==typeof a&&a&&a.length&&a.slice)return a[Math.floor(Math.random()*a.length)];if("normal"==a){var c,d,e,f,g;do c=Math.random(),d=1.7156*(Math.random()-.5),e=c-.449871,f=Math.abs(d)+.386595,g=e*e+f*(.196*f-.25472*e);while(g>.27597&&(g>.27846||d*d>-4*Math.log(c)*c*c));return d/c}return"position"==a?{pageX:qd(K()+1),pageY:qd(J()+1)}:"color"==a?"hsl("+Math.floor(360*Math.random())+",100%,50%)":"gray"==a?"hsl(0,0,"+Math.floor(100*Math.random())+"%)":a===!0?Math.random()>=.5:Math.random()}function rd(b,c){c||"function"!=typeof b||(c=b,b=30);var d=null,e=Math.max(Math.floor(1e3/Math.max(1/86400,b)),0);if(Yc()){var f=a(pf);f.plan(function(){d=c})}else d=c;var g={fn:c,timer:setInterval(function(){if(d)try{Df++,Bf.push(g),d()}finally{Df--,Bf.pop(g)}},e)};return Af.push(g),g.timer}function sd(a){for(var b=[],c=0;c0?1e3/b:0)}function wd(b){b===He&&(b=""),a("[id]").each(function(c,d){Ie[b+d.id]=a("#"+d.id)}),rf=!0}function xd(b){if(b===He&&(b="last"),Ef&&a(Ie).off(a.map(of,function(a,b){return b}).join(" "),Ef),b||""===b){Ef=function(a){var c,d=[b+a.type];for((a.originalEvent||a)instanceof MouseEvent&&d.push(b+"mouse"),c=0;c=a("html").outerHeight(!0);if(b(),e){var f=a(Ie).scrollTop(),g=Math.min(d,a("html").outerHeight(!0)-a(Ie).height());g>f&&a(Ie).scrollTop(g)}}function zd(){if(!Ff.timer){Ff.timer=setTimeout(function(){Ff.timer=null},0);var b=a("body").offset(),c=b?b.top:8;Ff.bottomSeen=Math.min(a(Ie).height()+a(Ie).scrollTop(),a("body").height()+c)}return Ff.bottomSeen}function Ad(b){var c=a(Ie).scrollTop();b(),a(Ie).scrollTop(c)}function Bd(){var a=document.body.lastChild;return a&&"PRE"==a.tagName||(a=document.createElement("pre"),document.body.appendChild(a)),a}function Cd(){var a=arguments;yd(function(){for(var b=Bd(),c=0;c").css({display:"inline-block",verticalAlign:"top",textAlign:"center",height:"1.2em",width:"1.2em",maxWidth:"1.2em",overflow:"hidden"}).appendTo(Bd()),e=function(){null!=b&&d.css({background:b}),null!=c&&d.text(c)};if(pf){var f=a(pf);Ec.call(f,null,d),f.eq(0).plan(e)}else e()}function Ed(){var a=this;yd(function(){a.result.appendTo("body"),a.setup&&a.setup()})}function Fd(b,c){var d="<"+c+' style="display:table">',e="";if(b===He||null===b)return{result:a(d+"
"+e)};if(b.jquery||b instanceof Element&&(b=a(b)))return{result:b};var f=null;return b=""+b,/^\s*<.*>\s*$/.test(b)&&(f=a(b)),(null==f||1!=f.length||1!=f[0].nodeType)&&(f=a(d+b+e)),{result:f}}function Gd(b,c){function d(b){return function(d){j||m&&"change"==d.type||(j=!0,a(this).prop("checked",!0),i.find("input[type=radio]").prop("disabled",!0),c(b))}}function e(a){return function(){j||(l=a,f())}}function f(a){a||(m+=1,setTimeout(function(){m-=1},0),i.find("input").eq(l).prop("checked",!0)),i.find("input").eq(l).focus()}function g(b,c){a.isFunction(b)&&(b=(k+1).toString());var f=a.isFunction(c)||null==c?b:c,g=a('').attr("value",f).on("change click",d(c)),h=a('")}}function ke(){if(!dg&&!Ie.document.getElementById("_logcss")){var a=Ie.document.createElement("style");a.id="_logcss",a.innerHTML=(Mf?"samp._log{"+Mf+"}":"")+cg,Ie.document.head.appendChild(a),dg=!0}}function le(a,b,c){b=b||3;var d=c||[],e=Xd(a);return"Error"==e||"ErrorEvent"==e?(d.push(''),je("",a,b,d),d.push("")):Yd(e)?d.push(ae(a)):de(a,!0,100)||0>=b?d.push(fe(a,100)):je("",a,b,d),c?void 0:d.join("")}function me(b,c){switch(typeof b){case"string":return"body"==b?document.body:document.querySelector?document.querySelector(b):a?a(b)[0]:null;case"undefined":return c;case"boolean":return b?c:null;default:return b}}function ne(){var a=!1,b=me(Of,null);return b&&(a=b.scrollHeight-b.scrollTop-10<=b.clientHeight),a?function(){b.scrollTop=b.scrollHeight-b.clientHeight}:function(){}}function oe(){var a,b=me(Pf,null);if(b&&b.appendChild&&gg.length){ke();var c=Ie.document.createElement("samp");c.innerHTML=gg.join(""),gg.length=0;for(var d=ne();a=c.firstChild;)b.appendChild(a);d()}!fg&&gg.length?("auto"==Qf&&Be(),fg=setTimeout(function(){fg=null,oe()},100)):fg&&!gg.length&&(clearTimeout(fg),fg=null)}function pe(){a("#_testpanel").hide()}function qe(){a("#_testpanel").show()}function re(){return a("#_testpanel").is(":visible")}function se(b){a("#_testinput").val(b)}function te(){hg&&a("#_testlog").find("._log").not("#_testpaneltitle").remove()}function ue(a){return''}function ve(){return Ie.getSelection?Ie.getSelection().toString():document.getSelection?document.getSelection():document.selection?document.selection.createRange().text:void 0}function we(a){return''+a+""}function xe(){if(Wf){var b,c={height:Xf,history:[]};try{b=Ie.JSON.parse(Ie.localStorage[Wf])}catch(d){b=mg||{}}return b&&b.slice&&b.length?(c.history=b,c):(a.extend(c,b),c)}}function ye(a){if(Wf){var b=xe(),c=!1;if("history"in a&&a.history.length&&(!b.history.length||b.history[b.history.length-1]!==a.history[a.history.length-1])&&(b.history.push(a.history[a.history.length-1]),c=!0),"height"in a&&a.height!==b.height&&(b.height=a.height,c=!0),c)try{Ie.localStorage[Wf]=Ie.JSON.stringify(b)}catch(d){mg=b}}}function ze(){return Ie.innerHeight||a(Ie).height()}function Ae(){try{if(Tf&&Ie.console&&!Ie.console._log&&"function"==typeof Ie.console.log){var a=Ie.console._log=Ie.console.log;Ie.console.log=function(){a.apply(this,arguments),Sf.apply(this,arguments)};var b=Ie.console._debug=Ie.console.debug;Ie.console.debug=function(){b.apply(this,arguments),Sf.apply(this,arguments)}}}catch(c){}}function Be(){ig||(ig=!0,Vd(Ce))}function Ce(){if(hg)Uf&&(a("#_testpaneltitle").length?a("#_testpaneltitle").html(Uf):a("#_testlog").prepend(we(Uf))),a("#_testpanel").show();else if(!Ie.document.getElementById("_testlog")&&Ie.document.body){Ae(),ke();var b=xe(),c=Uf?we(Uf):"";b.height>ze()-50&&(b.height=Math.min(ze(),Math.max(10,ze()-50))),a("body").prepend(''+c+''+ue("blue")+''),hg=!0,oe();var d=0,e={};a("#_testinput").on("keydown",function(c){if(13==c.which){var f=a(this).val();if(a(this).val(""),!f.trim().length||b.history.length&&b.history[b.history.length-1]===f||(b.history.push(f),ye({history:[f]})),e={},d=0,Wd(''+ue("lightgray")+he(f)+""),a(this).select(),f.trim().length&&":"==f.trim()[0]){var g=f.trim().substring(1).trim();if(!g||Zf.hasOwnProperty(g)){Yf=g;var h=g?"scope "+g:"default scope";Wd('switched to '+h+"")}else Wd('no scope '+g+"");return}a.turtle.interrupt("reset");var i=!1;try{var j;try{j=Pd(Yf,f)}finally{if(lg&&lg(f,j))i=!0;else{for(var k=kg.length-1;k>=0&&j!==kg[k];--k);0>k&&Wd(le(j))}}}catch(c){i||Sf(c)}}else if(38==c.which||40==c.which){e[d]=a(this).val(),d+=38==c.which?1:-1,d=Math.max(0,Math.min(b.history.length,d));var l=e[d]||b.history[b.history.length-d];"undefined"==typeof l&&(l=""),a(this).val(l),this.selectionStart=this.selectionEnd=l.length,c.preventDefault()}}),a("#_testdrag").on("mousedown",function(c){var d,e=this,f=a("#_testpanel").height()+c.pageY,g=a("#_testdrag").height(),h=c.which;return e.setCapture&&e.setCapture(!0),d=function(c){if("blur"!=c.type&&c.which==h){var e=ze(),i=Math.max(g,Math.min(e,f-c.pageY)),j=ne();a("#_testpanel").height(i),a("#_testscroll").height(i-g),j()}("mouseup"==c.type||"blur"==c.type||"mousemove"==c.type&&c.which!=h)&&(a(Ie).off("mousemove mouseup blur",d),document.releaseCapture&&document.releaseCapture(),a("#_testpanel").height()!=b.height&&(b.height=a("#_testpanel").height(),ye({height:b.height})))},a(Ie).on("mousemove mouseup blur",d),!1}),a("#_testpanel").on("mouseup",function(b){if(!ve()){var c=a("#_testscroll").scrollTop();a("#_testinput").focus(),a("#_testscroll").scrollTop(c)}})}jg&&hg?clearTimeout(jg):hg||jg||(jg=setTimeout(Ce,100))}function De(a,b){var c=document.createElement("canvas");b||(b=0),c.width=a.width,c.height=a.height;var d=c.getContext("2d");return d.drawImage(a,0,0),Ee(c,b)}function Ee(a,b){for(var c,d,e=a.getContext("2d"),f=e.getImageData(0,0,a.width,a.height).data,g=[],h=256*b,i=1/0,j=-1,k=0;kh&&(0>d&&(c=l),d=l);(d>=0||j>=0)&&(g.push({pageX:Math.min(c,i),pageY:k}),g.push({pageX:Math.max(d,j)+1,pageY:k})),i=c,j=d}return j>=0&&(g.push({pageX:i,pageY:a.height}),g.push({pageX:j+1,pageY:a.height})),la(g)}function Fe(a,b){var c=a.getContext("2d"),d=a.width,e=a.height,f=0;if(c.save(),c.beginPath(),c.moveTo(0,0),c.lineTo(d,0),c.lineTo(d,e),c.lineTo(0,e),c.closePath(),b.length)for(c.moveTo(b[0].pageX,b[0].pageY);f"),f||this.capture()}return c(d,b),d.prototype.capture=function(){return this.queue(function(b){var c=this,d=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;return d?void d.call(navigator,{video:!0},function(d){if(d){var e=Oa(c),f=(""+Math.random()).substr(2);e.stream&&e.stream.stop(),e.stream=d,a(c).on("play.capture"+f,function(){a(c).off("play.capture"+f),b()}),c.src=Ie.URL.createObjectURL(d)}},function(){b()}):void b()})},d.prototype.cut=function(){return this.plan(function(){var a=this.data("turtleData");a.stream&&(a.stream.stop(),a.stream=null),this.attr("src","")})},d}(Ue),Ye=function(a){function b(a){var c,i,j,k,l,m,n,o,p,q,r,s=null,t=this;a=f(a,"keys"),q=ef.pitchToMidi(a.lowest),r=ef.pitchToMidi(a.highest);var u=this._geom={};if(u.lineWidth="lineWidth"in a?a.lineWidth:1.5,u.color="color"in a?a.color:"white",u.blackColor="blackColor"in a?a.blackColor:"black",u.lineColor="lineColor"in a?a.lineColor:"black",j=Math.ceil(u.lineWidth),i=422,c=4.2,null!=q&&null!=r?s=e(r)-e(q)+1:"keys"in a&&(s=Math.ceil(a.keys/12*7)),s&&(c=s/5,i=Math.sqrt(42e3*c)+j),m="width"in a?a.width:"height"in a?Math.round((a.height-j)*c+j):i,l="height"in a?a.height:Math.round((m-j)/c+j),s||(s=Math.max(1,Math.round((m-j)/(l-j)*5))),n=Math.min(e(108),Math.ceil(42+(s-1)/2)),u.highest=null!=r?r:null!=q&&"keys"in a?q+a.keys-1:g(null!=q?e(q)+s-1:n),u.lowest=null!=q?q:"keys"in a?u.highest-a.keys+1:Math.min(u.highest,g(e(u.highest)-s+1)),k=e(u.lowest),n=e(u.highest),h(u.highest)&&(n+=1),s=n-k+1,u.kw=(m-j)/s,u.kh=("height"in a?a.height-j:5*u.kw)+(j-u.lineWidth),u.bkw=4*u.kw/7,u.bkh=3*u.kh/5,u.halfex=j/2,u.leftpx=k*u.kw,u.rightpx=(n+1)*u.kw,u.ckw=(3*u.kw-2*u.bkw)/3,u.fkw=(4*u.kw-3*u.bkw)/4,b.__super__.constructor.call(this,{width:Math.ceil(u.rightpx-u.leftpx+j),height:Math.ceil(u.kh+j)}),"timbre"in a)p=a.timbre;else for(o in ef.defaultTimbre)o in a&&(p||(p={}),p[o]=a[o]);return p||(p="piano"),this.css({turtleTimbre:p}),this.on("noteon",function(a){t.drawkey(a.midi,d(a.midi))}),this.on("noteoff",function(a){t.drawkey(a.midi)}),this.draw(),this}function d(a){return k[(a%12+12)%12]}function e(a){return Math.floor((a+7)/12*7)}function g(a){return Math.ceil(a/7*12)-7}function h(a){return i(a)>=8}function i(a){return[1,8,2,9,3,4,10,5,11,6,12,7][(a%12+12)%12]}function j(a,b,c){var d,f,g,j,k,l,m;switch(l=b.halfex+b.kw*e(c)-b.leftpx,m=b.halfex,d=i(c),g=c===b.lowest,k=c===b.highest,f=0,j=0,d){case 1:j=b.kw-b.ckw;break;case 2:j=f=(b.kw-b.ckw)/2;break;case 3:f=b.kw-b.ckw;break;case 4:j=b.kw-b.fkw;break;case 5:f=b.fkw+b.bkw-b.kw,j=2*b.kw-2*b.fkw-b.bkw;break;case 6:f=2*b.kw-2*b.fkw-b.bkw,j=b.fkw+b.bkw-b.kw;break;case 7:f=b.kw-b.fkw;break;case 8:l+=b.ckw;break;case 9:l+=2*b.ckw+b.bkw-b.kw;break;case 10:l+=b.fkw;break;case 11:l+=2*b.fkw+b.bkw-b.kw;break;case 12:l+=3*b.fkw+2*b.bkw-2*b.kw}return g&&(f=0),k&&(j=0),h(c)?(a.moveTo(l,m+b.bkh),a.lineTo(l+b.bkw,m+b.bkh),a.lineTo(l+b.bkw,m),a.lineTo(l,m),a.closePath()):(a.moveTo(l,m+b.kh),a.lineTo(l+b.kw,m+b.kh),a.lineTo(l+b.kw,m+b.bkh),a.lineTo(l+b.kw-j,m+b.bkh),a.lineTo(l+b.kw-j,m),a.lineTo(l+f,m),a.lineTo(l+f,m+b.bkh),a.lineTo(l,m+b.bkh),a.closePath())}c(b,a),b.prototype.drawkey=function(a,b){var c,d=this._geom;if(d.lowest<=a&&a<=d.highest)return null==b&&(b=h(a)?d.blackColor:d.color),c=this.canvas().getContext("2d"),c.save(),c.beginPath(),j(c,d,a),c.fillStyle=b,c.strokeStyle=d.lineColor,c.lineWidth=d.lineWidth,c.fill(),c.stroke(),c.restore()},b.prototype.draw=function(){for(var a=this._geom.lowest;a<=this._geom.highest;++a)this.drawkey(a)};var k=["#db4437","#ff5722","#f4b400","#ffeb3b","#cddc39","#0f9d58","#00bcd4","#03a9f4","#4285f4","#673ab7","#9c27b0","#e91e63"];return b}(Ue),Ze=!1,$e=function(){for(var a="undefined"!=typeof Ie?Ie.navigator.userAgent:"",b=/OS X/.test(a),c=/Opera/.test(a),d=!/like Gecko/.test(a)&&!c,e={0:"null",1:"mouse1",2:"mouse2",3:"break",4:"mouse3",5:"mouse4",6:"mouse5",8:"backspace",9:"tab",12:"clear",13:"enter",16:"shift",17:"control",18:"alt",19:"pause",20:"capslock",21:"hangulmode",23:"junjamode",24:"finalmode",25:"kanjimode",27:"escape",28:"convert",29:"nonconvert",30:"accept",31:"modechange",27:"escape",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",41:"select",42:"print",43:"execute",44:"snapshot",45:"insert",46:"delete",47:"help",91:"meta",92:"meta",93:b?"meta":"menu",95:"sleep",106:"numpad*",107:"numpad+",108:"numpadenter",109:"numpad-",110:"numpad.",111:"numpad/",144:"numlock",145:"scrolllock",160:"shiftleft",161:"shiftright",162:"controlleft",163:"controlright",164:"altleft",165:"altright",166:"browserback",167:"browserforward",168:"browserrefresh",169:"browserstop",170:"browsersearch",171:"browserfavorites",172:"browserhome",173:b&&d?"-":"volumemute",174:"volumedown",175:"volumeup",176:"mediatracknext",177:"mediatrackprev",178:"mediastop",179:"mediaplaypause",180:"launchmail",181:"launchmediaplayer",182:"launchapp1",183:"launchapp2",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",223:"meta",224:"meta",226:"altgraph",229:"process",231:c?"`":"unicode",246:"attention",247:"crsel",248:"exsel",249:"eraseeof",250:"play",251:"zoom",252:"noname",253:"pa1",254:"clear"},f=48;91>f;++f)e[f]=String.fromCharCode(f).toLowerCase();for(f=96;106>f;++f)e[f]="numpad"+(f-96);for(f=112;136>f;++f)e[f]="f"+(f-111);return e}(),_e=function(){function a(a,c){return function(d){var e,f=d.which;a?e="mouse"+f:(!f&&d.whichSynth&&(f=d.whichSynth),e=$e[f],f>=160&&165>=f&&b(e.replace(/(?:left|right)$/,""),c)),b(e,c)}}function b(a,b){null!=a&&(b?h[a]=!0:delete h[a])}function c(){for(var a in h)delete h[a]}function d(a){c();for(var b in g)a?Ie.addEventListener(b,g[b],!0):Ie.removeEventListener(b,g[b])}function e(){var a,b=[];for(a in h)h[a]&&b.push(a);return b}function f(a){return Nb(),a?(a=a.replace(/\s/g,"").toLowerCase(),h[a]?!0:!1):e()}var g={mousedown:a(1,1),mouseup:a(1,0),keydown:a(0,1),keyup:a(0,0),blur:c},h={};return f.enable=d,f.list=e,f}(),af=null,bf={C:0,D:2,E:4,F:5,G:7,A:9,B:11,c:12,d:14,e:16,f:17,g:19,a:21,b:23},cf={"^":1,"":0,"=":0,_:-1},df=["C","^C","D","_E","E","F","^F","G","_A","A","_B","B","c","^c","d","_e","e","f","^f","g","_a","a","_b","b"],ef=function(){function a(a){this._atop=ac(),this._timbre=b(a,this._atop),this._queue=[],this._minQueueTime=1/0,this._maxScheduledTime=0,this._unsortedQueue=!1,this._startSet=[],this._finishSet={},this._cleanupSet=[],this._callbackSet=[],this._handlers={},this._now=null,_b()&&this.silence()}function b(a,b){a||(a={}),"string"==typeof a&&(a={wave:a});var c,d={},f=b&&b.wavetable&&b.wavetable[a.wave];for(c in e)a.hasOwnProperty(c)?d[c]=a[c]:f&&f.defs&&f.defs.hasOwnProperty(c)?d[c]=f.defs[c]:d[c]=e[c];return d}function c(){for(var a=ac().ac,b=2*a.sampleRate,c=a.createBuffer(1,b,a.sampleRate),d=c.getChannelData(0),e=0;b>e;e++)d[e]=2*Math.random()-1;return c}function d(a,b,d){if("noise"==b){var e=a.ac.createBufferSource();return e.buffer=c(),e.loop=!0,e}var f,g,h,i,j=a.wavetable,k=a.ac.createOscillator();try{if(j.hasOwnProperty(b)){if(g=j[b].wave,j[b].freq){h=0;for(f in j[b].freq)i=Number(f),d>i&&i>h&&(h=i,g=j[b].freq[h])}!k.setPeriodicWave&&k.setWaveTable?k.setWaveTable(g):k.setPeriodicWave(g)}else k.type=b}catch(l){window.console&&window.console.log(l),k.type="square"}return k.frequency.value=d,k}a.timeOffset=.0625,a.dequeueTime=.5,a.bufferSecs=2,a.toneLength=1,a.cleanupDelay=.1,a.prototype.setTimbre=function(a){this._timbre=b(a,this._atop)},a.prototype.getTimbre=function(a){return b(this._timbre,this._atop)},a.prototype.setVolume=function(a){this._out&&(isNaN(a)||(this._out.gain.value=a))},a.prototype.getVolume=function(a){return this._out?this._out.gain.value:0},a.prototype.silence=function(){var a,b,c,d=1;this._queue.length=0,this._minQueueTime=1/0,this._maxScheduledTime=0,this._startSet.length=0,b=this._finishSet,this._finishSet={},c=this._callbackSet,this._callbackSet=[],this._out&&(this._out.disconnect(),d=this._out.gain.value),this._atop=ac(),this._out=this._atop.ac.createGain(),this._out.gain.value=d,this._out.connect(this._atop.out);for(a in b)this._trigger("noteoff",b[a]);for(a=0;a0&&b.velocity>0){for(c=q.createGain(),c.gain.setValueAtTime(0,i),c.gain.linearRampToValueAtTime(p,k);k+1/32>m&&j>m+1/256;)m+=1/256,c.gain.linearRampToValueAtTime(p*(h.sustain+(1-h.sustain)*Math.exp((k-m)/l)),m);c.gain.setTargetAtTime(p*h.sustain,m,l),c.gain.setValueAtTime(p*(h.sustain+(1-h.sustain)*Math.exp((k-j)/l)),j),c.gain.linearRampToValueAtTime(0,n),c.connect(this._out),!h.cutoff&&!h.cutfollow||h.cutoff==1/0?e=c:(e=q.createBiquadFilter(),e.frequency.value=h.cutoff+b.frequency*h.cutfollow,e.Q.value=h.resonance,e.connect(c)),f=d(this._atop,h.wave,b.frequency),f.connect(e),f.start(i),f.stop(n),o&&(g=d(this._atop,h.wave,b.frequency*h.detune),g.connect(e),g.start(i),g.stop(n)),b.gainNode=c,b.oscillators=[f],o&&b.oscillators.push(g),b.cleanuptime=n}else b.duration=0;this._startSet.push(b)},a.prototype._truncateSound=function(b,c){if(c=g?n.gain.setValueAtTime(0,g):h>=g?n.gain.linearRampToValueAtTime(m*(g-f)/(h-f)):n.gain.setValueAtTime(m*(e.sustain+(1-e.sustain)*Math.exp((h-g)/i)),g),n.gain.linearRampToValueAtTime(0,j),b.oscillators)for(d=0;da.bufferSecs);++b);if(b>0){for(c=this._queue.splice(0,b),b=0;b0?this._queue[0].time:1/0}}for(b=0;b=d&&(k.push({order:[d,0],f:this._trigger,t:this,a:["noteoff",f]}),f.cleanuptime!=1/0&&this._cleanupSet.push(f),delete this._finishSet[e]);for(b=0;b=d&&(k.push({order:[d,1],f:i.callback,t:null,a:[]}),this._callbackSet.splice(b,1),b-=1);for(b=0;b<=j&&(h=f=this._startSet[b],e=f.frequency,g=null,this._finishSet.hasOwnProperty(e)&&(g=this._finishSet[e],g.time0&&f.velocity>0&&g!==f&&(this._finishSet[e]=f,k.push({order:[f.time,2],f:this._trigger,t:this,a:["noteon",f]})));for(this._startPollTimer(),k.sort(function(a,b){return a.order[0]!=b.order[0]?a.order[0]-b.order[0]:a.order[1]-b.order[1]}),b=0;b0&&(g=Math.min(g,this._cleanupSet[0].cleanuptime+1)),g=Math.min(g,this._minQueueTime-a.dequeueTime),d=Math.max(.001,g-this._atop.ac.currentTime),isNaN(d)||d==1/0||(this._pollTimer=setTimeout(f,Math.round(1e3*d)))}},a.prototype.tone=function(b,c,d,f,g,h){if(this._atop){"object"==typeof b&&(null==d&&(d=b.velocity),null==c&&(c=b.duration),null==f&&(f=b.delay),null==g&&(g=b.timbre),null==h&&(h=b.origin),b=b.pitch);var i,j;if(b||(b="C"),isNaN(b)?(i=fc(b),j=dc(i)):(j=Number(b),0>j?(i=-j,j=dc(i)):i=ec(j)),g||(g=this._timbre),g!==this._timbre){var k,l=g;g={};for(k in e)k in l?g[k]=l[k]:g[k]=e[k]}var m=(this._atop.ac,this.now()),n=m+(f||0),o={time:n,on:!1,frequency:j,midi:i,velocity:null==d?1:d,duration:null==c?a.toneLength:c,timbre:g,instrument:this,gainNode:null,oscillators:null,cleanuptime:1/0,origin:h};n=1/8&&(q-=1/32),r=(o.velocity||1)*i*u.volume,this.tone(o.pitch,q,r,h,f,o));h+=n.time*p}v=Math.max(h,v)}}this._maxScheduledTime=Math.max(this._maxScheduledTime,this.now()+v),t&&this.schedule(v,t)};var e=a.defaultTimbre={wave:"square",gain:.1,attack:.002,decay:.4,decayfollow:0,sustain:0,release:.1,cutoff:0,cutfollow:0,resonance:0,detune:0};return a.pitchToMidi=function(a){return"string"==typeof a?fc(a):a},a.midiToPitch=function(a){return"number"==typeof a?gc(a):a},a}(),ff=/^([A-Za-z]):\s*(.*)$/,gf=/(?:\[[A-Za-z]:[^\]]*\])|\s+|%[^\n]*|![^\s!:|\[\]]*!|\+[^+|!]*\+|[_<>@^]?"[^"]*"|\[|\]|>+|<+|(?:(?:\^+|_+|=|)[A-Ga-g](?:,+|'+|))|\(\d+(?::\d+){0,2}|\d*\/\d+|\d+\/?|\/+|[xzXZ]|\[?\|\]?|:?\|:?|::|./g;a.extend(!0,a,{cssHooks:{turtlePenStyle:Ra(),turtlePenDown:Sa(),turtleSpeed:nb(),turtleEasing:ob(),turtleForward:rb(),turtleTurningRadius:Qa(),turtlePosition:zb("turtlePosition","tx","ty",!0),turtlePositionX:sb("tx",parseFloat,"px",!0),turtlePositionY:sb("ty",parseFloat,"px",!0),turtleRotation:sb("rot",vb,"deg",!0),turtleScale:zb("turtleScale","sx","sy",!1),turtleScaleX:sb("sx",h,"",!1),turtleScaleY:sb("sy",h,"",!1),turtleTwist:sb("twi",xa,"deg",!1),turtleHull:oa(),turtleTimbre:Xb(),turtleVolume:Yb()},cssNumber:{turtleRotation:!0,turtleSpeed:!0,turtleScale:!0,turtleScaleX:!0,turtleScaleY:!0,turtleTwist:!0},support:{turtle:!0}}),a.extend(!0,a.fx,{step:{turtlePosition:yb("turtlePosition",!0),turtleRotation:wb("turtleRotation"),turtleScale:yb("turtleScale",!1),turtleTwist:wb("turtleTwist")},speeds:{turtle:0}}),oc.helptext=[];var hf={rt:uc("rt",1,["rt(degrees) Right turn. Pivots clockwise by some degrees: rt 90","rt(degrees, radius) Right arc. Pivots with a turning radius: rt 90, 50"],Ac),lt:uc("lt",1,["lt(degrees) Left turn. Pivots counterclockwise by some degrees: lt 90","lt(degrees, radius) Left arc. Pivots with a turning radius: lt 90, 50"],Ac),fd:uc("fd",1,["fd(pixels) Forward. Moves ahead by some pixels: fd 100"],Bc),bk:uc("bk",1,["bk(pixels) Back. Moves in reverse by some pixels: bk 100"],Bc),slide:uc("slide",1,["move(x, y) Slides right x and forward y pixels without turning: slide 50, 100"],Cc),movexy:uc("movexy",1,["movexy(x, y) Changes graphing coordinates by x and y: movexy 50, 100"],Dc),moveto:uc("moveto",1,["moveto(x, y) Move to graphing coordinates (see getxy): moveto 50, 100","moveto(obj) Move to page coordinates or an object on the page (see pagexy): moveto lastmousemove"],Ec),jump:uc("jump",1,["jump(x, y) Move without drawing (compare to slide): jump 0, 50"],Fc(Cc)),jumpxy:uc("jumpxy",1,["jumpxy(x, y) Move without drawing (compare to movexy): jump 0, 50"],Fc(Dc)),jumpto:uc("jumpto",1,["jumpto(x, y) Move without drawing (compare to moveto): jumpto 50, 100"],Fc(Ec)),turnto:uc("turnto",1,["turnto(degrees) Turn to a direction. North is 0, East is 90: turnto 270","turnto(x, y) Turn to graphing coordinates: turnto 50, 100","turnto(obj) Turn to page coordinates or an object on the page: turnto lastmousemove"],function(b,c,d){a.isNumeric(d)&&a.isNumeric(c)&&(c=[c,d],d=null);var e=Df;return this.plan(function(f,g){if(b.appear(f),a.isWindow(g)||9===g.nodeType)return void b.resolve(f);var h,i,j,k=null,l=null,m=null;if(a.isNumeric(c))i=wa(c),j=Y(g),l={pageX:j.pageX+1024*Math.sin(i),pageY:j.pageY-1024*Math.cos(i)},k=d;else if(a.isArray(c))m=W(g),m[0]-=c[0],m[1]-=c[1];else if(mb(c))l=c;else try{l=a(c).pagexy()}catch(n){return void b.resolve(f)}m||(m=W(g,l));var o=va(Math.atan2(-m[0],-m[1]));h=ra(g,!0),null!==k&&(i=wa(h.rot),o=C(h.rot,o,null===k?360:k)),o=h.rot+xa(o-h.rot);var p=this.css("turtleTurningRadius");this.css({turtleTurningRadius:0}),this.animate({turtleRotation:o},pb(g,e),qb(g)),this.plan(function(){this.css({turtleTurningRadius:p}),b.resolve(f)})}),this}),home:uc("home",0,["home() Goes home. Jumps to the center without drawing: do home"],function(b,c){return this.plan(function(d,e){b.appear(d);var f=this.css("turtlePenDown"),g=this.css("turtleTurningRadius"),h=c||V(e);this.css({turtlePenDown:"up",turtleTurningRadius:0}),this.css({turtlePosition:U(e,a(h).pagexy(),null,0,0),turtleRotation:0,turtleScale:1}),this.css({turtlePenDown:f,turtleTurningRadius:g}),b.resolve(d)}),this}),copy:uc("copy",0,["copy() makes a new turtle that is a copy of this turtle."],function(b){var c=this.clone().insertAfter(this);return c.hide(),this.plan(function(d,e){b.appear(d);var f=Oa(this),g=Oa(c);for(var h in f)g[h]=f[h];c.attr("style",this.attr("style"));for(var i in a.cssHooks){var j=this.css(i);c.css(i,j)}var k=this.prop("attributes");for(var l in k)c.attr(k[l].name,k[l].value);var m=c.canvas(),n=this.canvas();if(m&&n){m.width=n.width,m.height=n.height;var o=m.getContext("2d");o.drawImage(n,0,0)}c.show(),b.resolve(d)}),lc(c,this),c}),pen:uc("pen",1,["pen(color, size) Selects a pen. Chooses a color and/or size for the pen: pen red; pen 0; pen erase; pen blue, 5.","pen(on-or-off) Turns the pen on or off: pen off; pen on."],function(b,c,d){var e=Md(arguments,1,{lineCap:/^(?:butt|square|round)$/,lineJoin:/^(?:bevel|round|miter)$/,lineWidth:a.isNumeric,penStyle:"*"});c=e.penStyle,c&&"function"==typeof c&&(c.helpname||c.name)&&(c=c.helpname||c.name),0===e.lineWidth||null===c?c="none":c===He?c="black":a.isPlainObject(c)&&(c=La(c));var f=Df;return this.plan(function(a,d){b.appear(a);var g=!L(d)&&!pc(this),h=g&&Ka(this.css("turtlePenStyle")),i=h&&"down"==this.css("turtlePenDown"),j=!1;if(c===!1||c===!0||"down"==c||"up"==c?(this.css("turtlePenDown",c),j=!0):(e.lineWidth&&(c+=";lineWidth:"+e.lineWidth),e.lineCap&&(c+=";lineCap:"+e.lineCap),e.lineJoin&&(c+=";lineJoin:"+e.lineJoin),this.css("turtlePenStyle",c),this.css("turtlePenDown","none"==c?"up":"down")),g){var k=Ka(this.css("turtlePenStyle")),l=k&&(k.strokeStyle||k.savePath&&"gray")||h&&h.strokeStyle||"gray",m={},n=k&&"down"==this.css("turtlePenDown"),o=new We(l+" pencil",this.parent()),p=this.height();o.css({zIndex:1,turtlePosition:U(o.get(0),this.pagexy(),null,0,0),turtleRotation:this.css("turtleRotation"),turtleSpeed:1/0}),i?n||(m.turtleForward="+="+p,m.opacity=0):(o.css({turtleForward:"+="+p,opacity:0}),n&&(m.turtleForward="-="+p,m.opacity=1)),h&&!k&&"down"==i?(m.turtleForward="+="+p,m.opacity=0):h==k||h&&k&&h.strokeStyle==k.strokeStyle||(o.css({opacity:0}),m.opacity=1),o.animate(m,pb(d,f)),this.queue(function(a){o.done(function(){o.remove(),a()})})}this.plan(function(){b.resolve(a)})}),this}),fill:uc("fill",0,["fill(color) Fills a path traced using pen path: pen path; rt 100, 90; fill blue"],function(b,c){c?a.isPlainObject(c)&&(c=La(c)):c="none";var d=Ka(c,"fillStyle");return this.plan(function(a,c){b.appear(a),cb(c,d),b.resolve(a)}),this}),dot:uc("dot",0,["dot(color, diameter) Draws a dot. Color and diameter are optional: dot blue"],Lc(Mc)),box:uc("box",0,["box(color, size) Draws a box. Color and size are optional: dot blue"],Lc(Nc)),arrow:uc("arrow",0,["arrow(color, size) Draws an arrow. arrow red, 100"],Lc(Oc)),mirror:uc("mirror",1,["mirror(flipped) Mirrors the turtle across its main axis, or unmirrors if flipped if false. mirror(true)"],function(b,c){return this.plan(function(d,e){b.appear(d);var f=a.map(a.css(e,"turtleScale").split(" "),parseFloat);1===f.length&&f.push(f[0]),f[0]*f[1]<0==!c&&(f[0]=-f[0],this.css("turtleScale",f.join(" "))),b.resolve(d)}),this}),twist:uc("twist",1,["twist(degrees) Set the primary direction of the turtle. Allows use of images that face a different direction than 'up': twist(-90)"],function(b,c){return this.plan(function(d,e){b.appear(d),a.isWindow(e)||9===e.nodeType||(this.css("turtleTwist",c),b.resolve(d))}),this}),scale:uc("scale",1,["scale(factor) Scales all motion up or down by a factor. To double all drawing: scale(2)"],Hc),grow:uc("grow",1,["grow(factor) Changes the size of the element by a factor. To double the size: grow(2)"],Ic),pause:uc("pause",1,["pause(seconds) Pauses some seconds before proceeding. fd 100; pause 2.5; bk 100","pause(turtle) Waits for other turtles to be done before proceeding. t = new Turtle().fd 100; pause t; bk 100"],function(b,c){var d=null,e=null;return c&&a.isFunction(c.done)?(d=c,e=c.done):a.isFunction(c)&&(e=c),e?this.queue(function(){var b=this;e.call(d,function(){var c=b;b=null,c&&a.dequeue(c)})}):this.plan(function(a,d){b.appear(a),rc(d,c),this.plan(b.resolver(a))}),this}),st:uc("st",0,["st() Show turtle. The reverse of ht(). do st"],function(a){return this.plan(function(b){a.appear(b),this.show(),a.resolve(b)}),this}),ht:uc("ht",0,["ht() Hide turtle. The turtle can be shown again with st(). do ht"],function(a){return this.plan(function(b){a.appear(b),this.hide(),a.resolve(b)}),this}),pu:function(){return this.pen(!1,sc(arguments,0))},pd:function(){return this.pen(!0,sc(arguments,0))},pe:function(){return this.pen("erase",sc(arguments,0))},pf:function(){return this.pen("path",sc(arguments,0))},clip:uc("clip",1,["Clips tranparent bits out of the image of the sprite, and sets the hit region."],function(b,c){return null==c&&(c=.125),this.plan(function(d,e){if(b.appear(d),"CANVAS"==e.tagName){var f=De(e,c),g=a(e),h=y(e);Fe(e,f),Ge(f,parseFloat(g.css("width"))/e.width,parseFloat(g.css("height"))/e.height,-h[0],-h[1]),g.css("turtleHull",f)}b.resolve(d)})}),say:uc("say",1,['say(words) Say something. Use English words.say "Let\'s go!"'],function(a,b){return this.plan(function(c,d){a.appear(c),this.queue(function(d){Sc(b,function(){a.resolve(c),d()})})}),this}),play:uc("play",1,['play(notes) Play notes. Notes are specified in ABC notation. play "de[dBFA]2[cGEC]4"'],function(b,c){return this.plan(function(c,d){b.appear(c),this.queue(function(e){var f=Zb(d),g=a.makeArray(b.args),h=!0,i=function(){b.resolve(c),e()};g.length>0&&a.isPlainObject(g[0])&&g[0].hasOwnProperty("wait")&&(h=g[0].wait),h&&g.push(i),f.play.apply(f,g),h||i()})}),this}),tone:zc("tone",["tone(freq) Immediately sound a tone. tone(freq, 0) Stop sounding the tone. tone(freq, v, secs) Play a tone with a volume and duration. Frequency may be a number in Hz or a letter pitch. tone 440, 5"],function(a,b){var c=arguments;return this.each(function(a,b){var d=Zb(b);d.tone.apply(d,c)})}),silence:zc("silence",["silence() immediately silences sound from play() or tone()."],function(){return this.each(function(a,b){var c=Zb(b);c.silence()})}),speed:uc("speed",1,["speed(persec) Set one turtle's speed in moves per second: turtle.speed 60"],function(a,b){return this.plan(function(c,d){a.appear(c),this.css("turtleSpeed",b),this.plan(function(){a.resolve(c)})}),this}),wear:uc("wear",1,["wear(color) Sets the turtle shell color: wear turquoise","wear(url) Sets the turtle image url: wear 'http://bit.ly/1bgrQ0p'"],function(b,c,d){if(("object"==typeof c||"number"==typeof c)&&"string"==typeof d){var e=d;d=c,c=e}"number"==typeof d&&(d={height:d});var f=nd(c,"turtle"),g=Df;return f?(d&&a.extend(f.css,d),this.plan(function(a,c){b.appear(a),this.css({backgroundImage:"none"});var d=!1,e=null;hb(this,f,function(){d=!0;var a=e;a&&(e=null,a())}),pc(this)||this.delay(pb(c,g)),d||this.pause({done:function(a){d?a():e=a}}),this.plan(function(){b.resolve(a)})}),this):this}),saveimg:uc("saveimg",1,["saveimg(filename) Saves the turtle's image as a file. t.saveimg 'mypicture.png'"],function(a,b){return this.plan(function(c,d){a.appear(c);var e=!1;b||(b="img");var f=this.canvas();if(f){var g=f.toDataURL(),h=/^data:image\/(\w+);base64,(.*)$/i.exec(g);h?(h[1]&&b.toLowerCase().lastIndexOf("."+h[1].toLowerCase())!=Math.max(0,b.length-h[1].length-1)&&(b+="."+h[1]),e=!0,sf.save(b,atob(h[2]),function(){a.resolve(c)})):Sf.html('Cannot saveimg: canvas toDataURL did not work as expected.')}else Sf.html('Cannot saveimg: not a canvas');e||a.resolve(c)})}),drawon:uc("drawon",1,["drawon(canvas) Switches to drawing on the specified canvas. A = new Sprite('100x100'); drawon A; pen red; fd 50; done -> A.rt 360"],function(b,c){return this.each(function(){var a=Oa(this);a.drawOnCanvasSync&&lc(this,a.drawOnCanvasSync),a.drawOnCanvasSync=c}),lc(c,this),this.plan(function(d,e){b.appear(d);var f=Oa(e);c&&c!==Ie?c.jquery&&a.isFunction(c.canvas)?f.drawOnCanvas=c.canvas():c.tagName&&"CANVAS"==c.tagName?f.drawOnCanvas=c:(1==c.nodeType||9==c.nodeType)&&(f.drawOnCanvas=a(c).canvas()):f.drawOnCanvas=null,b.resolve(d)})}),label:uc("label",1,["label(text) Labels the current position with HTML: label 'remember'","label(text, styles, labelsite) Optional position specifies 'top', 'bottom', 'left', 'right', and optional styles is a size or CSS object: label 'big', { color: red, fontSize: 100 }, 'bottom'"],function(b,c,d,e){if((!e||"string"==typeof e)&&(a.isNumeric(d)||a.isPlainObject(d))){var f=e;e=d,d=f}a.isNumeric(e)&&(e={fontSize:e}),null==d&&(d=e&&"labelSide"in e?e.labelSide:e&&"label-side"in e?e["label-side"]:d="rotated scaled");var g=Df;return this.plan(function(f,h){b.appear(f);for(var i={},j=this.prop("style"),k=0;kreload() Does a reload, recycling content (cycling animated gifs)."],function(b){return this.plan(function(c,d){if(b.appear(c),a.isWindow(d)||9===d.nodeType)return Ie.location.reload(),void b.resolve(c);if(d.src){var e=d.src;d.src="",d.src=e}b.resolve(c)}),this}),hatch:function(b,c){if(this.length){c!==He||a.isNumeric(b)||(c=b,b=1);var d=this[0];if(a.isWindow(d)||9===d.nodeType?d=za():/^(?:br|img|input|hr|canvas)$/i.test(d.tagName)&&(d=d.parentElement),1===b)return pd("function"==typeof c?c(0):c,d,"turtle");for(var e=0,f=[];b>e;++e)f.push(pd("function"==typeof c?c(e):c,d,"turtle")[0]);return a(f)}},pagexy:vc("pagexy",["pagexy() Page coordinates {pageX:, pageY}, top-left based: c = pagexy(); fd 500; moveto c"],function(){if(this.length){var a=Y(this[0]);return{pageX:a.pageX,pageY:a.pageY}}}),getxy:vc("getxy",["getxy() Graphing coordinates [x, y], center-based: v = getxy(); move -v[0], -v[1]"],function(){return this.length?W(this[0]):void 0}),direction:vc("direction",["direction() Current turtle direction. North is 0; East is 90: direction()","direction(obj) direction(x, y) Returns the direction from the turtle towards an object or coordinate. Also see turnto: direction lastclick"],function(b,c){if(this.length){var d,e=this[0],f=b;if(f!==He){if(d=a(e).pagexy(),a.isNumeric(c)&&a.isNumeric(b)&&(f=[b,c]),a.isArray(f)&&(f=X(e,[f])[0]),!mb(f))try{f=a(f).pagexy()}catch(g){}return f?va(Math.atan2(f.pageX-d.pageX,d.pageY-f.pageY)):NaN}return a.isWindow(e)||9===e.nodeType?0:aa(e)}}),distance:vc("distance",["distance(obj) Returns the distance from the turtle to another object: distance lastclick","distance(x, y) Returns the distance from the turtle to graphing coorindates: distance(100, 0)"],function(b,c){if(this.length){var d,e,f=this[0],g=a(f).pagexy();if(a.isNumeric(c)&&a.isNumeric(b)&&(b=[b,c]),a.isArray(b)&&(b=X(f,[b])[0]),!mb(b))try{b=a(b).pagexy()}catch(h){}return b?(d=b.pageX-g.pageX,e=b.pageY-g.pageY,Math.sqrt(d*d+e*e)):NaN}}),canvas:zc("canvas",["turtle.canvas() The canvas for the turtle image. Draw on the turtle: c = turtle.canvas().getContext('2d'); c.fillStyle = red; c.fillRect(10, 10, 30, 30)"],function(){return this.filter("canvas").get(0)||this.find("canvas").get(0)}),imagedata:zc("imagedata",["imagedata() Returns the image data for the turtle. imdat = imagedata(); write imdat.data.length, 'bytes'","imagedata(imdat) Sets the image data for the turtle. imagedata({width: 1, height:1, data:[255,0,0,255]});"],function(b){var c=this.canvas();if(!c){if(b)throw new Error("can only set imagedata on a canvas like a Sprite");var d=this.filter("img").get(0);if(!d)return;c=Ga(d.naturalWidth,d.naturalHeight),c.getContext("2d").drawImage(d,0,0)}var e=c.getContext("2d");if(!b)return e.getImageData(0,0,c.width,c.height);if(!(b instanceof ImageData)){if("object"!=typeof b||!a.isNumeric(b.width)||!a.isNumeric(b.height)||!(a.isArray(b.data)||b.data instanceof Uint8ClampedArray||b.data instanceof Uint8Array))return;for(var f=e.createImageData(Math.round(b.width),Math.round(b.height)),g=Math.min(b.data.length,f.data.length),h=0;g>h;++h)f.data[h]=b.data[h];b=f}if(b.width!=c.width||b.height!=c.height){var i=y(c);c.width=b.width,c.height=b.height;var j=y(c);Lb(c,i,j),a(c).css("turtleHull","auto"),e=c.getContext("2d")}e.putImageData(b,0,0)}),cell:zc("cell",["cell(r, c) Row r and column c in a table. Use together with the table function: g = table 8, 8; g.cell(0,2).text 'hello'"],function(b,c){var d=this.find(a.isNumeric(b)?"tr:nth-of-type("+(b+1)+")":"tr");return d.find(a.isNumeric(c)?"td:nth-of-type("+(c+1)+")":"td")}),shown:vc("shown",["shown() True if turtle is shown, false if hidden: do ht; write shown()"],function(){var a=this.get(0);return a&&!L(a)}),hidden:vc("hidden",["hidden() True if turtle is hidden: do ht; write hidden()"],function(){var a=this.get(0);return!a||L(a)}),inside:vc("inside",["inside(obj) True if the turtle is encircled by obj: inside(window)"],function(b){if(!b)return!1;if("string"==typeof b&&(b=a(b)),b.jquery){if(!b.length||L(b[0]))return!1;b=b[0]}for(var c,d=N(b),e=null,f=!1,g=!0,h=0;g&&htouches(obj) True if the turtle touches obj: touches(lastclick)","touches(color) True if the turtle touches a drawn color: touches red"],function(b,c){if(!this.length||L(this[0]))return!1;if("function"==typeof b&&cd(b.helpname)&&(b=b.helpname),"color"==b||cd(b))return gb(this[0],"color"==b?null:b);if(a.isNumeric(b)&&a.isNumeric(c)&&(b=[b,c]),a.isArray(b)&&2==b.length&&a.isNumeric(b[0])&&a.isNumeric(b[1])&&(b=X(this[0]||document.body,[b])[0]),!b)return!1;"string"==typeof b&&(b=a(b)),b.jquery||a.isArray(b)||(b=[b]);for(var d,e,f,g,h,i,j=!1,k=0;!j&&kwithin(distance, obj) Filters elements to those within distance of obj: $('.turtle').within(100, lastclick)"],function(a,b,c){return Mb(this,!0,a,b,c)}),notwithin:vc("notwithin",["within(distance, obj) Filters elements to those further than distance of obj: $('.turtle').notwithin(100, lastclick)"],function(a,b,c){return Mb(this,!1,a,b,c)}),nearest:vc("nearest",["nearest(obj) Filters elements to those nearest obj$('.turtle').neareest(lastclick)"],function(b,c){var d,e,f,g=[],h=1/0;if(d=a.isNumeric(d)&&a.isNumeric(c)?[b,c]:b,a.isArray(d)&&(d=X(this[0]||document.body,[d])[0]),!mb(d))try{d=a(d).pagexy()}catch(i){d=null}for(f=0;f=m&&(h>m&&(h=m,g.length=0),g.push(this[f]))}return a(g)}),done:zc("done",["done(fn) Calls fn when animation is complete. Use with await: await done defer()"],function(a){var b=this;return this.promise().done(function(){if(b){var c=b;Me+=1,setTimeout(function(){Me-=1,c.promise().done(a)},0)}else a.apply(this,arguments)})}),plan:zc("plan",["plan(fn) Runs fn in the animation queue. For planning logic: write getxy(); fd 50; plan -> write getxy(); bk 50"],function(c,d,e){function f(f,g,h){var i=e?function(){d.apply(a(f),e)}:function(){d.call(a(f),g,f)},j=(h.length&&h[h.length-1],function(){var d=a.queue(this,c),e=[];"inprogress"===d[0]&&e.unshift(d.shift()),a.queue(f,c,e),i(),Array.prototype.push.apply(a.queue(f,c),d),b(f,c)});j.finish=i,a.queue(f,c,j)}a.isFunction(c)&&(e=d,d=c,c="fx");for(var g,h=this.length,i=0;h>i;++i){g=this[i],Uc(g,c);var j=a.queue(g,c);j.length?f(g,i,j):e?d.apply(a(g),e):d.call(a(g),i,g)}return this})},jf={},kf=0,lf=null,mf=null;Xc(hf,"move","slide"),Xc(hf,"direct","plan"),Xc(hf,"enclosedby","inside"),Xc(hf,"bearing","direction"),a.fn.extend(hf);var nf="data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP///wAAAAAAACH5BAlkAAYAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAKAAwAAAD72i6zATEgBCAebHpzUnxhDAMAvhxKOoV3ziuZyo3RO26dTbvgXj/gsCO9ysOhENZz+gKJmcUkmA6PSKfSqrWieVtuU+KGNXbXofLEZgR/VHCgdua4isGz9mbmM6U7/94BmlyfUZ1fhqDhYuGgYqMkCOBgo+RfWsNlZZ3ewIpcZaIYaF6XaCkR6aokqqrk0qrqVinpK+fsbZkuK2ouRy0ob4bwJbCibthh6GYebGcY7/EsWqTbdNG1dd9jnXPyk2d38y0Z9Yub2yA6AvWPYk+zEnkv6xdCoPuw/X2gLqy9vJIGAN4b8pAgpQOIlzI8EkCACH5BAlkAAYALAAAAAAoADAAAAPuaLrMBMSAEIB5senNSfGEMAwC+HEo6hXfOK5nKjdE7bp1Nu+BeP+CwI73Kw6EQ1nP6AomZxSSYDo9Ip9KqtaJ5W25Xej3qqGYsdEfZbMcgZXtYpActzLMeLOP6c7f3nVNfEZ7TXSFg4lyZAYBio+LZYiQfHMbc3iTlG9ilGpdjp4ujESiI6RQpqegqkesqqhKrbEpoaa0KLaiuBy6nrxss6+3w7tomo+cDXmBnsoLza2nsb7SN2tl1nyozVOZTJhxysxnd9XYCrrAtT7KQaPruavBo2HQ8xrvffaN+GV5/JbE45fOG8Ek5Q4qXHgwAQA7",of={click:1,dblclick:1,mouseup:1,mousedown:1,mousemove:1},pf=null,qf=[],rf=!1,sf={interrupt:zc("interrupt",["interrupt() Interrupts and aborts all turtle commands."],function(b){if("reset"==b)return void(Le=!1);if("test"==b)return Le?!1:Cf?!0:a.timers.length?!0:Af.length?!0:Me?!0:Yc()?!0:a(":animated").length?!0:a(".turtle").filter(function(){return a.queue(this).length>0}).length>0?!0:a(".turtleinput").filter(function(){return!a(this).prop("disabled")}).length>0?!0:yc()?!0:!1;a(":animated,.turtle").clearQueue().stop(),bc(),a(".turtleinput").prop("disabled",!0),a(Ie).off(".turtleevent"),a("*").not("#_testpanel *").map(function(b,c){a._data(c,"events",null)}),Le=!0,ud(null,null),sd();for(var c=a.timers.length-1;c>=0;--c)a.timers[c].anim&&a.timers[c].anim.elem&&a(a.timers[c].anim.elem).stop(!0,!0);var d=b?"'"+b+"'":"";throw new Error("interrupt("+d+") called")}),cs:wc("cs",["cs() Clear screen. Erases both graphics canvas and body text: do cs"],function(){db()}),cg:wc("cg",["cg() Clear graphics. Does not alter body text: do cg"],function(){db("canvas labels")}),ct:wc("ct",["ct() Clear text. Does not alter graphics canvas: do ct"],function(){db("text")}),canvas:zc("canvas",["canvas() Returns the raw turtle canvas. c = canvas().getContext('2d'); c.fillStyle = red; c.fillRect(100,100,200,200)"],function(){return Fa()}),sizexy:zc("sizexy",["sizexy() Get the document pixel [width, height]. [w, h] = sizexy(); canvas('2d').fillRect(0, 0, w, h)"],Ia),forever:zc("forever",["forever(fn) Calls fn repeatedly, forever. forever -> fd 2; rt 2","forever(fps, fn) Calls fn repeating fps per second. forever 2, -> fd 25; dot blue"],rd),stop:zc("stop",["stop() stops the current forever loop. forever -> fd 10; if not inside window then stop()","stop(fn) stops the forever loop corresponding to fn.","Use break to stop a for or while loop."],td),tick:zc("tick",["tick(fps, fn) Calls fn fps times per second until tick is called again: c = 10; tick 1, -> c and write(c--) or tick()"],function(b,c){if(Yc()){var d=a(pf);d.plan(function(){ud(b,c)})}else ud(b,c)}),speed:wc("speed",["speed(mps) Sets default turtle speed in moves per second: speed Infinity"],function(a){vd(a)}),say:zc("say",['say(words) Say something. Use English words.say "Let\'s go!"'],function(b){if(pf){var c=a(pf);c.say.call(c,b)}else{var d=tc(null,"say",arguments,0);d.appear(null),Sc(b,function(){d.resolve(null)}),d.exit()}}),play:zc("play",['play(notes) Play notes. Notes are specified in ABC notation. play "de[dBFA]2[cGEC]4"'],function(){if(pf){var b=a(pf);b.play.apply(b,arguments)}else{var c=tc(null,"play",arguments,0);c.appear(null);var d=$b(),e=a.makeArray(c.args);e.push(function(){c.resolve(null)}),d.play.apply(d,e),c.exit()}}),tone:zc("tone",["tone(freq) Immediately sound a tone. tone(freq, 0) Stop sounding the tone. tone(freq, v, secs) Play a tone with a volume and duration. Frequency may be a number in Hz or a letter pitch. tone 440, 5"],function(){if(pf){var b=a(pf);b.tone.apply(b,arguments)}else{var c=$b();c.play.apply(c)}}),silence:zc("silence",["silence() Immediately silences sound from play() or tone()."],function(){if(pf){var b=a(pf);b.silence()}else{var c=$b();c.silence()}}),sync:zc("sync",["sync(t1, t2, t3,...) Selected turtles wait for each other to stop."],lc),remove:zc("remove",["remove(t) Remove selected turtles."],mc),done:zc("done",["done(fn) Calls fn when animation is complete. Use with await: await done defer()"],function(b){var c=a(".turtle");return c.promise().done(function(){if(c){var a=c;Me+=1,setTimeout(function(){Me-=1,a.promise().done(b)},0)}else b.apply(this,arguments)})}),load:zc("load",["load(url, cb) Loads data from the url and passes it to cb. load 'intro', (t) -> write 'intro contains', t"],function(b,c){var d;return a.ajax(Eb(b,"load"),{async:!!c,complete:function(e){try{if(d=e.responseObject=JSON.parse(e.responseText),"string"==typeof d.data&&"string"==typeof d.file){if(d=d.data,/\.json(?:$|\?|\#)/.test(b))try{d=JSON.parse(d)}catch(f){}}else a.isArray(d.list)&&"string"==typeof d.directory?d=d.list:d.error&&(d=null)}catch(f){null==d&&e&&e.responseText&&(d=e.responseText)}c&&c(d,e)}}),d}),save:zc("save",["save(url, data, cb) Posts data to the url and calls when done. save 'intro', 'pen gold, 20\\nfd 100\\n'"],function(b,c,d){if(!b)throw new Error("Missing url for save");var e,f={};if(b=Eb(b,"save"),/\.json(?:$|\?|\#)/.test(b)&&(c=JSON.stringify(c,null,2)),"string"==typeof c||"number"==typeof c)f.data=c;else for(e in c)c.hasOwnProperty(e)&&("string"==typeof c[e]?f[e]=c[e]:f[e]=JSON.stringify(c[e]));if(f&&!f.key){var g=Gb();g&&g.key&&g.user==Db(b)&&(f.key=g.key)}a.ajax(Eb(b,"save"),{type:"POST",data:f,complete:function(a){var b;try{b=JSON.parse(a.responseText)}catch(c){null==b&&a&&a.responseText&&(b=a.responseText)}d&&d(b,a)}})}),append:wc("append",["append(html) Appends text to the document without a new line. append 'try this twice...'"],function(b){a.fn.append.apply(a("body"),arguments)}),type:wc("type",["type(text) Types preformatted text like a typewriter. type 'Hello!\n'"],Cd),typebox:wc("typebox",["typebox(clr) Draws a colored box as typewriter output. typebox red"],function(a,b){null!=b||null==a||cd(a)||(b=a,a=null),Dd(a,b)}),typeline:wc("typebox",["typeline() Same as type '\\n'. typeline()"],function(a){Cd((a||"")+"\n")}),write:wc("write",["write(html) Writes a line of text. Arbitrary HTML may be written: write 'Hello, world!'"],Ed,function(){return Fd(Array.prototype.join.call(arguments," "),"div")}),read:wc("read",["read(fn) Reads text or numeric input. Calls fn once: read (x) -> write x","read(html, fn) Prompts for input: read 'Your name?', (v) -> write 'Hello ' + v"],Ed,function(a,b){return Id(a,b,0)}),readnum:wc("readnum",["readnum(html, fn) Reads numeric input. Only numbers allowed: readnum 'Amount?', (v) -> write 'Tip: ' + (0.15 * v)"],Ed,function(a,b){return Id(a,b,"number")}),readstr:wc("readstr",["readstr(html, fn) Reads text input. Never converts input to a number: readstr 'Enter code', (v) -> write v.length + ' long'"],Ed,function(a,b){return Id(a,b,"text")}),listen:wc("listen",["listen(html, fn) Reads voice input, if the browser supports it:listen 'Say something', (v) -> write v"],Ed,function(a,b){ +return Id(a,b,"voice")}),menu:wc("menu",["menu(map) shows a menu of choices and calls a function based on the user's choice: menu {A: (-> write 'chose A'), B: (-> write 'chose B')}"],Ed,Gd),button:wc("button",["button(text, fn) Writes a button. Calls fn whenever the button is clicked: button 'GO', -> fd 100"],Ed,Hd),table:wc("table",["table(m, n) Writes m rows and c columns. Access cells using cell: g = table 8, 8; g.cell(2,3).text 'hello'","table(array) Writes tabular data. Each nested array is a row: table [[1,2,3],[4,5,6]]"],Ed,Kd),img:wc("img",["img(url) Writes an image with the given address. Any URL can be provided. A name without slashes will be treated as '/img/name'.t = img 'tree'"],Ed,Jd),random:zc("random",["random(n) Random non-negative integer less than n: write random 10","random(list) Random member of the list: write random ['a', 'b', 'c']","random('position') Random page position: moveto random 'position'","random('color') Random color: pen random 'color'"],qd),rgb:zc("rgb",["rgb(r,g,b) Makes a color out of red, green, and blue parts. pen rgb(150,88,255)"],function(a,b,c){return Ld("rgb",[Math.max(0,Math.min(255,Math.floor(a))),Math.max(0,Math.min(255,Math.floor(b))),Math.max(0,Math.min(255,Math.floor(c)))])}),hatch:function(b,c){return a(document).hatch(b,c)},rgba:zc("rgba",["rgba(r,g,b,a) Makes a color out of red, green, blue, and alpha. pen rgba(150,88,255,0.5)"],function(a,b,c,d){return Ld("rgba",[Math.max(0,Math.min(255,Math.floor(a))),Math.max(0,Math.min(255,Math.floor(b))),Math.max(0,Math.min(255,Math.floor(c))),d])}),hsl:zc("hsl",["hsl(h,s,l) Makes a color out of hue, saturation, and lightness. pen hsl(120,0.65,0.75)"],function(a,b,c){return Ld("hsl",[a,(100*b).toFixed(0)+"%",(100*c).toFixed()+"%"])}),hsla:zc("hsla",["hsla(h,s,l,a) Makes a color out of hue, saturation, lightness, alpha. pen hsla(120,0.65,0.75,0.5)"],function(a,b,c,d){return Ld("hsla",[a,(100*b).toFixed(0)+"%",(100*c).toFixed(0)+"%",d])}),click:xc("click",["click(fn) Calls fn(event) whenever the mouse is clicked. click (e) -> moveto e; label 'clicked'"]),dblclick:xc("dblclick",["dblclick(fn) Calls fn(event) whenever the mouse is double-clicked. dblclick (e) -> moveto e; label 'double'"]),mouseup:xc("mouseup",["mouseup(fn) Calls fn(event) whenever the mouse is released. mouseup (e) -> moveto e; label 'up'"]),mousedown:xc("mousedown",["mousedown(fn) Calls fn(event) whenever the mouse is pressed. mousedown (e) -> moveto e; label 'down'"]),mousemove:xc("mousemove",["mousemove(fn) Calls fn(event) whenever the mouse is moved. mousemove (e) -> write 'at ', e.x, ',', e.y"]),keydown:xc("keydown",["keydown(fn) Calls fn(event) whenever a key is pushed down. keydown (e) -> write 'down ' + e.key"]),keyup:xc("keyup",["keyup(fn) Calls fn(event) whenever a key is released. keyup (e) -> write 'up ' + e.key"]),keypress:xc("keypress",["keypress(fn) Calls fn(event) whenever a character key is pressed. keypress (e) -> write 'press ' + e.key"]),send:zc("send",["send(name) Sends a message to be received by recv. send 'go'; recv 'go', -> fd 100"],function(a){var b=arguments,c=Array.prototype.slice.call(b,1),d=uf.sent[a];d||(d=uf.sent[a]=[]),d.push(c),Zc(a)}),recv:zc("recv",["recv(name, fn) Calls fn once when a sent message is received. recv 'go', (-> fd 100); send 'go'"],function(a,b){var c=uf.waiting[a];c||(c=uf.waiting[a]=[]),c.push(b),Zc(a)}),abs:zc("abs",["abs(x) The absolute value of x. see abs -5"],Math.abs),acos:zc("acos",["acos(x) Trigonometric arccosine, in radians. see acos 0.5"],Math.acos),asin:zc("asin",["asin(y) Trigonometric arcsine, in radians. see asin 0.5"],Math.asin),atan:zc("atan",["atan(y, x = 1) Trigonometric arctangent, in radians. see atan 0.5"],function(a,b){return Math.atan2(a,b==He?1:b)}),cos:zc("cos",["cos(radians) Trigonometric cosine, in radians. see cos 0"],Math.cos),sin:zc("sin",["sin(radians) Trigonometric sine, in radians. see sin 0"],Math.sin),tan:zc("tan",["tan(radians) Trigonometric tangent, in radians. see tan 0"],Math.tan),acosd:zc("acosd",["acosd(x) Trigonometric arccosine, in degrees. see acosd 0.5"],function(a){switch(a){case 1:return 0;case.5:return 60;case 0:return 90;case-.5:return 120;case-1:return 180}return 180*Math.acos(a)/Math.PI}),asind:zc("asind",["asind(x) Trigonometric arcsine, in degrees. see asind 0.5"],function(a){switch(a){case 1:return 90;case.5:return 30;case 0:return 0;case-.5:return-30;case-1:return-90}return 180*Math.asin(a)/Math.PI}),atand:zc("atand",["atand(y, x = 1) Trigonometric arctangent, in degrees. see atand -1, 0/mark>"],function(a,b){return b==He&&(b=1),0==a?0==b?NaN:b>0?0:180:0==b?a>0?1/0:-(1/0):Math.abs(a)==Math.abs(b)?a>0?b>0?45:135:b>0?-45:-135:180*Math.atan2(a,b)/Math.PI}),cosd:zc("cosd",["cosd(degrees) Trigonometric cosine, in degrees. see cosd 45"],function(a){if(a=ua(a,360),a%30===0)switch(0>a?a+360:a){case 0:return 1;case 60:return.5;case 90:return 0;case 120:return-.5;case 180:return-1;case 240:return-.5;case 270:return 0;case 300:return.5}return Math.cos(a/180*Math.PI)}),sind:zc("sind",["sind(degrees) Trigonometric sine, in degrees. see sind 45"],function(a){if(a=ua(a,360),a%30===0)switch(0>a?a+360:a){case 0:return 0;case 30:return.5;case 90:return 1;case 150:return.5;case 180:return 0;case 210:return-.5;case 270:return-1;case 330:return-.5}return Math.sin(a/180*Math.PI)}),tand:zc("tand",["tand(degrees) Trigonometric tangent, in degrees. see tand 45"],function(a){if(a=ua(a,360),a%45===0)switch(0>a?a+360:a){case 0:return 0;case 45:return 1;case 90:return 1/0;case 135:return-1;case 180:return 0;case 225:return 1;case 270:return-(1/0);case 315:return-1}return Math.tan(a/180*Math.PI)}),ceil:zc("ceil",["ceil(x) Round up. see ceil 1.9"],Math.ceil),floor:zc("floor",["floor(x) Round down. see floor 1.9"],Math.floor),round:zc("round",["round(x) Round to the nearest integer. see round 1.9"],Math.round),exp:zc("exp",["exp(x) Raise e to the power x. see exp 2"],Math.exp),ln:zc("ln",["ln(x) The natural logarithm of x. see ln 2"],Math.log),log10:zc("log10",["log10(x) The base 10 logarithm of x. see log10 0.01"],function(a){return Wa(Math.log(a)*Math.LOG10E)}),pow:zc("pow",["pow(x, y) Raise x to the power y. see pow 4, 1.5"],function(a,b){return Wa(Math.pow(a,b))}),sqrt:zc("sqrt",["sqrt(x) The square root of x. see sqrt 25"],Math.sqrt),max:zc("max",["max(x, y, ...) The maximum of a set of values. see max -5, 2, 1"],Math.max),min:zc("min",["min(x, y, ...) The minimum of a set of values. see min 2, -5, 1"],Math.min),Pencil:zc("Pencil",["new Pencil(canvas) Make an invisble pencil for drawing on a canvas. s = new Sprite; p = new Pencil(s); p.pen red; p.fd 100; remove p"],Ve),Turtle:zc("Turtle",["new Turtle(color) Make a new turtle. t = new Turtle; t.fd 100"],We),Piano:zc("Piano",["new Piano(keys) Make a new piano. t = new Piano 88; t.play 'edcdeee'"],Ye),Webcam:zc("Webcam",["new Webcam(options) Make a new webcam. v = new Webcam; v.plan -> pic = new Sprite v"],Xe),Sprite:zc("Sprite",["new Sprite({width:w,height:h,color:c}) Make a new sprite to drawon. s = new Sprite({width:50,height:50,color:blue}); s.fd 100"],Ue),loadscript:zc("loadscript",["loadscript(url, callback) Loads Javascript or Coffeescript from the given URL, calling callback when done."],function(b,c){Ie.CoffeeScript&&/\.(?:coffee|cs)$/.test(b)?CoffeeScript.load(b,c):a.getScript(b,c)}),pressed:zc("pressed",["pressed('control') Tests if a specific key is pressed. if pressed 'a' then write 'a was pressed'","pressed.list() Returns a list of pressed keys, by name. write 'Pressed keys: ' + pressed.list().join(',')"],_e),help:oc},tf={finish:{helptext:["finish() Finishes turtle animation. Does not pause for effect: do finish"]}},uf={sent:{},waiting:{},pollTimer:null};Xc(sf,"defaultspeed","speed"),sf.save.loginCookie=Gb;var vf={},wf=["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgrey","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgrey","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen","transparent"];!function(){for(var a=["none","erase","path","up","down","color","position","normal","touch"],b=a.concat(wf),c=0;csee(v)
Shows the value of v in the test panel: see document"]},tf["if"]=tf["else"]=tf.then={helptext:["if then else Tests a condition: if 1 <= (new Date).getDay() <= 5 then write 'Working hard!' else write 'Happy weekend!'"]},tf.await=tf.defer={helptext:['await defer Waits for results from an asynchronous event; from Iced CoffeeScript: await readnum defer n']}}(),a.turtle=function(b,c){var d=!1;if(1==arguments.length&&"object"==typeof b&&b&&!b.hasOwnProperty("length")&&(c=b,b="turtle"),b=b||"turtle",c=c||{},"turtle"in c&&(b=c.turtle),bd(),"htmlscript"in c&&!c.htmlscript||a('script[type="text/html"]').each(function(){a(this).replaceWith(a(this).html().replace(/^\x3c!\[CDATA\[\n?|\]\]\x3e$/g,""))}),!Re.ctx&&"subpixel"in c&&(Re.subpixel=parseInt(c.subpixel)),a.turtle.hangtime="hangtime"in c?parseFloat(c.hangtime):2e4,c.events!==!1&&xd(c.eventprefix),c.pressed!==!1&&(Wb(),_e.enable(!0)),c.see!==!1&&(Rd(),d=!0,Ie.addEventListener?Ie.addEventListener("error",Sf):Ie.onerror=Sf,Ie.debug=Sf,Xc(Ie,"log","debug")),c.queuehide!==!1&&Tc(),c.functions!==!1&&(Ie.printpage=Ie.print,a.extend(Ie,sf)),vd("defaultspeed"in c?c.defaultspeed:1),_b())try{ac()}catch(e){}var f=null,g=!1;if(b&&(f=a("#"+b),f.length||(a("body").length||(document.write(""),g=!0),f=new We(b))),f&&!f.length&&(f=null),f&&1===f.length&&c.global!==!1){var h={css:1,fadeIn:1,fadeOut:1,fadeTo:1,fadeToggle:1,animate:1,toggle:1,finish:1,promise:1,direct:1,show:1,hide:1},i=a.extend({},hf,h);qf.push.apply(qf,ad(f,i)),pf=f[0],f.css({zIndex:1})}if(c.ids!==!1&&(wd(c.idprefix),f&&b&&(Ie[b]=f)),c.panel!==!1){var j={title:"test panel (type help for help)",abbreviate:[He,vf],consolehook:$c};if(f&&j.abbreviate.push(f),c.title&&(j.title=c.title),c.panelheight&&(j.height=c.panelheight),Sf.init(j),d)return Ie.CoffeeScript?"see.init(eval(see.cs))":Sf.here}return a("#"+b)},a.extend(a.turtle,sf),a.turtle.colors=wf;a.cleanData;a.cleanData=function(b){for(var c,d=0;(c=b[d])!==He;d++){var e=a.data(c,"turtleData");e&&e.stream&&e.stream.stop(),c.id&&Ie[c.id]&&Ie[c.id].jquery&&1===Ie[c.id].length&&Ie[c.id][0]===c&&delete Ie[c.id],c===pf&&bd()}};var xf={},yf={turtle:function(a){return a||(a="mediumseagreen"),{url:fd(a),css:{width:20,height:24,transformOrigin:"10px 13px",turtleHull:"-8 -5 -8 6 -2 -13 2 -13 8 6 8 -5 0 9",opacity:.67,backgroundImage:"url("+nf+")",backgroundSize:"cover"}}},pointer:function(a){return a||(a="gray"),{url:gd(a),css:{width:20,height:24,transformOrigin:"10px 18px",turtleHull:"-10 6 0 -18 10 6",opacity:.67}}},radius:function(a){return a||(a="gray"),{url:hd(a),css:{width:20,height:20,transformOrigin:"10px 10px",turtleHull:"-10 0 -7 7 0 10 7 7 10 0 7 -7 0 -10 -7 -7",opacity:1}}},dot:function(a){return a||(a="black"),{url:id(a,24),css:{width:12,height:12,transformOrigin:"6px 6px",turtleHull:"-6 0 -4 4 0 6 4 4 6 0 4 -4 0 -6 -4 -4",opacity:1}}},point:function(a){return a||(a="black"),{url:id(a,6),css:{width:3,height:3,transformOrigin:"1.5px 1.5px",turtleHull:"-1.5 0 -1 1 0 1.5 1 1 1.5 0 1 -1 0 -1.5 -1 -1",opacity:1}}},pencil:function(a){return a||(a="dodgerblue"),{url:jd(a),css:{width:20,height:24,transformOrigin:"10px 24px",turtleHull:"0 0 -3 -6 -3 -24 3 -6 3 -24",opacity:1}}}},zf={"&":"&","<":"<",">":">",'"':"""},Af=[],Bf=[],Cf=null,Df=0,Ef=null,Ff={autoScrollTimer:null,bottomSeen:0},Gf="data:image/svg+xml,",Hf={init:function(){if(!this.ide){try{Ie.parent&&Ie.parent.ide&&Ie.parent.ide.bindframe&&Ie.parent.ide.bindframe(Ie,parent)&&(this.ide=Ie.parent.ide,this.attached=!0)}catch(a){}this.attached&&Ie.addEventListener&&Ie.addEventListener("error",function(a){Hf.reportEvent("error",[a])})}},attached:!1,ide:null,reportEvent:function(a,b){this.ide&&this.ide.reportEvent(a,b)},nextId:function(){return this.ide?this.ide.nextId():0}};Hf.init(),function(){function b(a){return a.toFixed(1).replace(/\.0$/,"")}function c(a){return''+a+""}function d(b,c){return b&&(b=b.get(0))&&c&&c.target&&(b==c.target||a.contains(b,c.target))}if(Hf.ide){var e,f,g,h,i=a("").css({position:"fixed",zIndex:1e6-1,fontFamily:"sans-serif",display:"none",background:"#ff8",border:"1px solid dimgray",padding:"1px",cursor:"move",fontSize:12}).appendTo("body"),j=null,k=null,l=null,m=0,n=0,o=0,p=0;a(Ie).on("mousedown mouseup mousemove keydown",function(q){if("keydown"==q.type){if(q.which<27)return;k&&k.remove(),k=j=l=null}if("mousedown"==q.type)if(k)if(l)k&&k.remove(),k=j=l=null;else if(j)k.css({cursor:"default"}),l=q;else{a.turtle.interrupt("reset");var r=a(".turtle").within("touch",q);j=r.length?r.eq(0):q}else if(d(i,q)){var s=Ia();l=j=null,k=a('').css({position:"absolute",top:0,left:0,cursor:"crosshair",zIndex:1e6}).appendTo("body")}if(k){var t,u,v,w,x,y,z=k.canvas(),A=z.getContext("2d"),B=!1,C=l||q;if(j&&"function"==typeof j.pagexy){var D=j.getxy(),E=j.pagexy();E.x=D[0],E.y=D[1],B=!0,x=j.direction(),t=["getxy is "+b(E.x)+", "+b(E.y),"direction is "+b(x)]}else E=j||C,t=[c("moveto "+b(E.x)+", "+b(E.y))];if(t.unshift(l?'click to close':j?'click to measure':'click on point'),u=C.x-E.x,v=C.y-E.y,w=Math.sqrt(u*u+v*v),y=Math.atan2(u,v)/Math.PI*180,j){if(A.save(),A.clearRect(m-10,n-10,o-m+20,p-n+20),m=o=E.pageX,n=p=E.pageY,A.fillStyle="red",A.beginPath(),A.arc(E.pageX,E.pageY,4,0,2*Math.PI,!1),A.closePath(),A.fill(),w>0){if(B){A.strokeStyle="black",A.fillStyle="black",g=(x-90)/180*Math.PI,h=(y-90)/180*Math.PI,e=E.pageX+100*Math.cos(g),f=E.pageY+100*Math.sin(g),Rc(A,2,E.pageX,E.pageY,e,f),m=Math.min(e,m),n=Math.min(f,n),o=Math.max(e,o),p=Math.max(f,p);var F=(360+y-x)%360;A.beginPath(),180>=F?(t.push(c("rt "+b(F))),w>=20&&A.arc(E.pageX,E.pageY,20,g,h)):(t.push(c("lt "+b(360-F))),w>=20&&A.arc(E.pageX,E.pageY,20,h,g)),A.stroke(),m=Math.min(E.pageX-20,m),n=Math.min(E.pageY-20,n),o=Math.max(E.pageX+20,o),p=Math.max(E.pageY+20,p)}else t.push(c("turnto "+b(y)));t.push(c("fd "+b(w))),t.push("end at "+b(C.x)+", "+b(C.y)),A.strokeStyle="red",A.fillStyle="red",Rc(A,2,E.pageX,E.pageY,C.pageX,C.pageY),m=Math.min(C.pageX,m),n=Math.min(C.pageY,n),o=Math.max(C.pageX,o),p=Math.max(C.pageY,p)}A.restore()}i.css({left:0,top:0}).html(t.join("
")).show();var G={left:"",top:"",right:"",bottom:""};C.pageX+5click to use'),null!=q.x&&t.push(q.x+", "+q.y),i.html(t.join("
")).css({left:"",top:"",right:0,bottom:0}).show()})}}();var If=null,Jf="see",Kf="0.2",Lf=Sd(Jf),Mf="position:relative;display:block;font-family:monospace;font-size:16px;word-break:break-all;margin-bottom:3px;padding-left:1em;",Nf=5,Of=!1,Pf="body",Qf="auto";try{Ie.self!==Ie.top&&screen.width>=800&&screen.height>=600&&parent&&parent.ide&&(Qf=parent.ide.getOptions().panel)}catch(Rf){}var Sf,Tf=Qf,Uf="",Vf=null,Wf="_loghistory",Xf=50,Yf="",Zf={"":{e:Ie.eval,t:Ie},top:{e:Ie.eval,t:Ie}},$f=Ie.CoffeeScript,_f="(function(){return eval(arguments[0]);})",ag="[_$a-zA-Z -￿][_$a-zA-Z0-9 -￿]*",bg=new RegExp("^\\s*var\\s+(?:"+ag+"\\s*,\\s*)*"+ag+"\\s*;\\s*"),cg="input._log:focus{outline:none;}samp._logcaret{position:absolute;left:0;font-size:120%;}samp._logcaret:before{content: '>'}label._log > span:first-of-type:hover{text-decoration:underline;}samp._log > label._log,samp_.log > span > label._log{display:inline-block;vertical-align:top;}label._log > span:first-of-type{margin-left:2em;text-indent:-1em;}label._log > ul{display:none;padding-left:14px;margin:0;}label._log > span:before{content:'';font-size:70%;font-style:normal;display:inline-block;width:0;text-align:center;}label._log > span:first-of-type:before{content:'\\0025B6';}label._log > ul > li{display:block;white-space:pre-line;margin-left:2em;text-indent:-1em}label._log > ul > li > samp{margin-left:-1em;text-indent:0;white-space:pre;}label._log > input[type=checkbox]:checked ~ span{margin-left:2em;text-indent:-1em;}label._log > input[type=checkbox]:checked ~ span:first-of-type:before{content:'\\0025BC';}label._log > input[type=checkbox]:checked ~ span:before{content:'';}label._log,label._log > input[type=checkbox]:checked ~ ul{display:block;}label._log > span:first-of-type,label._log > input[type=checkbox]:checked ~ span{display:inline-block;}label._log > input[type=checkbox],label._log > input[type=checkbox]:checked ~ span > span{display:none;}",dg=!1,eg={"\x00":"\\0","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r"," ":"\\t","\x0B":"\\v","'":"\\'",'"':'\\"',"\\":"\\\\"},fg=null,gg=[];Sf=function(){Vf&&"function"==typeof Vf.log&&Vf.log.apply(Ie.console,arguments);var a=Array.prototype.slice.call(arguments);for(gg.push('');a.length;){var b=a.shift();"String"==Xd(b)?gg.push(he(b)):gg.push(le(b,Nf,gg)),a.length&&gg.push(" ")}gg.push(""),oe()};var hg=!1,ig=!1,jg=null,kg=[{}.undefined],lg=null,mg=null}}).call(this,this.jQuery); \ No newline at end of file From 762c74b32db8e189b9d57a46cc4b269b0cb3a546 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Fri, 15 Jan 2016 00:20:14 +0100 Subject: [PATCH 178/180] Increased version number to 2.0.9 and adjusted example. --- BUILD.txt | 2 -- README.md | 6 +++--- bower.json | 2 +- jquery-turtle.js | 4 ++-- package.json | 2 +- turtle.jquery.json | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/BUILD.txt b/BUILD.txt index 6208a91..c22f4af 100644 --- a/BUILD.txt +++ b/BUILD.txt @@ -29,5 +29,3 @@ Testing Unit tests use a headless webkit. When setting it up on a system without GUI, you may find that you need to install font support. On debian. "apt-get libfontconfig1" provides enough font support. - - diff --git a/README.md b/README.md index 8af96ab..fe5db8e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ jQuery-turtle ============= -version 2.0.8 +version 2.0.9 jQuery-turtle is a jQuery plugin for turtle graphics. @@ -254,7 +254,7 @@ the functions:
 eval $.turtle()  # Create the default turtle and global functions.
 
-defaultspeed Infinity
+speed Infinity
 write "Catch blue before red gets you."
 bk 100
 r = hatch red
@@ -264,7 +264,7 @@ tick 10, ->
   fd 6
   r.turnto turtle
   r.fd 4
-  b.turnto bearing b
+  b.turnto direction b
   b.fd 3
   if b.touches(turtle)
     write "You win!"
diff --git a/bower.json b/bower.json
index f2e71d7..4eca85a 100644
--- a/bower.json
+++ b/bower.json
@@ -2,7 +2,7 @@
   "name": "jquery-turtle",
   "main": "jquery-turtle.js",
   "ignore": [],
-  "version": "2.0.8",
+  "version": "2.0.9",
   "description": "Turtle graphics plugin for jQuery.",
   "devDependencies": {
     "jquery": "latest",
diff --git a/jquery-turtle.js b/jquery-turtle.js
index b09f1ad..d2b6823 100644
--- a/jquery-turtle.js
+++ b/jquery-turtle.js
@@ -4,7 +4,7 @@
 jQuery-turtle
 =============
 
-version 2.0.8
+version 2.0.9
 
 jQuery-turtle is a jQuery plugin for turtle graphics.
 
@@ -261,7 +261,7 @@ the functions:
 
 eval $.turtle()  # Create the default turtle and global functions.
 
-defaultspeed Infinity
+speed Infinity
 write "Catch blue before red gets you."
 bk 100
 r = new Turtle red
diff --git a/package.json b/package.json
index 208270b..339f358 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "jquery-turtle",
-  "version": "2.0.8",
+  "version": "2.0.9",
   "description": "Turtle graphics plugin for jQuery.",
   "main": "jquery-turtle.js",
   "scripts": {
diff --git a/turtle.jquery.json b/turtle.jquery.json
index be5c732..cd8b8a3 100644
--- a/turtle.jquery.json
+++ b/turtle.jquery.json
@@ -14,7 +14,7 @@
         "audio",
         "collision"
     ],
-    "version": "2.0.8",
+    "version": "2.0.9",
     "author": {
         "name": "David Bau",
         "url": "http://davidbau.com/"

From 4d0e33b2eae8db92bb8d3e74fadfda6d0d5d938e Mon Sep 17 00:00:00 2001
From: David Bau 
Date: Tue, 9 Feb 2016 16:54:06 -0500
Subject: [PATCH 179/180] Repair ability to use arbitrary elements as turtles.

---
 jquery-turtle.js     | 6 +++---
 jquery-turtle.min.js | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/jquery-turtle.js b/jquery-turtle.js
index d2b6823..05e4676 100644
--- a/jquery-turtle.js
+++ b/jquery-turtle.js
@@ -9121,11 +9121,11 @@ function hatchone(name, container, defaultshape) {
 
   // Create an image element with the requested name.
   var result;
-  if (img) {
+  if (isTag) {
+    result = $(name);
+  } else if (img) {
     result = $('');
     applyImg(result, img);
-  } else if (isTag) {
-    result = $(name);
   } else {
     result = $('
' + escapeHtml(name) + '
'); } diff --git a/jquery-turtle.min.js b/jquery-turtle.min.js index 2718667..f66ba22 100644 --- a/jquery-turtle.min.js +++ b/jquery-turtle.min.js @@ -1,5 +1,5 @@ (function(a){function b(b,c){if(Ne>5)Oe.push({elem:b,qname:c});else{for(Ne+=1,a.dequeue(b,c);Oe.length>0;){var d=Oe.shift();a.dequeue(d.elem,d.qname),Vc()}Ne-=1}}function c(a,b){function c(){this.constructor=a}for(var d in b)Je.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a}function d(b){var c,d,e=b.charAt(0).toUpperCase()+b.slice(1),f=["Moz","Webkit","O","ms"],g=document.createElement("div");if(b in g.style)d=b;else for(var h=0;h<0?'"'+a+'"':"'"+a+"'":a}var c=[];for(var d in a)a.hasOwnProperty(d)&&c.push(d+":"+b(a[d])+";");return c.join(" ")}function h(a){return a}function i(a,b){var c=[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1]];return 6==a.length&&(c[0]+=a[4],c[1]+=a[5]),c}function j(a,b){var c=[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1],a[0]*b[2]+a[2]*b[3],a[1]*b[2]+a[3]*b[3]],d=6==a.length;return 6==b.length?(c.push(a[0]*b[4]+a[2]*b[5]+(d?a[4]:0)),c.push(a[1]*b[4]+a[3]*b[5]+(d?a[5]:0))):d&&(c.push(a[4]),c.push(a[5])),c}function k(a){return Math.abs(a)>1e-12}function l(a){return!(k(a[1])||k(a[2])||k(1-a[0])||k(1-a[3]))}function m(a){if(l(a))return[1,0,0,1];var b=u(a);return k(b[2])?j(o(-b[3]),j(p(1/b[1],1/b[2]),o(-b[0]))):null}function n(a){var b=m(a);if(4==a.length)return b;var c=i(b,[-a[4],-a[5]]);return b.push(c[0]),b.push(c[1]),b}function o(a){var b=Math.cos(a),c=Math.sin(a);return[b,c,-c,b]}function p(a,b){return 1==arguments.length&&(a=b),[a,0,0,b]}function q(a,b){return[a[0]+b[0],a[1]+b[1]]}function r(a,b){return[a[0]-b[0],a[1]-b[1]]}function s(a,b){return[a[0]*b,a[1]*b]}function t(a,b,c){return q(i(a,r(b,c)),c)}function u(a){var b,c,d=a[0]*a[0]+a[1]*a[1],e=a[0]*a[2]+a[1]*a[3],f=a[2]*a[2]+a[3]*a[3],g=-.5*Math.atan2(2*e,d-f),h=Math.cos(g),i=Math.sin(g),j=a[0]*h-a[2]*i,k=a[1]*h-a[3]*i,l=Math.atan2(k,j),m=Math.cos(l),n=Math.sin(l),o=(a[1]*i+a[3]*h)*m-(a[0]*i+a[2]*h)*n,p=(a[0]*h-a[2]*i)*m+(a[1]*h-a[3]*i)*n;return g<-Math.PI/4?(g+=Math.PI/2,c=p,b=o,l-=Math.PI/2):(b=p,c=o),l>Math.PI&&(l-=2*Math.PI),[l,b,c,g]}function v(a,b){var c=(b-a)/2,d=Math.cos(c),e=Math.sin(c),f=d,g=-e,h=1+f*d+g*e,i=f*e-g*d,j=i&&4/3*(Math.sqrt(2*h)-h)/i,k=f-j*g,l=g+j*f,m=k,n=-l,o=c+a,p=Math.cos(o),q=Math.sin(o);return[[k*p-l*q,k*q+l*p],[m*p-n*q,m*q+n*p],[Math.cos(b),Math.sin(b)]]}function w(a){var b=ra(a,!1);if(b)return[b.tx,b.ty];var c=x(a);return c?[c[4],c[5]]:[0,0]}function x(b){var c=Ie.getComputedStyle?Ie.getComputedStyle(b)[Pe]:a.css(b,"transform");if(!c||"none"===c)return null;var d=/^matrix\(([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+)(?:px)?,\s*([\-+.\de]+)(?:px)?\)$/.exec(c);return d?[parseFloat(d[1]),parseFloat(d[2]),parseFloat(d[3]),parseFloat(d[4]),parseFloat(d[5]),parseFloat(d[6])]:A(c)}function y(b,c){var d,e,f,g="none"===a.css(b,"display");if(g){d={position:"absolute",visibility:"hidden",display:"block"},e={};for(f in d)e[f]=b.style[f],b.style[f]=d[f]}var h=Ie.getComputedStyle?Ie.getComputedStyle(b):null;if(g)for(f in d)b.style[f]=e[f];var i=h&&h[Qe]||a.css(b,"transformOrigin");if(i&&i.indexOf("%")<0)return a.map(i.split(" "),parseFloat);if(c)return[c[0]/2,c[1]/2];var j=a(b);return[j.width()/2,j.height()/2]}function z(a){for(var b,c=[1,0,0,1];null!==a;)b=x(a),b&&!l(b)&&(c=j(b,c)),a=a.parentElement;return c.slice(0,4)}function A(b){var c=[1,0,0,1],d=[],e=[],f=/(?:^\s*|)(\w*)\s*\(([^)]*)\)\s*/g,g=b.replace(f,function(b){return d.push(b[1].toLowerCase()),e.push(a.map(b[2].split(","),function(a){var b=a.trim().toLowerCase();return{num:parseFloat(b),unit:b.replace(/^[+-.\de]*/,"")}})),""});if(g)return null;for(var h=d.length-1;h>=0;--h){var i,k,l,m=null,n=d[h],o=e[h];if("matrix"==n)o.length>=6&&(m=[o[0].num,o[1].num,o[2].num,o[3].num,o[4].num,o[5].num]);else if("rotate"==n)1==o.length&&(i=wa(o[0]),k=Math.cos(i),l=Math.sin(i),m=[k,-l,k,l]);else if("translate"==n||"translatex"==n||"translatey"==n){var p=0,q=0;if(o.length>=1){if(o[0].unit&&"px"!=o[0].unit)return null;if("translate"==n||"translatex"==n?p=o[0].num:"translatey"==n&&(q=o[0].num),"translate"==n&&o.length>=2){if(o[1].unit&&"px"!=o[1].unit)return null;q=o[1].num}m=[0,0,0,0,p,q]}}else if("scale"==n||"scalex"==n||"scaley"==n){var r=1,s=1;o.length>=1&&("scale"==n||"scalex"==n?r=o[0].num:"scaley"==n&&(s=o[0].num),"scale"==n&&o.length>=2&&(s=o[1].num),m=[r,0,0,s,0,0])}else{if("skew"!=n&&"skewx"!=n&&"skewy"!=n)return null;var t=0,u=0;o.length>=1&&("skew"==n||"skewx"==n?t=Math.tan(wa(o[0])):"skewy"==n&&(u=Math.tan(wa(o[0]))),"skew"==n&&o.length>=2&&(u=Math.tan(wa(o[0]))),m=[1,u,t,1,0,0])}c=j(c,m)}return c}function B(a,b,c){if(0>=c)return a;var d=b.pageX-a.pageX,e=b.pageY-a.pageY,f=d*d+e*e;if(c*c>=f)return b;var g=c/Math.sqrt(f);return{pageX:a.pageX+g*d,pageY:a.pageY+g*e}}function C(a,b,c){if(0>=c)b=a;else if(180>c){var d=xa(b-a);d>c?b=a+c:-c>d&&(b=a-c)}return xa(b)}function D(a,b,c,d){return{pageX:Math.floor(a+c/2),pageY:Math.floor(b+d/2)}}function E(a,b,c,d){var e=a+c,f=b+d;return[{pageX:a,pageY:b},{pageX:a,pageY:f},{pageX:e,pageY:f},{pageX:e,pageY:b}]}function F(a){if(!/e[\-+]/.exec(a))return a;var b=a.replace(/(?:\d+(?:\.\d*)?|\.\d+)e[\-+]\d+/g,function(a){return sa(parseFloat(a))});return b}function G(b,c,d){var e=a.data(b,"turtleData");if(e&&e.quickhomeorigin&&e.down&&e.style&&!d&&b.classList&&b.classList.contains("turtle"))return e.quickhomeorigin;var f,g,h,i="none"===a.css(b,"display"),j=i?{position:"absolute",visibility:"hidden",display:"block"}:{},k=(j[Pe]=c?"matrix("+a.map(c,sa).join(", ")+", 0, 0)":"none",{});for(f in j)k[f]=b.style[f],b.style[f]=j[f];g=N(b),h=y(b,[g.width,g.height]);for(f in j)b.style[f]=F(k[f]);d&&(d.gbcr=g,d.localorigin=h);var l=q([g.left,g.top],h);return e&&e.down&&e.style&&(e.quickhomeorigin=l),l}function H(){return Ie.innerHeight||a(Ie).height()}function I(){return Ie.innerWidth||a(Ie).width()}function J(){return document.body?a(document).height():document.height}function K(){return document.body?a(document).width():document.width}function L(a){return a.offsetHeight<=0&&a.offsetWidth<=0}function M(a,b,c,d){return{left:a,top:b,right:a+c,bottom:b+d,width:c,height:d}}function N(b){return mb(b)?M(b.pageX,b.pageY,0,0):a.isWindow(b)?M(a(Ie).scrollLeft(),a(Ie).scrollTop(),I(),H()):9===b.nodeType?M(0,0,K(),J()):"getBoundingClientRect"in b?T.apply(b):M(0,0,0,0)}function O(a,b,c){var d=Math.max(0,Math.max(c.top-a.pageY,a.pageY-c.bottom)),e=Math.max(0,Math.max(c.left-a.pageX,a.pageX-c.right));return e*e+d*d>b}function P(a,b,c){var d=Math.max(c.bottom-a.pageY,a.pageY-c.top),e=Math.max(c.right-a.pageX,a.pageX-c.left);return b>e*e+d*d}function Q(a,b){return b.right=a.top&&b.bottom<=a.bottom&&b.left>=a.left&&b.right<=a.right}function S(a,b){return 4===a.length&&a[0].pageX===b.left&&a[0].pageY===b.top&&a[1].pageX===b.left&&a[1].pageY===b.bottom&&a[2].pageX===b.right&&a[2].pageY===b.bottom&&a[3].pageX===b.right&&a[3].pageY===b.top}function T(){var a=this.getBoundingClientRect();return{top:a.top+Ie.pageYOffset,bottom:a.bottom+Ie.pageYOffset,left:a.left+Ie.pageXOffset,right:a.right+Ie.pageXOffset,width:a.width,height:a.height}}function U(b,c,d,e,f){var g,h,j,k,l=z(b.parentElement),n=m(l),o=G(b,n);if(n){if(a.isNumeric(d)&&(j=w(b),g=q(i(l,j),o),h={pageX:g[0],pageY:g[1]},c=B(h,c,d)),k=i(n,r([c.pageX,c.pageY],o)),e||f){var p=Gc(b);k[0]+=e*p,k[1]-=f*p}return sa(k[0])+" "+sa(k[1])}}function V(a){var b=a.offsetParent;return b?b:document}function W(b,c){c||(c=a(V(b)).pagexy());var d=z(b.parentElement),e=m(d),f=G(b,e),g=ra(b,!0),h=e&&i(e,r([c.pageX,c.pageY],f)),j=1/Gc(b);if(e)return[(g.tx-h[0])*j,(h[1]-g.ty)*j]}function X(b,c){var d,e,f=z(b.parentElement),g=(ra(b,!0),a(V(b)).pagexy()),h=Gc(b),j=[];for(e=0;ei-f&&(d=i-f),f>d&&(d=f),e>j-g&&(e=j-g),g>e&&(e=g);var l={pageX:d,pageY:e};a.isNumeric(c)&&(l=B(k.origin(),l,c)),k.scrollLeft(l.pageX-f),k.scrollTop(l.pageY-g)}function ca(a,b,c){var d=b.pageX-a.pageX,e=b.pageY-a.pageY,f=c.pageX-a.pageX,g=c.pageY-a.pageY;return f*e-d*g}function da(a,b,c){var d=c.pageX-a.pageX,e=c.pageY-a.pageY;return d*b.pageY-b.pageX*e}function ea(a,b){if(b.length<=0)return!1;if(1==b.length)return b[0].pageX==a.pageX&&b[0].pageY==a.pageY;var c=ca(a,b[b.length-1],b[0]);if(0===c)return!0;var d=c>0;if(2==b.length)return!1;for(var e=1;e0!=d)return!1}return!0}function fa(a,b){return{pageX:a.pageX-b.pageX,pageY:a.pageY-b.pageY}}function ga(a,b,c,d){var e,f,g=fa(c,b);for(e=0;e0?1:0>a?-1:0}function ia(a){if(a.length<=2)return 0;var b=ca(a[a.length-1],a[0],a[1]);if(0!==b)return ha(b);for(var c=1;c1&&1!=ha(ca(a[a.length-2],a[a.length-1],b));)a.pop();return a.length&&d(a[a.length-1],b)||a.push(b),a}function c(a,b,c){for(var d=0;db.pageX?1:a.pageYb.pageY?1:0}a.sort(e);var f=c(a,[],b),g=c(a.reverse(),[],b);return f.concat(g.slice(1,-1))}function ma(b){if(!b)return null;if(a.isArray(b))return b;for(var c=a.map(b.trim().split(/\s+/),parseFloat),d=[],e=0;e+1180&&(b-=360),b}function wa(a){return a/180*Math.PI}function xa(a){return Math.abs(a)>180&&(a%=360,a>180?a-=360:-180>=a&&(a+=360)),a}function ya(a){return Math.abs(a)>=720&&(a=a%360+(a>0?360:-360)),a}function za(){return Re.field||Ba(),Re.field}function Aa(){return Re.surface||Ba(),Re.surface}function Ba(){var b=document.createElement("samp"),c=document.createElement("samp"),d=Math.floor(I()/2),e=Math.floor(H()/2);a(b).css({position:"absolute",display:"inline-block",top:0,left:0,width:"100%",height:"100%",font:"inherit",zIndex:-1,transformOrigin:d+"px "+e+"px",pointerEvents:"none",overflow:"hidden"}).addClass("turtlefield"),a(c).attr("id","origin").css({position:"absolute",display:"inline-block",top:e,left:d,width:"100%",height:"0",font:"inherit",transformOrigin:"0px 0px",pointerEvents:"all",turtleSpeed:1/0}).appendTo(b),Re.surface=b,Re.field=c,Ca(),Sb()}function Ca(){document.body?(null==a("html").attr("style")&&a("html").css("min-height","100%"),a(Re.surface).prependTo("body"),Rb()):a(document).ready(Ca)}function Da(a){return a.drawOnCanvas||(a.drawOnCanvas=Fa()),a.drawOnCanvas}function Ea(b){var c=a.data(b,"turtleData");return c?c.drawOnCanvas?c.drawOnCanvas:Re.canvas:null}function Fa(){if(Re.canvas)return Re.canvas;var b=Aa();return Re.canvas=document.createElement("canvas"),a(Re.canvas).css({"z-index":-1}),b.insertBefore(Re.canvas,b.firstChild),Ja(),Ha(Ja),a(Ie).resize(Ja),Re.canvas}function Ga(a,b){return Re.offscreen&&Re.offscreen.width===a&&Re.offscreen.height===b?(Re.offscreen.getContext("2d").clearRect(0,0,a,b),Re.offscreen):(Re.offscreen||(Re.offscreen=document.createElement("canvas")),Re.offscreen.width=a,Re.offscreen.height=b,Re.offscreen)}function Ha(b){var c=a("body"),d=c.width(),e=c.height(),f=function(){(c.width()!=d||c.height()!=e)&&(b(),d=c.width(),e=c.height())};Re.timer&&clearInterval(Re.timer),Re.timer=setInterval(f,250)}function Ia(){var b=a("body");return[Math.max(b.outerWidth(!0),Ie.innerWidth||a(Ie).width()),Math.max(b.outerHeight(!0),Ie.innerHeight||a(Ie).height())]}function Ja(){if(Re.canvas){var b,c=Ia(),d=c[0],e=c[1],f=Re.canvas.width,g=Re.canvas.height,h=Math.max(Math.min(2e3,Math.max(200,f)),100*Math.ceil(d/100))*Re.subpixel,i=Math.max(Math.min(2e3,Math.max(200,f)),100*Math.ceil(e/100))*Re.subpixel;a(Re.surface).css({width:d+"px",height:e+"px"}),(f!=h||g!=i)&&(b=document.createElement("canvas"),b.width=Math.min(f,h),b.height=Math.min(g,i),b.getContext("2d").drawImage(Re.canvas,0,0),Re.canvas.width=h,Re.canvas.height=i,Re.canvas.getContext("2d").drawImage(b,0,0),a(Re.canvas).css({width:h/Re.subpixel,height:i/Re.subpixel}))}}function Ka(a,b){if(!a)return null;if(a&&"function"==typeof a&&(a.helpname||a.name)&&(a=a.helpname||a.name),a=String(a),a.trim&&(a=a.trim()),!a||"none"===a)return null;if("path"===a||"fill"===a)return{savePath:!0};var c=!1;/^erase\b/.test(a)&&(a=a.replace(/^erase\b/,"white; globalCompositeOperation:destination-out"),c=!0);var d=f(a,b);return c&&(d.eraseMode=!0),d}function La(a){return a?g(a):"none"}function Ma(a){return"down"==a||a===!0?!0:"up"==a||a===!1?!1:He}function Na(a){return a?"down":"up"}function Oa(b){var c=a.data(b,"turtleData");return c||(c=a.data(b,"turtleData",{style:null,corners:[[]],path:[[]],down:!1,speed:"turtle",easing:"swing",turningRadius:0,drawOnCanvas:null,quickpagexy:null,quickhomeorigin:null,oldscale:1,instrument:null,stream:null})),c}function Pa(b){var c=a.data(b,"turtleData");return c?c.turningRadius:0}function Qa(){return{get:function(a,b,c){return sa(Pa(a))+"px"},set:function(a,b){var c=parseFloat(b);if(!isNaN(c)&&(Oa(a).turningRadius=c,a.style.turtleTurningRadius=""+sa(c)+"px",0===c)){var d=ra(a,!1);d&&(d.rot>180||d.rot<=-180)&&(d.rot=xa(d.rot),a.style[Pe]=ta(d))}}}}function Ra(){return{get:function(a,b,c){return La(Oa(a).style)},set:function(a,b){var c=Ka(b,"strokeStyle"),d=Oa(a);d.style&&(d.style=null,bb(a,d,!0)),d.style=c,a.style.turtlePenStyle=La(c),bb(a,d,!0)}}}function Sa(){return{get:function(a,b,c){return Na(Oa(a).down)},set:function(a,b){var c=Ma(b);if(c!==He){var d=Oa(a);c!=d.down&&(d.down=c,d.quickpagexy=null,d.quickhomeorigin=null,a.style.turtlePenDown=Na(c),bb(a,d,!0))}}}}function Ta(a,b){return 0===Math.round(a.pageX-b.pageX)&&0===Math.round(a.pageY-b.pageY)}function Ua(a,b){return 0===Math.round(1e3*(a.pageX-b.pageX))&&0===Math.round(1e3*(a.pageY-b.pageY))}function Va(a,b){return Ta(a,b)&&0===Math.round(a.pageX-b.pageX1)&&0===Math.round(a.pageY-b.pageY1)&&0===Math.round(b.pageX2-b.pageX)&&0===Math.round(b.pageY2-b.pageY)}function Wa(a){var b=1e3*a,c=Math.round(b);return Math.abs(c-b)1){h=b[j],f=h.length>2&&Ta(h[0],h[h.length-1])&&!Ta(h[0],h[Math.floor(h.length/2)]),g=f&&!("pageX2"in h[h.length-1]);var k=h[0].pageX,l=h[0].pageY;i.moveTo(k,l);for(var m=1;m1&&(d.length=1),d[0].length&&(d[0].length=0),void(c&&(e?f.length&&f[0].length&&(1==f[0].length?f[0].length=0:f.unshift([])):(f.length>1&&(f.length=1),f[0].length&&(f[0].length=0))));if(c||!e.savePath){var g=Y(a);if(c&&(g.corner=!0,ab(f[0],g)),!e.savePath){ab(d[0],g);var h=Kc(a);$a(Da(b),b.path,e,h,2)}}}}function cb(b,c){var d=Oa(b);d.style&&(c=a.extend({},d.style,c));var e=Kc(b);$a(Da(d),d.corners,c,e,1)}function db(b){if((!b||/\bcanvas\b/.test(b))&&Re.canvas){var c=Re.canvas.getContext("2d");c.save(),c.setTransform(1,0,0,1,0,0),c.clearRect(0,0,Re.canvas.width,Re.canvas.height),c.restore()}if((!b||/\bturtles\b/.test(b))&&Re.surface){var d=a(Re.surface).find(".turtle").not(".turtlefield");pf&&(d=d.not(pf)),d.remove()}if((!b||/\blabels\b/.test(b))&&Re.surface){var d=a(Re.surface).find(".turtlelabel").not(".turtlefield");d.remove()}(!b||/\btext\b/.test(b))&&a("body").contents().not(".turtlefield").remove()}function eb(a,b){if(!a||a.length<1)return null;for(var c=1,d={left:Math.floor(a[0].pageX),top:Math.floor(a[0].pageY),right:Math.ceil(a[0].pageX),bottom:Math.ceil(a[0].pageY)};c=c[3]/2)return!0}else{var o=!c;for(m=0;m0==o)return!0}return!1}function hb(a,b,c){if(b.img)("CANVAS"==a[0].tagName||a[0].tagName==b.img.tagName)&&Kb(b.img,a[0],b.css);else if("IMG"==a[0].tagName||"CANVAS"==a[0].tagName)Hb(a[0],b.url,b.css,c),c=null;else{var d={backgroundImage:"url("+b.url+")",backgroundRepeat:"no-repeat",backgroundPosition:"center"};b.css.width&&b.css.height&&(d.backgroundSize=b.css.width+"px "+b.css.height+"px"),a.css(d)}c&&c()}function ib(b,c,d){var e,f=ra(b,!0),g=f&&wa(f.rot),h=Gc(b),i=f&&c*h,j=f&&(d||0)*h,k=-Math.cos(g)*i,l=Math.sin(g)*i,m=a.data(b,"turtleData");f&&(d&&(k+=Math.sin(g)*j,l+=Math.cos(g)*j),m&&(e=m.quickpagexy)&&(m.quickpagexy={pageX:e.pageX+l,pageY:e.pageY+k}),f.tx+=l,f.ty+=k,b.style[Pe]=ta(f),bb(b,m,!0))}function jb(b,c,d){var e,f=ra(b,!0),g=a.data(b,"turtleData");f&&(g&&(e=g.quickpagexy)&&(g.quickpagexy={pageX:e.pageX+c,pageY:e.pageY-d}),f.tx+=c,f.ty-=d,b.style[Pe]=ta(f),bb(b,g,!0))}function kb(a,b){var c=ra(a,!0);c&&(c.rot+=b,a.style[Pe]=ta(c))}function lb(a,b,c){var d=ra(a,!0);if(d){var e=Gc(a),f=wa(d.rot),g=b*e,h=(c||0)*e,i=-Math.cos(f)*g,j=Math.sin(f)*g;return h&&(i+=Math.sin(f)*h,j+=Math.cos(f)*h),sa(d.tx+j)+" "+sa(d.ty+i)}}function mb(b){return b&&a.isNumeric(b.pageX)&&a.isNumeric(b.pageY)}function nb(){return{get:function(a,b,c){return Oa(a).speed},set:function(b,c){(a.isNumeric(c)&&!(0>=c)||c in a.fx.speeds||""+c=="Infinity")&&(Oa(b).speed=""+c)}}}function ob(){return{get:function(a,b,c){return Oa(a).easing},set:function(b,c){c in a.easing&&(Oa(b).easing=c)}}}function pb(b,c){var d=a.data(b,"turtleData");return c=c||Df,d?a.isNumeric(d.speed)||"Infinity"==d.speed?1e3/d.speed:"turtle"==d.speed&&c?0:d.speed:c?0:"turtle"}function qb(b){var c=a.data(b,"turtleData");return c?c.easing:null}function rb(){return{get:function(a,b,c){var d=ra(a,b),e=y(a);if(d){var f=wa(d.rot),g=Math.cos(f),h=Math.sin(f),i=Gc(a);return sa(((d.tx+e[0])*h-(d.ty+e[1])*g)/i)+"px"}},set:function(b,c){var d,e=ra(b,!0)||{tx:0,ty:0,rot:0,sx:1,sy:1,twi:0},f=y(b),g=Gc(b),h=parseFloat(c)*g,i=wa(e.rot),j=Math.cos(i),k=Math.sin(i),l=(e.tx+f[0])*j+(e.ty+f[1])*k,m=l*j+h*k-f[0],n=l*k-h*j-f[1],o=a.data(b,"turtleData");o&&(d=o.quickpagexy)&&(o.quickpagexy={pageX:d.pageX+(m-e.tx),pageY:d.pageY+(n-e.ty)}),e.tx=m,e.ty=n,b.style[Pe]=ta(e),bb(b,o)}}}function sb(b,c,d,e){return{get:function(a,c,e){var f=ra(a,c);return f?f[b]+d:void 0},set:function(d,f){var g,h=ra(d,!0)||{tx:0,ty:0,rot:0,sx:1,sy:1,twi:0},i={displace:e},j=a.data(d,"turtleData"),k=h.tx,l=h.ty;h[b]=c(f,d,h,i),d.style[Pe]=ta(h),i.displace?(j&&(g=j.quickpagexy)&&(j.quickpagexy={pageX:g.pageX+(h.tx-k),pageY:g.pageY+(h.ty-l)}),bb(d,j)):Z(d)}}}function tb(a,b,c){var d=ya(b-a),e=d>0?c:-c,f=wa(a),g=[Math.cos(f)*e,Math.sin(f)*e],h=wa(b);return{delta:d,sradius:e,dc:g,dx:g[0]-Math.cos(h)*e,dy:g[1]-Math.sin(h)*e}}function ub(a,b,c,d,e,f){var g,h,j,k,l,m,n,o,p,r,t,u=tb(c,d,e),w=u.sradius,x=u.dc;for(n=1,o=u.delta,p=Math.abs(u.delta),p>45&&(n=Math.ceil(p/45),o=u.delta/n),r=[];--n>=0;)g=0===n?d:c+o,h=wa(c+180),j=wa(g+180),r.push.apply(r,v(h,j)),c=g;for(t=[],k=0;k2||(j.length>=1&&(i[c]=parseFloat(j[0])),j.length>=2?i[d]=parseFloat(j[1]):e?i[d]=0:i[d]=i[c],b.style[Pe]=ta(i),e?(k&&(h=k.quickpagexy)&&(k.quickpagexy={pageX:h.pageX+(i.tx-l),pageY:h.pageY+(i.ty-m)}),bb(b,k)):Z(b))}}}function Ab(a){return Se.href=a,Se}function Bb(a){return Ab(a).href}function Cb(a){return/(?:^|\.)pencil(?:code)?\./i.test(a)}function Db(a){var b=Ab(null==a?"":a).hostname,c=/^(\w+)\.pencil(?:code)?\./i.exec(b);return c?c[1]:null}function Eb(a,b){var c=Ab(null==a?"":a),d=c.href;return Cb(c.hostname)?/^\/(?:edit|home|code|load|save)(?:\/|$)/.test(c.pathname)&&(d=c.protocol+"//"+c.host+"/"+b+"/"+c.pathname.replace(/\/[^\/]*(?:\/|$)/,"")+c.search+c.hash):Cb(Ie.location.hostname)&&(d="/proxy/"+d),d}function Fb(a){return/\//.test(a)?a:(a="/img/"+a,Cb(Ie.location.hostname)?a:"//pencilcode.net"+a)}function Gb(){if(!document.cookie)return null;for(var a=document.cookie.split(/;\s*/),b=0;b0&&b.height>0)try{e=c.getContext("2d"),e.clearRect(0,0,b.width,b.height),e.drawImage(b,0,0)}catch(i){}}else c.src=b.src;d&&g.css(d);var j=y(c);if(b&&!d.turtleHull)try{var k=De(b);Ge(k,parseFloat(g.css("width"))/b.width,parseFloat(g.css("height"))/b.height,-j[0],-j[1]),g.css("turtleHull",k)}catch(i){}Lb(c,f,j)}function Lb(b,c,d){var e=a(b);if(e.hasClass("turtle")&&(d[0]!=c[0]||d[1]!=c[1]))if("absolute"==e.css("position")&&/px$/.test(e.css("left"))&&/px$/.test(e.css("top")))e.css("left",parseFloat(e.css("left"))+c[0]-d[0]),e.css("top",parseFloat(e.css("top"))+c[1]-d[1]);else{var f=ra(b,!0);f.tx+=c[0]-d[0],f.ty+=c[1]-d[1],b.style[Pe]=ta(f)}}function Mb(b,c,d,e,f){var g,h,i,j,k;return e===He&&f===He?(g=a(d),g.length?(h=g[0],i=N(h),S(_(h),i)?b.filter(function(){var b=N(this);return c===(R(i,b)||!Q(i,b)&&a(this).inside(h))}):b.filter(function(){return c===a(this).inside(h)})):[]):(j=a.isNumeric(e)&&a.isNumeric(f)?[e,f]:e,a.isArray(j)&&(j=X(b[0]||document.body,[j])[0]),"touch"===d?mb(j)?b.filter(function(){return c===a(this).touches(j)}):(g=a(j),i=N(g[0]),S(_(g[0]),i)?b.filter(function(){var a=N(this);return c===(!Q(i,a)&&(R(i,a)||g.touches(this)))}):b.filter(function(){return c===g.touches(this)})):(k=d*d,b.filter(function(){var a=N(this);if(O(j,k,a))return!c;if(P(j,k,a))return c;var b=Y(this),d=j.pageX-b.pageX,e=j.pageY-b.pageY;return c===k>=d*d+e*e})))}function Nb(){if(!Ze){Ze=!0;try{Ie.parent.document.activeElement.blur()}catch(a){}Ie.focus()}}function Ob(b,c,d,e,f){for(var g=e.split(/\s+/),h=0;h<0?void 0:c.apply(this,arguments)};c.guid&&(d.guid=c.guid),a.handler=d}}function Wb(){Ob(a.event.fixHooks,"filter",a.event.keyHooks,"keydown keyup",Tb),Ob(a.event.fixHooks,"filter",a.event.keyHooks,"keypress",Ub),Ob(a.event.special,"add",{},"keydown keyup keypress",Vb)}function Xb(){return{get:function(a,b,c){return g(Zb(a).getTimbre())},set:function(a,b){Zb(a).setTimbre(f(b,"wave"))}}}function Yb(){return{get:function(a,b,c){return Zb(a).getVolume()},set:function(a,b){Zb(a).setVolume(parseFloat(b))}}}function Zb(b){var c=Oa(b);if(c.instrument)return c.instrument;c.instrument=new ef("piano");var d=a(b);return c.instrument.on("noteon",function(b){var c=a.Event("noteon");c.midi=b.midi,d.trigger(c)}),c.instrument.on("noteoff",function(b){var c=a.Event("noteoff");c.midi=b.midi,d.trigger(c)}),c.instrument}function $b(){return af||(af=new ef),af}function _b(){return!(!Ie.AudioContext&&!Ie.webkitAudioContext)}function ac(){if(ac.audioTop)return ac.audioTop;if(!_b())return null;var a=new(Ie.AudioContext||Ie.webkitAudioContext);return ac.audioTop={ac:a,wavetable:jc(a),out:null,currentStart:null},bc(),ac.audioTop}function bc(){if(ac.audioTop){var a=ac.audioTop;a.out&&(a.out.disconnect(),a.out=null,a.currentStart=null);try{var b=a.ac.createDynamicsCompressor();b.ratio=16,b.attack=5e-4,b.connect(a.ac.destination),a.out=b}catch(c){ac.audioTop=null}}}function cc(){var a=ac();return null!=a.currentStart?a.currentStart:(a.currentStart=Math.max(.25,a.ac.currentTime),setTimeout(function(){a.currentStart=null},0),a.currentStart)}function dc(a){return 440*Math.pow(2,(a-69)/12)}function ec(a){return Math.round(69+12*Math.log(a/440)/Math.LN2)}function fc(a){var b=/^(\^+|_+|=|)([A-Ga-g])([,']*)$/.exec(a);if(!b)return null;var c=b[3].replace(/,/g,"").length-b[3].replace(/'/g,"").length,d=bf[b[2]]+cf[b[1].charAt(0)]*b[1].length+12*c;return d+60}function gc(a){var b=(a-72)%12;(a>60||0!=b)&&(b+=12);for(var c=Math.round((a-b-60)/12),d=df[b];0!=c;)d+=c>0?"'":",",c+=c>0?-1:1;return d}function hc(a){return dc(fc(a))}function ic(a){function b(a,b){switch(a){case"V":A!==z&&c(b.split(" ")[0]);break;case"M":f(b,A);break;case"L":g(b,A);break;case"Q":h(b,A)}A.hasOwnProperty(a)?A[a]+="\n"+b:A[a]=b,"K"==a&&(B=k(b),A===z&&c(d()))}function c(a){a=a||"",(a||A===z)&&(z.voice||(z.voice={}),z.voice.hasOwnProperty(a)?(A=z.voice[a],C=A.accent):(A={id:a,accent:{slurred:0}},z.voice[a]=A,C=A.accent))}function d(){return z.V?z.V.split(/\s+/)[0]:""}function e(a){var e,f=a.match(gf),g=null,h=0,i=0,j=null;if(!f)return null;for(;h/.test(f[h]))i=f[h++].length;else if(/^\(\d+(?::\d+)*/.test(f[h]))j=o(f[h++]);else if(/^[!+].*[!+]$/.test(f[h]))p(f[h++],C);else if(/^.?".*"$/.test(f[h]))h++;else if(/^[()]$/.test(f[h]))"("==f[h++]?C.slurred+=1:(C.slurred-=1,C.slurred<=0&&(C.slurred=0,A.stems&&A.stems.length>=1&&m(A.stems[A.stems.length-1],!1)));else if(/\|/.test(f[h])){for(e in C)1==e.length&&delete C[e];h++}else g=q(f,h,B,C),null!==g?(j&&(n(g.stem,j.time),j.count-=1,j.count||(j=null)),i&&A.stems&&A.stems.length&&(e=i>0?(1-Math.pow(.5,i))*g.stem.time:(Math.pow(.5,-i)-1)*A.stems[A.stems.length-1].time,l(A.stems[A.stems.length-1],e),l(g.stem,-e)),i=0,C.slurred&&m(g.stem,!0),A===z&&c(d()),"stems"in A||(A.stems=[]),A.stems.push(g.stem),h=g.index):h++}function f(a,b){var c=/^C/.test(a)?1:t(a);c&&(b.unitnote||(.75>c?b.unitnote=1/16:b.unitnote=1/8))}function g(a,b){var c=t(a);c&&(b.unitnote=c)}function h(a,b){var c,d=a.split(/\s+|=/),e=null,f=null;for(c=0;c=0||/^[1-4]$/.test(d[c])?e=e||t(d[c]):f=f||Number(d[c]);e&&(b.unitbeat=e),f&&(b.tempo=f)}function i(a){var b,c,d,e,f,g={};for(c=0;c0)for(b=0;a>b&&7>b;++b)d[c.charAt(b)]="^";else for(b=0;b>a&&b>-7;--b)d[c.charAt(6+b)]="_";return d}function k(a){if(!a)return{};var b,c={"c#":7,"f#":6,b:5,e:4,a:3,d:2,g:1,c:0,f:-1,bb:-2,eb:-3,ab:-4,db:-5,gb:-6,cb:-7,"a#m":7,"d#m":6,"g#m":5,"c#m":4,"f#m":3,bm:2,em:1,am:0,dm:-1,gm:-2,cm:-3,fm:-4,bbm:-5,ebm:-6,abm:-7,"g#mix":7,"c#mix":6,"f#mix":5,bmix:4,emix:3,amix:2,dmix:1,gmix:0,cmix:-1,fmix:-2,bbmix:-3,ebmix:-4,abmix:-5,dbmix:-6,gbmix:-7,"d#dor":7,"g#dor":6,"c#dor":5,"f#dor":4,bdor:3,edor:2,ador:1,ddor:0,gdor:-1,cdor:-2,fdor:-3,bbdor:-4,ebdor:-5,abdor:-6,dbdor:-7,"e#phr":7,"a#phr":6,"d#phr":5,"g#phr":4,"c#phr":3,"f#phr":2,bphr:1,ephr:0,aphr:-1,dphr:-2,gphr:-3,cphr:-4,fphr:-5,bbphr:-6,ebphr:-7,"f#lyd":7,blyd:6,elyd:5,alyd:4,dlyd:3,glyd:2,clyd:1,flyd:0,bblyd:-1,eblyd:-2,ablyd:-3,dblyd:-4,gblyd:-5,cblyd:-6,fblyd:-7,"b#loc":7,"e#loc":6,"a#loc":5,"d#loc":4,"g#loc":3,"c#loc":2,"f#loc":1,bloc:0,eloc:-1,aloc:-2,dloc:-3,gloc:-4,cloc:-5,floc:-6,bbloc:-7},d=a.replace(/\s+/g,"").toLowerCase().substr(0,5),e=d.match(/maj|min|mix|dor|phr|lyd|loc|m/);b=e?"maj"==e?d.substr(0,e.index):"min"==e?d.substr(0,e.index+1):d.substr(0,e.index+e[0].length):/^[a-g][#b]?/.exec(d)||"";var f=j(c[b]),g=a.substr(b.length).match(/(_+|=|\^+)[a-g]/gi);if(g)for(var h=0;h<2))switch(a=a.substring(1,a.length-1)){case"pppp":case"ppp":b.dynamics=.2;break;case"pp":b.dynamics=.4;break;case"p":b.dynamics=.6;break;case"mp":b.dynamics=.8;break;case"mf":b.dynamics=1;break;case"f":b.dynamics=1.2;break;case"ff":b.dynamics=1.4;break;case"fff":case"ffff":b.dynamics=1.5}}function q(a,b,c,d){var e,f,g,h,i=[],j="",k=!1,l=null,m=1/0;if(bf&&(j=e,m=f),b0&&"="==a.charAt(0)?a.substr(1):a}function s(a,b,c){var d,e=/^(\^+|_+|=|)([A-Ga-g])(.*)$/.exec(a);return e?(d=e[2].toUpperCase(),e[1].length>0?(c[d]=e[1],r(a)):r(c.hasOwnProperty(d)?c[d]+e[2]+e[3]:b.hasOwnProperty(d)?b[d]+e[2]+e[3]:a)):a}function t(a){var b,c,d,e=/^(\d*)(?:\/(\d*))?$|^(\/+)$/.exec(a),f=0;if(e){if(e[3])return Math.pow(.5,e[3].length);if(c=e[2]?parseFloat(e[2]):/\//.test(a)?2:1,d=0,b=e[1]?parseFloat(e[1]):1,e[2])for(;d+1c;)d+=1,f=parseFloat(e[1].substring(0,d)),b=parseFloat(e[1].substring(d));return f+b/c}}var u,v,w,x,y=a.split("\n"),z={},A=z,B={},C={slurred:0};for(u=0;uc;++c)e[c]=b.real[c],f[c]=b.imag[c];try{return a.createPeriodicWave(e,f)}catch(g){}try{return a.createWaveTable(e,f)}catch(g){}return null}function d(a,b,c){var d,e,f={real:[],imag:[]},g=a.real.length;for(d=0;g>d;++d)e=Math.log(b[Math.min(d,b.length-1)]),f.real.push(a.real[d]*Math.exp(c*e)),f.imag.push(a.imag[d]*Math.exp(c*e));return f}var e,f,g,h,i,j,k={};for(e in b)if(f=b[e],j=c(f)){if(i={wave:j},f.mult)for(h=b[e].freq,i.freq={},g=0;gc;++c)b[c]&&(b[c].constructor===a?e.push.apply(e,b[c].toArray()):a.isArray(b[c])?e.push.apply(e,b[c]):e.push(b[c]));return{elts:a.unique(e),completion:d}}function lc(){function b(){var a,b=g;for(g=null,f&&f(),a=0;a1)for(c=0;c")}function oc(b){var c,d,e=a.extend({},sf,hf,tf);if(b&&!a.isArray(b.helptext)&&b in e&&(b=e[b]),b&&a.isArray(b.helptext)&&b.helptext.length){for(d=0;d/g,'<$1 style="border:1px solid black;text-decoration:none;word-break:keep-all;white-space:nowrap">').replace(/<(mark)>/g,'<$1 style="border:1px solid blue;color:blue;text-decoration:none;word-break:keep-all;white-space:nowrap;cursor:pointer;" onclick="see.enter($(this).text())">'))}return vf}if("number"==typeof b)return nc("Equal to the number "+b+"."),vf;if("boolean"==typeof b)return nc("Equal to the boolean value "+b+"."),vf;if(null===b)return nc("The special null value represents the absence of a value."),vf;if(b===He)return nc("This is an unassigned value."),vf;if(b===Ie)return nc("The global window object represents the browser window."),vf;if(b===document)return nc("The HTML document running the program."),vf;if(b===jQuery)return nc('The jQuery function. Read about it at jquery.com.'),vf;if(b&&b!=oc)return nc("No help available for "+b),vf;c=[];for(var g in e)!e[g].helptext||!e[g].helptext.length||g in Ie&&"function"!=typeof Ie[g]||c.push(g);return c.sort(function(a,b){return a.length!=b.length?a.length-b.length:b>a?-1:a>b?1:0}),nc("help available for: "+c.map(function(a){return''+a+""}).join(" ")),vf}function pc(a){return 1==a.length&&qc(a[0])&&a[0]}function qc(b){var c;return b&&0==a.queue(b).length&&(!b.parentElement||!b.parentElement.style.transform)&&(0===(c=pb(b))||0===a.fx.speeds[c])}function rc(b,c){var d;if(null==c){if(qc(b))return;d=pb(b)}else d=1e3*c;var e=a(b);d>0&&e.delay(d)}function sc(a,b){if(b=b||0,a.length<=b)return null;var c=a[a.length-1];return"function"!=typeof c||c.helpname?null:c}function tc(b,c,d,e){function f(d,e){if(null!=d){var f=b&&b[d];e&&f&&bb(f,a.data(f,"turtleData"),!0),Hf.reportEvent("resolve",[c,m,j,d,f])}0==--k&&h&&(l?(Me+=1,setTimeout(function(){Me-=1,h()},0)):h())}function g(a){null!=a&&Hf.reportEvent("appear",[c,m,j,a,b&&b[a],i])}var h=sc(d,e),i=h?Array.prototype.slice.call(d,0,d.length-1):d,j=b?b.length||0:0,k=j+1,l=!0,m=Hf.nextId();return Hf.reportEvent("enter",[c,m,j,i]),{name:c,args:i,appear:g,resolve:f,resolver:function(a,b){return function(){f(a,b)}},exit:function(){Hf.reportEvent("exit",[c,m,j,i]),f(null),l=!1}}}function uc(b,c,d,e){var f=function(){if(Vc(b),Le)throw new Error(b+" interrupted");var d,f=tc(this,b,arguments,c),g=[f].concat(a.makeArray(f.args));try{d=e.apply(this,g)}finally{f.exit()}return d};return zc(b,d,f)}function vc(a,b,c){var d=function(){if(Vc(a),Le)throw new Error(a+" interrupted");return Wc(a,this),c.apply(this,arguments)};return zc(a,b,d)}function wc(b,c,d,e){var f=function(){if(Vc(b),Le)throw new Error(b+" interrupted");var c=null,f=0,g=pf;if(e&&(c=e.apply(null,arguments),f=arguments.length,g=Yc()),g){var h=a(pf).eq(0),i=arguments,j=tc(h,b,arguments,f);h.plan(function(a,b){j.appear(a),d.apply(c,i),this.plan(j.resolver(a))}),j.exit()}else j=tc(null,b,arguments,f),d.apply(c,arguments),j.exit();return c?(c.result&&c.result.constructor===jQuery&&pf&&lc(pf,c.result),c.result):void 0};return zc(b,c,f)}function xc(b,c){return zc(b,c,function(c,d){var e=/^key/.test(b),f=/^mouse|click$/.test(b),g=f?"input,button":e?"textarea,input:not([type]),input[type=text],input[type=password]":null;e&&Nb(),null==d&&"function"==typeof c&&(d=c,c=null),a(Ie).on(b+".turtleevent",null,c,g?function(b){return Le||a(b.target).closest(g).length?void 0:d.apply(this,arguments)}:d)})}function yc(){var b=a._data(Ie,"events");if(!b)return!1;for(var c in b)for(var d=b[c],e=0;eb?-c:c,l=null;i.style&&i.down&&(l=function(){var a=Y(h),c=ra(h,!0),d=z(h.parentElement);return function(){ub(i.corners[0],a,c.rot,c.rot+(e?-b:b),k*(i.oldscale?c.sy:1),d)}}()),i.turningRadius=k,this.animate({turtleRotation:g+sa(b)+"deg"},pb(h,f),qb(h)),this.plan(function(){l&&l(),i.turningRadius=j,a.resolve(d,!0)})}),this)}function Bc(a,b){null==b&&(b=100),"bk"===a.name&&(b=-b);var c,d=Df;return(c=pc(this))?(a.appear(0),ib(c,b,0),a.resolve(0,!0),this):(this.plan(function(c,e){a.appear(c),this.animate({turtleForward:"+="+sa(b||0)+"px"},pb(e,d),qb(e),a.resolver(c,!0))}),this)}function Cc(b,c,d){a.isArray(c)&&(d=c[1],c=c[0]),d||(d=0),c||(c=0);var e=Df;return this.plan(function(a,f){b&&b.appear(a),this.animate({turtlePosition:lb(f,d,c)},pb(f,e),qb(f),b&&b.resolver(a,!0))}),this}function Dc(b,c,d){a.isArray(c)&&(d=c[1],c=c[0]),d||(d=0),c||(c=0);var e,f=Df;return(e=pc(this))?(b&&b.appear(0),jb(e,c,d),b&&b.resolve(0),this):(this.plan(function(a,e){b&&b.appear(a);var g=w(e);this.animate({turtlePosition:sa(g[0]+c)+" "+sa(g[1]-d)},pb(e,f),qb(e),b&&b.resolver(a,!0))}),this)}function Ec(b,c,d){var e=c,f=0,g=0,h=null,i=Df;return a.isNumeric(e)&&a.isNumeric(d)?(f=parseFloat(e),g=parseFloat(d),e=null,h=null):a.isArray(e)?(f=e[0],g=e[1],e=null,h=d):a.isNumeric(d)&&(h=d),this.plan(function(c,d){var j=e;if(null===j&&(j=a(V(d)).pagexy()),j&&!mb(j))try{j=a(j).pagexy()}catch(k){return}return j&&mb(j)?a.isWindow(d)?(b&&b.appear(c),ba(j,h),void(b&&b.resolve(c))):void(9!==d.nodeType&&(b&&b.appear(c),this.animate({turtlePosition:U(d,j,h,f,g)},pb(d,i),qb(d),b&&b.resolver(c,!0)))):void 0}),this}function Fc(a){return function(b,c,d){return this.plan(function(e,f){b.appear(e);var g=this.css("turtlePenDown");this.css({turtlePenDown:"up"}),a.call(this,null,c,d),this.plan(function(){this.css({turtlePenDown:g}),b.resolve(e,!0)})}),this}}function Gc(b){var c=a.data(b,"turtleData");return c&&null!=c.oldscale?c.oldscale:1}function Hc(a,b,c){Jc.call(this,!0,a,b,c)}function Ic(a,b,c){Jc.call(this,!1,a,b,c)}function Jc(b,c,d,e){e===He&&(e=d),d&&e||(d=e=1);var f=Df;return this.plan(function(g,h){if(b&&(Oa(h).oldscale*=e),c.appear(g),a.isWindow(h)||9===h.nodeType)return void c.resolve(g);var i=a.map(a.css(h,"turtleScale").split(" "),parseFloat);1===i.length&&i.push(i[0]),i[0]*=d,i[1]*=e,this.animate({turtleScale:a.map(i,sa).join(" ")},pb(h,f),qb(h),c.resolver(g))}),this}function Kc(a,b){var c=z(a.parentElement),d=l(c),e=d?1:u(c)[1];return e*Gc(a)}function Lc(b){var c=Df;return function(d,e,f){if(a.isNumeric(e)){var g=e;e=f,f=g}return null==f&&(f=8.8),this.plan(function(g,h){var i=Oa(h),j=i.style;e||(e=j&&(j.fillStyle||j.strokeStyle)||"black"),d.appear(g);var k=this.pagexy(),l=ra(h,!0),m=Ka(e,"fillStyle"),n=Da(i),o=Kc(h),p=f*o,q=Math.max(0,p-2),r=p+(m.eraseMode?2:0),s=/rgba|hsla/.test(m.fillStyle);null==m.lineWidth&&j&&j.lineWidth&&(m.lineWidth=j.lineWidth),pc(this)?(b(n,k,r,l.rot,m,!0),d.resolve(g)):this.queue(function(e){a({radius:0}).animate({radius:q},{duration:pb(h,c),step:function(){s||b(n,k,this.radius,l.rot,m,!1)},complete:function(){b(n,k,r,l.rot,m,!0),d.resolve(g),e()}})})}),this}}function Mc(a,b,c,d,e){var f=a.getContext("2d");f.save(),Xa(f,e),c===1/0?(f.setTransform(1,0,0,1,0,0),f.fillRect(0,0,a.width,a.height)):(Za(f,a),f.beginPath(),f.arc(b.pageX,b.pageY,c/2,0,2*Math.PI,!1),f.closePath(),f.fill(),e.strokeStyle&&f.stroke()),f.restore()}function Nc(a,b,c,d,e){var f=a.getContext("2d");if(f.save(),Xa(f,e),c===1/0)f.setTransform(1,0,0,1,0,0),f.fillRect(0,0,a.width,a.height);else{var g=Math.sin((d+45)/180*Math.PI),h=Math.cos((d+45)/180*Math.PI),i=c*h/Math.SQRT2,j=c*g/Math.SQRT2;Za(f,a),f.beginPath(),f.moveTo(b.pageX-i,b.pageY-j),f.lineTo(b.pageX-j,b.pageY+i),f.lineTo(b.pageX+i,b.pageY+j),f.lineTo(b.pageX+j,b.pageY-i),f.closePath(),f.fill(),e.strokeStyle&&f.stroke()}f.restore()}function Oc(a,b,c,d,e,f){var g=a.getContext("2d");if(g.save(),Xa(g,e),!e.strokeStyle&&e.fillStyle&&(g.strokeStyle=e.fillStyle),c!==1/0){var h=Math.sin(d/180*Math.PI),i=-Math.cos(d/180*Math.PI),j=e.lineWidth||1.62,k=b.pageX+c*h,l=b.pageY+c*i,m=Pc(j,k,l,h,i),n=c-m.hs,o=n*h,p=n*i;Za(g,a),n>0&&(g.beginPath(),g.moveTo(b.pageX,b.pageY),g.lineTo(b.pageX+o,b.pageY+p),g.stroke()),f&&Qc(g,m)}g.restore()}function Pc(a,b,c,d,e){var f=Math.max(1.25*a,a+2),g=2*f,h=g-f/2;return{hs:h,x1:b,y1:c,xm:b-d*h,ym:c-e*h,x2:b-e*f-d*g,y2:c+d*f-e*g,x3:b+e*f-d*g,y3:c-d*f-e*g}}function Qc(a,b){a.beginPath(),a.moveTo(b.x2,b.y2),a.lineTo(b.x1,b.y1),a.lineTo(b.x3,b.y3),a.quadraticCurveTo(b.xm,b.ym,b.x2,b.y2),a.closePath(),a.fill()}function Rc(a,b,c,d,e,f){var g=e-c,h=f-d,i=Math.sqrt(g*g+h*h),j=g/i,k=h/i,l=Pc(b,e,f,j,k);i>l.hs&&(a.beginPath(),a.moveTo(c,d),a.lineTo(l.xm,l.ym),a.lineWidth=b,a.stroke()),Qc(a,l)}function Sc(a,b){function c(){d&&clearInterval(d),b&&b()}var d=null;if(!Ie.speechSynthesis)return console.log("No speech synthesis: "+a),void c();try{var e=new Ie.SpeechSynthesisUtterance(a);e.addEventListener("end",c),e.addEventListener("error",c),e.lang=navigator.language||"en-GB",Ie.speechSynthesis.speak(e),d=setInterval(function(){Ie.speechSynthesis.pending||Ie.speechSynthesis.speaking||c()},250)}catch(f){Ie.console&&Ie.console.log(f),c()}}function Tc(){a.each(["toggle","show","hide"],function(b,c){var d=a.fn[c];a.fn[c]=function(b,c,e){var f=arguments;!f.length&&this.hasClass("turtle")&&(this.length>1||!a._data(this[0],"fxshow"))&&(f=[0]),d.apply(this,f)}})}function Uc(c,d){if(d||(d="fx"),"IMG"==c.tagName&&c.src&&!c.complete){var e=a.queue(c,d);0==e.length&&(a.queue(c,d,function(a){Ib(c,null,a)}),b(c,d))}}function Vc(b){if(!(a.turtle.hangtime==1/0||kf++<100)){kf=0;var c=(new Date).getTime();return mf?void(c-mf>a.turtle.hangtime&&(Sf.visible()&&Sf.html('Oops: program interrupted because it was hanging the browser. Try reducing the number of repetitions. Or try using await done defer() or tick to make an animation.'),a.turtle.interrupt("hung"))):(mf=c,clearTimeout(lf),void(lf=setTimeout(function(){clearTimeout(lf),lf=null,mf=null},0)))}}function Wc(b,c){if(!a.turtle.nowarn){var d,e=!0;for(d=0;e&&d=100&&(e=!1);e||(jf[b]||(jf[b]=1,Sf.visible()?Sf.html('Oops: '+b+' may not return useful results when motion is queued. Try speed Infinity or await done defer() first.'):console.warn(b+' may not return useful results when motion is queued. Try "speed Infinity" or "await done defer()".')),c.finish())}}function Xc(a,b,d){a[b]=function(){return b in jf||(Sf.html(''+b+" deprecated. Use "+d+"."),jf[b]=1),a[d].apply(this,arguments)},a[d].__super__&&c(a[b],a[d])}function Yc(){return pf&&a.queue(pf).length>0}function Zc(a){if(null===uf.pollTimer){var b=uf.sent[a],c=uf.waiting[a];c&&c.length&&b&&b.length&&(uf.pollTimer=setTimeout(function(){uf.pollTimer=null,c&&c.length&&b&&b.length&&(c.shift().apply(null,b.shift()),Zc(a))},0))}}function $c(a,b){if("function"!=typeof b&&"undefined"!=typeof b||!/^\w+\s*$/.test(a)){if("undefined"==typeof b&&/^help\s+\S+$/.test(a))return oc(/^help\s+(\S+)$/.exec(a)[1]),!0}else{if(b&&b.helptext)return oc(b),!0;if(a in tf)return oc(a),!0}return!1}function _c(a,b,c,d){return a.helptext?d.helptext=a.helptext:b in c&&(d.helptext=c[b].helptext),d.method=a,d.helpname=b,d}function ad(a,b){var c=[];for(var d in b)!b.hasOwnProperty(d)||d in Ie||(c.push(d),Ie[d]=function(b){var c=a[b],d=a;return _c(c,b,tf,function(){return c.apply(d,arguments)})}(d));return c}function bd(){pf=null;for(var a=0;a=0;i--)null===h[i]?(i--,c.moveTo(d-h[i][0],e+h[i][1])):c.lineTo(d-h[i][0],e+h[i][1])}return c.lineWidth=1.1,c.strokeStyle="rgba(255,255,255,0.75)",c.stroke(),c.beginPath(),c.arc(d,e,15.5,0,2*Math.PI,!1),c.closePath(),c.strokeStyle="rgba(0,0,0,0.4)",c.stroke(),b.toDataURL()}function gd(a){var b=Ga(40,48),c=b.getContext("2d");return c.beginPath(),c.moveTo(0,48),c.lineTo(20,0),c.lineTo(40,48),c.lineTo(20,36),c.closePath(),c.fillStyle=a,c.fill(),b.toDataURL()}function hd(a){var b=Ga(40,40),c=b.getContext("2d");return c.beginPath(),c.arc(20,20,18,-5*Math.PI/2,-Math.PI/2),c.closePath(),c.lineTo(20,20),c.stroke(),c.strokeStyle=a,c.lineWidth=4,c.stroke(),b.toDataURL()}function id(a,b){b=b||12;var c=Ga(b,b),d=c.getContext("2d"),e=b/2;return d.beginPath(),d.arc(e,e,e,0,2*Math.PI),d.closePath(),d.fillStyle=a,d.fill(),c.toDataURL()}function jd(a){function b(){e.moveTo(19.5,43),e.lineTo(20.5,43),e.lineTo(21.5,43.5),e.lineTo(25.5,36.2),e.lineTo(24,35.5),e.lineTo(23,35.5),e.lineTo(20.5,36.5),e.lineTo(19.5,36.5),e.lineTo(17,35.5),e.lineTo(16,35.5),e.lineTo(14.5,36.2),e.lineTo(18.5,43.5),e.closePath()}function c(){e.moveTo(25.5,12),e.lineTo(25.5,8),e.lineTo(14.5,8),e.lineTo(14.5,12),e.closePath()}var d=Ga(40,48),e=d.getContext("2d");return e.beginPath(),b(),e.fillStyle="#ffcb6b",e.fill(),e.beginPath(),c(),e.fillStyle="#d1ebff",e.fill(),e.beginPath(),e.moveTo(19.5,48),e.lineTo(13,36),e.lineTo(13,1),e.lineTo(14,0),e.lineTo(26,0),e.lineTo(27,1),e.lineTo(27,36),e.lineTo(20.5,48),e.closePath(),b(),e.moveTo(25.5,12),e.lineTo(25.5,8),e.lineTo(14.5,8),e.lineTo(14.5,12),e.closePath(),e.fillStyle=a,e.fill(),d.toDataURL()}function kd(a,b,c){return c||(c=1),function(d){var e=document.createElement("canvas");e.width=a,e.height=b;var f=e.getContext("2d");d||(d="rgba(128,128,128,0.125)"),"transparent"!=d&&(f.fillStyle=d,f.fillRect(0,0,a,b));var g=a/c,h=b/c,i={width:g,height:h,transformOrigin:g/2+"px + "+h/2+"px",opacity:1};return 1>c&&(i.imageRendering="pixelated"),{img:e,css:i}}}function ld(a){if(!a)return null;if(a in yf)return yf[a];var b=a.match(/^(\d+)x(\d+)(?:\/(\d+))?$/);return b?kd(parseFloat(b[1]),parseFloat(b[2]),b[3]&&parseFloat(b[3])):null}function md(a,b){var c=a.width||a.height||256,d=a.height||a.width||256,e=a.subpixel||1/(a.scale||1),f=a.color||"transparent",g=kd(c,d,e);return g(f)}function nd(b,c){if(!b)return null;if(a.isPlainObject(b))return md(b,c);if(a.isFunction(b)&&(b.helpname||b.name)&&(b=b.helpname||b.name),b.constructor===jQuery){if(!b.length)return null;b=b.get(0)}if(b.tagName)return"CANVAS"!=b.tagName&&"IMG"!=b.tagName&&"VIDEO"!=b.tagName?null:{img:b,css:{opacity:1}};var d=b.toString().trim().split(/\s+/),e=null,f=null;if(d.length&&(f=ld(d[d.length-1]),f&&d.pop()),d.length&&cd(d.join(" "))&&(e=d.join(" "),d.length=0),!f&&e&&(f=ld(c)),f)return f(e);if(/\//.test(b)||(b=Fb(b)),/\//.test(b)){var g=Ab(b).hostname;return!Cb(g)&&Cb(Ie.location.hostname)&&(b=Ie.location.protocol+"//"+Ie.location.host+"/proxy/"+Bb(b)),{url:b,css:{transformOrigin:"50% 50%",opacity:1}}}return null}function od(a){return null==a?"":String(a).replace(/[&<>"]/g,function(a){return zf[a]})}function pd(b,c,d){var e=b&&/^[a-zA-Z]\w*$/.exec(b),f=b&&/^<.*>$/.exec(b),g=nd(b,d)||null==b&&nd(d);e&&a("#"+b).length&&(e=!1);var h;g?(h=a(""),hb(h,g)):h=a(f?b:"
"+od(b)+"
"),b&&"object"==typeof b&&("id"in b&&h.attr("id",b.id),"class"in b&&h.addClass(b["class"])),h.css({position:"absolute",display:"table",top:0,left:0}),(!c||9==c.nodeType||a.isWindow(c))&&(c=za()),h.appendTo(c);var i=y(h[0]);return h.css({top:-i[1],left:-i[0]}),h.css({turtlePosition:U(h[0],a(c).pagexy(),null,0,0),turtleRotation:0,turtleScale:1}),h.addClass("turtle"),e&&(h.attr("id",b),rf&&!Ie.hasOwnProperty(b)&&(Ie[b]=h)),h}function qd(a,b){if("number"==typeof a)return a=Math.ceil(a),"number"==typeof b?(b=Math.ceil(b),Math.floor(Math.random()*(b-a)+a)):Math.floor(Math.random()*a);if("object"==typeof a&&a&&a.length&&a.slice)return a[Math.floor(Math.random()*a.length)];if("normal"==a){var c,d,e,f,g;do c=Math.random(),d=1.7156*(Math.random()-.5),e=c-.449871,f=Math.abs(d)+.386595,g=e*e+f*(.196*f-.25472*e);while(g>.27597&&(g>.27846||d*d>-4*Math.log(c)*c*c));return d/c}return"position"==a?{pageX:qd(K()+1),pageY:qd(J()+1)}:"color"==a?"hsl("+Math.floor(360*Math.random())+",100%,50%)":"gray"==a?"hsl(0,0,"+Math.floor(100*Math.random())+"%)":a===!0?Math.random()>=.5:Math.random()}function rd(b,c){c||"function"!=typeof b||(c=b,b=30);var d=null,e=Math.max(Math.floor(1e3/Math.max(1/86400,b)),0);if(Yc()){var f=a(pf);f.plan(function(){d=c})}else d=c;var g={fn:c,timer:setInterval(function(){if(d)try{Df++,Bf.push(g),d()}finally{Df--,Bf.pop(g)}},e)};return Af.push(g),g.timer}function sd(a){for(var b=[],c=0;c0?1e3/b:0)}function wd(b){b===He&&(b=""),a("[id]").each(function(c,d){Ie[b+d.id]=a("#"+d.id)}),rf=!0}function xd(b){if(b===He&&(b="last"),Ef&&a(Ie).off(a.map(of,function(a,b){return b}).join(" "),Ef),b||""===b){Ef=function(a){var c,d=[b+a.type];for((a.originalEvent||a)instanceof MouseEvent&&d.push(b+"mouse"),c=0;c=a("html").outerHeight(!0);if(b(),e){var f=a(Ie).scrollTop(),g=Math.min(d,a("html").outerHeight(!0)-a(Ie).height());g>f&&a(Ie).scrollTop(g)}}function zd(){if(!Ff.timer){Ff.timer=setTimeout(function(){Ff.timer=null},0);var b=a("body").offset(),c=b?b.top:8;Ff.bottomSeen=Math.min(a(Ie).height()+a(Ie).scrollTop(),a("body").height()+c)}return Ff.bottomSeen}function Ad(b){var c=a(Ie).scrollTop();b(),a(Ie).scrollTop(c)}function Bd(){var a=document.body.lastChild;return a&&"PRE"==a.tagName||(a=document.createElement("pre"),document.body.appendChild(a)),a}function Cd(){var a=arguments;yd(function(){for(var b=Bd(),c=0;c").css({display:"inline-block",verticalAlign:"top",textAlign:"center",height:"1.2em",width:"1.2em",maxWidth:"1.2em",overflow:"hidden"}).appendTo(Bd()),e=function(){null!=b&&d.css({background:b}),null!=c&&d.text(c)};if(pf){var f=a(pf);Ec.call(f,null,d),f.eq(0).plan(e)}else e()}function Ed(){var a=this;yd(function(){a.result.appendTo("body"),a.setup&&a.setup()})}function Fd(b,c){var d="<"+c+' style="display:table">',e="";if(b===He||null===b)return{result:a(d+"
"+e)};if(b.jquery||b instanceof Element&&(b=a(b)))return{result:b};var f=null;return b=""+b,/^\s*<.*>\s*$/.test(b)&&(f=a(b)),(null==f||1!=f.length||1!=f[0].nodeType)&&(f=a(d+b+e)),{result:f}}function Gd(b,c){function d(b){return function(d){j||m&&"change"==d.type||(j=!0,a(this).prop("checked",!0),i.find("input[type=radio]").prop("disabled",!0),c(b))}}function e(a){return function(){j||(l=a,f())}}function f(a){a||(m+=1,setTimeout(function(){m-=1},0),i.find("input").eq(l).prop("checked",!0)),i.find("input").eq(l).focus()}function g(b,c){a.isFunction(b)&&(b=(k+1).toString());var f=a.isFunction(c)||null==c?b:c,g=a('').attr("value",f).on("change click",d(c)),h=a('