diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 00000000..1481f664 Binary files /dev/null and b/favicon.ico differ diff --git a/scripts/blocks.js b/scripts/blocks.js new file mode 100644 index 00000000..f49165b7 --- /dev/null +++ b/scripts/blocks.js @@ -0,0 +1,274 @@ +$.fn.extend({ + long_name: function() { + var names; + names = []; + this.each(function() { + var e, parts; + e = this; + parts = [e.tagName.toLowerCase()]; + e.id ? parts.push('#' + e.id) : null; + e.className ? parts.push('.' + e.className.split(/\s/).join('.')) : null; + return names.push(parts.join('')); + }); + return '[' + names.join(', ') + ']'; + }, + info: function(){ + return this.closest('.wrapper').long_name(); + }, + isSameNode: function(jqOrNode) { + var _a, other, self; + self = this.get(0); + other = (typeof (_a = jqOrNode.TEXT_NODE) !== "undefined" && _a !== null) ? jqOrNode.TEXT_NODE : (jqOrNode = jqOrNode.get(0)); + return self.isSameNode(other); + }, + matchesClass: function(jq, klass){ + return !(this.hasClass(klass) && !jq.hasClass(klass)); + }, + relOffset: function(){ + // this is horribly innefficient, I know + var canvas_pos = $('.scripts_workspace').offset(); + var this_pos = this.offset(); + return {left: this_pos.left - canvas_pos.left, top: this_pos.top - canvas_pos.top}; + }, + center: function() { + return this.each(function() { + var dx, dy, p, ph, pw, t; + t = $(this); + dx = t.outerWidth() / 2; + dy = t.outerHeight() / 2; + p = t.offsetParent(); + pw = p.innerWidth() / 2; + ph = p.innerHeight() / 2; + return t.css({ + position: 'absolute', + left: pw - dx, + top: ph - dy + }); + }); + } +}); + +function button(options){ + return $(''); +} + +function block(options){ + if (options.button){ + return button(options); + } + opts = $.extend({ + klass: 'control', + tab: true, // Something can come after + trigger: false, // This is the start of a handler + slot: true, // something can come before + containers: 0, // Something cannot be inside + label: 'Step', // label is its own mini-language + type: null + }, options); + if (opts.trigger){ + opts.slot = false; // can't have both slot and trigger + } + if (opts['type']){ + opts.tab = false; // values nest, but do not follow + opts.slot = false; + } + var wrapper = $('

'); + var block = wrapper.children(); + if (opts['type']){ + block.addClass(opts['type'].name.toLowerCase()); + wrapper.addClass('value').addClass(opts['type'].name.toLowerCase()); + } + if (opts.trigger){ + block.append(''); + }else if(opts.slot){ + block.append(''); + } + for (var i = 0; i < opts.containers; i++){ + block.append('
'); + } + if (opts.tab){ + wrapper.append(''); + } + return wrapper; +} + +function label(value){ + // Recognize special values in the label string and replace them with + // appropriate markup. Some values are dynamic and based on the objects currently + // in the environment + // + // values include: + // + // [flag] => the start flag image + // [key] => any valid key on the keyboard + // [sprite] => any currently defined sprite + // [number] => an empty number socket + // [number:default] => a number socket with a default value + // [boolean] => an empty boolean socket + // [boolean:default] => a boolean with a default value + // [string] => an empty string socket + // [string:default] => a string socket with a default value + // [message] => a message combo box + // [stop] => stop sign graphic /\[number:(-?\d*\.\d+)\]/ + value = value.replace(/\[number:(-?\d*\.?\d+)\]/g, '
'); + value = value.replace(/\[number\]/g, '
'); + value = value.replace(/\[boolean:(true|false)\]/g, '
'); + value = value.replace(/\[boolean\]/g, '
'); + value = value.replace(/\[string:(.+)\]/g, '
'); + value = value.replace(/\[string\]/g, '
'); + return value; +} + + +$('.content_wrap .wrapper').live('click', function(event) { + var copy = $(this).clone(); + $('.scripts_workspace').append(copy); + copy.center(); + $('#menu_overlay .close').click(); +}); + +$('.scripts_workspace .wrapper').live('dragstart', {handle: '.block'}, function(event, callback) { + var drag = $(this); + var pos; + if(drag.parent().is('.scripts_workspace')){ + pos = drag.position(); + }else{ + pos = drag.relOffset(); + } + if(drag.parent().is('.socket')){ + drag.siblings('input').show(); + } + if(!drag.parent().is('.scripts_workspace')){ + console.log('unparenting drag node'); + $('.scripts_workspace').append(this); + } + callback.offX = pos.left; + callback.offY = pos.top; + if(drag.parent().is('.socket')){ + drag.siblings('input').show(); + } + drag.css('z-index', 100); +}); + + +$('.scripts_workspace .wrapper').live('drag', {handle: '.block'}, function(event, callback){ + $(this).css({ + left: callback.offX + callback.deltaX, + top: callback.offY + callback.deltaY + }); +}); + +$('.scripts_workspace .wrapper').live('dragend', {handle: '.block'}, function(event, callback){ + $(this).css('z-index', 0); +}); + +$('.scripts_workspace .tab').live('dropstart', function(event, callback){ + // return false for any tab which is an inappropriate target + // console.log('tab dropstart for %s', $(this)); + var tab = $(this); + var self = tab.closest('.wrapper'); + var container = tab.parent(); // either .contained or .next + var drag = $(callback.drag); + if (container.find('.wrapper').length) { + // console.log('%s tab is filled'); + return false; + } + if($.contains(this.parentNode, callback.drag)){ + // console.log('drag target is already a child of parentNode'); + return false; + } + if($.contains(callback.drag, this)){ + // console.log('drag target is contained by this drag element'); + return false; + } + if(self.isSameNode(drag)){ + // console.log('drag target and dragged node are the same'); + return false; + } + if(drag.hasClass('value')){ + console.log('cannot attache a value block to a tab'); + return false; + } + // console.log('it appears to be OK to add %o to %s', callback.drag, $(this).info()); + $(this).css('border', '1px dashed red'); + $(event.drag).find('.slot').css('background-color', 'green'); + return true; +}); + +$('.scripts_workspace .tab').live('drop', function(event, callback) { + // console.log('tab drop'); + try{ + $(this).parent().append($(callback.drag).closest('.wrapper')); + $(callback.drag).closest('.wrapper').css({position: 'relative', top: 0, left: 0}); + }catch(e){ + console.log('What just happened? I was trying to append %o to %o', $(callback.drag).closest('.wrapper').get(), $(this).parent().get()); + } + $(this).css('border', ''); + $(event.drag).find('.slot').css('background-color', ''); + +}); + +$('.scripts_workspace .tab').live('dropend', function(event, callback) { + // console.log('tab dropend'); + $(this).css('border', 0); + $(callback.drag).find('.slot').css('background-color', ''); +}); + +$('.scripts_workspace .socket').live('dropstart', function(event, callback) { + // console.log('socket dropstart for %s', $(this)); + // console.log('drop targets: %o', event); + var socket = $(this); + var self = socket.closest('.wrapper'); + var drag = $(callback.drag); + if(socket.find('.wrapper').length){ + // console.log('Cannot drop on a socket that is already filled'); + return false; + } + if(!drag.is('.value')){ + // console.log('Cannot drop a non-value block on a socket'); + return false; + } + if($.contains(this, callback.drag)){ + // console.log('drag target is already in this socket'); + return false; + } + if($.contains(callback.drag, this)){ + // console.log('socket is contained by this drag element'); + return false; + } + if(self.isSameNode(drag)){ + // console.log('Cannot drag to the same node'); + return false; + } + if(!socket.matchesClass(drag, 'boolean')){ + // console.log('Can only add a boolean block to a boolean socket'); + return false; + } + if (!socket.matchesClass(drag, 'number')){ + // console.log('Can only add a number block to a number socket'); + return false; + } + if (!socket.matchesClass(drag, 'string')){ + // console.log('Can only add a string block to a string socket'); + return false; + } + // console.log('dropping %s on %s looks acceptable', $(this).info(), $(callback.drag).info()); + socket.find('input').css('border', '1px dashed red'); + socket.css('border', '1px dashed green'); + return true; +}); + +$('.scripts_workspace .socket').live('drop', function(event, callback) { + // console.log('socket drop'); + var socket = $(this); + var drag = $(callback.drag); + socket.find('input').css('border', '0px').hide(); + socket.css('border', ''); + socket.append(drag); + drag.css({position: 'relative', top: 0, left: 0, border: ''}); +}); + +$('.scripts_workspace .socket').live('dropend', function(event, callback) { + // console.log('socket dropend'); + // $(callback.drag).css('border', '1px solid white'); +}); diff --git a/scripts/dom.js b/scripts/dom.js new file mode 100644 index 00000000..b4548ae0 --- /dev/null +++ b/scripts/dom.js @@ -0,0 +1,137 @@ +var DomElem, Elements, HtmlElem, LeafElem, Leaves, _a, _b, _c, _d, _e, _f, elem, isEmpty, isIdClassString, isObject, isString, name, parseArgs, root; +var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice, __extends = function(child, parent) { + var ctor = function(){ }; + ctor.prototype = parent.prototype; + child.__superClass__ = parent.prototype; + child.prototype = new ctor(); + child.prototype.constructor = child; + }; +isIdClassString = function(str) { + return str && isString(str) && (str[0] === '#' || str[0] === '.'); +}; +isObject = function(obj) { + var _a; + return !(isString(obj) || (typeof (_a = obj.parseIdClass) !== "undefined" && _a !== null)); +}; +isString = function(obj) { + return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); +}; +isEmpty = function(obj) { + var _a, key; + _a = obj; + for (key in _a) { if (__hasProp.call(_a, key)) { + if (hasOwnProperty.call(obj, key)) { + return false;; + } + }} + return true; +}; +parseArgs = function() { + var args, attributes, idClassString; + var _a = arguments.length, _b = _a >= 1; + args = __slice.call(arguments, 0, _a - 0); + idClassString = args.length && isIdClassString(args[0]) ? args.shift() : null; + attributes = args.length && isObject(args[0]) ? args.shift() : {}; + return [idClassString, attributes, args]; +}; +DomElem = function(name, idClassString, attributes, children) { + this.name = name; + this.children = children; + this.attributes = attributes; + this.parseIdClass(idClassString); + return this; +}; +DomElem.prototype.parseIdClass = function(str) { + var names; + if (str) { + names = str.split('.'); + names[0][0] === '#' ? (this.id = names.shift().slice(1)) : null; + names.length && names[0] === '' ? names.shift() : null; + this.classes = names; + return this.classes; + } else { + this.id = null; + this.classes = []; + return this.classes; + } +}; +DomElem.prototype.attrsToString = function() { + var _a, atts, key, value; + atts = []; + if (this.id) { + atts.push("id=\"" + this.id + "\""); + } + if (!(isEmpty(this.classes))) { + atts.push(("class=\"" + (this.classes.join(' ')) + "\"")); + } + _a = this.attributes; + for (key in _a) { if (__hasProp.call(_a, key)) { + value = _a[key]; + atts.push("" + key + "=\"" + value + "\""); + }} + return atts.length ? ' ' + atts.join(' ') : ''; +}; +DomElem.prototype.childrenToString = function() { + var _a, _b, _c, _d, child, kids; + kids = (function() { + _a = []; _c = this.children; + for (_b = 0, _d = _c.length; _b < _d; _b++) { + child = _c[_b]; + _a.push(isString(child) ? child : child.toString()); + } + return _a; + }).call(this); + return kids.join(''); +}; +DomElem.prototype.toString = function() { + return "<" + this.name + (this.attrsToString()) + ">" + (this.childrenToString()) + ""; +}; + +LeafElem = function() { + return DomElem.apply(this, arguments); +}; +__extends(LeafElem, DomElem); +LeafElem.prototype.toString = function() { + return "<" + this.name + (this.attrsToString()) + "/>"; +}; + +HtmlElem = function() { + return DomElem.apply(this, arguments); +}; +__extends(HtmlElem, DomElem); +HtmlElem.prototype.toString = function() { + return "" + (HtmlElem.__superClass__.toString.call(this)); +}; + +elem = function(name, isLeaf) { + return function() { + var _c, args, attributes, children, idClassString; + var _a = arguments.length, _b = _a >= 1; + args = __slice.call(arguments, 0, _a - 0); + _c = parseArgs.apply(this, args); + idClassString = _c[0]; + attributes = _c[1]; + children = _c[2]; + if (name === 'html') { + return new HtmlElem(name, idClassString, attributes, children); + } else if (isLeaf) { + return new LeafElem(name, idClassString, attributes, children); + } else { + return new DomElem(name, idClassString, attributes, children); + } + }; +}; +Elements = ['a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdo', 'blockquote', 'body', 'button', 'canvas', 'caption', 'cite', 'code', 'colgroup', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'i', 'iframe', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'map', 'mark', 'menu', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 'samp', 'script', 'section', 'select', 'small', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'ul', 'variable', 'video']; +Leaves = ['area', 'col', 'base', 'br', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'wbr']; +root = (typeof exports !== "undefined" && exports !== null) ? exports : this; +root['html'] = elem('html'); +_b = Elements; +for (_a = 0, _c = _b.length; _a < _c; _a++) { + name = _b[_a]; + root[name] = elem(name, false); +} +_e = Leaves; +for (_d = 0, _f = _e.length; _d < _f; _d++) { + name = _e[_d]; + root[name] = elem(name, true); +} \ No newline at end of file diff --git a/scripts/scratch.js b/scripts/scratch.js new file mode 100644 index 00000000..2b4a727b --- /dev/null +++ b/scripts/scratch.js @@ -0,0 +1,167 @@ +$(function() { + return $('a[rel]').overlay({ + mask: 'darkred', + effect: 'apple', + onBeforeLoad: function() { + var klass = this.getTrigger().attr('class'); + var menu = menus[klass]; + return this.getOverlay() + .find('.content_wrap').empty() + .append(menus[this.getTrigger().attr('class')]); + } + }); +}); + +function log(msg){ + $('.workspace .stage').append('

' + msg + '

'); +} + +$('.scripts_workspace')[0].ontouchmove = function(event){ + rlog('cancelling a touch event'); + event.preventDefault(); + event.stopPropagation(true); +}; + +function menu(klass, specs){ + var col = $('
'); + var half = Math.round((specs.length + 1) / 2); + var col1 = col.find('td').eq(0); + var col2 = col.find('td').eq(1); + specs.forEach(function(spec, idx){ + spec.klass = klass; + if (idx < half){ + col1.append(block(spec)); + }else{ + col2.append(block(spec)); + } + }); + return col; +} + +var menus = { + motion: menu('motion', [ + {label: 'move [number: 10] steps'}, + {label: 'turn [clockwise] [number:15] degrees'}, + {label: 'turn [counterclockwise] [number:15] degrees'}, + {label: 'point in direction [degrees:90]'}, + {label: 'point towards [sprite_or_mouse]'}, + {label: 'go to x: [number:0] y: [number:0]'}, + {label: 'go to [sprite_or_mouse]'}, + {label: 'glide [number:1] secs to x: [number:0] y: [number:0]'}, + {label: 'change x by [number:10]'}, + {label: 'set x to [number:0]'}, + {label: 'change y by [number:10]'}, + {label: 'set y to [number:0]'}, + {label: 'if on edge, bounce'}, + {label: 'x position', 'type': Number}, + {label: 'y position', 'type': Number}, + {label: 'direction', 'type': Number} + ]), + looks: menu('looks', [ + {label: 'switch to costume [costume]'}, + {label: 'next costume'}, + {label: 'costume #', 'type': Number}, + {label: 'say [string:Hello!] for [number:2] secs'}, + {label: 'say [string:Hello!]'}, + {label: 'think [string:Hmm…] for [number:2] secs'}, + {label: 'think [string:Hmm…]'}, + {label: 'change [effect] effect by [number:25]'}, + {label: 'set [effect] effect to [number:0]'}, + {label: 'clear graphic effects'}, + {label: 'change size by [number:10]'}, + {label: 'set size to [number:100]%'}, + {label: 'size', 'type': Number}, + {label: 'show'}, + {label: 'hide'}, + {label: 'go to front'}, + {label: 'go back [number:1] layers'} + ]), + sound: menu('sound', [ + {label: 'play sound [sound_or_record]'}, + {label: 'play sound [sound_or_record] until done'}, + {label: 'stop all sounds'}, + {label: 'play drum [drum:48] for [number:0.2] beats'}, + {label: 'rest for [number:0.2] beats'}, + {label: 'play note [note:60] for [number:0.5] beats'}, + {label: 'set instrument to [instrument:1]'}, + {label: 'change volume by [number:-10]'}, + {label: 'set volume to [number:100]%'}, + {label: 'volume', 'type': Number}, + {label: 'change tempo by [number:20]'}, + {label: 'set tempo to [number:60] bpm'}, + {label: 'tempo', 'type': Number} + ]), + pen: menu('pen', [ + {label: 'clear'}, + {label: 'pen down'}, + {label: 'pen up'}, + {label: 'set pen color to [color_picker]'}, + {label: 'change pen color by [number:10]'}, + {label: 'set pen color to [number:0]'}, + {label: 'change pen shade by [number:10]'}, + {label: 'set pen shade to [number:50]'}, + {label: 'change pen size by [number:1]'}, + {label: 'set pen size to [number:1]'}, + {label: 'stamp'} + ]), + control: menu('control', [ + {label: 'when [flag] clicked', trigger: true}, + {label: 'when [key] key pressed', trigger: true}, + {label: 'when [sprite] clicked', trigger: true}, + {label: 'wait [number:1] secs'}, + {label: 'forever', containers: 1, tab: false}, + {label: 'repeat [number:10]', containers: 1}, + {label: 'broadcast [message]'}, + {label: 'broadcast [message] and wait'}, + {label: 'when I receive [message]', trigger: true}, + {label: 'forever if [boolean]', containers: 1, tab: false}, + {label: 'if [boolean]', containers: 1}, + {label: 'if [boolean] else', containers: 2}, + {label: 'wait until [boolean]'}, + {label: 'repeat until [boolean]'}, + {label: 'stop script'}, + {label: 'stop all [stop]', tab: false} + ]), + sensing: menu('sensing', [ + {label: 'touching [sprite_or_mouse_or_edge]?', 'type': Boolean}, + {label: 'touching [color_eyedropper]?', 'type': Boolean}, + {label: '[color_eyedropper] is touching [color_eyedropper]?', 'type': Boolean}, + {label: "[ask [string:What's your name?] and wait"}, + {label: 'answer', 'type': String}, + {label: 'mouse x', 'type': Number}, + {label: 'mouse y', 'type': Number}, + {label: 'mouse down', 'type': Boolean}, + {label: 'key [key] pressed?', 'type': Boolean}, + {label: 'distance to [sprite_or_mouse]', 'type': Number}, + {label: 'reset timer'}, + {label: 'timer', 'type': Number}, + {label: '[property] of [sprite_or_stage]', type: Number}, + {label: 'loudness', type: Number}, + {label: 'loud?', type: Boolean}, + {label: '[sensor] sensor value', type: Number}, + {label: 'sensor [button_or_connection]?', type: Boolean} + ]), + operators: menu('operators', [ + {label: '[number] + [number]', type: Number}, + {label: '[number] - [number]', type: Number}, + {label: '[number] * [number]', type: Number}, + {label: '[number] / [number]', type: Number}, + {label: 'pick random [number:1] to [number:10]', type: Number}, + {label: '[number] < [number]', type: Boolean}, + {label: '[number] = [number]', type: Boolean}, + {label: '[number] > [number]', type: Boolean}, + {label: '[boolean] and [boolean]', type: Boolean}, + {label: '[boolean] or [boolean]', type: Boolean}, + {label: 'not [boolean]', type: Boolean}, + {label: 'join [string:hello] [string:world]', type: String}, + {label: 'letter [number:1] of [string:world]', type: String}, + {label: 'length of [string:world]', type: Number}, + {label: '[number] mod [number]', type: Number}, + {label: 'round [number]', type: Number}, + {label: '[function] of [number:10]', type: Number} + ]), + variables: menu('variables', [ + {button: 'Make a variable'}, + {button: 'Make a list'} + ]) +}; \ No newline at end of file