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 + '' + defaulttag + '>';
- 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 = '' + tag + '>';
+ 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 = $('