From 1c32ecf0a5d8064e4fffd581c31eb8c6537b4de5 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Thu, 21 Jan 2016 15:32:41 -0500 Subject: [PATCH] Convert remaining modules --- src/editor/blocks/Block.js | 1027 ++++++++-------- src/editor/blocks/BlockArg.js | 803 ++++++------ src/editor/engine/Page.js | 967 ++++++++------- src/editor/engine/Runtime.js | 332 ++--- src/editor/engine/Sprite.js | 2174 +++++++++++++++++---------------- src/editor/engine/Stage.js | 1338 ++++++++++---------- src/editor/engine/Thread.js | 314 ++--- src/editor/ui/Scripts.js | 1293 ++++++++++---------- src/editor/ui/Scroll.js | 887 +++++++------- 9 files changed, 4628 insertions(+), 4507 deletions(-) diff --git a/src/editor/blocks/Block.js b/src/editor/blocks/Block.js index e2c85d8..074303c 100644 --- a/src/editor/blocks/Block.js +++ b/src/editor/blocks/Block.js @@ -1,519 +1,532 @@ -var Block = function (spec, isPalette, scale) { - this.div = document.createElement('div'); +import BlockSpecs from './BlockSpecs'; +import BlockArg from './BlockArg'; +import ScratchJr from '../ScratchJr'; +import {setProps, setCanvasSize, scaleMultiplier} from '../../utils/lib'; - // Top-level block parent shouldn't accept pointer events - setProps(this.div.style, { - pointerEvents: 'none' - }); +export default class Block { + constructor (spec, isPalette, scale) { + this.div = document.createElement('div'); - this.setBlockshapeFromSpecs(spec, isPalette, scale); - this.blockshape = document.createElement('canvas'); - setCanvasSize(this.div, this.getWidth() * this.scale, this.getHeight() * this.scale); - setCanvasSize(this.blockshape, this.getWidth() * this.scale * window.devicePixelRatio, - this.getHeight() * this.scale * window.devicePixelRatio); - this.addShadow(); - this.div.appendChild(this.blockshape); - setProps(this.blockshape.style, { - position: 'absolute', - left: '0px', - top: '0px', - webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + (-this.blockshape.height / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', - pointerEvents: 'all' - }); - this.addHighlight(); - this.drawBlock(); - setCanvasSize(this.div, this.blockshape.width / window.devicePixelRatio, - this.blockshape.height / window.devicePixelRatio); - if (this.isCaret) { - return; - } - this.createArgument(); - this.div.owner = this; -}; - -Block.prototype.getWidth = function () { - if (this.blocktype == 'repeat') { - return 176; - } - if (this.blocktype == 'gotopage') { - return 86; - } - if (this.aStart || this.anEnd) { - return 84; - } - return 76; -}; - -Block.prototype.getHeight = function () { - if (this.blocktype == 'repeat') { - return 82; - } - return 66; -}; - -Block.prototype.setBlockshapeFromSpecs = function (spec, isPalette, scale) { - this.spec = spec; - this.isReporter = (spec[1] == 'reporter'); - this.blocktype = spec[0]; - this.icon = spec[1]; - this.image = spec[2]; - this.aStart = (this.blocktype == 'caretstart') || (this.image == BlockSpecs.yellowStart); - this.anEnd = (this.blocktype == 'caretend') - || (this.image == BlockSpecs.redEnd) - || (this.image == BlockSpecs.redEndLong - ); - this.cShape = (this.blocktype == 'repeat') || (this.blocktype == 'caretrepeat'); - this.prev = null; - this.next = null; - this.inside = null; - this.isCaret = this.blocktype.indexOf('caret') > -1; - this.type = 'block'; - this.arg = null; - this.daddy = null; - this.scale = scale ? scale : 1; - this.repeatCounter = -1; - this.originalCount = -1; - this.threads = []; - this.inpalette = isPalette; - this.min = spec[6]; - this.max = spec[7]; - this.shadowimg = (this.spec.length < 9) ? null : spec[8]; - this.hrubberband = 0; - this.vrubberband = 0; - this.done = false; -}; - -Block.prototype.addShadow = function () { - this.shadow = document.createElement('canvas'); - this.div.appendChild(this.shadow); - setProps(this.shadow.style, { - position: 'absolute', - left: '1px', - top: '4px', - opacity: this.inpalette ? Settings.paletteBlockShadowOpacity : 1, - visibility: 'hidden', - webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + (-this.blockshape.height / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', - pointerEvents: 'all' - }); - setCanvasSize(this.shadow, this.blockshape.width, this.blockshape.height); - if (!this.shadowimg) { - return; - } - var ctx = this.shadow.getContext('2d'); - var img = this.shadowimg; - if (!img.complete) { - img.onload = function () { - ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, - img.width * this.scale * window.devicePixelRatio, img.height * this.scale * window.devicePixelRatio); - }; - } else { - ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, - img.width * this.scale * window.devicePixelRatio, img.height * this.scale * window.devicePixelRatio); - } -}; - -Block.prototype.lift = function () { - this.shadow.style.visibility = 'visible'; -}; - -Block.prototype.drop = function () { - this.shadow.style.visibility = 'hidden'; -}; - -Block.prototype.addHighlight = function () { - var img = this.spec[5]; - if (!img) { - return; - } - this.shine = document.createElement('canvas'); - this.div.appendChild(this.shine); - setCanvasSize(this.shine, this.blockshape.width, this.blockshape.height); - setProps(this.shine.style, { - position: 'absolute', - left: '0px', - top: '0px', - visibility: 'hidden', - webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + (-this.blockshape.height / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', - pointerEvents: 'all' - }); - var ctx = this.shine.getContext('2d'); - if (!img.complete) { - img.onload = function () { - ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, - img.width * this.scale * window.devicePixelRatio, img.height * this.scale * window.devicePixelRatio); - }; - } else { - ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, - img.width * this.scale * window.devicePixelRatio, img.height * this.scale * window.devicePixelRatio); - } -}; - -Block.prototype.drawBlock = function () { - var cnv = this.blockshape; - var ctx = this.blockshape.getContext('2d'); - ctx.clearRect(0, 0, cnv.width, cnv.height); - var me = this; - if (!this.image.complete) { - this.image.onload = function () { - me.drawBlockType(); - }; - } else { - this.drawBlockType(); - } -}; - -Block.prototype.drawBlockType = function () { - var ctx = this.blockshape.getContext('2d'); - ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0, - this.image.width * this.scale * window.devicePixelRatio, - this.image.height * this.scale * window.devicePixelRatio); - var icnv = document.createElement('canvas'); - this.blockicon = icnv; - this.div.appendChild(icnv); - setCanvasSize(icnv, this.blockshape.width, this.blockshape.height); - setProps(icnv.style, { - position: 'absolute', - left: '0px', - top: '0px', - webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + (-this.blockshape.height / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', - pointerEvents: 'all' - }); - if (this.icon && this.icon.tagName) { - this.drawIcon(); - } - this.done = true; -}; - -Block.prototype.updateBlock = function () { - if (this.arg && this.arg.argType == 'p') { - this.arg.updateIcon(); - } -}; - -Block.prototype.highlight = function () { - if (this.blocktype.indexOf('caret') > -1) { - return; - } - if (!this.div.parentNode) { - return; - } // deleted block - if ((this.div.parentNode.id != 'palette') && (this.div.parentNode != ScratchJr.getActiveScript())) { - return; - } - this.shine.style.visibility = 'visible'; -}; - -Block.prototype.unhighlight = function () { - if (this.blocktype.indexOf('caret') > -1) { - return; - } - this.shine.style.visibility = 'hidden'; -}; - -Block.prototype.drawIcon = function () { - var dx = 0; - var dy = 0; - var ctx = this.blockicon.getContext('2d'); - switch (this.blocktype) { - case 'repeat': - var w = Math.round(74 * this.scale * window.devicePixelRatio); - var h = Math.round(65 * this.scale * window.devicePixelRatio); - setCanvasSize(this.blockicon, w, h); - dx = 0; - this.blockicon.style.left = (this.shine.width / window.devicePixelRatio - - Math.round(this.scale * 77)) + 'px'; - dy = Math.round(this.scale * 14 * window.devicePixelRatio); - setProps(this.blockicon.style, { - position: 'absolute', - webkitTransform: 'translate(' + (-w / 2) + 'px, ' + (-h / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (w / 2) + 'px, ' + (h / 2) + 'px)' + // Top-level block parent shouldn't accept pointer events + setProps(this.div.style, { + pointerEvents: 'none' }); - break; - } - this.drawMyIcon(ctx, dx, dy); -}; -Block.prototype.drawMyIcon = function (ctx, dx, dy) { - var me = this; - var icon = this.icon; - if (!icon.complete) { - icon.onload = function () { - ctx.drawImage(icon, 0, 0, icon.width, icon.height, - dx, dy, icon.width * me.scale * window.devicePixelRatio, - icon.height * me.scale * window.devicePixelRatio); - }; - } else { - ctx.drawImage(icon, 0, 0, icon.width, icon.height, dx, dy, - icon.width * me.scale * window.devicePixelRatio, - icon.height * me.scale * window.devicePixelRatio); - } -}; - -Block.prototype.createArgument = function () { - if (this.spec[4] == null) { - return; - } - this.arg = new BlockArg(this); -}; - -Block.prototype.getArgValue = function () { - if (this.arg == null) { - return null; - } - return this.arg.argValue; -}; - -Block.prototype.getSoundName = function (list) { - var val = this.arg.argValue; - if (Number(val).toString() == 'NaN') { - return val; - } - if (list.length <= val) { - return list[0]; - } - return list[val]; -}; - -Block.prototype.update = function (spr) { - if (this.arg) { - this.arg.update(spr); - } -}; - -Block.prototype.setSound = function (bt) { - var p = this.arg.div; - p.parentNode.removeChild(p); - var icon = this.blockicon; - icon.parentNode.removeChild(icon); - var op = bt; - var specs = BlockSpecs.defs[op]; - this.setBlockshapeFromSpecs(specs); - this.drawBlock(); - this.createArgument(); -}; - -Block.prototype.duplicateBlock = function (dx, dy, spr) { - var op = this.blocktype; - var specs = BlockSpecs.defs[op]; - specs[4] = this.getArgValue(); - var bbx = new Block(specs, false, scaleMultiplier); - setProps(bbx.div.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - bbx.moveBlock(dx, dy); - bbx.update(spr); - return bbx; -}; - -Block.prototype.resolveDocks = function () { - var w = this.getWidth(); - var h = this.getHeight(); - if (this.aStart) { - return [['start', true, 0, h / 2], ['flow', false, w - this.notchSize(), h / 2]]; - } - if (this.anEnd) { - return [['flow', true, 0, h / 2], ['changestate', false, w - 3, h / 2]]; - } - if (this.isReporter) { - return [['input', true, 0, 0], ['input', false, w - this.notchSize(), h / 2]]; - } - if (this.cShape) { - return [['flow', true, 0, this.blockshape.height / this.scale / window.devicePixelRatio - 33], - ['flow', false, 35, this.blockshape.height / this.scale / window.devicePixelRatio - 33], - ['flow', false, this.blockshape.width / this.scale / window.devicePixelRatio - this.notchSize() - 1, - this.blockshape.height / this.scale / window.devicePixelRatio - 33]]; - } else { - return [['flow', true, 0, h / 2], ['flow', false, w - this.notchSize(), h / 2]]; - } -}; - -Block.prototype.notchSize = function () { - return 11; -}; - -////////////////////////////////////////// -// Connect / Disconnect -///////////////////////////////////////// - -Block.prototype.connectBlock = function (myn, you, yourn) { - if (this.isConnectedAfterFirst(myn, you, yourn)) { - return; - } - this.connectLast(myn, you, yourn); - this.setMyDock(myn, you); - you.setMyDock(yourn, this); - if (this.cShape && (myn == 1) && this.inside.findLast().anEnd) { - var theend = this.inside.findLast(); - theend.prev.next = null; - var last = this.findLast(); - last.next = theend; - theend.prev = last; - } -}; - -Block.prototype.getMyDock = function (dockn) { - var myprops = this.cShape ? ['prev', 'inside', 'next'] : ['prev', 'next']; - return this[myprops[dockn]]; -}; - -Block.prototype.setMyDock = function (dockn, you) { - var myprops = this.cShape ? ['prev', 'inside', 'next'] : ['prev', 'next']; - this[myprops[dockn]] = you; -}; - -Block.prototype.getMyDockNum = function (you) { - var connections = this.cShape ? [this.prev, this.inside, this.next] : [this.prev, this.next]; - return connections.indexOf(you); -}; - -Block.prototype.isConnectedAfterFirst = function (myn, you) { - if (myn == 0) { - return false; - } - var prev = you.prev; - if (prev == null) { - return false; - } - if (this == prev) { - return false; - } - var n = prev.getMyDockNum(you); - var thefirst = this.findFirst(); - thefirst.connectBlock(0, prev, n); - return true; -}; - -Block.prototype.findLast = function () { - if (this.next == null) { - return this; - } - return this.next.findLast(); -}; - -Block.prototype.findFirst = function () { - if (this.prev == null) { - return this; - } - return this.prev.findFirst(); -}; - -Block.prototype.connectLast = function (myn, you, yourn) { - if (myn != 0) { - return; - } - var yourtail = you.getMyDock(yourn); - var mylast = this.findLast(); - if (yourtail == mylast) { - return; - } - if (this.cShape && (this.inside == null) && (yourtail != null) && !yourtail.anEnd) { - var lastone = yourtail.findLast(); - this.inside = yourtail; - yourtail.prev = this; - if (lastone.anEnd) { - mylast.next = lastone; - var striplast = lastone.prev; - if (striplast) { - striplast.next = null; - } - lastone.prev = mylast; - } - } else { - mylast.next = yourtail; - if (yourtail == null) { + this.setBlockshapeFromSpecs(spec, isPalette, scale); + this.blockshape = document.createElement('canvas'); + setCanvasSize(this.div, this.getWidth() * this.scale, this.getHeight() * this.scale); + setCanvasSize(this.blockshape, this.getWidth() * this.scale * window.devicePixelRatio, + this.getHeight() * this.scale * window.devicePixelRatio); + this.addShadow(); + this.div.appendChild(this.blockshape); + setProps(this.blockshape.style, { + position: 'absolute', + left: '0px', + top: '0px', + webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + + (-this.blockshape.height / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', + pointerEvents: 'all' + }); + this.addHighlight(); + this.drawBlock(); + setCanvasSize(this.div, this.blockshape.width / window.devicePixelRatio, + this.blockshape.height / window.devicePixelRatio); + if (this.isCaret) { return; } - yourtail.prev = mylast; - } -}; - -Block.prototype.detachBlock = function () { - var you = this.prev; - if (you == null) { - return; - } - this.prev = null; - if ((you.cShape) && (you.inside == this)) { - you.inside = null; - } else { - you.next = null; - } -}; - -////////////////////////////////////////// -// Move -///////////////////////////////////////// - -Block.prototype.moveBlock = function (dx, dy) { - this.div.top = dy; - this.div.left = dx; - this.div.style.webkitTransform = 'translate3d(' + this.div.left + 'px,' + this.div.top + 'px, 0)'; -}; - - -///////////////////////////////// -// Forever and Repeat -//////////////////////////////// - -// Repeat size 176 by 82 - -Block.prototype.redrawRepeat = function () { - this.redrawShape(this.blockshape, this.image); - if (this.blocktype.indexOf('caret') < 0) { - this.redrawShape(this.shadow, this.shadowimg); + this.createArgument(); + this.div.owner = this; } - if (this.blocktype.indexOf('caret') > -1) { - return; + getWidth () { + if (this.blocktype == 'repeat') { + return 176; + } + if (this.blocktype == 'gotopage') { + return 86; + } + if (this.aStart || this.anEnd) { + return 84; + } + return 76; } - var dx = this.blockshape.width / window.devicePixelRatio - 78 * this.scale; - var dy = this.blockshape.height / window.devicePixelRatio - 82 * this.scale; - this.blockicon.style.left = dx + 'px'; - this.arg.div.style.left = (this.blockshape.width / window.devicePixelRatio - 66 * this.scale) + 'px'; - this.blockicon.style.top = dy + 'px'; - this.arg.div.style.top = (this.blockshape.height / window.devicePixelRatio - 11 * this.scale) + 'px'; -}; -Block.prototype.redrawShape = function (cnv, img) { - setCanvasSize(this.div, - (92 + this.hrubberband + 84) * this.scale, - (100 + this.vrubberband) * this.scale); - var scaleAndRatio = this.scale * window.devicePixelRatio; - setCanvasSize(cnv, - (92 + this.hrubberband + 84) * scaleAndRatio, - (82 + this.vrubberband) * scaleAndRatio); - setProps(cnv.style, { - webkitTransform: 'translate(' + (-cnv.width / 2) + 'px, ' + (-cnv.height / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (cnv.width / 2) + 'px, ' + (cnv.height / 2) + 'px)' - }); - var ctx = cnv.getContext('2d'); - // top line - ctx.drawImage(img, 0, 0, 92, 29, 0, 0, 92 * scaleAndRatio, 29 * scaleAndRatio); - ctx.drawImage(img, 92, 0, 1, 29, 92 * scaleAndRatio, 0, this.hrubberband * scaleAndRatio, 29 * scaleAndRatio); - ctx.drawImage(img, 93, 0, img.width - 93, 29, - 92 * scaleAndRatio + this.hrubberband * scaleAndRatio, 0, 83 * scaleAndRatio, 29 * scaleAndRatio); + getHeight () { + if (this.blocktype == 'repeat') { + return 82; + } + return 66; + } - // height streach - ctx.drawImage(img, 0, 29, 92, 1, 0, 29 * scaleAndRatio, 92 * scaleAndRatio, this.vrubberband * scaleAndRatio); - ctx.drawImage(img, 93, 29, img.width - 93, 1, - 92 * scaleAndRatio + this.hrubberband * scaleAndRatio, - 29 * scaleAndRatio, 83 * scaleAndRatio, this.vrubberband * scaleAndRatio); + setBlockshapeFromSpecs (spec, isPalette, scale) { + this.spec = spec; + this.isReporter = (spec[1] == 'reporter'); + this.blocktype = spec[0]; + this.icon = spec[1]; + this.image = spec[2]; + this.aStart = (this.blocktype == 'caretstart') || (this.image == BlockSpecs.yellowStart); + this.anEnd = (this.blocktype == 'caretend') + || (this.image == BlockSpecs.redEnd) + || (this.image == BlockSpecs.redEndLong + ); + this.cShape = (this.blocktype == 'repeat') || (this.blocktype == 'caretrepeat'); + this.prev = null; + this.next = null; + this.inside = null; + this.isCaret = this.blocktype.indexOf('caret') > -1; + this.type = 'block'; + this.arg = null; + this.daddy = null; + this.scale = scale ? scale : 1; + this.repeatCounter = -1; + this.originalCount = -1; + this.threads = []; + this.inpalette = isPalette; + this.min = spec[6]; + this.max = spec[7]; + this.shadowimg = (this.spec.length < 9) ? null : spec[8]; + this.hrubberband = 0; + this.vrubberband = 0; + this.done = false; + } - // bottom - ctx.drawImage(img, 0, 29, 45, 53, 0, 29 * scaleAndRatio + this.vrubberband * scaleAndRatio, - 45 * scaleAndRatio, 53 * scaleAndRatio); - ctx.drawImage(img, 93, 29, img.width - 93, 53, 92 * scaleAndRatio + this.hrubberband * scaleAndRatio, - 29 * scaleAndRatio + this.vrubberband * scaleAndRatio, 83 * scaleAndRatio, 53 * scaleAndRatio); -}; + addShadow () { + this.shadow = document.createElement('canvas'); + this.div.appendChild(this.shadow); + setProps(this.shadow.style, { + position: 'absolute', + left: '1px', + top: '4px', + opacity: this.inpalette ? Settings.paletteBlockShadowOpacity : 1, + visibility: 'hidden', + webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + + (-this.blockshape.height / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', + pointerEvents: 'all' + }); + setCanvasSize(this.shadow, this.blockshape.width, this.blockshape.height); + if (!this.shadowimg) { + return; + } + var ctx = this.shadow.getContext('2d'); + var img = this.shadowimg; + if (!img.complete) { + img.onload = function () { + ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, + img.width * this.scale * window.devicePixelRatio, + img.height * this.scale * window.devicePixelRatio); + }; + } else { + ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, + img.width * this.scale * window.devicePixelRatio, img.height * this.scale * window.devicePixelRatio); + } + } + + lift () { + this.shadow.style.visibility = 'visible'; + } + + drop () { + this.shadow.style.visibility = 'hidden'; + } + + addHighlight () { + var img = this.spec[5]; + if (!img) { + return; + } + this.shine = document.createElement('canvas'); + this.div.appendChild(this.shine); + setCanvasSize(this.shine, this.blockshape.width, this.blockshape.height); + setProps(this.shine.style, { + position: 'absolute', + left: '0px', + top: '0px', + visibility: 'hidden', + webkitTransform: 'translate(' + (-this.blockshape.width / 2) + + 'px, ' + (-this.blockshape.height / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', + pointerEvents: 'all' + }); + var ctx = this.shine.getContext('2d'); + if (!img.complete) { + img.onload = function () { + ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, + img.width * this.scale * window.devicePixelRatio, + img.height * this.scale * window.devicePixelRatio); + }; + } else { + ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, + img.width * this.scale * window.devicePixelRatio, img.height * this.scale * window.devicePixelRatio); + } + } + + drawBlock () { + var cnv = this.blockshape; + var ctx = this.blockshape.getContext('2d'); + ctx.clearRect(0, 0, cnv.width, cnv.height); + var me = this; + if (!this.image.complete) { + this.image.onload = function () { + me.drawBlockType(); + }; + } else { + this.drawBlockType(); + } + } + + drawBlockType () { + var ctx = this.blockshape.getContext('2d'); + ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0, + this.image.width * this.scale * window.devicePixelRatio, + this.image.height * this.scale * window.devicePixelRatio); + var icnv = document.createElement('canvas'); + this.blockicon = icnv; + this.div.appendChild(icnv); + setCanvasSize(icnv, this.blockshape.width, this.blockshape.height); + setProps(icnv.style, { + position: 'absolute', + left: '0px', + top: '0px', + webkitTransform: 'translate(' + (-this.blockshape.width / 2) + 'px, ' + + (-this.blockshape.height / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (this.blockshape.width / 2) + 'px, ' + (this.blockshape.height / 2) + 'px)', + pointerEvents: 'all' + }); + if (this.icon && this.icon.tagName) { + this.drawIcon(); + } + this.done = true; + } + + updateBlock () { + if (this.arg && this.arg.argType == 'p') { + this.arg.updateIcon(); + } + } + + highlight () { + if (this.blocktype.indexOf('caret') > -1) { + return; + } + if (!this.div.parentNode) { + return; + } // deleted block + if ((this.div.parentNode.id != 'palette') && (this.div.parentNode != ScratchJr.getActiveScript())) { + return; + } + this.shine.style.visibility = 'visible'; + } + + unhighlight () { + if (this.blocktype.indexOf('caret') > -1) { + return; + } + this.shine.style.visibility = 'hidden'; + } + + drawIcon () { + var dx = 0; + var dy = 0; + var ctx = this.blockicon.getContext('2d'); + switch (this.blocktype) { + case 'repeat': + var w = Math.round(74 * this.scale * window.devicePixelRatio); + var h = Math.round(65 * this.scale * window.devicePixelRatio); + setCanvasSize(this.blockicon, w, h); + dx = 0; + this.blockicon.style.left = (this.shine.width / window.devicePixelRatio - + Math.round(this.scale * 77)) + 'px'; + dy = Math.round(this.scale * 14 * window.devicePixelRatio); + setProps(this.blockicon.style, { + position: 'absolute', + webkitTransform: 'translate(' + (-w / 2) + 'px, ' + (-h / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (w / 2) + 'px, ' + (h / 2) + 'px)' + }); + break; + } + this.drawMyIcon(ctx, dx, dy); + } + + drawMyIcon (ctx, dx, dy) { + var me = this; + var icon = this.icon; + if (!icon.complete) { + icon.onload = function () { + ctx.drawImage(icon, 0, 0, icon.width, icon.height, + dx, dy, icon.width * me.scale * window.devicePixelRatio, + icon.height * me.scale * window.devicePixelRatio); + }; + } else { + ctx.drawImage(icon, 0, 0, icon.width, icon.height, dx, dy, + icon.width * me.scale * window.devicePixelRatio, + icon.height * me.scale * window.devicePixelRatio); + } + } + + createArgument () { + if (this.spec[4] == null) { + return; + } + this.arg = new BlockArg(this); + } + + getArgValue () { + if (this.arg == null) { + return null; + } + return this.arg.argValue; + } + + getSoundName (list) { + var val = this.arg.argValue; + if (Number(val).toString() == 'NaN') { + return val; + } + if (list.length <= val) { + return list[0]; + } + return list[val]; + } + + update (spr) { + if (this.arg) { + this.arg.update(spr); + } + } + + setSound (bt) { + var p = this.arg.div; + p.parentNode.removeChild(p); + var icon = this.blockicon; + icon.parentNode.removeChild(icon); + var op = bt; + var specs = BlockSpecs.defs[op]; + this.setBlockshapeFromSpecs(specs); + this.drawBlock(); + this.createArgument(); + } + + duplicateBlock (dx, dy, spr) { + var op = this.blocktype; + var specs = BlockSpecs.defs[op]; + specs[4] = this.getArgValue(); + var bbx = new Block(specs, false, scaleMultiplier); + setProps(bbx.div.style, { + position: 'absolute', + left: '0px', + top: '0px' + }); + bbx.moveBlock(dx, dy); + bbx.update(spr); + return bbx; + } + + resolveDocks () { + var w = this.getWidth(); + var h = this.getHeight(); + if (this.aStart) { + return [['start', true, 0, h / 2], ['flow', false, w - this.notchSize(), h / 2]]; + } + if (this.anEnd) { + return [['flow', true, 0, h / 2], ['changestate', false, w - 3, h / 2]]; + } + if (this.isReporter) { + return [['input', true, 0, 0], ['input', false, w - this.notchSize(), h / 2]]; + } + if (this.cShape) { + return [['flow', true, 0, this.blockshape.height / this.scale / window.devicePixelRatio - 33], + ['flow', false, 35, this.blockshape.height / this.scale / window.devicePixelRatio - 33], + ['flow', false, this.blockshape.width / this.scale / window.devicePixelRatio - this.notchSize() - 1, + this.blockshape.height / this.scale / window.devicePixelRatio - 33]]; + } else { + return [['flow', true, 0, h / 2], ['flow', false, w - this.notchSize(), h / 2]]; + } + } + + notchSize () { + return 11; + } + + ////////////////////////////////////////// + // Connect / Disconnect + ///////////////////////////////////////// + + connectBlock (myn, you, yourn) { + if (this.isConnectedAfterFirst(myn, you, yourn)) { + return; + } + this.connectLast(myn, you, yourn); + this.setMyDock(myn, you); + you.setMyDock(yourn, this); + if (this.cShape && (myn == 1) && this.inside.findLast().anEnd) { + var theend = this.inside.findLast(); + theend.prev.next = null; + var last = this.findLast(); + last.next = theend; + theend.prev = last; + } + } + + getMyDock (dockn) { + var myprops = this.cShape ? ['prev', 'inside', 'next'] : ['prev', 'next']; + return this[myprops[dockn]]; + } + + setMyDock (dockn, you) { + var myprops = this.cShape ? ['prev', 'inside', 'next'] : ['prev', 'next']; + this[myprops[dockn]] = you; + } + + getMyDockNum (you) { + var connections = this.cShape ? [this.prev, this.inside, this.next] : [this.prev, this.next]; + return connections.indexOf(you); + } + + isConnectedAfterFirst (myn, you) { + if (myn == 0) { + return false; + } + var prev = you.prev; + if (prev == null) { + return false; + } + if (this == prev) { + return false; + } + var n = prev.getMyDockNum(you); + var thefirst = this.findFirst(); + thefirst.connectBlock(0, prev, n); + return true; + } + + findLast () { + if (this.next == null) { + return this; + } + return this.next.findLast(); + } + + findFirst () { + if (this.prev == null) { + return this; + } + return this.prev.findFirst(); + } + + connectLast (myn, you, yourn) { + if (myn != 0) { + return; + } + var yourtail = you.getMyDock(yourn); + var mylast = this.findLast(); + if (yourtail == mylast) { + return; + } + if (this.cShape && (this.inside == null) && (yourtail != null) && !yourtail.anEnd) { + var lastone = yourtail.findLast(); + this.inside = yourtail; + yourtail.prev = this; + if (lastone.anEnd) { + mylast.next = lastone; + var striplast = lastone.prev; + if (striplast) { + striplast.next = null; + } + lastone.prev = mylast; + } + } else { + mylast.next = yourtail; + if (yourtail == null) { + return; + } + yourtail.prev = mylast; + } + } + + detachBlock () { + var you = this.prev; + if (you == null) { + return; + } + this.prev = null; + if ((you.cShape) && (you.inside == this)) { + you.inside = null; + } else { + you.next = null; + } + } + + ////////////////////////////////////////// + // Move + ///////////////////////////////////////// + + moveBlock (dx, dy) { + this.div.top = dy; + this.div.left = dx; + this.div.style.webkitTransform = 'translate3d(' + this.div.left + 'px,' + this.div.top + 'px, 0)'; + } + + + ///////////////////////////////// + // Forever and Repeat + //////////////////////////////// + + // Repeat size 176 by 82 + + redrawRepeat () { + this.redrawShape(this.blockshape, this.image); + if (this.blocktype.indexOf('caret') < 0) { + this.redrawShape(this.shadow, this.shadowimg); + } + + if (this.blocktype.indexOf('caret') > -1) { + return; + } + var dx = this.blockshape.width / window.devicePixelRatio - 78 * this.scale; + var dy = this.blockshape.height / window.devicePixelRatio - 82 * this.scale; + this.blockicon.style.left = dx + 'px'; + this.arg.div.style.left = (this.blockshape.width / window.devicePixelRatio - 66 * this.scale) + 'px'; + this.blockicon.style.top = dy + 'px'; + this.arg.div.style.top = (this.blockshape.height / window.devicePixelRatio - 11 * this.scale) + 'px'; + } + + redrawShape (cnv, img) { + setCanvasSize(this.div, + (92 + this.hrubberband + 84) * this.scale, + (100 + this.vrubberband) * this.scale); + var scaleAndRatio = this.scale * window.devicePixelRatio; + setCanvasSize(cnv, + (92 + this.hrubberband + 84) * scaleAndRatio, + (82 + this.vrubberband) * scaleAndRatio); + setProps(cnv.style, { + webkitTransform: 'translate(' + (-cnv.width / 2) + 'px, ' + (-cnv.height / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (cnv.width / 2) + 'px, ' + (cnv.height / 2) + 'px)' + }); + var ctx = cnv.getContext('2d'); + // top line + ctx.drawImage(img, 0, 0, 92, 29, 0, 0, 92 * scaleAndRatio, 29 * scaleAndRatio); + ctx.drawImage(img, 92, 0, 1, 29, 92 * scaleAndRatio, 0, this.hrubberband * scaleAndRatio, 29 * scaleAndRatio); + ctx.drawImage(img, 93, 0, img.width - 93, 29, + 92 * scaleAndRatio + this.hrubberband * scaleAndRatio, 0, 83 * scaleAndRatio, 29 * scaleAndRatio); + + // height streach + ctx.drawImage(img, 0, 29, 92, 1, 0, 29 * scaleAndRatio, 92 * scaleAndRatio, this.vrubberband * scaleAndRatio); + ctx.drawImage(img, 93, 29, img.width - 93, 1, + 92 * scaleAndRatio + this.hrubberband * scaleAndRatio, + 29 * scaleAndRatio, 83 * scaleAndRatio, this.vrubberband * scaleAndRatio); + + // bottom + ctx.drawImage(img, 0, 29, 45, 53, 0, 29 * scaleAndRatio + this.vrubberband * scaleAndRatio, + 45 * scaleAndRatio, 53 * scaleAndRatio); + ctx.drawImage(img, 93, 29, img.width - 93, 53, 92 * scaleAndRatio + this.hrubberband * scaleAndRatio, + 29 * scaleAndRatio + this.vrubberband * scaleAndRatio, 83 * scaleAndRatio, 53 * scaleAndRatio); + } +} diff --git a/src/editor/blocks/BlockArg.js b/src/editor/blocks/BlockArg.js index 4d81965..d2b525a 100644 --- a/src/editor/blocks/BlockArg.js +++ b/src/editor/blocks/BlockArg.js @@ -1,3 +1,12 @@ +import ScratchJr from '../ScratchJr'; +import BlockSpecs from './BlockSpecs'; +import Menu from './Menu'; +import Undo from '../ui/Undo'; +import {setCanvasSize, setProps, writeText, scaleMultiplier, + newHTML, newDiv, newCanvas, getStringSize, isTablet, + newP, globalx, globaly} from '../../utils/lib'; +import Localization from '../../utils/Localization'; + /* Argument types @@ -9,442 +18,448 @@ r: number for recorded sound block p: page icons */ -var BlockArg = function (block) { - this.daddy = block; - this.type = 'blockarg'; - this.argType = block.spec[3]; - switch (this.argType) { - case 'n': - this.argValue = block.spec[4]; - this.div = this.addNumArg(); - break; - case 't': - this.argValue = block.spec[4]; - if (Localization.isSampleLocalizedKey(this.argValue) && ScratchJr.isSampleOrStarter()) { - this.argValue = Localization.localize('SAMPLE_TEXT_' + this.argValue); +export default class BlockArg { + constructor (block) { + this.daddy = block; + this.type = 'blockarg'; + this.argType = block.spec[3]; + switch (this.argType) { + case 'n': + this.argValue = block.spec[4]; + this.div = this.addNumArg(); + break; + case 't': + this.argValue = block.spec[4]; + if (Localization.isSampleLocalizedKey(this.argValue) && ScratchJr.isSampleOrStarter()) { + this.argValue = Localization.localize('SAMPLE_TEXT_' + this.argValue); + } + this.div = this.addTextArg(); + break; + case 'm': + this.argValue = block.spec[4]; + this.list = JSON.stringify(block.spec[1]); + this.numperrow = 3; + this.icon = this.getIconFrom(block.spec[4], block.spec[1]); + this.div = this.addImageMenu(this.closePictureMenu); + break; + case 'd': + this.argValue = block.spec[4]; + this.list = JSON.stringify(block.spec[1]); + this.numperrow = 3; + this.icon = BlockSpecs.speeds[this.argValue]; + this.div = this.addImageMenu(this.menuCloseSpeeds); + break; + case 'p': + this.argValue = block.spec[4]; + this.div = this.pageIcon(this.argValue); + var ctx = block.blockshape.getContext('2d'); + ctx.drawImage(this.div, 0, 0, this.div.width, this.div.height, 0, 0, + this.div.width * block.scale, this.div.height * block.scale); + break; + case 's': + this.argValue = block.spec[4]; + this.div = newDiv(block.div, 2, 46, 60, 20, { + position: 'absolute', + zoom: (block.scale * 100) + '%' + }); + var p = newP(this.div, this.argValue.split('.')[0], { + width: '60px' + }); + p.setAttribute('class', 'soundname'); + break; + case 'r': + this.argValue = block.spec[4]; + this.div = newHTML('div', 'recordedCircle', block.div); + setProps(this.div.style, { + zoom: (block.scale * 100) + '%' + }); + var num = newHTML('p', 'recordedNumber', this.div); + num.textContent = this.daddy.inpalette ? this.argValue : '?'; + break; + default: + break; } - this.div = this.addTextArg(); - break; - case 'm': - this.argValue = block.spec[4]; - this.list = JSON.stringify(block.spec[1]); - this.numperrow = 3; - this.icon = this.getIconFrom(block.spec[4], block.spec[1]); - this.div = this.addImageMenu(this.closePictureMenu); - break; - case 'd': - this.argValue = block.spec[4]; - this.list = JSON.stringify(block.spec[1]); - this.numperrow = 3; - this.icon = BlockSpecs.speeds[this.argValue]; - this.div = this.addImageMenu(this.menuCloseSpeeds); - break; - case 'p': - this.argValue = block.spec[4]; - this.div = this.pageIcon(this.argValue); - var ctx = block.blockshape.getContext('2d'); - ctx.drawImage(this.div, 0, 0, this.div.width, this.div.height, 0, 0, - this.div.width * block.scale, this.div.height * block.scale); - break; - case 's': - this.argValue = block.spec[4]; - this.div = newDiv(block.div, 2, 46, 60, 20, { - position: 'absolute', - zoom: (block.scale * 100) + '%' - }); - var p = newP(this.div, this.argValue.split('.')[0], { - width: '60px' - }); - p.setAttribute('class', 'soundname'); - break; - case 'r': - this.argValue = block.spec[4]; - this.div = newHTML('div', 'recordedCircle', block.div); - setProps(this.div.style, { - zoom: (block.scale * 100) + '%' - }); - var num = newHTML('p', 'recordedNumber', this.div); - num.textContent = this.daddy.inpalette ? this.argValue : '?'; - break; - default: - break; } -}; -BlockArg.prototype.update = function () { - if (this.argType == 'r') { - this.div.childNodes[0].textContent = this.argValue; + update () { + if (this.argType == 'r') { + this.div.childNodes[0].textContent = this.argValue; + } + if (this.arg && (this.argType == 'p')) { + this.arg.updateIcon(); + } } - if (this.arg && (this.argType == 'p')) { - this.arg.updateIcon(); - } -}; -BlockArg.prototype.getScreenPt = function () { - return { - x: globalx(this.daddy.div), - y: globaly(this.daddy.div) - }; -}; - -BlockArg.prototype.addNumArg = function () { - var str = this.argValue.toString(); - if (this.daddy.inpalette) { - return this.addLabel(str, false); - } else { - return this.addNumArgument(str); - } -}; - -BlockArg.prototype.addTextArg = function () { - var str = this.argValue.toString(); - if (this.daddy.inpalette) { - return this.addLabel(str, true); - } else { - return this.addTextArgument(str, true); - } -}; - -BlockArg.prototype.addLabel = function (str, isText) { - var scale = this.daddy.scale; - var dx = isText ? 8 : 16; - var dy = 57; - if (this.daddy.blocktype == 'repeat') { - dx = Math.round(this.daddy.blockshape.width / window.devicePixelRatio / scale) - 60; - dy = Math.round(this.daddy.blockshape.height / window.devicePixelRatio / scale) - 10; - } - var img = isText ? BlockSpecs.textfieldimg : BlockSpecs.numfieldimg; - var w = isText ? 53 : 36; - var h = 17; - var field = newCanvas(this.daddy.div, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio, { - position: 'absolute', - webkitTransform: 'translate(' + (-w * window.devicePixelRatio / 2) + 'px, ' + - (-h * window.devicePixelRatio / 2) + 'px) ' + - 'scale(' + (scale / window.devicePixelRatio) + ') ' + - 'translate(' + (dx * window.devicePixelRatio + (w * window.devicePixelRatio / 2)) + 'px, ' + - (dy * window.devicePixelRatio + (h * window.devicePixelRatio / 2)) + 'px)', - pointerEvents: 'all' - - }); - var ctx = field.getContext('2d'); - if (!img.complete) { - img.onload = function () { - ctx.drawImage(img, 0, 0, w, h, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio); + getScreenPt () { + return { + x: globalx(this.daddy.div), + y: globaly(this.daddy.div) }; - } else { - ctx.drawImage(img, 0, 0, w, h, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio); } - var div = newDiv(this.daddy.div, dx, dy, w, h, { - position: 'absolute', - zoom: (scale * 100) + '%', - margin: '0px', - padding: '0px' - }); - var cnv = newCanvas(div, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio, { - position: 'absolute', - webkitTransform: 'translate(' + (-w * window.devicePixelRatio / 2) + 'px, ' + - (-h * window.devicePixelRatio / 2) + 'px) ' + - 'scale(' + (1 / window.devicePixelRatio) + ') ' + - 'translate(' + (w * window.devicePixelRatio / 2) + 'px, ' + (h * window.devicePixelRatio / 2) + 'px)' - }); - ctx = cnv.getContext('2d'); - var font = (12 * window.devicePixelRatio) + 'px ' + Settings.blockArgFont; - var lsize = getStringSize(ctx, font, str).width; - writeText(ctx, font, '#77787b', str, h * window.devicePixelRatio - 3, - Math.round((w * window.devicePixelRatio - lsize) / 2)); - return div; -}; -BlockArg.prototype.addNumArgument = function (str) { - var div = newHTML('div', 'numfield', this.daddy.div); - if (this.daddy.blocktype == 'repeat') { - setProps(div.style, { - left: (this.daddy.blockshape.width / window.devicePixelRatio - 62 * this.daddy.scale) + 'px', - top: (this.daddy.blockshape.height / window.devicePixelRatio - 11 * this.daddy.scale) + 'px' - }); - } - var ti = newHTML('h3', undefined, div); - this.input = ti; - ti.owner = this; - ti.textContent = str; - this.arg = div; - // Expand the parent div to incorporate the size of the button, else on Android 4.2 the bottom part of the button - // will not be clickable. - div.parentNode.height += 10 * window.devicePixelRatio; - setCanvasSize(div.parentNode, div.parentNode.width, div.parentNode.height); - return div; -}; - -BlockArg.prototype.addTextArgument = function (str) { - var div = newHTML('div', 'textfield', this.daddy.div); - var ti = newHTML('h3', undefined, div); - this.input = ti; - ti.owner = this; - ti.textContent = str; - this.arg = div; - // Expand the parent div to incorporate the size of the button, else on Android 4.2 the bottom part of the button - // will not be clickable. - div.parentNode.height += 10 * window.devicePixelRatio; - setCanvasSize(div.parentNode, div.parentNode.width, div.parentNode.height); - return div; -}; - -BlockArg.prototype.setValue = function (val) { - if (!this.input) { - return; - } - this.argValue = val; - this.input.textContent = val; -}; - -BlockArg.prototype.isText = function () { - return (this.argType != 'n'); -}; - -///////////////////////////////// -// Menu drop downs -////////////////////////////// - -BlockArg.prototype.getIconFrom = function (key, list) { - for (var i = 0; i < list.length; i++) { - if (list[i].indexOf(key) > -1) { - return list[i]; + addNumArg () { + var str = this.argValue.toString(); + if (this.daddy.inpalette) { + return this.addLabel(str, false); + } else { + return this.addNumArgument(str); } } - return list[0]; -}; -BlockArg.prototype.addImageMenu = function (fcn) { - this.drawChoice(this.daddy.blockicon); - this.button = this.addPressButton(); - if (!this.daddy.inpalette) { - var ba = this; - if (isTablet) { - ba.button.ontouchstart = function (evt) { - ba.pressDropDown(evt, fcn); + addTextArg () { + var str = this.argValue.toString(); + if (this.daddy.inpalette) { + return this.addLabel(str, true); + } else { + return this.addTextArgument(str, true); + } + } + + addLabel (str, isText) { + var scale = this.daddy.scale; + var dx = isText ? 8 : 16; + var dy = 57; + if (this.daddy.blocktype == 'repeat') { + dx = Math.round(this.daddy.blockshape.width / window.devicePixelRatio / scale) - 60; + dy = Math.round(this.daddy.blockshape.height / window.devicePixelRatio / scale) - 10; + } + var img = isText ? BlockSpecs.textfieldimg : BlockSpecs.numfieldimg; + var w = isText ? 53 : 36; + var h = 17; + var field = newCanvas(this.daddy.div, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio, { + position: 'absolute', + webkitTransform: 'translate(' + (-w * window.devicePixelRatio / 2) + 'px, ' + + (-h * window.devicePixelRatio / 2) + 'px) ' + + 'scale(' + (scale / window.devicePixelRatio) + ') ' + + 'translate(' + (dx * window.devicePixelRatio + (w * window.devicePixelRatio / 2)) + 'px, ' + + (dy * window.devicePixelRatio + (h * window.devicePixelRatio / 2)) + 'px)', + pointerEvents: 'all' + + }); + var ctx = field.getContext('2d'); + if (!img.complete) { + img.onload = function () { + ctx.drawImage(img, 0, 0, w, h, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio); }; } else { - ba.button.onmousedown = function (evt) { - ba.pressDropDown(evt, fcn); - }; + ctx.drawImage(img, 0, 0, w, h, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio); + } + var div = newDiv(this.daddy.div, dx, dy, w, h, { + position: 'absolute', + zoom: (scale * 100) + '%', + margin: '0px', + padding: '0px' + }); + var cnv = newCanvas(div, 0, 0, w * window.devicePixelRatio, h * window.devicePixelRatio, { + position: 'absolute', + webkitTransform: 'translate(' + (-w * window.devicePixelRatio / 2) + 'px, ' + + (-h * window.devicePixelRatio / 2) + 'px) ' + + 'scale(' + (1 / window.devicePixelRatio) + ') ' + + 'translate(' + (w * window.devicePixelRatio / 2) + 'px, ' + (h * window.devicePixelRatio / 2) + 'px)' + }); + ctx = cnv.getContext('2d'); + var font = (12 * window.devicePixelRatio) + 'px ' + Settings.blockArgFont; + var lsize = getStringSize(ctx, font, str).width; + writeText(ctx, font, '#77787b', str, h * window.devicePixelRatio - 3, + Math.round((w * window.devicePixelRatio - lsize) / 2)); + return div; + } + + addNumArgument (str) { + var div = newHTML('div', 'numfield', this.daddy.div); + if (this.daddy.blocktype == 'repeat') { + setProps(div.style, { + left: (this.daddy.blockshape.width / window.devicePixelRatio - 62 * this.daddy.scale) + 'px', + top: (this.daddy.blockshape.height / window.devicePixelRatio - 11 * this.daddy.scale) + 'px' + }); + } + var ti = newHTML('h3', undefined, div); + this.input = ti; + ti.owner = this; + ti.textContent = str; + this.arg = div; // Expand the parent div to incorporate the size of the button, // else on Android 4.2 the bottom part of the button // will not be clickable. - } - this.button.parentNode.height += this.button.height / 2; - setCanvasSize(this.button.parentNode, this.button.parentNode.width, this.button.parentNode.height); + div.parentNode.height += 10 * window.devicePixelRatio; + setCanvasSize(div.parentNode, div.parentNode.width, div.parentNode.height); + return div; } - return this.daddy.blockicon; -}; -BlockArg.prototype.drawChoice = function (cnv) { - var ctx = cnv.getContext('2d'); - ctx.clearRect(0, 0, cnv.width, cnv.height); - var icon = BlockSpecs.getImageFrom('assets/blockicons/' + this.icon, 'svg'); - var scale = this.daddy.scale; - if (!icon.complete) { - icon.onload = function () { + addTextArgument (str) { + var div = newHTML('div', 'textfield', this.daddy.div); + var ti = newHTML('h3', undefined, div); + this.input = ti; + ti.owner = this; + ti.textContent = str; + this.arg = div; + // Expand the parent div to incorporate the size of the button, + // else on Android 4.2 the bottom part of the button + // will not be clickable. + div.parentNode.height += 10 * window.devicePixelRatio; + setCanvasSize(div.parentNode, div.parentNode.width, div.parentNode.height); + return div; + } + + setValue (val) { + if (!this.input) { + return; + } + this.argValue = val; + this.input.textContent = val; + } + + isText () { + return (this.argType != 'n'); + } + + ///////////////////////////////// + // Menu drop downs + ////////////////////////////// + + getIconFrom (key, list) { + for (var i = 0; i < list.length; i++) { + if (list[i].indexOf(key) > -1) { + return list[i]; + } + } + return list[0]; + } + + addImageMenu (fcn) { + this.drawChoice(this.daddy.blockicon); + this.button = this.addPressButton(); + if (!this.daddy.inpalette) { + var ba = this; + if (isTablet) { + ba.button.ontouchstart = function (evt) { + ba.pressDropDown(evt, fcn); + }; + } else { + ba.button.onmousedown = function (evt) { + ba.pressDropDown(evt, fcn); + }; + // Expand the parent div to incorporate the size of the button, + // else on Android 4.2 the bottom part of the button + // will not be clickable. + } + this.button.parentNode.height += this.button.height / 2; + setCanvasSize(this.button.parentNode, this.button.parentNode.width, this.button.parentNode.height); + } + return this.daddy.blockicon; + } + + drawChoice (cnv) { + var ctx = cnv.getContext('2d'); + ctx.clearRect(0, 0, cnv.width, cnv.height); + var icon = BlockSpecs.getImageFrom('assets/blockicons/' + this.icon, 'svg'); + var scale = this.daddy.scale; + if (!icon.complete) { + icon.onload = function () { + ctx.drawImage(icon, 0, 0, icon.width, icon.height, + 0, 0, icon.width * scale * window.devicePixelRatio, + icon.height * scale * window.devicePixelRatio); + }; + } else { ctx.drawImage(icon, 0, 0, icon.width, icon.height, 0, 0, icon.width * scale * window.devicePixelRatio, icon.height * scale * window.devicePixelRatio); - }; - } else { - ctx.drawImage(icon, 0, 0, icon.width, icon.height, - 0, 0, icon.width * scale * window.devicePixelRatio, - icon.height * scale * window.devicePixelRatio); + } + return cnv; } - return cnv; -}; -BlockArg.prototype.addPressButton = function () { - var scale = this.daddy.scale; - var dx; - if (this.daddy.inpalette) { - dx = this.daddy.aStart ? 26 : 16; - } else { - dx = this.daddy.aStart ? 20 : 10; - } - var dy = 56; - var w = (this.daddy.inpalette) ? 36 : 48; - var h = (this.daddy.inpalette) ? 20 : 27; - var img = (this.daddy.inpalette) ? BlockSpecs.pressbuttonSmall : BlockSpecs.pressbutton; - var field = newCanvas(this.daddy.div, dx, dy, w, h, { - position: 'absolute', - zoom: (scale * 100) + '%', - pointerEvents: 'all', - webkitTransform: 'translateZ(0)' - }); - var ctx = field.getContext('2d'); - if (!img.complete) { - img.onload = function () { + addPressButton () { + var scale = this.daddy.scale; + var dx; + if (this.daddy.inpalette) { + dx = this.daddy.aStart ? 26 : 16; + } else { + dx = this.daddy.aStart ? 20 : 10; + } + var dy = 56; + var w = (this.daddy.inpalette) ? 36 : 48; + var h = (this.daddy.inpalette) ? 20 : 27; + var img = (this.daddy.inpalette) ? BlockSpecs.pressbuttonSmall : BlockSpecs.pressbutton; + var field = newCanvas(this.daddy.div, dx, dy, w, h, { + position: 'absolute', + zoom: (scale * 100) + '%', + pointerEvents: 'all', + webkitTransform: 'translateZ(0)' + }); + var ctx = field.getContext('2d'); + if (!img.complete) { + img.onload = function () { + ctx.drawImage(img, 0, 0); + }; + } else { ctx.drawImage(img, 0, 0); - }; - } else { - ctx.drawImage(img, 0, 0); + } + return field; } - return field; -}; -BlockArg.prototype.pressDropDown = function (e, fcn) { - if (isTablet && e.touches && (e.touches.length > 1)) { - return; + pressDropDown (e, fcn) { + if (isTablet && e.touches && (e.touches.length > 1)) { + return; + } + if (ScratchJr.onHold) { + return; + } + e.preventDefault(); + e.stopPropagation(); + ScratchJr.unfocus(e); + if (!this.daddy) { + return; + } + Menu.openDropDown(this.daddy.div, fcn); } - if (ScratchJr.onHold) { - return; - } - e.preventDefault(); - e.stopPropagation(); - ScratchJr.unfocus(e); - if (!this.daddy) { - return; - } - Menu.openDropDown(this.daddy.div, fcn); -}; -BlockArg.prototype.closePictureMenu = function (e, mu, b, c) { - e.preventDefault(); - var value = b.owner.arg.argValue; - b.owner.arg.argValue = c.substring(c.indexOf('_') + 1, c.length); - var ctx = b.owner.blockicon.getContext('2d'); - b.icon = BlockSpecs.getImageFrom('assets/blockicons/' + c, 'svg'); - ctx.clearRect(0, 0, 85 * scaleMultiplier * window.devicePixelRatio, 66 * scaleMultiplier * window.devicePixelRatio); - if (!b.icon.complete) { - b.icon.onload = function () { + closePictureMenu (e, mu, b, c) { + e.preventDefault(); + var value = b.owner.arg.argValue; + b.owner.arg.argValue = c.substring(c.indexOf('_') + 1, c.length); + var ctx = b.owner.blockicon.getContext('2d'); + b.icon = BlockSpecs.getImageFrom('assets/blockicons/' + c, 'svg'); + ctx.clearRect(0, 0, 85 * scaleMultiplier * window.devicePixelRatio, + 66 * scaleMultiplier * window.devicePixelRatio); + if (!b.icon.complete) { + b.icon.onload = function () { + var w = b.icon.width; + var h = b.icon.height; + ctx.drawImage(b.icon, 0, 0, w, h, 0, 0, + w * scaleMultiplier * window.devicePixelRatio, + h * scaleMultiplier * window.devicePixelRatio); + }; + } else { var w = b.icon.width; var h = b.icon.height; ctx.drawImage(b.icon, 0, 0, w, h, 0, 0, w * scaleMultiplier * window.devicePixelRatio, h * scaleMultiplier * window.devicePixelRatio); - }; - } else { - var w = b.icon.width; - var h = b.icon.height; - ctx.drawImage(b.icon, 0, 0, w, h, 0, 0, - w * scaleMultiplier * window.devicePixelRatio, - h * scaleMultiplier * window.devicePixelRatio); + } + if (Menu.openMenu) { + Menu.openMenu.parentNode.removeChild(Menu.openMenu); + } + if (b.owner.arg.argValue != value) { + var spr = b.parentNode.owner.spr; + var action = { + action: 'scripts', + where: spr.div.parentNode.owner.id, + who: spr.id + }; + Undo.record(action); + ScratchJr.storyStart('BlockArg.prototype.closePictureMenu'); + } + Menu.openMenu = undefined; } - if (Menu.openMenu) { - Menu.openMenu.parentNode.removeChild(Menu.openMenu); - } - if (b.owner.arg.argValue != value) { - var spr = b.parentNode.owner.spr; - var action = { - action: 'scripts', - where: spr.div.parentNode.owner.id, - who: spr.id - }; - Undo.record(action); - ScratchJr.storyStart('BlockArg.prototype.closePictureMenu'); - } - Menu.openMenu = undefined; -}; -BlockArg.prototype.menuCloseSpeeds = function (e, mu, b, c) { - e.preventDefault(); - var value = b.owner.arg.argValue; - b.owner.arg.argValue = BlockSpecs.speeds.indexOf(c); - var ctx = b.owner.blockicon.getContext('2d'); - b.icon = BlockSpecs.getImageFrom('assets/blockicons/' + c, 'svg'); - ctx.clearRect(0, 0, 64 * scaleMultiplier * window.devicePixelRatio, 64 * scaleMultiplier * window.devicePixelRatio); - // On Android 4.2, clearRect does not work right away. Need to tickle the DOM - b.owner.blockicon.style.display = 'none'; - b.owner.blockicon.offsetHeight; - b.owner.blockicon.style.display = 'inherit'; - if (!b.icon.complete) { - b.icon.onload = function () { + menuCloseSpeeds (e, mu, b, c) { + e.preventDefault(); + var value = b.owner.arg.argValue; + b.owner.arg.argValue = BlockSpecs.speeds.indexOf(c); + var ctx = b.owner.blockicon.getContext('2d'); + b.icon = BlockSpecs.getImageFrom('assets/blockicons/' + c, 'svg'); + ctx.clearRect(0, 0, 64 * scaleMultiplier * window.devicePixelRatio, + 64 * scaleMultiplier * window.devicePixelRatio); + // On Android 4.2, clearRect does not work right away. Need to tickle the DOM + b.owner.blockicon.style.display = 'none'; + b.owner.blockicon.offsetHeight; + b.owner.blockicon.style.display = 'inherit'; + if (!b.icon.complete) { + b.icon.onload = function () { + var w = b.icon.width; + var h = b.icon.height; + ctx.drawImage(b.icon, 0, 0, w, h, 0, 0, + w * scaleMultiplier * window.devicePixelRatio, h * scaleMultiplier * window.devicePixelRatio); + }; + } else { var w = b.icon.width; var h = b.icon.height; ctx.drawImage(b.icon, 0, 0, w, h, 0, 0, w * scaleMultiplier * window.devicePixelRatio, h * scaleMultiplier * window.devicePixelRatio); - }; - } else { - var w = b.icon.width; - var h = b.icon.height; - ctx.drawImage(b.icon, 0, 0, w, h, 0, 0, - w * scaleMultiplier * window.devicePixelRatio, h * scaleMultiplier * window.devicePixelRatio); + } + if (Menu.openMenu) { + Menu.openMenu.parentNode.removeChild(Menu.openMenu); + } + if (b.owner.arg.argValue != value) { + var spr = b.parentNode.owner.spr; + var action = { + action: 'scripts', + where: spr.div.parentNode.owner.id, + who: spr.id + }; + Undo.record(action); + ScratchJr.storyStart('BlockArg.prototype.menuCloseSpeeds'); + } + Menu.openMenu = undefined; } - if (Menu.openMenu) { - Menu.openMenu.parentNode.removeChild(Menu.openMenu); - } - if (b.owner.arg.argValue != value) { - var spr = b.parentNode.owner.spr; - var action = { - action: 'scripts', - where: spr.div.parentNode.owner.id, - who: spr.id - }; - Undo.record(action); - ScratchJr.storyStart('BlockArg.prototype.menuCloseSpeeds'); - } - Menu.openMenu = undefined; -}; -////////////////////////// -// Page Icon -////////////////////////// + ////////////////////////// + // Page Icon + ////////////////////////// -BlockArg.prototype.pageIcon = function (num) { - var dpr = window.devicePixelRatio; - var page = ScratchJr.stage.pages[num - 1]; - var icon = document.createElement('canvas'); - setCanvasSize(icon, 86 * dpr, 66 * dpr); - if (!page) { + pageIcon (num) { + var dpr = window.devicePixelRatio; + var page = ScratchJr.stage.pages[num - 1]; + var icon = document.createElement('canvas'); + setCanvasSize(icon, 86 * dpr, 66 * dpr); + if (!page) { + return icon; + } + var canvas = document.createElement('canvas'); + setCanvasSize(canvas, 52 * dpr, 42 * dpr); + var mainctx = canvas.getContext('2d'); + mainctx.fillStyle = '#AE1F24'; + mainctx.fillRect(0, 0, canvas.width, canvas.height); + mainctx.fillStyle = '#28A5DA'; + mainctx.fillRect(1 * dpr, 1 * dpr, 50 * dpr, 40 * dpr); + var c = document.createElement('canvas'); + var w = (52 - 6) * dpr; + var h = (42 - 6) * dpr; + setCanvasSize(c, w, h); + var ctx = c.getContext('2d'); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, c.width, c.height); + if (page.bkg.childElementCount > 0) { + var img = page.bkg.childNodes[0]; + var imgw = img.naturalWidth ? img.naturalWidth : img.width; + var imgh = img.naturalHeight ? img.naturalHeight : img.height; + ctx.drawImage(img, 0, 0, imgw, imgh, 0, 0, w, h); + } + var scale = w / 480; + for (var i = 0; i < page.div.childElementCount; i++) { + var spr = page.div.childNodes[i].owner; + if (!spr) { + continue; + } + page.stampSpriteAt(ctx, spr, scale); + } + mainctx.drawImage(c, 3 * dpr, 3 * dpr); + var ictx = icon.getContext('2d'); + ictx.fillStyle = '#AE1F24'; + ictx.beginPath(); + ictx.arc(63 * dpr, 19 * dpr, 10 * dpr, 0 * dpr, Math.PI * 2, true); + ictx.closePath(); + ictx.fill(); + ictx.drawImage(canvas, 14 * dpr, 16 * dpr); + ictx.beginPath(); + ictx.fillStyle = '#28A5DA'; + ictx.strokeStyle = '#355E7C'; + ictx.arc(63 * dpr, 19 * dpr, 8 * dpr, 0 * dpr, Math.PI * 2, true); + ictx.closePath(); + ictx.stroke(); + ictx.fill(); + writeText(ictx, 'bold ' + (12 * dpr) + 'px ' + Settings.blockArgFont, 'white', page.num, 26 * dpr, 58 * dpr); return icon; } - var canvas = document.createElement('canvas'); - setCanvasSize(canvas, 52 * dpr, 42 * dpr); - var mainctx = canvas.getContext('2d'); - mainctx.fillStyle = '#AE1F24'; - mainctx.fillRect(0, 0, canvas.width, canvas.height); - mainctx.fillStyle = '#28A5DA'; - mainctx.fillRect(1 * dpr, 1 * dpr, 50 * dpr, 40 * dpr); - var c = document.createElement('canvas'); - var w = (52 - 6) * dpr; - var h = (42 - 6) * dpr; - setCanvasSize(c, w, h); - var ctx = c.getContext('2d'); - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, c.width, c.height); - if (page.bkg.childElementCount > 0) { - var img = page.bkg.childNodes[0]; - var imgw = img.naturalWidth ? img.naturalWidth : img.width; - var imgh = img.naturalHeight ? img.naturalHeight : img.height; - ctx.drawImage(img, 0, 0, imgw, imgh, 0, 0, w, h); - } - var scale = w / 480; - for (var i = 0; i < page.div.childElementCount; i++) { - var spr = page.div.childNodes[i].owner; - if (!spr) { - continue; - } - page.stampSpriteAt(ctx, spr, scale); - } - mainctx.drawImage(c, 3 * dpr, 3 * dpr); - var ictx = icon.getContext('2d'); - ictx.fillStyle = '#AE1F24'; - ictx.beginPath(); - ictx.arc(63 * dpr, 19 * dpr, 10 * dpr, 0 * dpr, Math.PI * 2, true); - ictx.closePath(); - ictx.fill(); - ictx.drawImage(canvas, 14 * dpr, 16 * dpr); - ictx.beginPath(); - ictx.fillStyle = '#28A5DA'; - ictx.strokeStyle = '#355E7C'; - ictx.arc(63 * dpr, 19 * dpr, 8 * dpr, 0 * dpr, Math.PI * 2, true); - ictx.closePath(); - ictx.stroke(); - ictx.fill(); - writeText(ictx, 'bold ' + (12 * dpr) + 'px ' + Settings.blockArgFont, 'white', page.num, 26 * dpr, 58 * dpr); - return icon; -}; -BlockArg.prototype.updateIcon = function () { - var num = this.argValue; - var page = ScratchJr.stage.pages[num - 1]; - page.num = num; - this.div = this.pageIcon(num); - var block = this.daddy; - var ctx = block.blockshape.getContext('2d'); - ctx.drawImage(this.div, 0, 0, this.div.width, this.div.height, 0, 0, - this.div.width * block.scale, this.div.height * block.scale); -}; + updateIcon () { + var num = this.argValue; + var page = ScratchJr.stage.pages[num - 1]; + page.num = num; + this.div = this.pageIcon(num); + var block = this.daddy; + var ctx = block.blockshape.getContext('2d'); + ctx.drawImage(this.div, 0, 0, this.div.width, this.div.height, 0, 0, + this.div.width * block.scale, this.div.height * block.scale); + } +} diff --git a/src/editor/engine/Page.js b/src/editor/engine/Page.js index 9a95cf4..d46ddc6 100644 --- a/src/editor/engine/Page.js +++ b/src/editor/engine/Page.js @@ -1,526 +1,545 @@ -var Page = function (id, data, fcn) { - var container = ScratchJr.stage.pagesdiv; - this.div = newHTML('div', 'stagepage', container); // newDiv(container,0,0, 480, 360, {position: 'absolute'}); - this.div.owner = this; - this.id = id; - this.textstartat = 36; - this.div.setAttribute('id', this.id); - ScratchJr.stage.currentPage = this; - this.num = data ? data.num : ScratchJr.stage.pages.length + 1; - this.sprites = JSON.stringify([]); - this.bkg = newDiv(this.div, 0, 0, 480, 360, { - position: 'absolute', - background: ScratchJr.stagecolor - }); - this.bkg.type = 'background'; - ScratchJr.stage.pages.push(this); - if (!data) { - this.emptyPage(); - } else { - this.loadPageData(data, fcn); - } -}; +import ScratchJr from '../ScratchJr'; +import Project from '../../ui/Project'; +import Thumbs from '../../ui/Thumbs'; +import UI from '../../ui/UI'; +import Sprite from './Sprite'; +import Palette from './Palette'; +import BlockSpecs from '../blocks/BlockSpecs'; +import iOS from '../../iPad/iOS'; +import IO from '../../iPad/IO'; +import Undo from '../ui/Undo'; +import Matrix from '../geom/Matrix'; +import Vector from '../geom/Vector'; +import {newHTML, newDiv, gn, + setCanvasSizeScaledToWindowDocumentHeight, + DEGTOR, getIdFor, setProps, isTablet} from '../../utils/lib'; -Page.prototype.loadPageData = function (data, fcn) { - this.currentSpriteName = data.lastSprite; - if (data.textstartat) { - this.textstartat = Number(data.textstartat); - } - if (data.md5 && (data.md5 != 'undefined')) { - Project.mediaCount++; - this.setBackground(data.md5, checkBkgDone); - } else { - this.clearBackground(); - } - var list = data.sprites; - for (var j = 0; j < list.length; j++) { - Project.recreateObject(this, list[j], data[list[j]], checkCount); - } - for (var i = 0; i < data.layers.length; i++) { - var obj = gn(data.layers[i]); - if (obj) { - this.div.appendChild(obj); - } - } - function checkCount () { - if (!fcn) { - return; - } - if (Project.mediaCount < 1) { - fcn(); - } - } - - function checkBkgDone () { - Project.substractCount(); - if (!fcn) { - return; - } - if (Project.mediaCount < 1) { - fcn(); - } - } -}; - -Page.prototype.emptyPage = function () { - this.clearBackground(); - this.createCat(); -}; - -Page.prototype.setCurrentSprite = function (spr) { // set the sprite and toggles UI if no sprite is available - if (ScratchJr.getSprite()) { - ScratchJr.getSprite().unselect(); - } - if (spr) { - this.currentSpriteName = spr.id; - spr.div.style.visibility = 'visible'; - Palette.show(); - gn('scripts').style.display = ScratchJr.inFullscreen ? 'none' : 'block'; - spr.activate(); - } else { - this.currentSpriteName = undefined; - Palette.hide(); - gn('scripts').style.display = 'none'; - } -}; - -Page.prototype.clearBackground = function () { - while (this.bkg.childElementCount > 0) { - this.bkg.removeChild(this.bkg.childNodes[0]); - } -}; - -Page.prototype.setBackground = function (name, fcn) { - if (name == 'undefined') { - return; - } - this.clearBackground(); - this.md5 = undefined; - if (name == 'none') { - if (fcn) { - fcn(); - } - return; - } - this.md5 = name; - if (!name) { - return; - } - var me = this; - var url = (MediaLib.keys[name]) ? MediaLib.path + name : (name.indexOf('/') < 0) ? iOS.path + name : name; - var md5 = (MediaLib.keys[name]) ? MediaLib.path + name : name; - - if (md5.substr(md5.length - 3) == 'png') { - this.setBackgroundImage(url, fcn); - this.svg = null; - return; - } - - if (md5.indexOf('/') > -1) { - IO.requestFromServer(md5, doNext); - } else { - iOS.getmedia(md5, nextStep); - } - function nextStep (base64) { - doNext(atob(base64)); - } - function doNext (str) { - str = str.replace(/>\s*<'); - me.setSVG(str); - if ((str.indexOf('xlink:href') < 0) && iOS.path) { - me.setBackgroundImage(url, fcn); // does not have embedded images +export default class Page { + constructor (id, data, fcn) { + var container = ScratchJr.stage.pagesdiv; + this.div = newHTML('div', 'stagepage', container); // newDiv(container,0,0, 480, 360, {position: 'absolute'}); + this.div.owner = this; + this.id = id; + this.textstartat = 36; + this.div.setAttribute('id', this.id); + ScratchJr.stage.currentPage = this; + this.num = data ? data.num : ScratchJr.stage.pages.length + 1; + this.sprites = JSON.stringify([]); + this.bkg = newDiv(this.div, 0, 0, 480, 360, { + position: 'absolute', + background: ScratchJr.stagecolor + }); + this.bkg.type = 'background'; + ScratchJr.stage.pages.push(this); + if (!data) { + this.emptyPage(); } else { - var base64 = IO.getImageDataURL(me.md5, btoa(str)); - IO.getImagesInSVG(str, function () { - me.setBackgroundImage(base64, fcn); - }); + this.loadPageData(data, fcn); } } -}; -Page.prototype.setSVG = function (str) { - var xmlDoc = new DOMParser().parseFromString(str, 'text/xml'); - var extxml = document.importNode(xmlDoc.documentElement, true); - if (extxml.childNodes[0].nodeName == '#comment') { - extxml.removeChild(extxml.childNodes[0]); + loadPageData (data, fcn) { + this.currentSpriteName = data.lastSprite; + if (data.textstartat) { + this.textstartat = Number(data.textstartat); + } + if (data.md5 && (data.md5 != 'undefined')) { + Project.mediaCount++; + this.setBackground(data.md5, checkBkgDone); + } else { + this.clearBackground(); + } + var list = data.sprites; + for (var j = 0; j < list.length; j++) { + Project.recreateObject(this, list[j], data[list[j]], checkCount); + } + for (var i = 0; i < data.layers.length; i++) { + var obj = gn(data.layers[i]); + if (obj) { + this.div.appendChild(obj); + } + } + function checkCount () { + if (!fcn) { + return; + } + if (Project.mediaCount < 1) { + fcn(); + } + } + + function checkBkgDone () { + Project.substractCount(); + if (!fcn) { + return; + } + if (Project.mediaCount < 1) { + fcn(); + } + } } - this.svg = extxml; -}; -Page.prototype.setBackgroundImage = function (url, fcn) { - var img = document.createElement('img'); - img.src = url; - this.bkg.originalImg = img.cloneNode(false); - this.bkg.appendChild(img); - setProps(img.style, { - position: 'absolute', - left: '0px', - top: '0px', - width: '100%', - height: '100%' - }); - this.bkg.img = img; - if (!img.complete) { - img.onload = function () { + emptyPage () { + this.clearBackground(); + this.createCat(); + } + + setCurrentSprite (spr) { // set the sprite and toggles UI if no sprite is available + if (ScratchJr.getSprite()) { + ScratchJr.getSprite().unselect(); + } + if (spr) { + this.currentSpriteName = spr.id; + spr.div.style.visibility = 'visible'; + Palette.show(); + gn('scripts').style.display = ScratchJr.inFullscreen ? 'none' : 'block'; + spr.activate(); + } else { + this.currentSpriteName = undefined; + Palette.hide(); + gn('scripts').style.display = 'none'; + } + } + + clearBackground () { + while (this.bkg.childElementCount > 0) { + this.bkg.removeChild(this.bkg.childNodes[0]); + } + } + + setBackground (name, fcn) { + if (name == 'undefined') { + return; + } + this.clearBackground(); + this.md5 = undefined; + if (name == 'none') { + if (fcn) { + fcn(); + } + return; + } + this.md5 = name; + if (!name) { + return; + } + var me = this; + var url = (MediaLib.keys[name]) ? MediaLib.path + name : (name.indexOf('/') < 0) ? iOS.path + name : name; + var md5 = (MediaLib.keys[name]) ? MediaLib.path + name : name; + + if (md5.substr(md5.length - 3) == 'png') { + this.setBackgroundImage(url, fcn); + this.svg = null; + return; + } + + if (md5.indexOf('/') > -1) { + IO.requestFromServer(md5, doNext); + } else { + iOS.getmedia(md5, nextStep); + } + function nextStep (base64) { + doNext(atob(base64)); + } + function doNext (str) { + str = str.replace(/>\s*<'); + me.setSVG(str); + if ((str.indexOf('xlink:href') < 0) && iOS.path) { + me.setBackgroundImage(url, fcn); // does not have embedded images + } else { + var base64 = IO.getImageDataURL(me.md5, btoa(str)); + IO.getImagesInSVG(str, function () { + me.setBackgroundImage(base64, fcn); + }); + } + } + } + + setSVG (str) { + var xmlDoc = new DOMParser().parseFromString(str, 'text/xml'); + var extxml = document.importNode(xmlDoc.documentElement, true); + if (extxml.childNodes[0].nodeName == '#comment') { + extxml.removeChild(extxml.childNodes[0]); + } + this.svg = extxml; + } + + setBackgroundImage (url, fcn) { + var img = document.createElement('img'); + img.src = url; + this.bkg.originalImg = img.cloneNode(false); + this.bkg.appendChild(img); + setProps(img.style, { + position: 'absolute', + left: '0px', + top: '0px', + width: '100%', + height: '100%' + }); + this.bkg.img = img; + if (!img.complete) { + img.onload = function () { + if (gn('backdrop').className == 'modal-backdrop fade in') { + Project.setProgress(Project.getMediaLoadRatio(70)); + } + if (fcn) { + fcn(); + } + }; + } else { if (gn('backdrop').className == 'modal-backdrop fade in') { Project.setProgress(Project.getMediaLoadRatio(70)); } if (fcn) { fcn(); } - }; - } else { - if (gn('backdrop').className == 'modal-backdrop fade in') { - Project.setProgress(Project.getMediaLoadRatio(70)); - } - if (fcn) { - fcn(); } } -}; -Page.prototype.setPageSprites = function (showstate) { - var list = JSON.parse(this.sprites); - for (var i = 0; i < list.length; i++) { - gn(list[i]).style.visibility = showstate; + setPageSprites (showstate) { + var list = JSON.parse(this.sprites); + for (var i = 0; i < list.length; i++) { + gn(list[i]).style.visibility = showstate; + } } -}; -Page.prototype.redoChangeBkg = function (data) { - var me = this; - var md5 = data[this.id].md5 ? data[this.id].md5 : 'none'; - this.setBackground(md5, me.updateThumb); -}; - -////////////////////////////////////// -// page thumbnail -///////////////////////////////////// - -Page.prototype.updateThumb = function (page) { - var me = page ? page : ScratchJr.stage.currentPage; - if (!me.thumbnail) { - return; + redoChangeBkg (data) { + var me = this; + var md5 = data[this.id].md5 ? data[this.id].md5 : 'none'; + this.setBackground(md5, me.updateThumb); } - var c = me.thumbnail.childNodes[0].childNodes[0]; - me.setPageThumb(c); -}; -Page.prototype.pageThumbnail = function (p) { - var tb = newHTML('div', 'pagethumb', p); - tb.setAttribute('id', getIdFor('pagethumb')); - tb.owner = this.id; - tb.type = 'pagethumb'; - var container = newHTML('div', 'pc-container', tb); - var c = newHTML('canvas', 'pc', container); - this.setPageThumb(c); - var num = newHTML('div', 'pagenum', tb); - var pq = newHTML('p', undefined, num); - pq.textContent = this.num; - newHTML('div', 'deletethumb', tb); - if (isTablet) { - tb.ontouchstart = function (evt) { - Thumbs.pageMouseDown(evt); - }; - } else { - tb.onmousedown = function (evt) { - Thumbs.pageMouseDown(evt); - }; + ////////////////////////////////////// + // page thumbnail + ///////////////////////////////////// + + updateThumb (page) { + var me = page ? page : ScratchJr.stage.currentPage; + if (!me.thumbnail) { + return; + } + var c = me.thumbnail.childNodes[0].childNodes[0]; + me.setPageThumb(c); } - this.thumbnail = tb; - return tb; -}; -Page.prototype.setPageThumb = function (c) { - var w0, h0; - if (Settings.edition == 'PBS') { - w0 = 136; - h0 = 101; - } else { - w0 = 132; - h0 = 99; + pageThumbnail (p) { + var tb = newHTML('div', 'pagethumb', p); + tb.setAttribute('id', getIdFor('pagethumb')); + tb.owner = this.id; + tb.type = 'pagethumb'; + var container = newHTML('div', 'pc-container', tb); + var c = newHTML('canvas', 'pc', container); + this.setPageThumb(c); + var num = newHTML('div', 'pagenum', tb); + var pq = newHTML('p', undefined, num); + pq.textContent = this.num; + newHTML('div', 'deletethumb', tb); + if (isTablet) { + tb.ontouchstart = function (evt) { + Thumbs.pageMouseDown(evt); + }; + } else { + tb.onmousedown = function (evt) { + Thumbs.pageMouseDown(evt); + }; + } + this.thumbnail = tb; + return tb; } - setCanvasSizeScaledToWindowDocumentHeight(c, w0, h0); - var w = c.width; - var h = c.height; - var ctx = c.getContext('2d'); - if (Settings.edition == 'PBS') { + setPageThumb (c) { + var w0, h0; + if (Settings.edition == 'PBS') { + w0 = 136; + h0 = 101; + } else { + w0 = 132; + h0 = 99; + } + setCanvasSizeScaledToWindowDocumentHeight(c, w0, h0); + var w = c.width; + var h = c.height; + var ctx = c.getContext('2d'); - ctx.rect(0, 0, w, h); - ctx.fillStyle = '#fff'; - ctx.fill(); - } else { - ctx.drawImage(BlockSpecs.canvasMask, 0, 0, w, h); + if (Settings.edition == 'PBS') { + + ctx.rect(0, 0, w, h); + ctx.fillStyle = '#fff'; + ctx.fill(); + } else { + ctx.drawImage(BlockSpecs.canvasMask, 0, 0, w, h); + } + if (this.bkg.childElementCount > 0) { + var img = this.bkg.originalImg; + var imgw = img.naturalWidth ? img.naturalWidth : img.width; + var imgh = img.naturalHeight ? img.naturalHeight : img.height; + ctx.drawImage(img, 0, 0, imgw, imgh, 0, 0, w, h); + } + var scale = w / 480; + for (var i = 0; i < this.div.childElementCount; i++) { + var spr = this.div.childNodes[i].owner; + if (!spr) { + continue; + } + this.stampSpriteAt(ctx, spr, scale); + } + if (Settings.edition != 'PBS') { + ctx.save(); + ctx.globalCompositeOperation = 'destination-in'; + ctx.drawImage(BlockSpecs.canvasMask, 0, 0, w, h); + ctx.restore(); + } } - if (this.bkg.childElementCount > 0) { - var img = this.bkg.originalImg; + + stampSpriteAt (ctx, spr, scale) { + if (!spr.shown) { + return; + } + var img = (spr.type == 'sprite') ? spr.originalImg : spr.outline; + this.drawSpriteImage(ctx, img, spr, scale); + } + + drawSpriteImage (ctx, img, spr, scale) { + if (!spr.shown) { + return; + } + if (!img) { + return; + } var imgw = img.naturalWidth ? img.naturalWidth : img.width; var imgh = img.naturalHeight ? img.naturalHeight : img.height; - ctx.drawImage(img, 0, 0, imgw, imgh, 0, 0, w, h); - } - var scale = w / 480; - for (var i = 0; i < this.div.childElementCount; i++) { - var spr = this.div.childNodes[i].owner; - if (!spr) { - continue; - } - this.stampSpriteAt(ctx, spr, scale); - } - if (Settings.edition != 'PBS') { + var sw = imgw * spr.scale; + var sh = imgh * spr.scale; ctx.save(); - ctx.globalCompositeOperation = 'destination-in'; - ctx.drawImage(BlockSpecs.canvasMask, 0, 0, w, h); + var pt = { + x: spr.cx * spr.scale * scale, + y: spr.cy * spr.scale * scale + }; + ctx.translate(pt.x, pt.y); + ctx.rotate(spr.angle * DEGTOR); + ctx.translate(-pt.x, -pt.y); + if (spr.flip) { + ctx.scale(-1, 1); + ctx.translate(-img.width * scale * spr.scale, 0); + } + var mtx = this.getMatrixFor(spr, scale); + var pos = Vector.floor(mtx.transformPoint({ + x: Math.floor(spr.screenLeft() * scale), + y: Math.floor(spr.screenTop() * scale) + })); + ctx.drawImage(img, 0, 0, imgw, imgh, pos.x, pos.y, Math.floor(sw * scale), Math.floor(sh * scale)); ctx.restore(); } -}; -Page.prototype.stampSpriteAt = function (ctx, spr, scale) { - if (!spr.shown) { - return; - } - var img = (spr.type == 'sprite') ? spr.originalImg : spr.outline; - this.drawSpriteImage(ctx, img, spr, scale); -}; - -Page.prototype.drawSpriteImage = function (ctx, img, spr, scale) { - if (!spr.shown) { - return; - } - if (!img) { - return; - } - var imgw = img.naturalWidth ? img.naturalWidth : img.width; - var imgh = img.naturalHeight ? img.naturalHeight : img.height; - var sw = imgw * spr.scale; - var sh = imgh * spr.scale; - ctx.save(); - var pt = { - x: spr.cx * spr.scale * scale, - y: spr.cy * spr.scale * scale - }; - ctx.translate(pt.x, pt.y); - ctx.rotate(spr.angle * DEGTOR); - ctx.translate(-pt.x, -pt.y); - if (spr.flip) { - ctx.scale(-1, 1); - ctx.translate(-img.width * scale * spr.scale, 0); - } - var mtx = this.getMatrixFor(spr, scale); - var pos = Vector.floor(mtx.transformPoint({ - x: Math.floor(spr.screenLeft() * scale), - y: Math.floor(spr.screenTop() * scale) - })); - ctx.drawImage(img, 0, 0, imgw, imgh, pos.x, pos.y, Math.floor(sw * scale), Math.floor(sh * scale)); - ctx.restore(); -}; - -Page.prototype.getMatrixFor = function (spr) { - var sx = new Matrix(); - var angle = spr.angle ? -spr.angle : 0; - if (spr.flip) { - sx.a = -1; - sx.d = 1; - } - var rx = new Matrix(); - rx.rotate(angle); - return sx.multiply(rx); -}; - -///////////////////// -// Saving -///////////////////// - -Page.prototype.encodePage = function () { - var p = this.div; - var spritelist = JSON.parse(this.sprites); - var data = {}; - data.textstartat = this.textstartat; - data.sprites = spritelist; - var md5 = this.md5; - if (md5) { - data.md5 = md5; - } - data.num = this.num; - this.currentSpriteName = !this.currentSpriteName ? - undefined : (gn(this.currentSpriteName).owner.type == 'sprite') ? this.currentSpriteName : this.getSprites()[0]; - data.lastSprite = this.currentSpriteName; - for (var j = 0; j < spritelist.length; j++) { - data[spritelist[j]] = Project.encodeSprite(spritelist[j]); - } - var layers = []; - for (var i = 1; i < p.childElementCount; i++) { - var layerid = p.childNodes[i].id; - if (layerid && (layerid != '')) { - layers.push(layerid); + getMatrixFor (spr) { + var sx = new Matrix(); + var angle = spr.angle ? -spr.angle : 0; + if (spr.flip) { + sx.a = -1; + sx.d = 1; } + var rx = new Matrix(); + rx.rotate(angle); + return sx.multiply(rx); } - data.layers = layers; - return data; -}; -Page.prototype.getSprites = function () { - var spritelist = JSON.parse(this.sprites); - var res = []; - for (var i = 0; i < spritelist.length; i++) { - if (gn(spritelist[i]).owner.type == 'sprite') { - res.push(spritelist[i]); + ///////////////////// + // Saving + ///////////////////// + + encodePage () { + var p = this.div; + var spritelist = JSON.parse(this.sprites); + var data = {}; + data.textstartat = this.textstartat; + data.sprites = spritelist; + var md5 = this.md5; + if (md5) { + data.md5 = md5; } + data.num = this.num; + this.currentSpriteName = !this.currentSpriteName ? + undefined : (gn(this.currentSpriteName).owner.type == 'sprite') ? + this.currentSpriteName : this.getSprites()[0]; + data.lastSprite = this.currentSpriteName; + for (var j = 0; j < spritelist.length; j++) { + data[spritelist[j]] = Project.encodeSprite(spritelist[j]); + } + var layers = []; + for (var i = 1; i < p.childElementCount; i++) { + var layerid = p.childNodes[i].id; + if (layerid && (layerid != '')) { + layers.push(layerid); + } + } + data.layers = layers; + return data; } - return res; -}; - -///////////////////////////// -// Object creation -///////////////////////////// - -Page.prototype.createText = function () { - var textAttr = { - shown: true, - type: 'text', - scale: 1, - defaultScale: 1, - speed: 2, - dirx: 1, - diry: 1, - angle: 0, - homex: 240, - homey: this.textstartat, - xcoor: 240, - ycoor: this.textstartat, - str: '', - color: BlockSpecs.fontcolors[BlockSpecs.fontcolors.length - 1], - fontsize: 36, - cx: 0, - cy: (32 * 1.35 / 2), - w: 0, - h: 36 * 1.35 - }; - textAttr.page = this; - textAttr.id = getIdFor('Text'); - new Sprite(textAttr); -}; - -Page.prototype.createCat = function () { - var sprAttr = UI.mascotData(ScratchJr.stage.currentPage); - Project.mediaCount++; - var me = this; - new Sprite(sprAttr, me.pageAdded); -}; - -Page.prototype.update = function (spr) { - if (spr) { - Undo.record({ - action: 'modify', - where: this.id, - who: spr.id - }); - } else { - Undo.record({ - action: 'recreatepage', - where: this.id, - who: this.id - }); + getSprites () { + var spritelist = JSON.parse(this.sprites); + var res = []; + for (var i = 0; i < spritelist.length; i++) { + if (gn(spritelist[i]).owner.type == 'sprite') { + res.push(spritelist[i]); + } + } + return res; } - if (spr) { - Thumbs.updateSprite(spr); - } else { - Thumbs.updateSprites(); + + + ///////////////////////////// + // Object creation + ///////////////////////////// + + createText () { + var textAttr = { + shown: true, + type: 'text', + scale: 1, + defaultScale: 1, + speed: 2, + dirx: 1, + diry: 1, + angle: 0, + homex: 240, + homey: this.textstartat, + xcoor: 240, + ycoor: this.textstartat, + str: '', + color: BlockSpecs.fontcolors[BlockSpecs.fontcolors.length - 1], + fontsize: 36, + cx: 0, + cy: (32 * 1.35 / 2), + w: 0, + h: 36 * 1.35 + }; + textAttr.page = this; + textAttr.id = getIdFor('Text'); + new Sprite(textAttr); } - Thumbs.updatePages(); -}; -Page.prototype.updateBkg = function () { - var me = ScratchJr.stage.currentPage; - ScratchJr.storyStart('Page.prototype.updateBkg'); - Undo.record({ - action: 'changebkg', - where: me.id, - who: me.id - }); - Thumbs.updatePages(); -}; + createCat () { + var sprAttr = UI.mascotData(ScratchJr.stage.currentPage); + Project.mediaCount++; + var me = this; + new Sprite(sprAttr, me.pageAdded); + } -Page.prototype.spriteAdded = function (spr) { - var me = spr.div.parentNode.owner; - me.setCurrentSprite(spr); - me.update(spr); - UI.spriteInView(spr); - ScratchJr.onHold = false; -}; + update (spr) { + if (spr) { + Undo.record({ + action: 'modify', + where: this.id, + who: spr.id + }); + } else { + Undo.record({ + action: 'recreatepage', + where: this.id, + who: this.id + }); + } + if (spr) { + Thumbs.updateSprite(spr); + } else { + Thumbs.updateSprites(); + } + Thumbs.updatePages(); + } -Page.prototype.pageAdded = function (spr) { - var me = spr.div.parentNode.owner; - Project.mediaCount--; - me.setCurrentSprite(spr); - ScratchJr.storyStart('Page.prototype.pageAdded'); - if (ScratchJr.stage.pages.length > 1) { + updateBkg () { + var me = ScratchJr.stage.currentPage; + ScratchJr.storyStart('Page.prototype.updateBkg'); Undo.record({ - action: 'addpage', + action: 'changebkg', where: me.id, who: me.id }); + Thumbs.updatePages(); } - Thumbs.updateSprites(); - Thumbs.updatePages(); -}; -Page.prototype.addSprite = function (scale, md5, cname) { - ScratchJr.onHold = true; - var sprAttr = { - flip: false, - angle: 0, - shown: true, - type: 'sprite', - scale: scale, - defaultScale: scale, - speed: 2, - dirx: 1, - diry: 1, - sounds: ['pop.mp3'], - homex: 240, - homescale: scale, - homey: 180, - xcoor: 240, - ycoor: 180, - homeshown: true - }; - sprAttr.page = ScratchJr.stage.currentPage; - sprAttr.id = getIdFor(cname); - sprAttr.name = cname; - sprAttr.md5 = md5; - new Sprite(sprAttr, this.spriteAdded); -}; - -Page.prototype.createSprite = function (data) { - new Sprite(data, this.spriteAdded); -}; - -Page.prototype.modifySprite = function (md5, cid, sid) { - var sprite = gn(unescape(sid)).owner; - if (!sprite) { - sprite = ScratchJr.getSprite(); + spriteAdded (spr) { + var me = spr.div.parentNode.owner; + me.setCurrentSprite(spr); + me.update(spr); + UI.spriteInView(spr); + ScratchJr.onHold = false; } - sprite.md5 = md5; - sprite.name = cid; - var me = this; - sprite.getAsset(gotImage); - function gotImage (dataurl) { - sprite.setCostume(dataurl, me.spriteAdded); - } -}; -Page.prototype.modifySpriteName = function (cid, sid) { - var sprite = gn(unescape(sid)).owner; - if (!sprite) { - sprite = ScratchJr.getSprite(); + pageAdded (spr) { + var me = spr.div.parentNode.owner; + Project.mediaCount--; + me.setCurrentSprite(spr); + ScratchJr.storyStart('Page.prototype.pageAdded'); + if (ScratchJr.stage.pages.length > 1) { + Undo.record({ + action: 'addpage', + where: me.id, + who: me.id + }); + } + Thumbs.updateSprites(); + Thumbs.updatePages(); } - sprite.name = cid; - sprite.thumbnail.childNodes[1].textContent = cid; - Undo.record({ - action: 'modify', - where: this.id, - who: sprite.id - }); - ScratchJr.storyStart('Page.prototype.modifySpriteName'); -}; + + addSprite (scale, md5, cname) { + ScratchJr.onHold = true; + var sprAttr = { + flip: false, + angle: 0, + shown: true, + type: 'sprite', + scale: scale, + defaultScale: scale, + speed: 2, + dirx: 1, + diry: 1, + sounds: ['pop.mp3'], + homex: 240, + homescale: scale, + homey: 180, + xcoor: 240, + ycoor: 180, + homeshown: true + }; + sprAttr.page = ScratchJr.stage.currentPage; + sprAttr.id = getIdFor(cname); + sprAttr.name = cname; + sprAttr.md5 = md5; + new Sprite(sprAttr, this.spriteAdded); + } + + createSprite (data) { + new Sprite(data, this.spriteAdded); + } + + modifySprite (md5, cid, sid) { + var sprite = gn(unescape(sid)).owner; + if (!sprite) { + sprite = ScratchJr.getSprite(); + } + sprite.md5 = md5; + sprite.name = cid; + var me = this; + sprite.getAsset(gotImage); + function gotImage (dataurl) { + sprite.setCostume(dataurl, me.spriteAdded); + } + } + + modifySpriteName (cid, sid) { + var sprite = gn(unescape(sid)).owner; + if (!sprite) { + sprite = ScratchJr.getSprite(); + } + sprite.name = cid; + sprite.thumbnail.childNodes[1].textContent = cid; + Undo.record({ + action: 'modify', + where: this.id, + who: sprite.id + }); + ScratchJr.storyStart('Page.prototype.modifySpriteName'); + } +} diff --git a/src/editor/engine/Runtime.js b/src/editor/engine/Runtime.js index 66213dc..5985e8c 100755 --- a/src/editor/engine/Runtime.js +++ b/src/editor/engine/Runtime.js @@ -1,179 +1,187 @@ -var Runtime = function () { - this.threadsRunning = []; - this.thread = undefined; - this.intervalId = undefined; - this.yield = false; -}; +import ScratchJr from '../ScratchJr'; +import Project from '../../ui/Project'; +import Prims from './Prims'; +import Thread from './Thread'; -Runtime.prototype.beginTimer = function () { - if (this.intervalId != null) { - window.clearInterval(this.intervalId); +export default class Runtime { + constructor () { + this.threadsRunning = []; + this.thread = undefined; + this.intervalId = undefined; + this.yield = false; } - var rt = this; - this.intervalId = window.setInterval(function () { - rt.tickTask(); - }, 32); - Project.saving = false; - // Prims.time = (new Date() - 0); - this.threadsRunning = []; -}; -Runtime.prototype.tickTask = function () { - ScratchJr.updateRunStopButtons(); - if (this.threadsRunning.length < 1) { - return; - } - var activeThreads = []; - for (var i = 0; i < this.threadsRunning.length; i++) { - if (this.threadsRunning[i].isRunning) { - activeThreads.push(this.threadsRunning[i]); + beginTimer () { + if (this.intervalId != null) { + window.clearInterval(this.intervalId); } + var rt = this; + this.intervalId = window.setInterval(function () { + rt.tickTask(); + }, 32); + Project.saving = false; + // Prims.time = (new Date() - 0); + this.threadsRunning = []; } - this.threadsRunning = activeThreads; - for (var j = 0; j < this.threadsRunning.length; j++) { - this.step(j); - } -}; -Runtime.prototype.inactive = function () { - if (this.threadsRunning.length < 1) { - return true; - } - var inactive = true; - for (var i = 0; i < this.threadsRunning.length; i++) { - var t = this.threadsRunning[i]; - if (!t) { - continue; - } - if (t.isRunning && (t.firstBlock.blocktype != 'ontouch')) { - inactive = false; - } - if ((t.firstBlock.blocktype == 'ontouch') && (t.thisblock != null) && (t.thisblock.blocktype != 'ontouch')) { - inactive = false; - } - } - return inactive; -}; - -Runtime.prototype.step = function (n) { - this.yield = false; - this.thread = this.threadsRunning[n]; - while (true) { // eslint-disable-line no-constant-condition - if (!this.thread.isRunning) { + tickTask () { + ScratchJr.updateRunStopButtons(); + if (this.threadsRunning.length < 1) { return; } - if (this.thread.waitTimer > 0) { - this.thread.waitTimer += -1; - return; - } - // if (this.thread.spr.parentNode.id == "frame") return; // object is being dragged - if (this.yield) { - return; - } - if (this.thread.thisblock == null) { - this.endCase(); - this.yield = true; - } else { - this.runPrim(); - } - } -}; - -Runtime.prototype.addRunScript = function (spr, b) { - this.restartThread(spr, b); -}; - -Runtime.prototype.stopThreads = function () { - for (var i in this.threadsRunning) { - this.threadsRunning[i].stop(); - } - this.threadsRunning = []; -}; - -Runtime.prototype.stopThreadBlock = function (b) { - for (var i in this.threadsRunning) { - if (this.threadsRunning[i].firstBlock == b) { - this.threadsRunning[i].stop(); - } - } -}; - -Runtime.prototype.stopThreadSprite = function (spr) { - for (var i in this.threadsRunning) { - if (this.threadsRunning[i].spr == spr) { - this.threadsRunning[i].stop(); - } - } -}; - -Runtime.prototype.removeRunScript = function (spr) { - var res = []; - for (var i in this.threadsRunning) { - if (this.threadsRunning[i].spr == spr) { + var activeThreads = []; + for (var i = 0; i < this.threadsRunning.length; i++) { if (this.threadsRunning[i].isRunning) { - if (this.threadsRunning[i].thisblock != null) { - this.threadsRunning[i].endPrim(); + activeThreads.push(this.threadsRunning[i]); + } + } + this.threadsRunning = activeThreads; + for (var j = 0; j < this.threadsRunning.length; j++) { + this.step(j); + } + } + + inactive () { + if (this.threadsRunning.length < 1) { + return true; + } + var inactive = true; + for (var i = 0; i < this.threadsRunning.length; i++) { + var t = this.threadsRunning[i]; + if (!t) { + continue; + } + if (t.isRunning && (t.firstBlock.blocktype != 'ontouch')) { + inactive = false; + } + if ((t.firstBlock.blocktype == 'ontouch') && (t.thisblock != null) + && (t.thisblock.blocktype != 'ontouch')) { + inactive = false; + } + } + return inactive; + } + + step (n) { + this.yield = false; + this.thread = this.threadsRunning[n]; + while (true) { // eslint-disable-line no-constant-condition + if (!this.thread.isRunning) { + return; + } + if (this.thread.waitTimer > 0) { + this.thread.waitTimer += -1; + return; + } + // if (this.thread.spr.parentNode.id == "frame") return; // object is being dragged + if (this.yield) { + return; + } + if (this.thread.thisblock == null) { + this.endCase(); + this.yield = true; + } else { + this.runPrim(); + } + } + } + + addRunScript (spr, b) { + this.restartThread(spr, b); + } + + stopThreads () { + for (var i in this.threadsRunning) { + this.threadsRunning[i].stop(); + } + this.threadsRunning = []; + } + + stopThreadBlock (b) { + for (var i in this.threadsRunning) { + if (this.threadsRunning[i].firstBlock == b) { + this.threadsRunning[i].stop(); + } + } + } + + stopThreadSprite (spr) { + for (var i in this.threadsRunning) { + if (this.threadsRunning[i].spr == spr) { + this.threadsRunning[i].stop(); + } + } + } + + removeRunScript (spr) { + var res = []; + for (var i in this.threadsRunning) { + if (this.threadsRunning[i].spr == spr) { + if (this.threadsRunning[i].isRunning) { + if (this.threadsRunning[i].thisblock != null) { + this.threadsRunning[i].endPrim(); + } + res.push(this.threadsRunning[i].duplicate()); } - res.push(this.threadsRunning[i].duplicate()); - } - this.threadsRunning[i].isRunning = false; - if (this.threadsRunning[i].oldblock != null) { - this.threadsRunning[i].oldblock.unhighlight(); - } - } - } - return res; -}; - -Runtime.prototype.runPrim = function () { - if (this.thread.oldblock != null) { - this.thread.oldblock.unhighlight(); - } - this.thread.oldblock = null; - var token = Prims.table[this.thread.thisblock.blocktype]; - if (token == null) { - token = Prims.table.missing; - } else { - var noh = ['repeat', 'gotopage']; - if (noh.indexOf(this.thread.thisblock.blocktype) < 0) { - this.thread.thisblock.highlight(); - this.thread.oldblock = this.thread.thisblock; - } - Prims.time = (new Date() - 0); - token(this.thread); - } -}; - -Runtime.prototype.endCase = function () { - if (this.thread.oldblock != null) { - this.thread.oldblock.unhighlight(); - } - if (this.thread.stack.length == 0) { - Prims.Done(this.thread); - } else { - var thing = (this.thread.stack).pop(); - this.thread.thisblock = thing; - this.runPrim(); - } -}; - -Runtime.prototype.restartThread = function (spr, b, active) { - var newThread = new Thread(spr, b); - var wasRunning = false; - for (var i = 0; i < this.threadsRunning.length; i++) { - if (this.threadsRunning[i].firstBlock == b) { - wasRunning = true; - if (b.blocktype != 'ontouch') { // on touch demons are special - they are not interruptable + this.threadsRunning[i].isRunning = false; if (this.threadsRunning[i].oldblock != null) { this.threadsRunning[i].oldblock.unhighlight(); } - this.threadsRunning[i].stopping(active); - newThread = this.threadsRunning[i]; } } + return res; } - if (!wasRunning) { - this.threadsRunning.push(newThread); + + runPrim () { + if (this.thread.oldblock != null) { + this.thread.oldblock.unhighlight(); + } + this.thread.oldblock = null; + var token = Prims.table[this.thread.thisblock.blocktype]; + if (token == null) { + token = Prims.table.missing; + } else { + var noh = ['repeat', 'gotopage']; + if (noh.indexOf(this.thread.thisblock.blocktype) < 0) { + this.thread.thisblock.highlight(); + this.thread.oldblock = this.thread.thisblock; + } + Prims.time = (new Date() - 0); + token(this.thread); + } } - return newThread; -}; + + endCase () { + if (this.thread.oldblock != null) { + this.thread.oldblock.unhighlight(); + } + if (this.thread.stack.length == 0) { + Prims.Done(this.thread); + } else { + var thing = (this.thread.stack).pop(); + this.thread.thisblock = thing; + this.runPrim(); + } + } + + restartThread (spr, b, active) { + var newThread = new Thread(spr, b); + var wasRunning = false; + for (var i = 0; i < this.threadsRunning.length; i++) { + if (this.threadsRunning[i].firstBlock == b) { + wasRunning = true; + if (b.blocktype != 'ontouch') { // on touch demons are special - they are not interruptable + if (this.threadsRunning[i].oldblock != null) { + this.threadsRunning[i].oldblock.unhighlight(); + } + this.threadsRunning[i].stopping(active); + newThread = this.threadsRunning[i]; + } + } + } + if (!wasRunning) { + this.threadsRunning.push(newThread); + } + return newThread; + } +} diff --git a/src/editor/engine/Sprite.js b/src/editor/engine/Sprite.js index 39b1bc9..06d8f65 100755 --- a/src/editor/engine/Sprite.js +++ b/src/editor/engine/Sprite.js @@ -7,1142 +7,1168 @@ // d. Create Mask for pixel detection and cache it on the browser //////////////////////////////////////////////////////////// -var Sprite = function (attr, whenDone) { - if (attr.type == 'sprite') { - this.createSprite(attr.page, attr.md5, attr.id, attr, whenDone); - } else { - this.createText(attr, whenDone); - } -}; +import ScratchJr from '../ScratchJr'; +import Project from '../../ui/Project'; +import Thumbs from '../../ui/Thumbs'; +import UI from '../../ui/UI'; +import BlockSpecs from '../blocks/BlockSpecs'; +import iOS from '../../iPad/iOS'; +import IO from '../../iPad/IO'; +import Undo from '../ui/Undo'; +import ScriptsPane from '../ui/ScriptsPane'; +import SVG2Canvas from '../../utils/SVG2Canvas'; +import SVGTools from '../../painteditor/SVGTools'; +import Rectangle from '../../geom/Rectangle'; +import Events from '../../utils/Events'; +import Localization from '../../utils/Localization'; +import ScratchAudio from '../../utils/ScratchAudio'; +import Scripts from '../ui/Scripts'; +import {newHTML, newDiv, newP, gn, + setCanvasSizeScaledToWindowDocumentHeight, + DEGTOR, getIdFor, setProps, isTablet, isiOS, + isAndroid, fitInRect, scaleMultiplier, setCanvasSize, + globaly, globalx, rgbToHex} from '../../utils/lib'; -Sprite.prototype.createSprite = function (page, md5, id, attr, fcn) { - ScratchJr.storyStart('Sprite.prototype.createSprite'); - this.div = document.createElement('div'); - setProps(this.div.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - //document.createElement('img'); - this.div.owner = this; - this.div.id = id; - this.id = id; - this.md5 = md5; - this.borderOn = false; - this.outline = document.createElement('canvas'); - this.code = new Scripts(this); - setProps(this, attr); - if (Localization.isSampleLocalizedKey(this.name) && ScratchJr.isSampleOrStarter()) { - this.name = Localization.localize('SAMPLE_TEXT_' + this.name); - } - for (var i = 0; i < this.sounds.length; i++) { - ScratchAudio.loadProjectSound(this.sounds[i]); - } - var sprites = JSON.parse(page.sprites); - sprites.push(this.id); - page.sprites = JSON.stringify(sprites); - var me = this; - page.div.appendChild(this.div); - this.div.style.visibility = 'hidden'; - this.getAsset(gotImage); // sets the SVG and the image - function gotImage (dataurl) { - me.setCostume(dataurl, fcn); - } -}; - -Sprite.prototype.getAsset = function (whenDone) { - var md5 = this.md5; - var spr = this; - var url = (MediaLib.keys[md5]) ? MediaLib.path + md5 : (md5.indexOf('/') < 0) ? iOS.path + md5 : md5; - md5 = (MediaLib.keys[md5]) ? MediaLib.path + md5 : md5; - if (md5.indexOf('/') > -1) { - IO.requestFromServer(md5, doNext); - } else { - iOS.getmedia(md5, nextStep); - } - function nextStep (base64) { - doNext(atob(base64)); - } - function doNext (str) { - str = str.replace(/>\s*<'); - spr.setSVG(str); - if ((str.indexOf('xlink:href') < 0) && iOS.path { - whenDone(url); // does not have embedded images +export default class Sprite { + constructor (attr, whenDone) { + if (attr.type == 'sprite') { + this.createSprite(attr.page, attr.md5, attr.id, attr, whenDone); } else { - var base64 = IO.getImageDataURL(spr.md5, btoa(str)); - IO.getImagesInSVG(str, function () { - whenDone(base64); - }); + this.createText(attr, whenDone); } } -}; -Sprite.prototype.setSVG = function (str) { - var xmlDoc = new DOMParser().parseFromString(str, 'text/xml'); - var extxml = document.importNode(xmlDoc.documentElement, true); - if (extxml.childNodes[0].nodeName == '#comment') { - extxml.removeChild(extxml.childNodes[0]); + createSprite (page, md5, id, attr, fcn) { + ScratchJr.storyStart('Sprite.prototype.createSprite'); + this.div = document.createElement('div'); + setProps(this.div.style, { + position: 'absolute', + left: '0px', + top: '0px' + }); + //document.createElement('img'); + this.div.owner = this; + this.div.id = id; + this.id = id; + this.md5 = md5; + this.borderOn = false; + this.outline = document.createElement('canvas'); + this.code = new Scripts(this); + setProps(this, attr); + if (Localization.isSampleLocalizedKey(this.name) && ScratchJr.isSampleOrStarter()) { + this.name = Localization.localize('SAMPLE_TEXT_' + this.name); + } + for (var i = 0; i < this.sounds.length; i++) { + ScratchAudio.loadProjectSound(this.sounds[i]); + } + var sprites = JSON.parse(page.sprites); + sprites.push(this.id); + page.sprites = JSON.stringify(sprites); + var me = this; + page.div.appendChild(this.div); + this.div.style.visibility = 'hidden'; + this.getAsset(gotImage); // sets the SVG and the image + function gotImage (dataurl) { + me.setCostume(dataurl, fcn); + } } - this.svg = extxml; -}; -Sprite.prototype.setCostume = function (dataurl, fcn) { - var img = document.createElement('img'); - img.src = dataurl; - this.img = img; - // Make a copy that is not affected by zoom transformation - this.originalImg = img.cloneNode(false); - setProps(this.img.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - this.div.appendChild(img); - var sprite = this; - if (!img.complete) { - img.onload = function () { + getAsset (whenDone) { + var md5 = this.md5; + var spr = this; + var url = (MediaLib.keys[md5]) ? MediaLib.path + md5 : (md5.indexOf('/') < 0) ? iOS.path + md5 : md5; + md5 = (MediaLib.keys[md5]) ? MediaLib.path + md5 : md5; + if (md5.indexOf('/') > -1) { + IO.requestFromServer(md5, doNext); + } else { + iOS.getmedia(md5, nextStep); + } + function nextStep (base64) { + doNext(atob(base64)); + } + function doNext (str) { + str = str.replace(/>\s*<'); + spr.setSVG(str); + if ((str.indexOf('xlink:href') < 0) && iOS.path) { + whenDone(url); // does not have embedded images + } else { + var base64 = IO.getImageDataURL(spr.md5, btoa(str)); + IO.getImagesInSVG(str, function () { + whenDone(base64); + }); + } + } + } + + setSVG (str) { + var xmlDoc = new DOMParser().parseFromString(str, 'text/xml'); + var extxml = document.importNode(xmlDoc.documentElement, true); + if (extxml.childNodes[0].nodeName == '#comment') { + extxml.removeChild(extxml.childNodes[0]); + } + this.svg = extxml; + } + + setCostume (dataurl, fcn) { + var img = document.createElement('img'); + img.src = dataurl; + this.img = img; + // Make a copy that is not affected by zoom transformation + this.originalImg = img.cloneNode(false); + setProps(this.img.style, { + position: 'absolute', + left: '0px', + top: '0px' + }); + this.div.appendChild(img); + var sprite = this; + if (!img.complete) { + img.onload = function () { + sprite.displaySprite(fcn); + }; + } else { sprite.displaySprite(fcn); - }; - } else { - sprite.displaySprite(fcn); - } -}; - -Sprite.prototype.displaySprite = function (whenDone) { - var w = this.img.width; - var h = this.img.height; - this.div.style.width = this.img.width + 'px'; - this.div.style.height = this.img.height + 'px'; - this.cx = Math.floor(w / 2); - this.cy = Math.floor(h / 2); - this.w = w; - this.h = h; - this.setPos(this.xcoor, this.ycoor); - this.doRender(whenDone); -}; - -Sprite.prototype.doRender = function (whenDone) { - this.drawBorder(); // canvas draw border - this.render(); - SVG2Canvas.drawInCanvas(this); // canvas draws mask for pixel detection - this.readOnly = SVG2Canvas.svgerror; - this.watermark = SVGTools.getWatermark(this.svg, '#B3B3B3'); // svg for watermark - if (whenDone) { - whenDone(this); - } -}; - -Sprite.prototype.drawBorder = function () { - // TODO: Merge these to get better thumbnail rendering on iOS - var w, h, extxml; - if (isAndroid) { - this.border = document.createElement('canvas'); - w = this.originalImg.width; - h = this.originalImg.height; - extxml = this.svg; - this.border.width = w; - this.border.height = h; - this.border.style.width = (w * this.scale) + 'px'; - this.border.style.height = (h * this.scale) + 'px'; - SVG2Canvas.drawBorder(extxml, this.border.getContext('2d')); - } else { - this.border = document.createElement('canvas'); - w = this.img.width; - h = this.img.height; - extxml = this.svg; - setCanvasSize(this.border, w, h); - SVG2Canvas.drawBorder(extxml, this.border.getContext('2d')); - } -}; - -////////////////////////////////////// -// sprite thumbnail -///////////////////////////////////// - -Sprite.prototype.spriteThumbnail = function (p) { - var tb = newHTML('div', 'spritethumb off', p); - tb.setAttribute('id', getIdFor('spritethumb')); - tb.type = 'spritethumb'; - tb.owner = this.id; - var c = newHTML('canvas', 'thumbcanvas', tb); - - // TODO: Merge these to get better thumbnail rendering on iOS - if (isAndroid) { - setCanvasSizeScaledToWindowDocumentHeight(c, 64, 64); - } else { - setCanvasSize(c, 64, 64); - } - - this.drawMyImage(c, c.width, c.height); - p = newHTML('p', 'sname', tb); - p.textContent = this.name; - newHTML('div', 'brush', tb); - this.thumbnail = tb; - return tb; -}; - -Sprite.prototype.updateSpriteThumb = function () { - var tb = this.thumbnail; - if (!tb) { - return; - } - var cnv = tb.childNodes[0]; - this.drawMyImage(cnv, cnv.width, cnv.height); - tb.childNodes[1].textContent = this.name; -}; - -Sprite.prototype.drawMyImage = function (cnv, w, h) { - if (!this.img) { - return; - } - setCanvasSize(cnv, w, h); - - // TODO: Merge these to get better thumbnail rendering on iOS - var img; - if (isAndroid) { - img = this.originalImg; - } else { - img = this.img; - } - var imgw = img.naturalWidth ? img.naturalWidth : img.width; - var imgh = img.naturalHeight ? img.naturalHeight : img.height; - var scale = Math.min(w / imgw, h / imgh); - var ctx = cnv.getContext('2d'); - var iw = Math.floor(scale * imgw); - var ih = Math.floor(scale * imgh); - var ix = Math.floor((w - (scale * imgw)) / 2); - var iy = Math.floor((h - (scale * imgh)) / 2); - ctx.drawImage(this.border, 0, 0, this.border.width, this.border.height, ix, iy, iw, ih); - if (!img.complete) { - img.onload = function () { - ctx.drawImage(img, 0, 0, imgw, imgh, ix, iy, iw, ih); - }; - } else { - ctx.drawImage(img, 0, 0, imgw, imgh, ix, iy, iw, ih); - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// sprite Primitives -////////////////////////////////////////////////////////////////////////////// - -Sprite.prototype.goHome = function () { - this.setPos(this.homex, this.homey); - this.scale = this.homescale; - this.shown = this.homeshown; - // this.flip = this.homeflip; // kept here just in case we want it - this.div.style.opacity = this.shown ? 1 : 0; - this.setHeading(0); - this.render(); -}; - -Sprite.prototype.touchingAny = function () { - if (!this.shown) { - return false; - } - setCanvasSize(ScratchJr.workingCanvas, 480, 360); - setCanvasSize(ScratchJr.workingCanvas2, 480, 360); - var page = this.div.parentNode; - var box = this.getBoxWithEffects(); // box with effects is a scale and 1.5 times to count for rotations - for (var i = 0; i < page.childElementCount; i++) { - var other = page.childNodes[i].owner; - if (!other) { - continue; - } - if (other.type == 'text') { - continue; - } - if (!other.shown) { - continue; - } - if (other.id == this.id) { - continue; - } - if (Events.dragthumbnail && (other == Events.dragthumbnail.owner)) { - continue; - } - var box2 = other.getBoxWithEffects(); - if (!box.intersects(box2)) { - continue; - } - if (this.verifyHit(other)) { - return true; } } - return false; -}; -Sprite.prototype.verifyHit = function (other) { - var ctx = ScratchJr.workingCanvas.getContext('2d'); - var ctx2 = ScratchJr.workingCanvas2.getContext('2d'); - ctx.clearRect(0, 0, 480, 360); - ctx2.clearRect(0, 0, 480, 360); - var box = this.getBoxWithEffects(); - var box2 = other.getBoxWithEffects(); - var rect = box.intersection(box2); - if (rect.width == 0) { - return false; + displaySprite (whenDone) { + var w = this.img.width; + var h = this.img.height; + this.div.style.width = this.img.width + 'px'; + this.div.style.height = this.img.height + 'px'; + this.cx = Math.floor(w / 2); + this.cy = Math.floor(h / 2); + this.w = w; + this.h = h; + this.setPos(this.xcoor, this.ycoor); + this.doRender(whenDone); } - if (rect.height == 0) { - return false; - } - ctx.globalCompositeOperation = 'source-over'; - this.stamp(ctx); - // Normally, we could do a source-over followed by a source-in to detect where the two images collide. - // However, unfortunately, behavior on Android 4.2 and Android 4.4+ varies. - // On Android 4.4+, we could potentially use this more efficient strategy, but we opted for using a single strategy - // that works on all platforms, despite it being less efficient. - // A future optimization could detect the behavior and use - // the right strategy. - // On Android 4.2, source-in does not clear the full source image - // - only the rectangle that the second image being drawn - // occupies. Rotation, scaling, etc. makes this hard to isolate, - // so we opted to just draw the transformed image to a second - // canvas and do a source-in for the entire second canvas. - ctx2.globalCompositeOperation = 'source-over'; - other.stamp(ctx2); - ctx.globalCompositeOperation = 'source-in'; - ctx.drawImage(ScratchJr.workingCanvas2, 0, 0); - var pixels = ctx.getImageData(rect.x, rect.y, rect.width, rect.height).data; - var max = Math.floor(pixels.length / 4); - for (var i = 0; i < max; i++) { - var pt = { - x: i % rect.width, - y: Math.floor(i / rect.width) - }; - if (this.getAlpha(pixels, pt, rect.width) > 0) { - return true; - } - } - return false; -}; -Sprite.prototype.getAlpha = function (data, node, w) { - return data[(node.x * 4) + node.y * w * 4 + 3]; -}; - -Sprite.prototype.setHeading = function (angle) { - this.angle = angle % 360; - this.render(); -}; - -Sprite.prototype.setPos = function (dx, dy) { - this.dirx = ((dx - this.xcoor) == 0) ? 1 : (dx - this.xcoor) / Math.abs(dx - this.xcoor); - this.diry = ((dy - this.ycoor) == 0) ? 1 : (dy - this.ycoor) / Math.abs(dy - this.ycoor); - this.xcoor = dx; - this.ycoor = dy; - this.wrap(); - this.render(); - setProps(this.div.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - this.updateBubble(); -}; - -Sprite.prototype.wrap = function () { - if (this.type == 'text') { - this.wrapText(); - } else { - this.wrapChar(); - } -}; - -Sprite.prototype.wrapChar = function () { - if (this.xcoor < 0) { - this.xcoor = 480 + this.xcoor; - } - if (this.ycoor < 0) { - this.ycoor = 360 + this.ycoor; - } - if (this.xcoor >= 480) { - this.xcoor = this.xcoor - 480; - } - if (this.ycoor >= 360) { - this.ycoor = this.ycoor - 360; - } -}; - -Sprite.prototype.wrapText = function () { - var max = this.cx > 480 ? this.cx : 480; - var min = this.cx > 480 ? 480 - this.cx : 0; - if (this.xcoor < min) { - this.xcoor = max + this.xcoor; - } - if (this.ycoor < 0) { - this.ycoor = 360 + this.ycoor; - } - if (this.xcoor >= max) { - this.xcoor = this.xcoor - max; - } - if (this.ycoor >= 360) { - this.ycoor = this.ycoor - 360; - } -}; - -Sprite.prototype.render = function () { - // TODO: Merge these to get better thumbnail rendering on iOS - var dx, dy, mtx; - if (isAndroid) { - mtx = ''; - if (this.img) { - dx = this.xcoor - this.cx * this.scale; - dy = this.ycoor - this.cy * this.scale; - mtx = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; - mtx += ' rotate(' + this.angle + 'deg)'; - if (this.flip) { - mtx += ' scale(-1, 1)'; - } else { - mtx += ' scale(1, 1)'; - } - var w = (this.originalImg.width * this.scale); - var h = (this.originalImg.height * this.scale); - this.div.style.width = w + 'px'; - this.div.style.height = h + 'px'; - if (this.border) { - this.border.style.width = w + 'px'; - this.border.style.height = h + 'px'; - } - this.img.style.width = w + 'px'; - this.img.style.height = h + 'px'; - } else { - dx = this.xcoor - this.cx; - dy = this.ycoor - this.cy; - mtx = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; - } - this.setTransform(mtx); - } else { - dx = this.xcoor - this.cx; - dy = this.ycoor - this.cy; - mtx = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; - if (this.img) { - mtx += ' rotate(' + this.angle + 'deg)'; - if (this.flip) { - mtx += 'scale(' + -this.scale + ', ' + this.scale + ')'; - } else { - mtx += 'scale(' + this.scale + ', ' + this.scale + ')'; - } - } - this.setTransform(mtx); - } -}; - -Sprite.prototype.select = function () { - if (this.borderOn) { - return; - } - if (!this.img) { - return; - } - if (!this.border) { - return; - } - this.div.appendChild(this.border); - setProps(this.border.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - this.div.appendChild(this.img); - setProps(this.img.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - this.borderOn = true; - this.render(); -}; - -Sprite.prototype.unselect = function () { - if (!this.borderOn) { - return; - } - while (this.div.childElementCount > 0) { - this.div.removeChild(this.div.childNodes[0]); - } - this.div.appendChild(this.img); - this.borderOn = false; -}; - -Sprite.prototype.setTransform = function (transform) { - this.div.style.webkitTransform = transform; -}; - -Sprite.prototype.screenLeft = function () { - return Math.round(this.xcoor - this.cx * this.scale); -}; - -Sprite.prototype.screenTop = function () { - return Math.round(this.ycoor - this.cy * this.scale); -}; - -Sprite.prototype.noScaleFor = function () { - this.setScaleTo(this.defaultScale); -}; - -Sprite.prototype.changeSizeBy = function (num) { - var n = Number(num) + Number(this.scale) * 100; - this.scale = this.getScale(n / 100); - this.setPos(this.xcoor, this.ycoor); - this.render(); -}; - -Sprite.prototype.setScaleTo = function (n) { - n = this.getScale(n); - if (n == this.scale) { - return; - } - this.scale = n; - this.setPos(this.xcoor, this.ycoor); - this.render(); -}; - -Sprite.prototype.getScale = function (n) { - var mins = Math.max(Math.max(this.w, this.h) * n, 36); - var maxs = Math.min(Math.min(this.w, this.h) * n, 360); - if (mins == 36) { - return 36 / Math.max(this.w, this.h); - } - if (maxs == 360) { - return 360 / Math.min(this.w, this.h); - } - return n; -}; - -Sprite.prototype.getBox = function () { - var box = { - x: this.screenLeft(), - y: this.screenTop(), - width: this.w * this.scale, - height: this.h * this.scale - }; - return box; -}; - -Sprite.prototype.getBoxWithEffects = function () { - if (this.type == 'text') { - return new Rectangle(this.screenLeft(), this.screenTop(), this.w * this.scale, this.h * this.scale); - } - var max = Math.max(this.outline.width, this.outline.height); - var w = Math.floor(max * 1.5 * this.scale); - var h = Math.floor(max * 1.5 * this.scale); - return new Rectangle(Math.floor(this.xcoor - w / 2), Math.floor(this.ycoor - h / 2), Math.floor(w), Math.floor(h)); -}; - -////////////////////////////////////////////////// -// Balloon -////////////////////////////////////////////////// - -Sprite.prototype.closeBalloon = function () { - if (!this.balloon) { - return; - } - this.balloon.parentNode.removeChild(this.balloon); - this.balloon = undefined; -}; - -Sprite.prototype.openBalloon = function (label) { - if (this.balloon) { - this.closeBalloon(); - } - var w = 200; - var h = 36; - var curve = 6; - var dy = this.screenTop(); - this.balloon = newDiv(ScratchJr.stage.currentPage.div, 0, 0, w, h, { - position: 'absolute', - zIndex: 2, - visibility: 'hidden' - }); - var bimg = document.createElement('img'); - setProps(bimg.style, { - position: 'absolute', - zIndex: 2 - }); - this.balloon.appendChild(bimg); - var p = newP(this.balloon, label, {}); - p.setAttribute('class', 'balloon'); - w = p.offsetWidth; - if (w < 36) { - w = 36; - } - if (w > 200) { - w = 200; - } - w += (10 * gn('stage').owner.currentZoom); - setProps(p.style, { - position: 'absolute', - width: w + 'px' - }); - w += 10; - w = Math.round(w); - var offset = (this.screenLeft() + (this.div.offsetWidth * this.scale / 2)) - (w / 2); - var dx = (offset < 0) ? 0 : (offset + w) > 480 ? 478 - w : offset; - dx = Math.round(dx); - h = p.offsetHeight + curve * 2 + 7; - setCanvasSize(this.balloon, w, h); - dy -= h; - if (dy < 2) { - dy = 2; - } - this.balloon.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; - this.balloon.left = dx; - this.balloon.top = dy; - setProps(this.balloon.style, { - position: 'absolute', - left: '0px', - top: '0px', - visibility: 'visible' - }); - this.drawBalloon(); -}; - -Sprite.prototype.updateBubble = function () { - if (this.balloon == null) { - return; - } - var w = this.balloon.offsetWidth; - var h = this.balloon.offsetHeight; - var dy = this.screenTop(); - var offset = (this.screenLeft() + (this.div.offsetWidth * this.scale / 2)) - (w / 2); - var dx = (offset < 0) ? 0 : (offset + w) > 480 ? 478 - w : offset; - dx = Math.round(dx); - dy -= h; - if (dy < 2) { - dy = 2; - } - this.balloon.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; - this.balloon.left = dx; - this.balloon.top = dy; - this.drawBalloon(); -}; - -Sprite.prototype.drawBalloon = function () { - var img = this.balloon.childNodes[0]; - var w = this.balloon.offsetWidth; - var h = this.balloon.offsetHeight; - var curve = 6; - var dx = this.balloon.left; - var x = this.xcoor; - var h2 = h - 8; - var w2 = w - 1; - var side2 = x - dx; - var margin = 20; - if (side2 < margin) { - side2 = margin; - } - if (side2 > (w2 - margin)) { - side2 = w2 - margin; - } - var side1 = w2 - side2; - var str = BlockSpecs.balloon.concat(); - str = str.replace('width="30px"', 'width="' + w + 'px"'); - str = str.replace('height="44px"', 'height="' + h + 'px"'); - str = str.replace('viewBox="0 0 30 44"', 'viewBox="0 0 ' + w + ' ' + h + '"'); - str = str.replace('h17', 'h' + (w2 - curve * 2)); - str = str.replace('v24', 'v' + (h2 - curve * 2)); - var a = str.split('h-2'); - var b = a[1].split('h-1'); - str = a[0] + 'h' + (-side1 + 7 + curve) + b[0] + 'h' + (-side2 + 7 + curve) + b[1]; - img.src = 'data:image/svg+xml;base64,' + btoa(str); -}; - -///////////////////////////////////// -// Sprite rendering -//////////////////////////////////// - -Sprite.prototype.stamp = function (ctx, deltax, deltay) { - var w = this.outline.width * this.scale; - var h = this.outline.height * this.scale; - var dx = deltax ? deltax : 0; - var dy = deltay ? deltay : 0; - ctx.save(); - ctx.translate(this.xcoor + dx, this.ycoor + dy); - ctx.rotate(this.angle * DEGTOR); - if (this.flip) { - ctx.scale(-1, 1); - } - ctx.drawImage(this.outline, -w / 2, -h / 2, w, h); - ctx.restore(); -}; - -///////////////////////////////////// -// Text Creation -///////////////////////////////////// - -Sprite.prototype.createText = function (attr, whenDone) { - var page = attr.page; - setProps(this, attr); - this.div = newHTML('p', 'textsprite', page.div); - setProps(this.div.style, { - fontSize: this.fontsize + 'px', - color: this.color, - fontFamily: Settings.textSpriteFont - }); - this.div.owner = this; - this.div.id = this.id; - this.scale = 1; - this.homescale = 1; - this.homeshown = true; - this.homeflip = false; - this.outline = document.createElement('canvas'); - var sprites = JSON.parse(page.sprites); - sprites.push(this.id); - page.sprites = JSON.stringify(sprites); - if ((this.str == '') && !whenDone) { - this.setTextBox(); - this.activateInput(); - var delta = this.fontsize * 1.35; - page.textstartat += delta; - if ((page.textstartat + delta) > 360) { - page.textstartat = 42; - } - } else { - if (Localization.isSampleLocalizedKey(this.str) && ScratchJr.isSampleOrStarter()) { - this.str = Localization.localize('SAMPLE_TEXT_' + this.str); - } - this.recalculateText(); + doRender (whenDone) { + this.drawBorder(); // canvas draw border + this.render(); + SVG2Canvas.drawInCanvas(this); // canvas draws mask for pixel detection + this.readOnly = SVG2Canvas.svgerror; + this.watermark = SVGTools.getWatermark(this.svg, '#B3B3B3'); // svg for watermark if (whenDone) { whenDone(this); } } -}; -Sprite.prototype.setTextBox = function () { - var sform = document.forms.activetextbox; - sform.textsprite = this; - var box = this.getBox(); - var ti = document.forms.activetextbox.typing; - ti.value = this.str; - - // TODO: Merge these for iOS - var styles; - if (isAndroid) { - styles = { - color: this.color, - fontSize: (this.fontsize * scaleMultiplier) + 'px' - }; - } else { - styles = { - color: this.color, - fontSize: this.fontsize + 'px' - }; - } - var ci = BlockSpecs.fontcolors.indexOf(rgbToHex(this.color)); - UI.setMenuTextColor(gn('textcolormenu').childNodes[(ci < 0) ? 9 : ci]); - setProps(ti.style, styles); - - // TODO: Merge these for iOS - var dy; - if (isAndroid) { - dy = box.y * scaleMultiplier + globaly(gn('stage')) - 10 * scaleMultiplier; - } else { - dy = box.y + globaly(gn('stage'), gn('stage').offsetTop) - 10; - } - var formsize = 470; - gn('textbox').className = 'pagetext on'; - - // TODO: Merge these for iOS - var dx; - if (isAndroid) { - AndroidInterface.scratchjr_setsoftkeyboardscrolllocation(dy * window.devicePixelRatio, (dy + - ti.parentNode.parentNode.getBoundingClientRect().height * 1.7) * window.devicePixelRatio); - dx = (-10 + 240 - Math.round(formsize / 2)) * scaleMultiplier + globalx(gn('stage')); - setProps(gn('textbox').style, { - top: dy + 'px', - left: dx + 'px', - zIndex: 10 - }); - setProps(sform.style, { - height: ((this.fontsize + 10) * scaleMultiplier) + 'px' - }); - setTimeout(function () { - AndroidInterface.scratchjr_forceShowKeyboard(); - }, 500); - } else { - dx = -10 + 240 - Math.round(formsize / 2) + globalx(gn('stage'), gn('stage').offsetLeft); - setProps(gn('textbox').style, { - top: dy + 'px', - left: dx + 'px', - zIndex: 10 - }); - setProps(sform.style, { - height: (this.fontsize + 10) + 'px' - }); - } -}; - -Sprite.prototype.unfocusText = function () { - ScratchJr.blur(); - document.body.scrollTop = 0; - document.body.scrollLeft = 0; - var form = document.forms.activetextbox; - var changed = (this.oldvalue != form.typing.value); - if (this.noChars(form.typing.value)) { - this.deleteText(this.oldvalue != ''); - } else { - this.contractText(); - this.div.style.visibility = 'visible'; + drawBorder () { + // TODO: Merge these to get better thumbnail rendering on iOS + var w, h, extxml; if (isAndroid) { - gn('textbox').style.visibility = 'hidden'; - } - gn('textbox').className = 'pagetext off'; - gn('textcolormenu').className = 'textuicolormenu off'; - gn('textfontsizes').className = 'textuifont off'; - gn('fontsizebutton').className = 'fontsizeText off'; - gn('fontcolorbutton').className = 'changecolorText off'; - form.textsprite = null; - this.deactivateInput(); - if (changed) { - Undo.record({ - action: 'edittext', - where: this.div.parentNode.owner.id, - who: this.id - }); - ScratchJr.storyStart('Sprite.prototype.unfocusText'); + this.border = document.createElement('canvas'); + w = this.originalImg.width; + h = this.originalImg.height; + extxml = this.svg; + this.border.width = w; + this.border.height = h; + this.border.style.width = (w * this.scale) + 'px'; + this.border.style.height = (h * this.scale) + 'px'; + SVG2Canvas.drawBorder(extxml, this.border.getContext('2d')); + } else { + this.border = document.createElement('canvas'); + w = this.img.width; + h = this.img.height; + extxml = this.svg; + setCanvasSize(this.border, w, h); + SVG2Canvas.drawBorder(extxml, this.border.getContext('2d')); } } - Thumbs.updatePages(); - if (isAndroid) { - ScratchJr.onBackButtonCallback.pop(); - AndroidInterface.scratchjr_forceHideKeyboard(); - } -}; -Sprite.prototype.deleteText = function (record) { - var id = this.id; - var page = ScratchJr.stage.currentPage; - page.textstartat = (this.ycoor + (this.fontsize * 1.35)) > 360 ? 36 : this.ycoor; - var list = JSON.parse(page.sprites); - var n = list.indexOf(this.id); - if (n < 0) { - return; - } - list.splice(n, 1); - this.div.parentNode.removeChild(this.div); - page.sprites = JSON.stringify(list); - var form = document.forms.activetextbox; - gn('textbox').style.visibility = 'hidden'; - form.textsprite = null; - if (record) { - Undo.record({ - action: 'deletesprite', - who: id, - where: ScratchJr.stage.currentPage.id - }); - ScratchJr.storyStart('Sprite.prototype.deleteText'); - } -}; + ////////////////////////////////////// + // sprite thumbnail + ///////////////////////////////////// -Sprite.prototype.noChars = function (str) { - for (var i = 0; i < str.length; i++) { - if (str[i] != ' ') { + spriteThumbnail (p) { + var tb = newHTML('div', 'spritethumb off', p); + tb.setAttribute('id', getIdFor('spritethumb')); + tb.type = 'spritethumb'; + tb.owner = this.id; + var c = newHTML('canvas', 'thumbcanvas', tb); + + // TODO: Merge these to get better thumbnail rendering on iOS + if (isAndroid) { + setCanvasSizeScaledToWindowDocumentHeight(c, 64, 64); + } else { + setCanvasSize(c, 64, 64); + } + + this.drawMyImage(c, c.width, c.height); + p = newHTML('p', 'sname', tb); + p.textContent = this.name; + newHTML('div', 'brush', tb); + this.thumbnail = tb; + return tb; + } + + updateSpriteThumb () { + var tb = this.thumbnail; + if (!tb) { + return; + } + var cnv = tb.childNodes[0]; + this.drawMyImage(cnv, cnv.width, cnv.height); + tb.childNodes[1].textContent = this.name; + } + + drawMyImage (cnv, w, h) { + if (!this.img) { + return; + } + setCanvasSize(cnv, w, h); + + // TODO: Merge these to get better thumbnail rendering on iOS + var img; + if (isAndroid) { + img = this.originalImg; + } else { + img = this.img; + } + var imgw = img.naturalWidth ? img.naturalWidth : img.width; + var imgh = img.naturalHeight ? img.naturalHeight : img.height; + var scale = Math.min(w / imgw, h / imgh); + var ctx = cnv.getContext('2d'); + var iw = Math.floor(scale * imgw); + var ih = Math.floor(scale * imgh); + var ix = Math.floor((w - (scale * imgw)) / 2); + var iy = Math.floor((h - (scale * imgh)) / 2); + ctx.drawImage(this.border, 0, 0, this.border.width, this.border.height, ix, iy, iw, ih); + if (!img.complete) { + img.onload = function () { + ctx.drawImage(img, 0, 0, imgw, imgh, ix, iy, iw, ih); + }; + } else { + ctx.drawImage(img, 0, 0, imgw, imgh, ix, iy, iw, ih); + } + } + + /////////////////////////////////////////////////////////////////////////////// + // sprite Primitives + ////////////////////////////////////////////////////////////////////////////// + + goHome () { + this.setPos(this.homex, this.homey); + this.scale = this.homescale; + this.shown = this.homeshown; + // this.flip = this.homeflip; // kept here just in case we want it + this.div.style.opacity = this.shown ? 1 : 0; + this.setHeading(0); + this.render(); + } + + touchingAny () { + if (!this.shown) { return false; } + setCanvasSize(ScratchJr.workingCanvas, 480, 360); + setCanvasSize(ScratchJr.workingCanvas2, 480, 360); + var page = this.div.parentNode; + var box = this.getBoxWithEffects(); // box with effects is a scale and 1.5 times to count for rotations + for (var i = 0; i < page.childElementCount; i++) { + var other = page.childNodes[i].owner; + if (!other) { + continue; + } + if (other.type == 'text') { + continue; + } + if (!other.shown) { + continue; + } + if (other.id == this.id) { + continue; + } + if (Events.dragthumbnail && (other == Events.dragthumbnail.owner)) { + continue; + } + var box2 = other.getBoxWithEffects(); + if (!box.intersects(box2)) { + continue; + } + if (this.verifyHit(other)) { + return true; + } + } + return false; } - return true; -}; -Sprite.prototype.contractText = function () { - var form = document.forms.activetextbox; - this.str = form.typing.value.substring(0, form.typing.maxLength); - this.recalculateText(); -}; + verifyHit (other) { + var ctx = ScratchJr.workingCanvas.getContext('2d'); + var ctx2 = ScratchJr.workingCanvas2.getContext('2d'); + ctx.clearRect(0, 0, 480, 360); + ctx2.clearRect(0, 0, 480, 360); + var box = this.getBoxWithEffects(); + var box2 = other.getBoxWithEffects(); + var rect = box.intersection(box2); + if (rect.width == 0) { + return false; + } + if (rect.height == 0) { + return false; + } + ctx.globalCompositeOperation = 'source-over'; + this.stamp(ctx); + // Normally, we could do a source-over followed by a source-in to detect where the two images collide. + // However, unfortunately, behavior on Android 4.2 and Android 4.4+ varies. + // On Android 4.4+, we could potentially use this more efficient strategy, + // but we opted for using a single strategy + // that works on all platforms, despite it being less efficient. + // A future optimization could detect the behavior and use + // the right strategy. + // On Android 4.2, source-in does not clear the full source image + // - only the rectangle that the second image being drawn + // occupies. Rotation, scaling, etc. makes this hard to isolate, + // so we opted to just draw the transformed image to a second + // canvas and do a source-in for the entire second canvas. + ctx2.globalCompositeOperation = 'source-over'; + other.stamp(ctx2); + ctx.globalCompositeOperation = 'source-in'; + ctx.drawImage(ScratchJr.workingCanvas2, 0, 0); + var pixels = ctx.getImageData(rect.x, rect.y, rect.width, rect.height).data; + var max = Math.floor(pixels.length / 4); + for (var i = 0; i < max; i++) { + var pt = { + x: i % rect.width, + y: Math.floor(i / rect.width) + }; + if (this.getAlpha(pixels, pt, rect.width) > 0) { + return true; + } + } + return false; + } -Sprite.prototype.clickOnText = function (e) { - e.stopPropagation(); - this.setTextBox(); - gn('textbox').style.visibility = 'visible'; - this.div.style.visibility = 'hidden'; - this.activateInput(); -}; + getAlpha (data, node, w) { + return data[(node.x * 4) + node.y * w * 4 + 3]; + } -Sprite.prototype.activateInput = function () { - this.oldvalue = this.str; - var ti = document.forms.activetextbox.typing; - gn('textbox').style.visibility = 'visible'; - var me = this; - ti.onblur = function () { - me.unfocusText(); - }; - ti.onkeypress = function (evt) { - me.handleWrite(evt); - }; - ti.onkeyup = function (evt) { - me.handleKeyUp(evt); - }; - ti.onsubmit = function () { - me.unfocusText(); - }; - if (isAndroid) { - setTimeout(function () { - ti.focus(); - }, 500); + setHeading (angle) { + this.angle = angle % 360; + this.render(); + } - ScratchJr.onBackButtonCallback.push(function () { - me.unfocusText(); + setPos (dx, dy) { + this.dirx = ((dx - this.xcoor) == 0) ? 1 : (dx - this.xcoor) / Math.abs(dx - this.xcoor); + this.diry = ((dy - this.ycoor) == 0) ? 1 : (dy - this.ycoor) / Math.abs(dy - this.ycoor); + this.xcoor = dx; + this.ycoor = dy; + this.wrap(); + this.render(); + setProps(this.div.style, { + position: 'absolute', + left: '0px', + top: '0px' }); - } else { - if (isTablet) { - ti.focus(); + this.updateBubble(); + } + + wrap () { + if (this.type == 'text') { + this.wrapText(); } else { + this.wrapChar(); + } + } + + wrapChar () { + if (this.xcoor < 0) { + this.xcoor = 480 + this.xcoor; + } + if (this.ycoor < 0) { + this.ycoor = 360 + this.ycoor; + } + if (this.xcoor >= 480) { + this.xcoor = this.xcoor - 480; + } + if (this.ycoor >= 360) { + this.ycoor = this.ycoor - 360; + } + } + + wrapText () { + var max = this.cx > 480 ? this.cx : 480; + var min = this.cx > 480 ? 480 - this.cx : 0; + if (this.xcoor < min) { + this.xcoor = max + this.xcoor; + } + if (this.ycoor < 0) { + this.ycoor = 360 + this.ycoor; + } + if (this.xcoor >= max) { + this.xcoor = this.xcoor - max; + } + if (this.ycoor >= 360) { + this.ycoor = this.ycoor - 360; + } + } + + render () { + // TODO: Merge these to get better thumbnail rendering on iOS + var dx, dy, mtx; + if (isAndroid) { + mtx = ''; + if (this.img) { + dx = this.xcoor - this.cx * this.scale; + dy = this.ycoor - this.cy * this.scale; + mtx = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; + mtx += ' rotate(' + this.angle + 'deg)'; + if (this.flip) { + mtx += ' scale(-1, 1)'; + } else { + mtx += ' scale(1, 1)'; + } + var w = (this.originalImg.width * this.scale); + var h = (this.originalImg.height * this.scale); + this.div.style.width = w + 'px'; + this.div.style.height = h + 'px'; + if (this.border) { + this.border.style.width = w + 'px'; + this.border.style.height = h + 'px'; + } + this.img.style.width = w + 'px'; + this.img.style.height = h + 'px'; + } else { + dx = this.xcoor - this.cx; + dy = this.ycoor - this.cy; + mtx = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; + } + this.setTransform(mtx); + } else { + dx = this.xcoor - this.cx; + dy = this.ycoor - this.cy; + mtx = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; + if (this.img) { + mtx += ' rotate(' + this.angle + 'deg)'; + if (this.flip) { + mtx += 'scale(' + -this.scale + ', ' + this.scale + ')'; + } else { + mtx += 'scale(' + this.scale + ', ' + this.scale + ')'; + } + } + this.setTransform(mtx); + } + } + + select () { + if (this.borderOn) { + return; + } + if (!this.img) { + return; + } + if (!this.border) { + return; + } + this.div.appendChild(this.border); + setProps(this.border.style, { + position: 'absolute', + left: '0px', + top: '0px' + }); + this.div.appendChild(this.img); + setProps(this.img.style, { + position: 'absolute', + left: '0px', + top: '0px' + }); + this.borderOn = true; + this.render(); + } + + unselect () { + if (!this.borderOn) { + return; + } + while (this.div.childElementCount > 0) { + this.div.removeChild(this.div.childNodes[0]); + } + this.div.appendChild(this.img); + this.borderOn = false; + } + + setTransform (transform) { + this.div.style.webkitTransform = transform; + } + + screenLeft () { + return Math.round(this.xcoor - this.cx * this.scale); + } + + screenTop () { + return Math.round(this.ycoor - this.cy * this.scale); + } + + noScaleFor () { + this.setScaleTo(this.defaultScale); + } + + changeSizeBy (num) { + var n = Number(num) + Number(this.scale) * 100; + this.scale = this.getScale(n / 100); + this.setPos(this.xcoor, this.ycoor); + this.render(); + } + + setScaleTo (n) { + n = this.getScale(n); + if (n == this.scale) { + return; + } + this.scale = n; + this.setPos(this.xcoor, this.ycoor); + this.render(); + } + + getScale (n) { + var mins = Math.max(Math.max(this.w, this.h) * n, 36); + var maxs = Math.min(Math.min(this.w, this.h) * n, 360); + if (mins == 36) { + return 36 / Math.max(this.w, this.h); + } + if (maxs == 360) { + return 360 / Math.min(this.w, this.h); + } + return n; + } + + getBox () { + var box = { + x: this.screenLeft(), + y: this.screenTop(), + width: this.w * this.scale, + height: this.h * this.scale + }; + return box; + } + + getBoxWithEffects () { + if (this.type == 'text') { + return new Rectangle(this.screenLeft(), this.screenTop(), this.w * this.scale, this.h * this.scale); + } + var max = Math.max(this.outline.width, this.outline.height); + var w = Math.floor(max * 1.5 * this.scale); + var h = Math.floor(max * 1.5 * this.scale); + return new Rectangle(Math.floor(this.xcoor - w / 2), + Math.floor(this.ycoor - h / 2), Math.floor(w), Math.floor(h)); + } + + ////////////////////////////////////////////////// + // Balloon + ////////////////////////////////////////////////// + + closeBalloon () { + if (!this.balloon) { + return; + } + this.balloon.parentNode.removeChild(this.balloon); + this.balloon = undefined; + } + + openBalloon (label) { + if (this.balloon) { + this.closeBalloon(); + } + var w = 200; + var h = 36; + var curve = 6; + var dy = this.screenTop(); + this.balloon = newDiv(ScratchJr.stage.currentPage.div, 0, 0, w, h, { + position: 'absolute', + zIndex: 2, + visibility: 'hidden' + }); + var bimg = document.createElement('img'); + setProps(bimg.style, { + position: 'absolute', + zIndex: 2 + }); + this.balloon.appendChild(bimg); + var p = newP(this.balloon, label, {}); + p.setAttribute('class', 'balloon'); + w = p.offsetWidth; + if (w < 36) { + w = 36; + } + if (w > 200) { + w = 200; + } + w += (10 * gn('stage').owner.currentZoom); + setProps(p.style, { + position: 'absolute', + width: w + 'px' + }); + w += 10; + w = Math.round(w); + var offset = (this.screenLeft() + (this.div.offsetWidth * this.scale / 2)) - (w / 2); + var dx = (offset < 0) ? 0 : (offset + w) > 480 ? 478 - w : offset; + dx = Math.round(dx); + h = p.offsetHeight + curve * 2 + 7; + setCanvasSize(this.balloon, w, h); + dy -= h; + if (dy < 2) { + dy = 2; + } + this.balloon.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; + this.balloon.left = dx; + this.balloon.top = dy; + setProps(this.balloon.style, { + position: 'absolute', + left: '0px', + top: '0px', + visibility: 'visible' + }); + this.drawBalloon(); + } + + updateBubble () { + if (this.balloon == null) { + return; + } + var w = this.balloon.offsetWidth; + var h = this.balloon.offsetHeight; + var dy = this.screenTop(); + var offset = (this.screenLeft() + (this.div.offsetWidth * this.scale / 2)) - (w / 2); + var dx = (offset < 0) ? 0 : (offset + w) > 480 ? 478 - w : offset; + dx = Math.round(dx); + dy -= h; + if (dy < 2) { + dy = 2; + } + this.balloon.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px, 0px)'; + this.balloon.left = dx; + this.balloon.top = dy; + this.drawBalloon(); + } + + drawBalloon () { + var img = this.balloon.childNodes[0]; + var w = this.balloon.offsetWidth; + var h = this.balloon.offsetHeight; + var curve = 6; + var dx = this.balloon.left; + var x = this.xcoor; + var h2 = h - 8; + var w2 = w - 1; + var side2 = x - dx; + var margin = 20; + if (side2 < margin) { + side2 = margin; + } + if (side2 > (w2 - margin)) { + side2 = w2 - margin; + } + var side1 = w2 - side2; + var str = BlockSpecs.balloon.concat(); + str = str.replace('width="30px"', 'width="' + w + 'px"'); + str = str.replace('height="44px"', 'height="' + h + 'px"'); + str = str.replace('viewBox="0 0 30 44"', 'viewBox="0 0 ' + w + ' ' + h + '"'); + str = str.replace('h17', 'h' + (w2 - curve * 2)); + str = str.replace('v24', 'v' + (h2 - curve * 2)); + var a = str.split('h-2'); + var b = a[1].split('h-1'); + str = a[0] + 'h' + (-side1 + 7 + curve) + b[0] + 'h' + (-side2 + 7 + curve) + b[1]; + img.src = 'data:image/svg+xml;base64,' + btoa(str); + } + + ///////////////////////////////////// + // Sprite rendering + //////////////////////////////////// + + stamp (ctx, deltax, deltay) { + var w = this.outline.width * this.scale; + var h = this.outline.height * this.scale; + var dx = deltax ? deltax : 0; + var dy = deltay ? deltay : 0; + ctx.save(); + ctx.translate(this.xcoor + dx, this.ycoor + dy); + ctx.rotate(this.angle * DEGTOR); + if (this.flip) { + ctx.scale(-1, 1); + } + ctx.drawImage(this.outline, -w / 2, -h / 2, w, h); + ctx.restore(); + } + + ///////////////////////////////////// + // Text Creation + ///////////////////////////////////// + + createText (attr, whenDone) { + var page = attr.page; + setProps(this, attr); + this.div = newHTML('p', 'textsprite', page.div); + setProps(this.div.style, { + fontSize: this.fontsize + 'px', + color: this.color, + fontFamily: Settings.textSpriteFont + }); + this.div.owner = this; + this.div.id = this.id; + this.scale = 1; + this.homescale = 1; + this.homeshown = true; + this.homeflip = false; + this.outline = document.createElement('canvas'); + var sprites = JSON.parse(page.sprites); + sprites.push(this.id); + page.sprites = JSON.stringify(sprites); + if ((this.str == '') && !whenDone) { + this.setTextBox(); + this.activateInput(); + var delta = this.fontsize * 1.35; + page.textstartat += delta; + if ((page.textstartat + delta) > 360) { + page.textstartat = 42; + } + } else { + if (Localization.isSampleLocalizedKey(this.str) && ScratchJr.isSampleOrStarter()) { + this.str = Localization.localize('SAMPLE_TEXT_' + this.str); + } + this.recalculateText(); + if (whenDone) { + whenDone(this); + } + } + } + + setTextBox () { + var sform = document.forms.activetextbox; + sform.textsprite = this; + var box = this.getBox(); + var ti = document.forms.activetextbox.typing; + ti.value = this.str; + + // TODO: Merge these for iOS + var styles; + if (isAndroid) { + styles = { + color: this.color, + fontSize: (this.fontsize * scaleMultiplier) + 'px' + }; + } else { + styles = { + color: this.color, + fontSize: this.fontsize + 'px' + }; + } + var ci = BlockSpecs.fontcolors.indexOf(rgbToHex(this.color)); + UI.setMenuTextColor(gn('textcolormenu').childNodes[(ci < 0) ? 9 : ci]); + setProps(ti.style, styles); + + // TODO: Merge these for iOS + var dy; + if (isAndroid) { + dy = box.y * scaleMultiplier + globaly(gn('stage')) - 10 * scaleMultiplier; + } else { + dy = box.y + globaly(gn('stage'), gn('stage').offsetTop) - 10; + } + var formsize = 470; + gn('textbox').className = 'pagetext on'; + + // TODO: Merge these for iOS + var dx; + if (isAndroid) { + AndroidInterface.scratchjr_setsoftkeyboardscrolllocation(dy * window.devicePixelRatio, (dy + + ti.parentNode.parentNode.getBoundingClientRect().height * 1.7) * window.devicePixelRatio); + dx = (-10 + 240 - Math.round(formsize / 2)) * scaleMultiplier + globalx(gn('stage')); + setProps(gn('textbox').style, { + top: dy + 'px', + left: dx + 'px', + zIndex: 10 + }); + setProps(sform.style, { + height: ((this.fontsize + 10) * scaleMultiplier) + 'px' + }); + setTimeout(function () { + AndroidInterface.scratchjr_forceShowKeyboard(); + }, 500); + } else { + dx = -10 + 240 - Math.round(formsize / 2) + globalx(gn('stage'), gn('stage').offsetLeft); + setProps(gn('textbox').style, { + top: dy + 'px', + left: dx + 'px', + zIndex: 10 + }); + setProps(sform.style, { + height: (this.fontsize + 10) + 'px' + }); + } + } + + unfocusText () { + ScratchJr.blur(); + document.body.scrollTop = 0; + document.body.scrollLeft = 0; + var form = document.forms.activetextbox; + var changed = (this.oldvalue != form.typing.value); + if (this.noChars(form.typing.value)) { + this.deleteText(this.oldvalue != ''); + } else { + this.contractText(); + this.div.style.visibility = 'visible'; + if (isAndroid) { + gn('textbox').style.visibility = 'hidden'; + } + gn('textbox').className = 'pagetext off'; + gn('textcolormenu').className = 'textuicolormenu off'; + gn('textfontsizes').className = 'textuifont off'; + gn('fontsizebutton').className = 'fontsizeText off'; + gn('fontcolorbutton').className = 'changecolorText off'; + form.textsprite = null; + this.deactivateInput(); + if (changed) { + Undo.record({ + action: 'edittext', + where: this.div.parentNode.owner.id, + who: this.id + }); + ScratchJr.storyStart('Sprite.prototype.unfocusText'); + } + } + Thumbs.updatePages(); + if (isAndroid) { + ScratchJr.onBackButtonCallback.pop(); + AndroidInterface.scratchjr_forceHideKeyboard(); + } + } + + deleteText (record) { + var id = this.id; + var page = ScratchJr.stage.currentPage; + page.textstartat = (this.ycoor + (this.fontsize * 1.35)) > 360 ? 36 : this.ycoor; + var list = JSON.parse(page.sprites); + var n = list.indexOf(this.id); + if (n < 0) { + return; + } + list.splice(n, 1); + this.div.parentNode.removeChild(this.div); + page.sprites = JSON.stringify(list); + var form = document.forms.activetextbox; + gn('textbox').style.visibility = 'hidden'; + form.textsprite = null; + if (record) { + Undo.record({ + action: 'deletesprite', + who: id, + where: ScratchJr.stage.currentPage.id + }); + ScratchJr.storyStart('Sprite.prototype.deleteText'); + } + } + + noChars (str) { + for (var i = 0; i < str.length; i++) { + if (str[i] != ' ') { + return false; + } + } + return true; + } + + contractText () { + var form = document.forms.activetextbox; + this.str = form.typing.value.substring(0, form.typing.maxLength); + this.recalculateText(); + } + + clickOnText (e) { + e.stopPropagation(); + this.setTextBox(); + gn('textbox').style.visibility = 'visible'; + this.div.style.visibility = 'hidden'; + this.activateInput(); + } + + activateInput () { + this.oldvalue = this.str; + var ti = document.forms.activetextbox.typing; + gn('textbox').style.visibility = 'visible'; + var me = this; + ti.onblur = function () { + me.unfocusText(); + }; + ti.onkeypress = function (evt) { + me.handleWrite(evt); + }; + ti.onkeyup = function (evt) { + me.handleKeyUp(evt); + }; + ti.onsubmit = function () { + me.unfocusText(); + }; + if (isAndroid) { setTimeout(function () { ti.focus(); - }, 100); + }, 500); + + ScratchJr.onBackButtonCallback.push(function () { + me.unfocusText(); + }); + } else { + if (isTablet) { + ti.focus(); + } else { + setTimeout(function () { + ti.focus(); + }, 100); + } } } -}; -Sprite.prototype.handleWrite = function (e) { - var key = e.keyCode || e.which; - var ti = e.target; - if (key == 13) { - e.preventDefault(); - e.target.blur(); - } else { + handleWrite (e) { + var key = e.keyCode || e.which; + var ti = e.target; + if (key == 13) { + e.preventDefault(); + e.target.blur(); + } else { + if (!(ti.parentNode).textsprite) { + gn('textbox').style.visibility = 'hidden'; + this.deactivateInput(); + } + } + } + + handleKeyUp (e) { + var ti = e.target; if (!(ti.parentNode).textsprite) { - gn('textbox').style.visibility = 'hidden'; - this.deactivateInput(); + return; } + (ti.parentNode).textsprite.str = ti.value; } -}; -Sprite.prototype.handleKeyUp = function (e) { - var ti = e.target; - if (!(ti.parentNode).textsprite) { - return; + deactivateInput () { + var ti = document.forms.activetextbox.typing; + ti.onblur = undefined; + ti.onkeypress = undefined; + ti.onsubmit = undefined; } - (ti.parentNode).textsprite.str = ti.value; -}; -Sprite.prototype.deactivateInput = function () { - var ti = document.forms.activetextbox.typing; - ti.onblur = undefined; - ti.onkeypress = undefined; - ti.onsubmit = undefined; -}; - -Sprite.prototype.activate = function () { - var list = fitInRect(this.w, this.h, ScriptsPane.watermark.offsetWidth, ScriptsPane.watermark.offsetHeight); - var div = ScriptsPane.watermark; - while (div.childElementCount > 0) { - div.removeChild(div.childNodes[0]); - } - var img = this.getSVGimage(this.watermark); - div.appendChild(img); - var attr = { - width: this.w + 'px', - height: this.h + 'px', - left: list[0] + 'px', - top: list[1] + 'px', - zoom: Math.floor((list[2] / this.w) * 100) + '%' - }; - setProps(img.style, attr); -}; - -Sprite.prototype.getSVGimage = function (svg) { - var img = document.createElement('img'); - var str = (new XMLSerializer()).serializeToString(svg); - str = str.replace(/ href="data:image/g, ' xlink:href="data:image'); - img.src = 'data:image/svg+xml;base64,' + btoa(str); - return img; -}; - -///////////////////////////////////////////////// -// Text fcn -//////////////////////////////////////////////// - -Sprite.prototype.setColor = function (c) { - this.color = c; - this.div.style.color = this.color; -}; - -Sprite.prototype.setFontSize = function (n) { - if (n < 12) { - n = 12; - } - if (n > 72) { - n = 72; - } - this.fontsize = n; -}; - -Sprite.prototype.recalculateText = function () { - this.div.style.color = this.color; - this.div.style.fontSize = this.fontsize + 'px'; - this.div.textContent = this.str; - var ctx = this.outline.getContext('2d'); - ctx.font = 'bold ' + this.fontsize + 'px ' + Settings.textSpriteFont; - var w = ctx.measureText(this.str).width; - this.w = (Math.round(w) + 1); - this.div.style.width = (this.w * 2) + 'px'; - this.h = this.div.offsetHeight; - this.cx = this.w / 2; - this.cy = this.h / 2; - setCanvasSize(this.outline, this.w, this.h); - ctx.clearRect(0, 0, this.outline.width, this.outline.height); - ctx.font = 'bold ' + this.fontsize + 'px ' + Settings.textSpriteFont; - ctx.fillStyle = this.color; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - ctx.fillText(this.str, 0, 0); - this.setPos(this.xcoor, this.ycoor); -}; - -Sprite.prototype.startShaking = function () { - var p = this.div.parentNode; - var shake = newHTML('div', 'shakeme', p); - shake.id = 'shakediv'; - - // TODO: merge these for iOS - if (isAndroid) { - setProps(shake.style, { - position: 'absolute', - left: this.screenLeft() + 'px', - top: this.screenTop() + 'px', - width: (this.w * this.scale) + 'px', - height: (this.h * this.scale) + 'px' - }); - } else { - setProps(shake.style, { - position: 'absolute', - left: (this.screenLeft() / this.scale) + 'px', - top: (this.screenTop() / this.scale) + 'px', + activate () { + var list = fitInRect(this.w, this.h, ScriptsPane.watermark.offsetWidth, ScriptsPane.watermark.offsetHeight); + var div = ScriptsPane.watermark; + while (div.childElementCount > 0) { + div.removeChild(div.childNodes[0]); + } + var img = this.getSVGimage(this.watermark); + div.appendChild(img); + var attr = { width: this.w + 'px', height: this.h + 'px', - zoom: Math.floor(this.scale * 100) + '%' - }); + left: list[0] + 'px', + top: list[1] + 'px', + zoom: Math.floor((list[2] / this.w) * 100) + '%' + }; + setProps(img.style, attr); } - var mtx = 'translate3d(0px, 0px, 0px)'; - if (this.img) { - mtx += ' rotate(' + this.angle + 'deg)'; - if (this.flip) { - mtx += 'scale(' + -1 + ', ' + 1 + ')'; - } else { - mtx += 'scale(' + 1 + ', ' + 1 + ')'; + + getSVGimage (svg) { + var img = document.createElement('img'); + var str = (new XMLSerializer()).serializeToString(svg); + str = str.replace(/ href="data:image/g, ' xlink:href="data:image'); + img.src = 'data:image/svg+xml;base64,' + btoa(str); + return img; + } + + ///////////////////////////////////////////////// + // Text fcn + //////////////////////////////////////////////// + + setColor (c) { + this.color = c; + this.div.style.color = this.color; + } + + setFontSize (n) { + if (n < 12) { + n = 12; } - } - this.setTransform(mtx); - shake.appendChild(this.div); - var cb = newHTML('div', (this.type == 'sprite') ? 'deletesprite' : 'deletetext', shake); - if (isiOS && this.type == 'sprite') { - cb.style.zoom = Math.floor((1 / this.scale) * 100) + '%'; - } - if ((globalx(cb) - globalx(ScratchJr.stage.div)) < 0) { - cb.style.left = Math.abs(globalx(cb) - globalx(ScratchJr.stage.div)) * this.scale + 'px'; - } - if ((globaly(cb) - globaly(ScratchJr.stage.div)) < 0) { - cb.style.top = Math.abs(globaly(cb) - globaly(ScratchJr.stage.div)) * this.scale + 'px'; - } - cb.id = 'deletesprite'; - this.div = shake; - this.div.owner = this; -}; - -Sprite.prototype.stopShaking = function () { - if (this.div.id != 'shakediv') { - return; - } - var p = this.div; - this.div = this.div.childNodes[0]; - ScratchJr.stage.currentPage.div.appendChild(this.div); - if (p.id == 'shakediv') { - p.parentNode.removeChild(p); + if (n > 72) { + n = 72; + } + this.fontsize = n; } - // TODO: merge these for iOS - if (isAndroid) { - this.render(); - } else { - var mtx = 'translate3d(' + (this.xcoor - this.cx) + 'px,' + (this.ycoor - this.cy) + 'px, 0px)'; + recalculateText () { + this.div.style.color = this.color; + this.div.style.fontSize = this.fontsize + 'px'; + this.div.textContent = this.str; + var ctx = this.outline.getContext('2d'); + ctx.font = 'bold ' + this.fontsize + 'px ' + Settings.textSpriteFont; + var w = ctx.measureText(this.str).width; + this.w = (Math.round(w) + 1); + this.div.style.width = (this.w * 2) + 'px'; + this.h = this.div.offsetHeight; + this.cx = this.w / 2; + this.cy = this.h / 2; + setCanvasSize(this.outline, this.w, this.h); + ctx.clearRect(0, 0, this.outline.width, this.outline.height); + ctx.font = 'bold ' + this.fontsize + 'px ' + Settings.textSpriteFont; + ctx.fillStyle = this.color; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.fillText(this.str, 0, 0); + this.setPos(this.xcoor, this.ycoor); + } + + startShaking () { + var p = this.div.parentNode; + var shake = newHTML('div', 'shakeme', p); + shake.id = 'shakediv'; + + // TODO: merge these for iOS + if (isAndroid) { + setProps(shake.style, { + position: 'absolute', + left: this.screenLeft() + 'px', + top: this.screenTop() + 'px', + width: (this.w * this.scale) + 'px', + height: (this.h * this.scale) + 'px' + }); + } else { + setProps(shake.style, { + position: 'absolute', + left: (this.screenLeft() / this.scale) + 'px', + top: (this.screenTop() / this.scale) + 'px', + width: this.w + 'px', + height: this.h + 'px', + zoom: Math.floor(this.scale * 100) + '%' + }); + } + var mtx = 'translate3d(0px, 0px, 0px)'; if (this.img) { mtx += ' rotate(' + this.angle + 'deg)'; if (this.flip) { - mtx += 'scale(' + -this.scale + ', ' + this.scale + ')'; + mtx += 'scale(' + -1 + ', ' + 1 + ')'; } else { - mtx += 'scale(' + this.scale + ', ' + this.scale + ')'; + mtx += 'scale(' + 1 + ', ' + 1 + ')'; } } this.setTransform(mtx); + shake.appendChild(this.div); + var cb = newHTML('div', (this.type == 'sprite') ? 'deletesprite' : 'deletetext', shake); + if (isiOS && this.type == 'sprite') { + cb.style.zoom = Math.floor((1 / this.scale) * 100) + '%'; + } + if ((globalx(cb) - globalx(ScratchJr.stage.div)) < 0) { + cb.style.left = Math.abs(globalx(cb) - globalx(ScratchJr.stage.div)) * this.scale + 'px'; + } + if ((globaly(cb) - globaly(ScratchJr.stage.div)) < 0) { + cb.style.top = Math.abs(globaly(cb) - globaly(ScratchJr.stage.div)) * this.scale + 'px'; + } + cb.id = 'deletesprite'; + this.div = shake; + this.div.owner = this; } -}; -Sprite.prototype.drawCloseButton = function () { - var ctx = this.div.getContext('2d'); - var img = document.createElement('img'); - img.src = 'assets/ui/closeit.svg'; - if (!img.complete) { - img.onload = function () { - ctx.drawImage(0, 0); - }; - } else { - ctx.drawImage(img, 0, 0); + stopShaking () { + if (this.div.id != 'shakediv') { + return; + } + var p = this.div; + this.div = this.div.childNodes[0]; + ScratchJr.stage.currentPage.div.appendChild(this.div); + if (p.id == 'shakediv') { + p.parentNode.removeChild(p); + } + + // TODO: merge these for iOS + if (isAndroid) { + this.render(); + } else { + var mtx = 'translate3d(' + (this.xcoor - this.cx) + 'px,' + (this.ycoor - this.cy) + 'px, 0px)'; + if (this.img) { + mtx += ' rotate(' + this.angle + 'deg)'; + if (this.flip) { + mtx += 'scale(' + -this.scale + ', ' + this.scale + ')'; + } else { + mtx += 'scale(' + this.scale + ', ' + this.scale + ')'; + } + } + this.setTransform(mtx); + } } -}; -////////////////////////////////////////// -// Save data -///////////////////////////////////////// + drawCloseButton () { + var ctx = this.div.getContext('2d'); + var img = document.createElement('img'); + img.src = 'assets/ui/closeit.svg'; + if (!img.complete) { + img.onload = function () { + ctx.drawImage(0, 0); + }; + } else { + ctx.drawImage(img, 0, 0); + } + } -Sprite.prototype.getData = function () { - var data = (this.type == 'sprite') ? this.getSpriteData() : this.getTextBoxData(); - if (this.type != 'sprite') { + ////////////////////////////////////////// + // Save data + ///////////////////////////////////////// + + getData () { + var data = (this.type == 'sprite') ? this.getSpriteData() : this.getTextBoxData(); + if (this.type != 'sprite') { + return data; + } + var sc = gn(this.id + '_scripts').owner; + var res = []; + var topblocks = sc.getEncodableBlocks(); + for (var i = 0; i < topblocks.length; i++) { + res.push(Project.encodeStrip(topblocks[i])); + } + data.scripts = res; return data; } - var sc = gn(this.id + '_scripts').owner; - var res = []; - var topblocks = sc.getEncodableBlocks(); - for (var i = 0; i < topblocks.length; i++) { - res.push(Project.encodeStrip(topblocks[i])); + + getSpriteData () { + var data = {}; + data.shown = this.shown; + data.type = this.type; + data.md5 = this.md5; + data.id = this.id; + data.flip = this.flip; + data.name = this.name; + data.angle = this.angle; + data.scale = this.scale; + data.speed = this.speed; + data.defaultScale = this.defaultScale; + data.sounds = this.sounds; + data.xcoor = this.xcoor; + data.ycoor = this.ycoor; + data.cx = this.cx; + data.cy = this.cy; + data.w = this.w; + data.h = this.h; + data.homex = this.homex; + data.homey = this.homey; + data.homescale = this.homescale; + data.homeshown = this.homeshown; + data.homeflip = this.homeflip; + return data; } - data.scripts = res; - return data; -}; -Sprite.prototype.getSpriteData = function () { - var data = {}; - data.shown = this.shown; - data.type = this.type; - data.md5 = this.md5; - data.id = this.id; - data.flip = this.flip; - data.name = this.name; - data.angle = this.angle; - data.scale = this.scale; - data.speed = this.speed; - data.defaultScale = this.defaultScale; - data.sounds = this.sounds; - data.xcoor = this.xcoor; - data.ycoor = this.ycoor; - data.cx = this.cx; - data.cy = this.cy; - data.w = this.w; - data.h = this.h; - data.homex = this.homex; - data.homey = this.homey; - data.homescale = this.homescale; - data.homeshown = this.homeshown; - data.homeflip = this.homeflip; - return data; -}; - -Sprite.prototype.getTextBoxData = function () { - var data = {}; - data.shown = this.shown; - data.type = this.type; - data.id = this.id; - data.speed = this.speed; - data.cx = this.cx; - data.cy = this.cy; - data.w = Math.floor(this.w); - data.h = Math.floor(this.h); - data.xcoor = this.xcoor; - data.ycoor = this.ycoor; - data.homex = this.homex; - data.homey = this.homey; - data.str = this.str; - data.color = this.color; - data.fontsize = this.fontsize; - return data; -}; + getTextBoxData () { + var data = {}; + data.shown = this.shown; + data.type = this.type; + data.id = this.id; + data.speed = this.speed; + data.cx = this.cx; + data.cy = this.cy; + data.w = Math.floor(this.w); + data.h = Math.floor(this.h); + data.xcoor = this.xcoor; + data.ycoor = this.ycoor; + data.homex = this.homex; + data.homey = this.homey; + data.str = this.str; + data.color = this.color; + data.fontsize = this.fontsize; + return data; + } +} diff --git a/src/editor/engine/Stage.js b/src/editor/engine/Stage.js index 00c6115..f2b6ee0 100644 --- a/src/editor/engine/Stage.js +++ b/src/editor/engine/Stage.js @@ -1,717 +1,735 @@ -var Stage = function (div) { - this.currentPage = undefined; - this.div = newHTML('div', 'stage', div); - this.div.setAttribute('id', 'stage'); - this.div.style.webkitTextSizeAdjust = '100%'; - this.width = 480; - this.height = 360; - this.setStageScaleAndPosition(scaleMultiplier, 46, 74); - this.pages = []; - this.pagesdiv = newDiv(this.div, 0, 0, 480, 360, { - position: 'absolute' - }); - var me = this; - this.div.ontouchstart = function (evt) { - me.mouseDown(evt); - }; - this.div.owner = this; - this.currentZoom = 1; - this.initialPoint = { - x: 0, - y: 0 - }; - this.deltaPoint = { - x: 0, - y: 0 - }; -}; +import ScratchJr from '../ScratchJr'; +import Project from '../../ui/Project'; +import Thumbs from '../../ui/Thumbs'; +import UI from '../../ui/UI'; +import Undo from '../ui/Undo'; +import ScriptsPane from '../ui/ScriptsPane'; +import Rectangle from '../../geom/Rectangle'; +import Events from '../../utils/Events'; +import ScratchAudio from '../../utils/ScratchAudio'; +import Vector from '../../geom/Vector'; +import Page from './Page'; +import {newHTML, newDiv, gn, + getIdFor, setProps, + scaleMultiplier, setCanvasSize, + globaly, globalx} from '../../utils/lib'; -Stage.prototype.setStageScaleAndPosition = function (scale, x, y) { - this.stageScale = scale; - setProps(gn('stage').style, { - webkitTransform: 'translate(' + (-this.width / 2) + 'px, ' + (-this.height / 2) + 'px) ' + - 'scale(' + scale + ') ' + - 'translate(' + (this.width / 2 + x) + 'px, ' + (this.height / 2 + y) + 'px)' - }); -}; - -Stage.prototype.getPagesID = function () { - var res = []; - for (var i = 0; i < this.pages.length; i++) { - res.push(this.pages[i].id); - } - return res; -}; - -Stage.prototype.getPage = function (id) { - for (var i = 0; i < this.pages.length; i++) { - if (this.pages[i].id == id) { - return this.pages[i]; - } - } - return this.pages[0]; -}; - -Stage.prototype.resetPage = function (obj) { - var page = obj.div; - for (var i = 0; i < page.childElementCount; i++) { - var spr = page.childNodes[i].owner; - if (!spr) { - continue; - } - if (spr.type == 'sprite') { - spr.goHome(); - } - } -}; - -Stage.prototype.resetPages = function () { - for (var i = 0; i < ScratchJr.stage.pages.length; i++) { - Stage.prototype.resetPage(ScratchJr.stage.pages[i]); - } -}; - - -//goto page - - -Stage.prototype.gotoPage = function (n) { - if (n < 1) { - return; - } - if (n > this.pages.length) { - return; - } - if (Events.dragthumbnail && Events.dragthumbnail.owner) { - return; - } - this.setPage(this.pages[n - 1], true); -}; - -Stage.prototype.setPage = function (page, isOn) { - ScratchJr.stopStrips(); - var sc = ScratchJr.getSprite() ? gn(ScratchJr.stage.currentPage.currentSpriteName + '_scripts') : undefined; - if (sc) { - sc.owner.deactivate(); - } - this.currentPage.div.style.visibility = 'hidden'; - this.currentPage.setPageSprites('hidden'); - this.currentPage = page; - this.currentPage.div.style.visibility = 'visible'; - this.currentPage.setPageSprites('visible'); - // if (page == obj['currentPage']) this.currentPage.currentSpriteName = obj[page]["lastSprite"]; - Thumbs.updateSprites(); - Thumbs.updatePages(); - var spr = ScratchJr.getSprite(); - if (spr) { - spr.activate(); - } - if (isOn) { - this.loadPageThreads(); - } -}; - -Stage.prototype.loadPageThreads = function () { - ScratchJr.blur(); - var page = this.currentPage; - for (var i = 0; i < page.div.childElementCount; i++) { - var spr = page.div.childNodes[i].owner; - if (!spr) { - continue; - } - spr.goHome(); - var sc = gn(spr.id + '_scripts'); - if (!sc) { - continue; - } - var topblocks = sc.owner.getBlocksType(['onflag', 'ontouch']); - for (var j = 0; j < topblocks.length; j++) { - var b = topblocks[j]; - ScratchJr.runtime.addRunScript(spr, b); - } - } -}; - - -//Copy Sprite -/////////////////////////////////' - -Stage.prototype.copySprite = function (el, thumb) { - ScratchAudio.sndFX('copy.wav'); - Thumbs.overpage(thumb); - var data = Project.encodeSprite(el.owner); - if (gn(thumb.owner).owner == this.currentPage) { - data.xcoor += 10; - data.ycoor += 10; - data.homex = data.xcoor; - data.homey = data.ycoor; - } - var a = data.id.split(' '); - if (Number(a[a.length - 1]).toString() != 'NaN') { - a.pop(); - } - var page = gn(thumb.owner).owner; - var name = getIdFor(a.join(' ')); - data.id = name; - var stg = this; - var whenDone = function (spr) { - if (spr.page.id == ScratchJr.stage.currentPage.id) { - spr.div.style.visibility = 'visible'; - } - if (!page.currentSpriteName) { - page.currentSpriteName = spr.id; - } - Thumbs.updateSprites(); - Thumbs.updatePages(); - Undo.record({ - action: 'copy', - who: name, - where: gn(thumb.owner).owner.id +export default class Stage { + constructor (div) { + this.currentPage = undefined; + this.div = newHTML('div', 'stage', div); + this.div.setAttribute('id', 'stage'); + this.div.style.webkitTextSizeAdjust = '100%'; + this.width = 480; + this.height = 360; + this.setStageScaleAndPosition(scaleMultiplier, 46, 74); + this.pages = []; + this.pagesdiv = newDiv(this.div, 0, 0, 480, 360, { + position: 'absolute' }); - ScratchJr.storyStart('Stage.prototype.copySprite'); - }; - Project.recreateObject(page, name, data, whenDone, page.id == stg.currentPage.id); -}; - - -//Delete page - - -Stage.prototype.deletePage = function (str, data) { - // reserve a next id to be able to Undo deleting the first page - ScratchJr.storyStart('Stage.prototype.deletePage'); // Record a change for sample projects in story-starter mode - var pageid = getIdFor('page'); - var sprAttr = UI.mascotData(); - var newp = new Object(); - var catid = sprAttr.id; - newp.sprites = [catid]; - newp.num = 1; - newp.lastSprite = catid; - newp[catid] = sprAttr; - newp.layers = [catid]; - var page = gn(str).owner; - var indx = this.getPagesID().indexOf(str); - if (indx < 0) { - return; + var me = this; + this.div.ontouchstart = function (evt) { + me.mouseDown(evt); + }; + this.div.owner = this; + this.currentZoom = 1; + this.initialPoint = { + x: 0, + y: 0 + }; + this.deltaPoint = { + x: 0, + y: 0 + }; } - var form = document.forms.activetextbox; - var cnv = form.textsprite; - if (cnv && gn(cnv.id)) { + + setStageScaleAndPosition (scale, x, y) { + this.stageScale = scale; + setProps(gn('stage').style, { + webkitTransform: 'translate(' + (-this.width / 2) + 'px, ' + (-this.height / 2) + 'px) ' + + 'scale(' + scale + ') ' + + 'translate(' + (this.width / 2 + x) + 'px, ' + (this.height / 2 + y) + 'px)' + }); + } + + getPagesID () { + var res = []; + for (var i = 0; i < this.pages.length; i++) { + res.push(this.pages[i].id); + } + return res; + } + + getPage (id) { + for (var i = 0; i < this.pages.length; i++) { + if (this.pages[i].id == id) { + return this.pages[i]; + } + } + return this.pages[0]; + } + + resetPage (obj) { + var page = obj.div; + for (var i = 0; i < page.childElementCount; i++) { + var spr = page.childNodes[i].owner; + if (!spr) { + continue; + } + if (spr.type == 'sprite') { + spr.goHome(); + } + } + } + + resetPages () { + for (var i = 0; i < ScratchJr.stage.pages.length; i++) { + Stage.prototype.resetPage(ScratchJr.stage.pages[i]); + } + } + + + //goto page + + + gotoPage (n) { + if (n < 1) { + return; + } + if (n > this.pages.length) { + return; + } + if (Events.dragthumbnail && Events.dragthumbnail.owner) { + return; + } + this.setPage(this.pages[n - 1], true); + } + + setPage (page, isOn) { + ScratchJr.stopStrips(); + var sc = ScratchJr.getSprite() ? gn(ScratchJr.stage.currentPage.currentSpriteName + '_scripts') : undefined; + if (sc) { + sc.owner.deactivate(); + } + this.currentPage.div.style.visibility = 'hidden'; + this.currentPage.setPageSprites('hidden'); + this.currentPage = page; + this.currentPage.div.style.visibility = 'visible'; + this.currentPage.setPageSprites('visible'); + // if (page == obj['currentPage']) this.currentPage.currentSpriteName = obj[page]["lastSprite"]; + Thumbs.updateSprites(); + Thumbs.updatePages(); + var spr = ScratchJr.getSprite(); + if (spr) { + spr.activate(); + } + if (isOn) { + this.loadPageThreads(); + } + } + + loadPageThreads () { ScratchJr.blur(); - } - this.removePageBlocks(str); - this.pages.splice(indx, 1); - if (!data) { - ScratchAudio.sndFX('cut.wav'); - } - this.removePage(page); - if (this.pages.length == 0) { - var p = new Page(pageid, newp, refreshPage); - sprAttr.page = p; - } else { - if (str == this.currentPage.id) { - this.setViewPage(this.pages[0]); - } - Thumbs.updateSprites(); - Thumbs.updatePages(); - if (!data) { - Undo.record({ - action: 'deletepage', - where: str, - who: str - }); - } - } - function refreshPage () { - ScratchJr.stage.setViewPage(ScratchJr.stage.currentPage); - Thumbs.updateSprites(); - Thumbs.updatePages(); - if (!data) { - Undo.record({ - action: 'deletepage', - where: str, - who: str - }); - } - } -}; - -Stage.prototype.setViewPage = function (page) { - this.currentPage = page; - this.currentPage.div.style.visibility = 'visible'; - this.currentPage.setPageSprites('visible'); -}; - -Stage.prototype.removePageBlocks = function (str) { - var indx = this.getPagesID().indexOf(str); - for (var n = 0; n < this.pages.length; n++) { - var page = this.pages[n]; + var page = this.currentPage; for (var i = 0; i < page.div.childElementCount; i++) { var spr = page.div.childNodes[i].owner; if (!spr) { continue; } + spr.goHome(); var sc = gn(spr.id + '_scripts'); if (!sc) { continue; } - var gotoblocks = sc.owner.getBlocksType(['gotopage']); - for (var j = 0; j < gotoblocks.length; j++) { - var b = gotoblocks[j]; - var pageindex = b.getArgValue() - 1; - if (this.pages[pageindex].id == str) { - var prev = b.prev; - b.detachBlock(); - b.div.parentNode.removeChild(b.div); - if (prev && prev.aStart) { - prev.div.parentNode.removeChild(prev.div); + var topblocks = sc.owner.getBlocksType(['onflag', 'ontouch']); + for (var j = 0; j < topblocks.length; j++) { + var b = topblocks[j]; + ScratchJr.runtime.addRunScript(spr, b); + } + } + } + + + //Copy Sprite + /////////////////////////////////' + + copySprite (el, thumb) { + ScratchAudio.sndFX('copy.wav'); + Thumbs.overpage(thumb); + var data = Project.encodeSprite(el.owner); + if (gn(thumb.owner).owner == this.currentPage) { + data.xcoor += 10; + data.ycoor += 10; + data.homex = data.xcoor; + data.homey = data.ycoor; + } + var a = data.id.split(' '); + if (Number(a[a.length - 1]).toString() != 'NaN') { + a.pop(); + } + var page = gn(thumb.owner).owner; + var name = getIdFor(a.join(' ')); + data.id = name; + var stg = this; + var whenDone = function (spr) { + if (spr.page.id == ScratchJr.stage.currentPage.id) { + spr.div.style.visibility = 'visible'; + } + if (!page.currentSpriteName) { + page.currentSpriteName = spr.id; + } + Thumbs.updateSprites(); + Thumbs.updatePages(); + Undo.record({ + action: 'copy', + who: name, + where: gn(thumb.owner).owner.id + }); + ScratchJr.storyStart('Stage.prototype.copySprite'); + }; + Project.recreateObject(page, name, data, whenDone, page.id == stg.currentPage.id); + } + + + //Delete page + + + deletePage (str, data) { + // reserve a next id to be able to Undo deleting the first page + ScratchJr.storyStart('Stage.prototype.deletePage'); // Record a change for sample projects in story-starter mode + var pageid = getIdFor('page'); + var sprAttr = UI.mascotData(); + var newp = new Object(); + var catid = sprAttr.id; + newp.sprites = [catid]; + newp.num = 1; + newp.lastSprite = catid; + newp[catid] = sprAttr; + newp.layers = [catid]; + var page = gn(str).owner; + var indx = this.getPagesID().indexOf(str); + if (indx < 0) { + return; + } + var form = document.forms.activetextbox; + var cnv = form.textsprite; + if (cnv && gn(cnv.id)) { + ScratchJr.blur(); + } + this.removePageBlocks(str); + this.pages.splice(indx, 1); + if (!data) { + ScratchAudio.sndFX('cut.wav'); + } + this.removePage(page); + if (this.pages.length == 0) { + var p = new Page(pageid, newp, refreshPage); + sprAttr.page = p; + } else { + if (str == this.currentPage.id) { + this.setViewPage(this.pages[0]); + } + Thumbs.updateSprites(); + Thumbs.updatePages(); + if (!data) { + Undo.record({ + action: 'deletepage', + where: str, + who: str + }); + } + } + function refreshPage () { + ScratchJr.stage.setViewPage(ScratchJr.stage.currentPage); + Thumbs.updateSprites(); + Thumbs.updatePages(); + if (!data) { + Undo.record({ + action: 'deletepage', + where: str, + who: str + }); + } + } + } + + setViewPage (page) { + this.currentPage = page; + this.currentPage.div.style.visibility = 'visible'; + this.currentPage.setPageSprites('visible'); + } + + removePageBlocks (str) { + var indx = this.getPagesID().indexOf(str); + for (var n = 0; n < this.pages.length; n++) { + var page = this.pages[n]; + for (var i = 0; i < page.div.childElementCount; i++) { + var spr = page.div.childNodes[i].owner; + if (!spr) { + continue; + } + var sc = gn(spr.id + '_scripts'); + if (!sc) { + continue; + } + var gotoblocks = sc.owner.getBlocksType(['gotopage']); + for (var j = 0; j < gotoblocks.length; j++) { + var b = gotoblocks[j]; + var pageindex = b.getArgValue() - 1; + if (this.pages[pageindex].id == str) { + var prev = b.prev; + b.detachBlock(); + b.div.parentNode.removeChild(b.div); + if (prev && prev.aStart) { + prev.div.parentNode.removeChild(prev.div); + } + } else if ((b.getArgValue() - 1) > indx) { + b.arg.argValue -= 1; + this.pages[pageindex].num = b.arg.argValue; + b.arg.updateIcon(); } - } else if ((b.getArgValue() - 1) > indx) { - b.arg.argValue -= 1; - this.pages[pageindex].num = b.arg.argValue; - b.arg.updateIcon(); } } } } -}; -//Events MouseDown + //Events MouseDown -Stage.prototype.mouseDown = function (e) { - if (e.touches && (e.touches.length > 1)) { - return; - } - if (ScratchJr.onHold) { - return; - } - e.preventDefault(); - ScratchJr.blur(); - if (!this.currentPage) { - return; - } - if (document.forms.activetextbox.textsprite) { - return; - } - var pt = this.getStagePt(e); - setCanvasSize(ScratchJr.workingCanvas, 480, 360); - var ctx = ScratchJr.workingCanvas.getContext('2d'); - var target = (e.target.nodeName == 'CANVAS') ? this.checkShaking(pt, e.target) : e.target; - if (ScratchJr.shaking && (target.id == 'deletesprite')) { - this.removeSprite(ScratchJr.shaking.owner); - return; - } - ctx.clearRect(0, 0, 480, 360); - var hitobj = this.whoIsIt(ctx, pt); - if (ScratchJr.shaking && hitobj && (hitobj.id == ScratchJr.shaking.id)) { // check grid case - var sprname = ScratchJr.shaking.id; - if (((pt.x - gn(sprname).owner.screenLeft()) < 45) && ((pt.y - gn(sprname).owner.screenTop()) < 45)) { + mouseDown (e) { + if (e.touches && (e.touches.length > 1)) { + return; + } + if (ScratchJr.onHold) { + return; + } + e.preventDefault(); + ScratchJr.blur(); + if (!this.currentPage) { + return; + } + if (document.forms.activetextbox.textsprite) { + return; + } + var pt = this.getStagePt(e); + setCanvasSize(ScratchJr.workingCanvas, 480, 360); + var ctx = ScratchJr.workingCanvas.getContext('2d'); + var target = (e.target.nodeName == 'CANVAS') ? this.checkShaking(pt, e.target) : e.target; + if (ScratchJr.shaking && (target.id == 'deletesprite')) { this.removeSprite(ScratchJr.shaking.owner); return; } - } - if (!hitobj) { - ScratchJr.clearSelection(); - return; - } - if (ScratchJr.shaking) { - ScratchJr.clearSelection(); - } else { - this.mouseDownOnSprite(hitobj, pt); - } -}; - -Stage.prototype.checkShaking = function (pt, target) { - if (!ScratchJr.shaking) { - return target; - } - var dx = globalx(gn('deletesprite')) - globalx(ScratchJr.stage.pagesdiv); - var dy = globaly(gn('deletesprite')) - globaly(ScratchJr.stage.pagesdiv); - var w = gn('deletesprite').offsetWidth; - var h = gn('deletesprite').offsetHeight; - var rect = new Rectangle(dx, dy, w, h); - return rect.hitRect(pt) ? gn('deletesprite') : target; -}; - -Stage.prototype.mouseDownOnSprite = function (spr, pt) { - this.initialPoint = { - x: pt.x, - y: pt.y - }; - Events.dragthumbnail = spr.div; - Events.clearEvents(); - if (!ScratchJr.inFullscreen && ScratchJr.isEditable()) { - Events.holdit(spr.div, this.startShaking); - } - this.setEvents(); -}; - -Stage.prototype.whoIsIt = function (ctx, pt) { - var page = this.currentPage.div; - var spr, pixel; - for (var i = page.childElementCount - 1; i > -1; i--) { - spr = page.childNodes[i].owner; - if (!spr) { - continue; + ctx.clearRect(0, 0, 480, 360); + var hitobj = this.whoIsIt(ctx, pt); + if (ScratchJr.shaking && hitobj && (hitobj.id == ScratchJr.shaking.id)) { // check grid case + var sprname = ScratchJr.shaking.id; + if (((pt.x - gn(sprname).owner.screenLeft()) < 45) && ((pt.y - gn(sprname).owner.screenTop()) < 45)) { + this.removeSprite(ScratchJr.shaking.owner); + return; + } } - if (!spr.shown) { - continue; + if (!hitobj) { + ScratchJr.clearSelection(); + return; } - spr.stamp(ctx); - pixel = ctx.getImageData(pt.x, pt.y, 1, 1).data; - if (pixel[3] != 0) { - return spr; + if (ScratchJr.shaking) { + ScratchJr.clearSelection(); + } else { + this.mouseDownOnSprite(hitobj, pt); } } - var fuzzy = 5; - ctx.clearRect(0, 0, 480, 360); - for (var j = page.childElementCount - 1; j > -1; j--) { - spr = page.childNodes[j].owner; - if (!spr) { - continue; + + checkShaking (pt, target) { + if (!ScratchJr.shaking) { + return target; } - if (!spr.shown) { - continue; - } - spr.stamp(ctx); - spr.stamp(ctx, fuzzy, 0); - spr.stamp(ctx, 0, fuzzy); - spr.stamp(ctx, -fuzzy, 0); - spr.stamp(ctx, 0, -fuzzy); - pixel = ctx.getImageData(pt.x, pt.y, 1, 1).data; - if (pixel[3] != 0) { - return spr; + var dx = globalx(gn('deletesprite')) - globalx(ScratchJr.stage.pagesdiv); + var dy = globaly(gn('deletesprite')) - globaly(ScratchJr.stage.pagesdiv); + var w = gn('deletesprite').offsetWidth; + var h = gn('deletesprite').offsetHeight; + var rect = new Rectangle(dx, dy, w, h); + return rect.hitRect(pt) ? gn('deletesprite') : target; + } + + mouseDownOnSprite (spr, pt) { + this.initialPoint = { + x: pt.x, + y: pt.y + }; + Events.dragthumbnail = spr.div; + Events.clearEvents(); + if (!ScratchJr.inFullscreen && ScratchJr.isEditable()) { + Events.holdit(spr.div, this.startShaking); } + this.setEvents(); } - return undefined; -}; -Stage.prototype.getStagePt = function (evt) { - var pt = Events.getTargetPoint(evt); - var mc = this.div; - var dx = globalx(mc); - var dy = globaly(mc); - pt.x -= dx; - pt.y -= dy; - pt.x /= this.stageScale; - pt.y /= this.stageScale; - return pt; -}; - -Stage.prototype.setEvents = function () { - var me = this; - window.ontouchmove = function (evt) { - me.mouseMove(evt); - }; - window.ontouchend = function (evt) { - me.mouseUp(evt); - }; -}; - -Stage.prototype.startShaking = function (b) { - if (!b.owner) { - return; - } - Events.clearEvents(); - ScratchJr.shaking = b; - ScratchJr.stopShaking = ScratchJr.stage.stopShaking; - b.owner.startShaking(); -}; - -Stage.prototype.stopShaking = function (b) { - if (!b.owner) { - return; - } - b.owner.stopShaking(); - ScratchJr.shaking = undefined; - ScratchJr.stopShaking = undefined; -}; - -Stage.prototype.startSpriteDrag = function () { - var spr = Events.dragthumbnail.owner; - spr.threads = ScratchJr.runtime.removeRunScript(spr); - this.currentPage.div.appendChild(Events.dragthumbnail); - this.deltaPoint = { - x: this.initialPoint.x, - y: this.initialPoint.y - }; - Events.dragged = true; - ScratchJr.changed = true; -}; - -Stage.prototype.mouseMove = function (e) { - if (!Events.dragthumbnail) { - return; - } - var pt = this.getStagePt(e); - var delta = Vector.diff(pt, this.initialPoint); - var dist = ScratchJr.inFullscreen ? 15 : 5; - if (!Events.dragged && (Vector.len(delta) > dist)) { - this.startSpriteDrag(e); - } - if (!Events.dragged) { - return; - } - if (Events.timeoutEvent) { - clearTimeout(Events.timeoutEvent); - } - Events.timeoutEvent = undefined; - var spr = Events.dragthumbnail.owner; - delta = this.wrapDelta(spr, Vector.diff(pt, this.deltaPoint)); - spr.xcoor += delta.x; - spr.ycoor += delta.y; - spr.render(); - this.deltaPoint = { - x: pt.x, - y: pt.y - }; -}; - -Stage.prototype.wrapDelta = function (spr, delta) { - if (spr.type == 'text') { - return this.wrapText(spr, delta); - } else { - return this.wrapChar(spr, delta); - } -}; - -Stage.prototype.wrapChar = function (spr, delta) { - if ((delta.x + spr.xcoor) < 0) { - delta.x -= (spr.xcoor + delta.x); - } - if ((delta.y + spr.ycoor) < 0) { - delta.y -= (spr.ycoor + delta.y); - } - if ((delta.x + spr.xcoor) >= 480) { - delta.x += (479 - (spr.xcoor + delta.x)); - } - if ((delta.y + spr.ycoor) >= 360) { - delta.y += (359 - (spr.ycoor + delta.y)); - } - return delta; -}; - -Stage.prototype.wrapText = function (spr, delta) { - var max = spr.cx > 480 ? spr.cx : 480; - var min = spr.cx > 480 ? 480 - spr.cx : 0; - if ((delta.x + spr.xcoor) <= min) { - delta.x -= (spr.xcoor + delta.x - min); - } - if ((delta.y + spr.ycoor) < 0) { - delta.y -= (spr.ycoor + delta.y); - } - if ((delta.x + spr.xcoor) > max) { - delta.x += (max - 1 - (spr.xcoor + delta.x)); - } - if ((delta.y + spr.ycoor) >= 360) { - delta.y += (359 - (spr.ycoor + delta.y)); - } - return delta; -}; - -Stage.prototype.mouseUp = function (e) { - var spr = Events.dragthumbnail.owner; - if (Events.timeoutEvent) { - clearTimeout(Events.timeoutEvent); - } - Events.timeoutEvent = undefined; - if (!Events.dragged) { - this.clickOnElement(e, Events.dragthumbnail); - } else { - this.moveElementBy(spr); - if (spr.type == 'sprite') { - ScratchJr.runtime.threadsRunning = ScratchJr.runtime.threadsRunning.concat(spr.threads); - ScratchJr.startCurrentPageStrips(['ontouch']); - } - } - Events.clearEvents(); - Events.dragged = false; - Events.dragthumbnail = undefined; -}; - -Stage.prototype.moveElementBy = function (spr) { - if (!ScratchJr.inFullscreen) { - spr.homex = spr.xcoor; - spr.homey = spr.ycoor; - spr.homeflip = spr.flip; - } - Thumbs.updatePages(); -}; - -Stage.prototype.clickOnSprite = function (e, spr) { - e.preventDefault(); - ScratchJr.clearSelection(); - ScratchJr.startScriptsFor(spr, ['onclick']); - ScratchJr.startCurrentPageStrips(['ontouch']); -}; - - -//Delete Sprite -/////////////////////////////////' - -Stage.prototype.removeSprite = function (sprite) { - ScratchJr.shaking = undefined; - ScratchJr.stopShaking = undefined; - ScratchAudio.sndFX('cut.wav'); - if (sprite.type == 'text') { - sprite.deleteText(true); - } else { - this.removeCharacter(sprite); - } - this.currentPage.updateThumb(); - this.updatePageBlocks(); -}; - -Stage.prototype.removeCharacter = function (spr) { - ScratchJr.runtime.stopThreadSprite(spr); - this.removeFromPage(spr); - Undo.record({ - action: 'deletesprite', - who: spr.id, - where: ScratchJr.stage.currentPage.id - }); - ScratchJr.storyStart('Stage.prototype.removeCharacter'); - Thumbs.updateSprites(); -}; - -Stage.prototype.updatePageBlocks = function () { - for (var i = 0; i < ScratchJr.stage.pages.length; i++) { - var page = ScratchJr.stage.pages[i]; - ScriptsPane.updateScriptsPageBlocks(JSON.parse(page.sprites)); - } -}; - -Stage.prototype.removeFromPage = function (spr) { - var id = spr.id; - var sc = gn(id + '_scripts'); - var page = this.currentPage; - var list = JSON.parse(page.sprites); - var n = list.indexOf(id); - if (n < 0) { - return; - } - var th = spr.thumbnail; - var sprite = ScratchJr.getSprite(); - ScratchAudio.sndFX('cut.wav'); - list.splice(n, 1); - spr.div.parentNode.removeChild(spr.div); - if (sc) { - sc.parentNode.removeChild(sc); - } - page.sprites = JSON.stringify(list); - th.parentNode.removeChild(th); - if (sprite && (sprite.id == spr.id)) { - var sprites = page.getSprites(); - page.setCurrentSprite((sprites.length > 0) ? gn(sprites[0]).owner : undefined); - } -}; - -Stage.prototype.renumberPageBlocks = function (list) { - var pages = this.getPagesID(); - for (var n = 0; n < this.pages.length; n++) { - var page = this.pages[n]; - for (var i = 0; i < page.div.childElementCount; i++) { - var spr = page.div.childNodes[i].owner; + whoIsIt (ctx, pt) { + var page = this.currentPage.div; + var spr, pixel; + for (var i = page.childElementCount - 1; i > -1; i--) { + spr = page.childNodes[i].owner; if (!spr) { continue; } - var sc = gn(spr.id + '_scripts'); - if (!sc) { + if (!spr.shown) { continue; } - var gotoblocks = sc.owner.getBlocksType(['gotopage']); - for (var j = 0; j < gotoblocks.length; j++) { - var b = gotoblocks[j]; - var indx = b.getArgValue() - 1; - b.arg.argValue = pages.indexOf(list[indx]) + 1; - b.updateBlock(); + spr.stamp(ctx); + pixel = ctx.getImageData(pt.x, pt.y, 1, 1).data; + if (pixel[3] != 0) { + return spr; } } - } -}; - -Stage.prototype.clickOnElement = function (e, spr) { - if (spr.owner.type == 'text') { - if (!ScratchJr.inFullscreen) { - spr.owner.clickOnText(e); + var fuzzy = 5; + ctx.clearRect(0, 0, 480, 360); + for (var j = page.childElementCount - 1; j > -1; j--) { + spr = page.childNodes[j].owner; + if (!spr) { + continue; + } + if (!spr.shown) { + continue; + } + spr.stamp(ctx); + spr.stamp(ctx, fuzzy, 0); + spr.stamp(ctx, 0, fuzzy); + spr.stamp(ctx, -fuzzy, 0); + spr.stamp(ctx, 0, -fuzzy); + pixel = ctx.getImageData(pt.x, pt.y, 1, 1).data; + if (pixel[3] != 0) { + return spr; + } } - } else if (spr.owner.type == 'sprite') { - this.clickOnSprite(e, spr.owner); + return undefined; } -}; - -//Stage clear -/////////////////////////////////////// - -Stage.prototype.clear = function () { - for (var i = 0; i < this.pages.length; i++) { - this.removePage(this.pages[i]); + getStagePt (evt) { + var pt = Events.getTargetPoint(evt); + var mc = this.div; + var dx = globalx(mc); + var dy = globaly(mc); + pt.x -= dx; + pt.y -= dy; + pt.x /= this.stageScale; + pt.y /= this.stageScale; + return pt; } - this.pages = []; - while (this.pagesdiv.childElementCount > 0) { - this.pagesdiv.removeChild(this.pagesdiv.childNodes[0]); - } -}; -Stage.prototype.removePage = function (p) { - var list = JSON.parse(p.sprites); - for (var j = 0; j < list.length; j++) { - var name = list[j]; - var sprite = gn(name); - var sc = gn(name + '_scripts'); + setEvents () { + var me = this; + window.ontouchmove = function (evt) { + me.mouseMove(evt); + }; + window.ontouchend = function (evt) { + me.mouseUp(evt); + }; + } + + startShaking (b) { + if (!b.owner) { + return; + } + Events.clearEvents(); + ScratchJr.shaking = b; + ScratchJr.stopShaking = ScratchJr.stage.stopShaking; + b.owner.startShaking(); + } + + stopShaking (b) { + if (!b.owner) { + return; + } + b.owner.stopShaking(); + ScratchJr.shaking = undefined; + ScratchJr.stopShaking = undefined; + } + + startSpriteDrag () { + var spr = Events.dragthumbnail.owner; + spr.threads = ScratchJr.runtime.removeRunScript(spr); + this.currentPage.div.appendChild(Events.dragthumbnail); + this.deltaPoint = { + x: this.initialPoint.x, + y: this.initialPoint.y + }; + Events.dragged = true; + ScratchJr.changed = true; + } + + mouseMove (e) { + if (!Events.dragthumbnail) { + return; + } + var pt = this.getStagePt(e); + var delta = Vector.diff(pt, this.initialPoint); + var dist = ScratchJr.inFullscreen ? 15 : 5; + if (!Events.dragged && (Vector.len(delta) > dist)) { + this.startSpriteDrag(e); + } + if (!Events.dragged) { + return; + } + if (Events.timeoutEvent) { + clearTimeout(Events.timeoutEvent); + } + Events.timeoutEvent = undefined; + var spr = Events.dragthumbnail.owner; + delta = this.wrapDelta(spr, Vector.diff(pt, this.deltaPoint)); + spr.xcoor += delta.x; + spr.ycoor += delta.y; + spr.render(); + this.deltaPoint = { + x: pt.x, + y: pt.y + }; + } + + wrapDelta (spr, delta) { + if (spr.type == 'text') { + return this.wrapText(spr, delta); + } else { + return this.wrapChar(spr, delta); + } + } + + wrapChar (spr, delta) { + if ((delta.x + spr.xcoor) < 0) { + delta.x -= (spr.xcoor + delta.x); + } + if ((delta.y + spr.ycoor) < 0) { + delta.y -= (spr.ycoor + delta.y); + } + if ((delta.x + spr.xcoor) >= 480) { + delta.x += (479 - (spr.xcoor + delta.x)); + } + if ((delta.y + spr.ycoor) >= 360) { + delta.y += (359 - (spr.ycoor + delta.y)); + } + return delta; + } + + wrapText (spr, delta) { + var max = spr.cx > 480 ? spr.cx : 480; + var min = spr.cx > 480 ? 480 - spr.cx : 0; + if ((delta.x + spr.xcoor) <= min) { + delta.x -= (spr.xcoor + delta.x - min); + } + if ((delta.y + spr.ycoor) < 0) { + delta.y -= (spr.ycoor + delta.y); + } + if ((delta.x + spr.xcoor) > max) { + delta.x += (max - 1 - (spr.xcoor + delta.x)); + } + if ((delta.y + spr.ycoor) >= 360) { + delta.y += (359 - (spr.ycoor + delta.y)); + } + return delta; + } + + mouseUp (e) { + var spr = Events.dragthumbnail.owner; + if (Events.timeoutEvent) { + clearTimeout(Events.timeoutEvent); + } + Events.timeoutEvent = undefined; + if (!Events.dragged) { + this.clickOnElement(e, Events.dragthumbnail); + } else { + this.moveElementBy(spr); + if (spr.type == 'sprite') { + ScratchJr.runtime.threadsRunning = ScratchJr.runtime.threadsRunning.concat(spr.threads); + ScratchJr.startCurrentPageStrips(['ontouch']); + } + } + Events.clearEvents(); + Events.dragged = false; + Events.dragthumbnail = undefined; + } + + moveElementBy (spr) { + if (!ScratchJr.inFullscreen) { + spr.homex = spr.xcoor; + spr.homey = spr.ycoor; + spr.homeflip = spr.flip; + } + Thumbs.updatePages(); + } + + clickOnSprite (e, spr) { + e.preventDefault(); + ScratchJr.clearSelection(); + ScratchJr.startScriptsFor(spr, ['onclick']); + ScratchJr.startCurrentPageStrips(['ontouch']); + } + + + //Delete Sprite + /////////////////////////////////' + + removeSprite (sprite) { + ScratchJr.shaking = undefined; + ScratchJr.stopShaking = undefined; + ScratchAudio.sndFX('cut.wav'); + if (sprite.type == 'text') { + sprite.deleteText(true); + } else { + this.removeCharacter(sprite); + } + this.currentPage.updateThumb(); + this.updatePageBlocks(); + } + + removeCharacter (spr) { + ScratchJr.runtime.stopThreadSprite(spr); + this.removeFromPage(spr); + Undo.record({ + action: 'deletesprite', + who: spr.id, + where: ScratchJr.stage.currentPage.id + }); + ScratchJr.storyStart('Stage.prototype.removeCharacter'); + Thumbs.updateSprites(); + } + + updatePageBlocks () { + for (var i = 0; i < ScratchJr.stage.pages.length; i++) { + var page = ScratchJr.stage.pages[i]; + ScriptsPane.updateScriptsPageBlocks(JSON.parse(page.sprites)); + } + } + + removeFromPage (spr) { + var id = spr.id; + var sc = gn(id + '_scripts'); + var page = this.currentPage; + var list = JSON.parse(page.sprites); + var n = list.indexOf(id); + if (n < 0) { + return; + } + var th = spr.thumbnail; + var sprite = ScratchJr.getSprite(); + ScratchAudio.sndFX('cut.wav'); + list.splice(n, 1); + spr.div.parentNode.removeChild(spr.div); if (sc) { sc.parentNode.removeChild(sc); } - sprite.parentNode.removeChild(sprite); + page.sprites = JSON.stringify(list); + th.parentNode.removeChild(th); + if (sprite && (sprite.id == spr.id)) { + var sprites = page.getSprites(); + page.setCurrentSprite((sprites.length > 0) ? gn(sprites[0]).owner : undefined); + } + } + + renumberPageBlocks (list) { + var pages = this.getPagesID(); + for (var n = 0; n < this.pages.length; n++) { + var page = this.pages[n]; + for (var i = 0; i < page.div.childElementCount; i++) { + var spr = page.div.childNodes[i].owner; + if (!spr) { + continue; + } + var sc = gn(spr.id + '_scripts'); + if (!sc) { + continue; + } + var gotoblocks = sc.owner.getBlocksType(['gotopage']); + for (var j = 0; j < gotoblocks.length; j++) { + var b = gotoblocks[j]; + var indx = b.getArgValue() - 1; + b.arg.argValue = pages.indexOf(list[indx]) + 1; + b.updateBlock(); + } + } + } + } + + clickOnElement (e, spr) { + if (spr.owner.type == 'text') { + if (!ScratchJr.inFullscreen) { + spr.owner.clickOnText(e); + } + } else if (spr.owner.type == 'sprite') { + this.clickOnSprite(e, spr.owner); + } } - p.div.parentNode.removeChild(p.div); -}; -//Debugging hit masks -/////////////////////////// + //Stage clear + /////////////////////////////////////// -Stage.prototype.sd = function () { - var stg = gn('stage'); - var mask = newDiv(gn('stageframe'), stg.offsetLeft + 1, stg.offsetTop + 1, 482, 362, - { - position: 'absolute', - zIndex: ScratchJr.layerTop + 20, - visibility: 'hidden' - }); - mask.setAttribute('id', 'pagemask'); - mask.appendChild(ScratchJr.workingCanvas); -}; + clear () { + for (var i = 0; i < this.pages.length; i++) { + this.removePage(this.pages[i]); + } + this.pages = []; + while (this.pagesdiv.childElementCount > 0) { + this.pagesdiv.removeChild(this.pagesdiv.childNodes[0]); + } + } -Stage.prototype.on = function () { - gn('pagemask').style.visibility = 'visible'; -}; + removePage (p) { + var list = JSON.parse(p.sprites); + for (var j = 0; j < list.length; j++) { + var name = list[j]; + var sprite = gn(name); + var sc = gn(name + '_scripts'); + if (sc) { + sc.parentNode.removeChild(sc); + } + sprite.parentNode.removeChild(sprite); + } + p.div.parentNode.removeChild(p.div); + } -Stage.prototype.off = function () { - gn('pagemask').style.visibility = 'hidden'; -}; -Stage.prototype.sm = function (spr) { - var stg = gn('stage'); - var w = spr.outline.width; - var h = spr.outline.height; - var mask = newDiv(gn('stageframe'), stg.offsetLeft + 1, stg.offsetTop + 1, w, h, - { - position: 'absolute', - zIndex: ScratchJr.layerTop + 20, - visibility: 'hidden' - }); - mask.setAttribute('id', 'spritemask'); - mask.appendChild(spr.outline); -}; + //Debugging hit masks + /////////////////////////// -Stage.prototype.son = function () { - gn('spritemask').style.visibility = 'visible'; -}; + sd () { + var stg = gn('stage'); + var mask = newDiv(gn('stageframe'), stg.offsetLeft + 1, stg.offsetTop + 1, 482, 362, + { + position: 'absolute', + zIndex: ScratchJr.layerTop + 20, + visibility: 'hidden' + }); + mask.setAttribute('id', 'pagemask'); + mask.appendChild(ScratchJr.workingCanvas); + } -Stage.prototype.soff = function () { - gn('spritemask').style.visibility = 'hidden'; -}; + on () { + gn('pagemask').style.visibility = 'visible'; + } + + off () { + gn('pagemask').style.visibility = 'hidden'; + } + + sm (spr) { + var stg = gn('stage'); + var w = spr.outline.width; + var h = spr.outline.height; + var mask = newDiv(gn('stageframe'), stg.offsetLeft + 1, stg.offsetTop + 1, w, h, + { + position: 'absolute', + zIndex: ScratchJr.layerTop + 20, + visibility: 'hidden' + }); + mask.setAttribute('id', 'spritemask'); + mask.appendChild(spr.outline); + } + + son () { + gn('spritemask').style.visibility = 'visible'; + } + + soff () { + gn('spritemask').style.visibility = 'hidden'; + } +} diff --git a/src/editor/engine/Thread.js b/src/editor/engine/Thread.js index 88942c1..6a5f064 100644 --- a/src/editor/engine/Thread.js +++ b/src/editor/engine/Thread.js @@ -1,159 +1,165 @@ -var Thread = function (s, block) { - this.firstBlock = block.findFirst(); - this.thisblock = block; - this.oldblock = null; - this.spr = s; - this.audio = undefined; - this.stack = []; - this.firstTime = true; - this.count = -1; - this.waitTimer = 0; - this.distance = -1; - this.called = []; - this.vector = { - x: 0, - y: 0 - }; - this.isRunning = true; - this.time = 0; // for debugging purposes - return this; -}; +import Prims from './Prims'; +import Grid from '../ui/Grid'; +import Vector from '../../geom/Vector'; -Thread.prototype.clear = function () { - this.stack = []; - this.firstTime = true; - this.count = -1; - this.waitTimer = 0; - this.vector = { - x: 0, - y: 0 - }; - this.distance = -1; - this.called = []; - this.thisblock = this.firstBlock; -}; - -Thread.prototype.duplicate = function () { - var thread = new Thread(this.spr, this.firstBlock); - thread.count = -1; - thread.firstBlock = this.firstBlock; - thread.thisblock = this.thisblock; - thread.oldblock = null; - thread.spr = this.spr; - thread.stack = this.stack; - thread.firstTime = this.firstTime; - thread.vector = { - x: 0, - y: 0 - }; - thread.waitTimer = 0; - thread.distance = -1; - thread.called = this.called; - thread.isRunning = this.isRunning; - return thread; -}; - -Thread.prototype.deselect = function (b) { - while (b != null) { - b.unhighlight(); - if (b.inside) { - b.repeatCounter = -1; - this.deselect(b.inside); - } - b = b.next; +export default class Thread { + constructor (s, block) { + this.firstBlock = block.findFirst(); + this.thisblock = block; + this.oldblock = null; + this.spr = s; + this.audio = undefined; + this.stack = []; + this.firstTime = true; + this.count = -1; + this.waitTimer = 0; + this.distance = -1; + this.called = []; + this.vector = { + x: 0, + y: 0 + }; + this.isRunning = true; + this.time = 0; // for debugging purposes + return this; } -}; -Thread.prototype.stop = function (b) { - this.stopping(b); - this.isRunning = false; -}; - -Thread.prototype.stopping = function (b) { - this.endPrim(b); - this.deselect(this.firstBlock); - this.clear(); - this.spr.closeBalloon(); -}; - -Thread.prototype.endPrim = function (stopMine) { - if (!this.thisblock) { - return; + clear () { + this.stack = []; + this.firstTime = true; + this.count = -1; + this.waitTimer = 0; + this.vector = { + x: 0, + y: 0 + }; + this.distance = -1; + this.called = []; + this.thisblock = this.firstBlock; } - var b = this.thisblock; - var s = this.spr; - switch (b.blocktype) { - case 'down': - case 'back': - case 'forward': - case 'up': - if ((this.distance > -1) && !stopMine) { - var vector = Vector.scale(this.vector, this.distance % 24); - s.setPos(s.xcoor + vector.x, s.ycoor + vector.y); - } - break; - case 'hop': - var count = this.count; - var n = Number(b.getArgValue()); - count--; - if (count > 0) { - var delta = 0; - for (var i = count; i > -1; i--) { - delta += Prims.hopList[count]; - } - this.vector = { - x: 0, - y: delta - }; - var dy = s.ycoor - this.vector.y / 5 * n; - if (dy < 0) { - dy = 0; - } - if (dy >= (360 - Grid.size)) { - dy = (360 - Grid.size); - } - s.setPos(s.xcoor + this.vector.x, dy); - } - break; - case 'playsnd': - if (this.audio) { - this.audio.stop(); - this.audio = undefined; - } - break; - case 'playusersnd': - if (this.audio) { - this.audio.stop(); - this.audio = undefined; - } - break; - case 'hide': - s.div.style.opacity = 0; - if (!this.firstBlock.aStart && !stopMine) { - s.homeshown = false; - } - break; - case 'show': - s.div.style.opacity = 1; - if (!this.firstBlock.aStart && !stopMine) { - s.homeshown = true; - } - break; - case 'same': s.noScaleFor(); - break; - case 'grow': - case 'shrink': - if (!this.firstBlock.aStart && !stopMine) { - s.homescale = s.scale; - } - break; - case 'right': - case 'left': - var angle = s.angle; - if ((angle % 30) != 0) { - angle = (Math.floor(angle / 30) + 1) * 30; - } - s.setHeading(angle); - break; + + duplicate () { + var thread = new Thread(this.spr, this.firstBlock); + thread.count = -1; + thread.firstBlock = this.firstBlock; + thread.thisblock = this.thisblock; + thread.oldblock = null; + thread.spr = this.spr; + thread.stack = this.stack; + thread.firstTime = this.firstTime; + thread.vector = { + x: 0, + y: 0 + }; + thread.waitTimer = 0; + thread.distance = -1; + thread.called = this.called; + thread.isRunning = this.isRunning; + return thread; } -}; + + deselect (b) { + while (b != null) { + b.unhighlight(); + if (b.inside) { + b.repeatCounter = -1; + this.deselect(b.inside); + } + b = b.next; + } + } + + stop (b) { + this.stopping(b); + this.isRunning = false; + } + + stopping (b) { + this.endPrim(b); + this.deselect(this.firstBlock); + this.clear(); + this.spr.closeBalloon(); + } + + endPrim (stopMine) { + if (!this.thisblock) { + return; + } + var b = this.thisblock; + var s = this.spr; + switch (b.blocktype) { + case 'down': + case 'back': + case 'forward': + case 'up': + if ((this.distance > -1) && !stopMine) { + var vector = Vector.scale(this.vector, this.distance % 24); + s.setPos(s.xcoor + vector.x, s.ycoor + vector.y); + } + break; + case 'hop': + var count = this.count; + var n = Number(b.getArgValue()); + count--; + if (count > 0) { + var delta = 0; + for (var i = count; i > -1; i--) { + delta += Prims.hopList[count]; + } + this.vector = { + x: 0, + y: delta + }; + var dy = s.ycoor - this.vector.y / 5 * n; + if (dy < 0) { + dy = 0; + } + if (dy >= (360 - Grid.size)) { + dy = (360 - Grid.size); + } + s.setPos(s.xcoor + this.vector.x, dy); + } + break; + case 'playsnd': + if (this.audio) { + this.audio.stop(); + this.audio = undefined; + } + break; + case 'playusersnd': + if (this.audio) { + this.audio.stop(); + this.audio = undefined; + } + break; + case 'hide': + s.div.style.opacity = 0; + if (!this.firstBlock.aStart && !stopMine) { + s.homeshown = false; + } + break; + case 'show': + s.div.style.opacity = 1; + if (!this.firstBlock.aStart && !stopMine) { + s.homeshown = true; + } + break; + case 'same': s.noScaleFor(); + break; + case 'grow': + case 'shrink': + if (!this.firstBlock.aStart && !stopMine) { + s.homescale = s.scale; + } + break; + case 'right': + case 'left': + var angle = s.angle; + if ((angle % 30) != 0) { + angle = (Math.floor(angle / 30) + 1) * 30; + } + s.setHeading(angle); + break; + } + } +} diff --git a/src/editor/ui/Scripts.js b/src/editor/ui/Scripts.js index 1af3a33..3cb14af 100644 --- a/src/editor/ui/Scripts.js +++ b/src/editor/ui/Scripts.js @@ -2,698 +2,709 @@ // Scripts /////////////////////////////////////////////// -var Scripts = function (spr) { - this.flowCaret = null; - this.spr = spr; - this.dragList = []; - var dc = gn('scriptscontainer'); - this.sc = newHTML('div', 'look', dc); - setCanvasSize(this.sc, dc.offsetWidth, dc.offsetHeight); - this.sc.setAttribute('id', spr.id + '_scripts'); - this.sc.setAttribute('class', 'look'); - this.sc.owner = this; - this.sc.top = 0; - this.sc.left = 0; -}; +import ScratchJr from '../ScratchJr'; +import Block from '../blocks/Block'; +import BlockSpecs from '../blocks/BlockSpecs'; +import ScriptsPane from './ScriptsPane'; +import Events from '../../utils/Events'; +import ScratchAudio from '../../utils/ScratchAudio'; +import {gn, newHTML, setCanvasSize, setProps, + localx, localy, scaleMultiplier, hit3DRect, isTablet} from '../../utils/lib'; -Scripts.prototype.activate = function () { - setProps(this.sc.style, { - visibility: 'visible' - }); -}; - -Scripts.prototype.deactivate = function () { - setProps(this.sc.style, { - visibility: 'hidden' - }); -}; - -//////////////////////////////////////////////// -// Events MouseDown -//////////////////////////////////////////////// - -Scripts.prototype.scriptsMouseDown = function (e) { - if (isTablet && e.touches && (e.touches.length > 1)) { - return; - } - if (ScratchJr.onHold) { - return; - } - if (window.event) { - t = window.event.srcElement; - } else { - t = e.target; - } - if ((t.nodeName == 'H3') && (t.owner == ScratchJr.activeFocus)) { - return; - } // editing the current field - ScratchJr.clearSelection(); - if (t.nodeName == 'H3') { - ScratchJr.blur(); - ScratchJr.editArg(e, t); - return; +export default class Scripts { + constructor (spr) { + this.flowCaret = null; + this.spr = spr; + this.dragList = []; + var dc = gn('scriptscontainer'); + this.sc = newHTML('div', 'look', dc); + setCanvasSize(this.sc, dc.offsetWidth, dc.offsetHeight); + this.sc.setAttribute('id', spr.id + '_scripts'); + this.sc.setAttribute('class', 'look'); + this.sc.owner = this; + this.sc.top = 0; + this.sc.left = 0; } - if (t.firstChild && t.firstChild.nodeName == 'H3') { - ScratchJr.blur(); - ScratchJr.editArg(e, t.firstChild); - return; + activate () { + setProps(this.sc.style, { + visibility: 'visible' + }); } - ScratchJr.unfocus(e); - var sc = ScratchJr.getActiveScript(); - var spt = Events.getTargetPoint(e); - var pt = { - x: localx(sc, spt.x), - y: localy(sc, spt.y) - }; - for (var i = sc.childElementCount - 1; i > -1; i--) { - var ths = sc.childNodes[i]; - if (!ths.owner) { - continue; - } - if (ths.owner.isCaret) { - continue; - } - if (!hit3DRect(ths, pt)) { - continue; - } - var t = new WebKitCSSMatrix(window.getComputedStyle(ths).webkitTransform); - // This line was causing repeat blocks to only drag when touched in the front and top - // It seems to have been checking if the drag was on the invisible shadow of the repeat block - // It's not clear to me why we would want this, and seems functional without it. -- TM - //if ((ths.owner.blocktype == "repeat") && !hitTest(ths.childNodes[1], pixel)) continue; - Events.startDrag(e, ths, ScriptsPane.prepareToDrag, - ScriptsPane.dropBlock, ScriptsPane.draggingBlock, ScriptsPane.runBlock); - return; + deactivate () { + setProps(this.sc.style, { + visibility: 'hidden' + }); } - ScriptsPane.dragBackground(e); -}; -//////////////////////////////////////////////// -// Events MouseUP -//////////////////////////////////////////////// + //////////////////////////////////////////////// + // Events MouseDown + //////////////////////////////////////////////// -Scripts.prototype.addBlockToScripts = function (b, dx, dy) { - if ((this.flowCaret != null) && (this.flowCaret.div.parentNode == this.sc)) { - this.sc.removeChild(this.flowCaret.div); - } - this.flowCaret = null; - Events.dragDiv.removeChild(b); - this.sc.appendChild(b); - // b.owner.drop(); - b.owner.moveBlock(dx, dy); - for (var i = 1; i < this.dragList.length; i++) { - var piece = this.dragList[i].div; - piece.parentNode.removeChild(piece); - this.sc.appendChild(piece); - // piece.owner.drop(); - } - this.layout(b.owner); - this.snapToPlace(this.dragList); - if (b.owner.cShape) { - this.sendToBack(b.owner); - } - this.dragList = []; -}; - - -Scripts.prototype.sendToBack = function (b) { - if (!b.inside) { - return; - } - var you = b.inside; - while (you != null) { - var p = you.div.parentNode; - p.appendChild(you.div); - if (you.cShape) { - this.sendToBack(you); - } - you = you.next; - } - this.layout(b); -}; - -Scripts.prototype.snapToPlace = function (drag) { - if ((drag.length < 2) && drag[0].cShape) { - this.snapCshape(drag); - } else { - this.snapBlock(drag); - } -}; - -Scripts.prototype.snapBlock = function (drag) { - var me = drag[0]; - var last = me.findLast(); - var res = this.findClosest(this.available(0, me, drag), me); - if (this.isValid(me, res, 0)) { - this.snapToDock(res, me, 0, drag); - return; - } - res = this.findClosest(this.available(last.cShape ? 2 : 1, last, drag), last); - if (!this.isValid(last, res, last.cShape ? 2 : 1)) { - return; - } - this.snapToDock(res, last, last.cShape ? 2 : 1, drag); -}; - -Scripts.prototype.snapCshape = function (drag) { - var me = drag[0]; - var last = me.findLast(); - var res = this.findClosest(this.available(0, me, drag), me); - if (this.isValid(me, res, 0)) { - this.snapToDock(res, me, 0, drag); - return; - } - var allowInside = me.isCaret ? (this.dragList[0].inside == null) : (me.inside == null); - if (allowInside) { - res = this.findClosest(this.available(1, me, drag), me); - if (this.isValid(me, res, 1)) { - this.snapToDock(res, me, 1, drag); + scriptsMouseDown (e) { + if (isTablet && e.touches && (e.touches.length > 1)) { + return; + } + if (ScratchJr.onHold) { + return; + } + if (window.event) { + t = window.event.srcElement; + } else { + t = e.target; + } + if ((t.nodeName == 'H3') && (t.owner == ScratchJr.activeFocus)) { + return; + } // editing the current field + ScratchJr.clearSelection(); + if (t.nodeName == 'H3') { + ScratchJr.blur(); + ScratchJr.editArg(e, t); return; } - } - res = this.findClosest(this.available(2, last, drag), last); - if (this.isValid(me, res, 2)) { - this.snapToDock(res, last, 2, drag); - } -}; -Scripts.prototype.isValid = function (me, res, myn) { - if (res == null) { - return false; + if (t.firstChild && t.firstChild.nodeName == 'H3') { + ScratchJr.blur(); + ScratchJr.editArg(e, t.firstChild); + return; + } + + ScratchJr.unfocus(e); + var sc = ScratchJr.getActiveScript(); + var spt = Events.getTargetPoint(e); + var pt = { + x: localx(sc, spt.x), + y: localy(sc, spt.y) + }; + for (var i = sc.childElementCount - 1; i > -1; i--) { + var ths = sc.childNodes[i]; + if (!ths.owner) { + continue; + } + if (ths.owner.isCaret) { + continue; + } + if (!hit3DRect(ths, pt)) { + continue; + } + var t = new WebKitCSSMatrix(window.getComputedStyle(ths).webkitTransform); + // This line was causing repeat blocks to only drag when touched in the front and top + // It seems to have been checking if the drag was on the invisible shadow of the repeat block + // It's not clear to me why we would want this, and seems functional without it. -- TM + //if ((ths.owner.blocktype == "repeat") && !hitTest(ths.childNodes[1], pixel)) continue; + Events.startDrag(e, ths, ScriptsPane.prepareToDrag, + ScriptsPane.dropBlock, ScriptsPane.draggingBlock, ScriptsPane.runBlock); + return; + } + ScriptsPane.dragBackground(e); } - var you = res[0]; - var yourn = res[1]; - if (res[2] > 30) { - return false; + + //////////////////////////////////////////////// + // Events MouseUP + //////////////////////////////////////////////// + + addBlockToScripts (b, dx, dy) { + if ((this.flowCaret != null) && (this.flowCaret.div.parentNode == this.sc)) { + this.sc.removeChild(this.flowCaret.div); + } + this.flowCaret = null; + Events.dragDiv.removeChild(b); + this.sc.appendChild(b); + // b.owner.drop(); + b.owner.moveBlock(dx, dy); + for (var i = 1; i < this.dragList.length; i++) { + var piece = this.dragList[i].div; + piece.parentNode.removeChild(piece); + this.sc.appendChild(piece); + // piece.owner.drop(); + } + this.layout(b.owner); + this.snapToPlace(this.dragList); + if (b.owner.cShape) { + this.sendToBack(b.owner); + } + this.dragList = []; } - if (me.cShape && (myn == 1) && you.anEnd) { - return false; - } - if (me.anEnd && (you.next != null)) { - return false; - } - if (me.findFirst().aStart && (you.prev != null)) { - return false; - } // a strip starting with a start cannot be inserted between 2 blocks - if ((myn == 0) && me.findLast().anEnd && - (((you.blocktype == 'repeat') && (yourn == 1)) || this.insideCShape(you))) { - return false; - } - if (me.findLast().anEnd && (you.next != null)) { - return false; - } - if (me.findLast().anEnd && you.findLast().anEnd) { - return false; - } - return true; -}; + sendToBack (b) { + if (!b.inside) { + return; + } + var you = b.inside; + while (you != null) { + var p = you.div.parentNode; + p.appendChild(you.div); + if (you.cShape) { + this.sendToBack(you); + } + you = you.next; + } + this.layout(b); + } -Scripts.prototype.insideCShape = function (you) { - while (you != null) { - var next = you.prev; - if (next == null) { + snapToPlace (drag) { + if ((drag.length < 2) && drag[0].cShape) { + this.snapCshape(drag); + } else { + this.snapBlock(drag); + } + } + + snapBlock (drag) { + var me = drag[0]; + var last = me.findLast(); + var res = this.findClosest(this.available(0, me, drag), me); + if (this.isValid(me, res, 0)) { + this.snapToDock(res, me, 0, drag); + return; + } + res = this.findClosest(this.available(last.cShape ? 2 : 1, last, drag), last); + if (!this.isValid(last, res, last.cShape ? 2 : 1)) { + return; + } + this.snapToDock(res, last, last.cShape ? 2 : 1, drag); + } + + snapCshape (drag) { + var me = drag[0]; + var last = me.findLast(); + var res = this.findClosest(this.available(0, me, drag), me); + if (this.isValid(me, res, 0)) { + this.snapToDock(res, me, 0, drag); + return; + } + var allowInside = me.isCaret ? (this.dragList[0].inside == null) : (me.inside == null); + if (allowInside) { + res = this.findClosest(this.available(1, me, drag), me); + if (this.isValid(me, res, 1)) { + this.snapToDock(res, me, 1, drag); + return; + } + } + res = this.findClosest(this.available(2, last, drag), last); + if (this.isValid(me, res, 2)) { + this.snapToDock(res, last, 2, drag); + } + } + + isValid (me, res, myn) { + if (res == null) { return false; } - var docknum = next.getMyDockNum(you); - if (next.cShape && (docknum == 1)) { - return true; + var you = res[0]; + var yourn = res[1]; + if (res[2] > 30) { + return false; } - you = next; + if (me.cShape && (myn == 1) && you.anEnd) { + return false; + } + if (me.anEnd && (you.next != null)) { + return false; + } + if (me.findFirst().aStart && (you.prev != null)) { + return false; + } // a strip starting with a start cannot be inserted between 2 blocks + if ((myn == 0) && me.findLast().anEnd && + (((you.blocktype == 'repeat') && (yourn == 1)) || this.insideCShape(you))) { + return false; + } + if (me.findLast().anEnd && (you.next != null)) { + return false; + } + if (me.findLast().anEnd && you.findLast().anEnd) { + return false; + } + return true; } - return false; -}; -Scripts.prototype.snapToDock = function (choice, me, place, drag) { - if (choice == null) { - return; - } - if (me.blocktype.indexOf('caret') < 0) { - ScratchJr.storyStart('Scripts.snapToDock'); - ScratchAudio.sndFX('snap.wav'); - } - var you = choice[0]; - var yourn = choice[1]; - var bestxy; - if (me.cShape && (place == 1)) { - var res = this.getDockDxDy(you, yourn, me, place); - bestxy = [res[0], res[1]]; - } else { - bestxy = this.getDockDxDy(you, yourn, me, place); - } - if (me.isCaret) { - me.div.style.visibility = 'visible'; - } - for (var i = 0; i < drag.length; i++) { - drag[i].moveBlock(drag[i].div.left + bestxy[0], drag[i].div.top + bestxy[1]); - } - me.connectBlock(place, choice[0], choice[1]); -}; -Scripts.prototype.available = function (myn, me, drag) { - var thisxy = null; - var res = []; - var you = null; - var allblocks = this.getBlocks(); - for (var i = 0; i < allblocks.length; i++) { - you = allblocks[i]; - if (you == null) { - continue; + + insideCShape (you) { + while (you != null) { + var next = you.prev; + if (next == null) { + return false; + } + var docknum = next.getMyDockNum(you); + if (next.cShape && (docknum == 1)) { + return true; + } + you = next; } - if (you == me) { - continue; + return false; + } + + snapToDock (choice, me, place, drag) { + if (choice == null) { + return; } - if (you.isCaret) { - continue; + if (me.blocktype.indexOf('caret') < 0) { + ScratchJr.storyStart('Scripts.snapToDock'); + ScratchAudio.sndFX('snap.wav'); } - if (you.isReporter) { - continue; + var you = choice[0]; + var yourn = choice[1]; + var bestxy; + if (me.cShape && (place == 1)) { + var res = this.getDockDxDy(you, yourn, me, place); + bestxy = [res[0], res[1]]; + } else { + bestxy = this.getDockDxDy(you, yourn, me, place); } - if (you.div.style.visibility == 'hidden') { - continue; + if (me.isCaret) { + me.div.style.visibility = 'visible'; } - if (drag.indexOf(you) == -1) { - var yourdocks = you.resolveDocks(); - for (var yourn = 0; yourn < yourdocks.length; yourn++) { - thisxy = this.getDockDxDy(you, yourn, me, myn); - if (thisxy != null) { - res.push([you, yourn, this.magnitude(thisxy)]); + for (var i = 0; i < drag.length; i++) { + drag[i].moveBlock(drag[i].div.left + bestxy[0], drag[i].div.top + bestxy[1]); + } + me.connectBlock(place, choice[0], choice[1]); + } + + available (myn, me, drag) { + var thisxy = null; + var res = []; + var you = null; + var allblocks = this.getBlocks(); + for (var i = 0; i < allblocks.length; i++) { + you = allblocks[i]; + if (you == null) { + continue; + } + if (you == me) { + continue; + } + if (you.isCaret) { + continue; + } + if (you.isReporter) { + continue; + } + if (you.div.style.visibility == 'hidden') { + continue; + } + if (drag.indexOf(you) == -1) { + var yourdocks = you.resolveDocks(); + for (var yourn = 0; yourn < yourdocks.length; yourn++) { + thisxy = this.getDockDxDy(you, yourn, me, myn); + if (thisxy != null) { + res.push([you, yourn, this.magnitude(thisxy)]); + } } } } + return res; } - return res; -}; -Scripts.prototype.magnitude = function (p) { - var x = p[0]; - var y = p[1]; - return Math.sqrt((x * x) + (y * y)); -}; + magnitude (p) { + var x = p[0]; + var y = p[1]; + return Math.sqrt((x * x) + (y * y)); + } -Scripts.prototype.findClosest = function (choices) { - var min = 9999; - var item = null; - for (var i = 0; i < choices.length; i++) { - var c = choices[i]; - if (c[2] < min) { - min = c[2]; - item = c; + findClosest (choices) { + var min = 9999; + var item = null; + for (var i = 0; i < choices.length; i++) { + var c = choices[i]; + if (c[2] < min) { + min = c[2]; + item = c; + } + } + return item; + } + + getDockDxDy (b1, n1, b2, n2) { + var d1 = (b1.resolveDocks())[n1]; + var d2 = (b2.resolveDocks())[n2]; + if (b1 == b2) { + return null; + } // same block + if ((d1 == null) || (d2 == null)) { + return null; + } // no block + if (d1[0] != d2[0]) { + return null; + } // not the same type of notch like "flow" + if (d1[1] == d2[1]) { + return null; + } // not an "inny" with and "outie" (both true) + var x1 = b1.div.left + d1[2] * b1.scale; + var y1 = b1.div.top + d1[3] * b1.scale; + var x2 = b2.div.left + d2[2] * b2.scale; + var y2 = b2.div.top + d2[3] * b2.scale; + return [x1 - x2, y1 - y2]; + } + + layout (block) { + var first = block.findFirst(); + this.layoutStrip(first); + } + + layoutStrip (b) { + while (b != null) { + if (b.cShape) { + this.layoutCshape(b); + } + this.layoutNextBlock(b); + b = b.next; } } - return item; -}; -Scripts.prototype.getDockDxDy = function (b1, n1, b2, n2) { - var d1 = (b1.resolveDocks())[n1]; - var d2 = (b2.resolveDocks())[n2]; - if (b1 == b2) { - return null; - } // same block - if ((d1 == null) || (d2 == null)) { - return null; - } // no block - if (d1[0] != d2[0]) { - return null; - } // not the same type of notch like "flow" - if (d1[1] == d2[1]) { - return null; - } // not an "inny" with and "outie" (both true) - var x1 = b1.div.left + d1[2] * b1.scale; - var y1 = b1.div.top + d1[3] * b1.scale; - var x2 = b2.div.left + d2[2] * b2.scale; - var y2 = b2.div.top + d2[3] * b2.scale; - return [x1 - x2, y1 - y2]; -}; - -Scripts.prototype.layout = function (block) { - var first = block.findFirst(); - this.layoutStrip(first); -}; - -Scripts.prototype.layoutStrip = function (b) { - while (b != null) { - if (b.cShape) { - this.layoutCshape(b); + layoutNextBlock (b) { + if (b.next != null) { + var you = b.next; + var bestxy = this.getDockDxDy(b, b.cShape ? 2 : 1, you, 0); + if (bestxy == null) { + return; + } + you.moveBlock(you.div.left + bestxy[0], you.div.top + bestxy[1]); } - this.layoutNextBlock(b); + } + + layoutCshape (b) { + var inside = 0; + var maxh = 0; + var oldh = b.hrubberband; + var cblock = b.inside; + if (cblock != null) { + this.adjustPos(cblock, 0, b, 1); + this.layoutStrip(cblock); + inside += this.adjustCinside(cblock); + maxh += this.adjustCheight(cblock); + } + oldh = b.vrubberband; + b.vrubberband = (maxh < 0) ? 0 : maxh; + b.hrubberband = inside; + b.redrawRepeat(); + b.moveBlock(b.div.left, b.div.top + (oldh - b.vrubberband) * b.scale); + } + + adjustPos (me, myn, you, yourn) { + var bestxy = this.getDockDxDy(you, yourn, me, myn); + me.moveBlock(me.div.left + bestxy[0], me.div.top + bestxy[1]); + } + + adjustCheight (b) { + var old = b; + var h = b.blockshape.height; b = b.next; + while (b != null) { + if (b.blockshape.height > h) { + h = b.blockshape.height; + } + b = b.next; + } + h /= (old.scale * window.devicePixelRatio); + return (h > 66) ? h - 66 : 0; } -}; -Scripts.prototype.layoutNextBlock = function (b) { - if (b.next != null) { - var you = b.next; - var bestxy = this.getDockDxDy(b, b.cShape ? 2 : 1, you, 0); - if (bestxy == null) { + adjustCinside (b) { + var first = b; + var last = b; + while (b != null) { + last = b; + b = b.next; + } + var w = last.blockshape.width / last.scale / window.devicePixelRatio + + (last.div.left - first.div.left) / last.scale; + return w - (last.cShape ? 76 : 76); + } + + getBlocks () { + var res = []; + var sc = this.sc; + for (var i = 0; i < sc.childElementCount; i++) { + var b = sc.childNodes[i].owner; + if (!b) { + continue; + } + if (b.type != 'block') { + continue; + } + if (b.isCaret) { + continue; + } + res.push(b); + } + return res; + } + + findGroup (b) { + if (b.type != 'block') { + return []; + } + var res = []; + return this.findingGroup(res, b); + } + + findingGroup (res, b) { + while (b != null) { + res.push(b); + if (b.cShape) { + this.findingGroup(res, b.inside); + } + b = b.next; + } + return res; + } + + gettopblocks () { + var list = this.getBlocks(); + var res = []; + for (var n = 0; n < list.length; n++) { + if ((list[n].prev == null) && !list[n].isReporter) { + res.push(list[n]); + } + if ((list[n].isReporter) && (list[n].daddy = null)) { + res.push(list[n]); + } + } + return res; + } + + // A version of gettopblocks that also returns strips which + // may be currently starting with a caret and blocks in the dragDiv + getEncodableBlocks () { + var list = []; + var sc = this.sc; + for (var i =0; i < sc.childElementCount; i++){ + var b = sc.childNodes[i].owner; + if (!b || b.type != 'block') { + continue; + } + list.push(b); + } + + var res = []; + for (var n = 0; n < list.length; n++) { + if (list[n].prev == null) res.push(list[n]); + } + return res; + } + + redisplay () { + var list = this.gettopblocks(); + for (var n = 0; n < list.length; n++) { + this.layout(list[n]); + } + } + + getBlocksType (list) { + var res = []; + var blocks = this.getBlocks(); + for (var i = 0; i < list.length; i++) { + var key = list[i]; + for (var n = 0; n < blocks.length; n++) { + if (key == blocks[n].blocktype) { + res.push(blocks[n]); + } + } + } + return res; + } + + prepareCaret (b) { // Block data structure + var last = b.findLast(); + var bt = this.getCaretType(last); + if (this.flowCaret != null) { + this.sc.removeChild(this.flowCaret.div); + } + this.flowCaret = null; + if (bt == null) { + return; + } // don't have a caret + this.flowCaret = this.newCaret(bt); + this.flowCaret.isCaret = true; + } + + newCaret (bt) { // Block data structure + var parent = this.sc; + var bbx = new Block(BlockSpecs.defs[bt], false, scaleMultiplier); + setProps(bbx.div.style, { + position: 'absolute', + left: '0px', + top: '0px', + visibility: 'hidden', + zIndex: 10 + }); + parent.appendChild(bbx.div); + bbx.moveBlock(0, 0); + return bbx; + } + + //////////////////////////////////////////// + // Caret + /////////////////////////////////////////// + + getCaretType (b) { + if (this.dragList[0].aStart) { + return 'caretstart'; + } + if (b.anEnd) { + return 'caretend'; + } + if ((this.dragList.length < 2) && this.dragList[0].cShape) { + return 'caretrepeat'; + } + return 'caretcmd'; + } + + //////////////////////////////////////////////// + // Events MouseMove + //////////////////////////////////////////////// + + removeCaret () { + if (this.flowCaret == null) { return; } - you.moveBlock(you.div.left + bestxy[0], you.div.top + bestxy[1]); - } -}; - -Scripts.prototype.layoutCshape = function (b) { - var inside = 0; - var maxh = 0; - var oldh = b.hrubberband; - var cblock = b.inside; - if (cblock != null) { - this.adjustPos(cblock, 0, b, 1); - this.layoutStrip(cblock); - inside += this.adjustCinside(cblock); - maxh += this.adjustCheight(cblock); - } - oldh = b.vrubberband; - b.vrubberband = (maxh < 0) ? 0 : maxh; - b.hrubberband = inside; - b.redrawRepeat(); - b.moveBlock(b.div.left, b.div.top + (oldh - b.vrubberband) * b.scale); -}; - -Scripts.prototype.adjustPos = function (me, myn, you, yourn) { - var bestxy = this.getDockDxDy(you, yourn, me, myn); - me.moveBlock(me.div.left + bestxy[0], me.div.top + bestxy[1]); -}; - -Scripts.prototype.adjustCheight = function (b) { - var old = b; - var h = b.blockshape.height; - b = b.next; - while (b != null) { - if (b.blockshape.height > h) { - h = b.blockshape.height; - } - b = b.next; - } - h /= (old.scale * window.devicePixelRatio); - return (h > 66) ? h - 66 : 0; -}; - -Scripts.prototype.adjustCinside = function (b) { - var first = b; - var last = b; - while (b != null) { - last = b; - b = b.next; - } - var w = last.blockshape.width / last.scale / window.devicePixelRatio + - (last.div.left - first.div.left) / last.scale; - return w - (last.cShape ? 76 : 76); -}; - -Scripts.prototype.getBlocks = function () { - var res = []; - var sc = this.sc; - for (var i = 0; i < sc.childElementCount; i++) { - var b = sc.childNodes[i].owner; - if (!b) { - continue; - } - if (b.type != 'block') { - continue; - } - if (b.isCaret) { - continue; - } - res.push(b); - } - return res; -}; - -Scripts.prototype.findGroup = function (b) { - if (b.type != 'block') { - return []; - } - var res = []; - return this.findingGroup(res, b); -}; - -Scripts.prototype.findingGroup = function (res, b) { - while (b != null) { - res.push(b); - if (b.cShape) { - this.findingGroup(res, b.inside); - } - b = b.next; - } - return res; -}; - -Scripts.prototype.gettopblocks = function () { - var list = this.getBlocks(); - var res = []; - for (var n = 0; n < list.length; n++) { - if ((list[n].prev == null) && !list[n].isReporter) { - res.push(list[n]); - } - if ((list[n].isReporter) && (list[n].daddy = null)) { - res.push(list[n]); - } - } - return res; -}; - -// A version of gettopblocks that also returns strips which -// may be currently starting with a caret and blocks in the dragDiv -Scripts.prototype.getEncodableBlocks = function () { - var list = []; - var sc = this.sc; - for (var i =0; i < sc.childElementCount; i++){ - var b = sc.childNodes[i].owner; - if (!b || b.type != 'block') { - continue; - } - list.push(b); - } - - var res = []; - for (var n = 0; n < list.length; n++) { - if (list[n].prev == null) res.push(list[n]); - } - return res; -}; - -Scripts.prototype.redisplay = function () { - var list = this.gettopblocks(); - for (var n = 0; n < list.length; n++) { - this.layout(list[n]); - } -}; - -Scripts.prototype.getBlocksType = function (list) { - var res = []; - var blocks = this.getBlocks(); - for (var i = 0; i < list.length; i++) { - var key = list[i]; - for (var n = 0; n < blocks.length; n++) { - if (key == blocks[n].blocktype) { - res.push(blocks[n]); - } - } - } - return res; -}; - -Scripts.prototype.prepareCaret = function (b) { // Block data structure - var last = b.findLast(); - var bt = this.getCaretType(last); - if (this.flowCaret != null) { - this.sc.removeChild(this.flowCaret.div); - } - this.flowCaret = null; - if (bt == null) { - return; - } // don't have a caret - this.flowCaret = this.newCaret(bt); - this.flowCaret.isCaret = true; -}; - -Scripts.prototype.newCaret = function (bt) { // Block data structure - var parent = this.sc; - var bbx = new Block(BlockSpecs.defs[bt], false, scaleMultiplier); - setProps(bbx.div.style, { - position: 'absolute', - left: '0px', - top: '0px', - visibility: 'hidden', - zIndex: 10 - }); - parent.appendChild(bbx.div); - bbx.moveBlock(0, 0); - return bbx; -}; - -//////////////////////////////////////////// -// Caret -/////////////////////////////////////////// - -Scripts.prototype.getCaretType = function (b) { - if (this.dragList[0].aStart) { - return 'caretstart'; - } - if (b.anEnd) { - return 'caretend'; - } - if ((this.dragList.length < 2) && this.dragList[0].cShape) { - return 'caretrepeat'; - } - return 'caretcmd'; -}; - -//////////////////////////////////////////////// -// Events MouseMove -//////////////////////////////////////////////// - -Scripts.prototype.removeCaret = function () { - if (this.flowCaret == null) { - return; - } - var before = this.flowCaret.prev; - var after = this.flowCaret.next; - var inside = this.flowCaret.inside; - this.flowCaret.prev = null; - this.flowCaret.next = null; - this.flowCaret.inside = null; - var n; - if (after != null) { - n = after.getMyDockNum(this.flowCaret); - after.setMyDock(n, (inside != null) ? inside.findLast() : before); - if ((inside == null) && (before == null)) { - this.layout(after); - } - } - if (inside != null) { - n = inside.getMyDockNum(this.flowCaret); - inside.setMyDock(n, before); + var before = this.flowCaret.prev; + var after = this.flowCaret.next; + var inside = this.flowCaret.inside; + this.flowCaret.prev = null; + this.flowCaret.next = null; + this.flowCaret.inside = null; + var n; if (after != null) { - inside.findLast().next = after; - } - if (before == null) { - this.layout(inside); - } - } - if (before != null) { - n = before.getMyDockNum(this.flowCaret); - before.setMyDock(n, (inside != null) ? inside : after); - this.layout(before); - } - if (this.flowCaret.cShape) { - this.flowCaret.vrubberband = 0; - this.flowCaret.hrubberband = 0; - this.flowCaret.redrawRepeat(); - } - this.flowCaret.div.style.visibility = 'hidden'; -}; - -Scripts.prototype.insertCaret = function (x, y) { - if (this.flowCaret == null) { - return; - } - var sc = ScratchJr.getActiveScript(); - var dx = localx(sc, x); - var dy = localy(sc, y) + this.adjustCheight(this.dragList[0]); - this.flowCaret.moveBlock(dx, dy); - this.snapToPlace(new Array(this.flowCaret)); - if (this.flowCaret.div.style.visibility == 'visible') { - this.layout(this.flowCaret); - } -}; - -Scripts.prototype.deleteBlocks = function () { - ScratchJr.storyStart('Scripts.prototype.deleteBlocks'); - ScriptsPane.cleanCarets(); - ScratchAudio.sndFX('cut.wav'); - if (this.dragList.length > 0) { - ScratchJr.runtime.stopThreadBlock(this.dragList[0].findFirst()); - } - for (var i = 0; i < this.dragList.length; i++) { - var b = this.dragList[i]; - if (b.blocktype == undefined) { - continue; - } - b.div.parentNode.removeChild(b.div); - } -}; - -Scripts.prototype.recreateStrip = function (list) { - var res = []; - var b = null; - var loops = ['repeat']; - for (var i = 0; i < list.length; i++) { - if (!BlockSpecs.defs[list[i][0]]) { - continue; - } - switch (list[i][0]) { - case 'say': - list[i][1] = unescape(list[i][1]); - break; - case 'gotopage': - var n = ScratchJr.stage.pages.indexOf(this.spr.page); - if ((list[i][1] - 1) == n) { - list[i][1] = ((n + 1) % ScratchJr.stage.pages.length) + 1; + n = after.getMyDockNum(this.flowCaret); + after.setMyDock(n, (inside != null) ? inside.findLast() : before); + if ((inside == null) && (before == null)) { + this.layout(after); } - break; - case 'playusersnd': - if (this.spr.sounds.length <= list[i][1]) { - list[i][0] = 'playsnd'; - list[i][1] = this.spr.sounds[0]; - } - break; - case 'playsnd': - var snd = this.spr.sounds.indexOf(list[i][1]); - if (snd < 0) { - list[i][0] = 'playsnd'; - list[i][1] = this.spr.sounds[0]; - } - break; } - var cb = this.recreateBlock(list[i]); - res.push(cb); - if (loops.indexOf(cb.blocktype) > -1) { - var strip = this.recreateStrip(list[i][4]); - if (strip.length > 0) { - cb.inside = strip[0]; - strip[0].prev = cb; + if (inside != null) { + n = inside.getMyDockNum(this.flowCaret); + inside.setMyDock(n, before); + if (after != null) { + inside.findLast().next = after; + } + if (before == null) { + this.layout(inside); } - cb.redrawRepeat(); } - if (b) { - cb.prev = b; - b.next = cb; + if (before != null) { + n = before.getMyDockNum(this.flowCaret); + before.setMyDock(n, (inside != null) ? inside : after); + this.layout(before); } - b = cb; + if (this.flowCaret.cShape) { + this.flowCaret.vrubberband = 0; + this.flowCaret.hrubberband = 0; + this.flowCaret.redrawRepeat(); + } + this.flowCaret.div.style.visibility = 'hidden'; } - if (res.length > 0) { - this.layout(res[0]); + + insertCaret (x, y) { + if (this.flowCaret == null) { + return; + } + var sc = ScratchJr.getActiveScript(); + var dx = localx(sc, x); + var dy = localy(sc, y) + this.adjustCheight(this.dragList[0]); + this.flowCaret.moveBlock(dx, dy); + this.snapToPlace(new Array(this.flowCaret)); + if (this.flowCaret.div.style.visibility == 'visible') { + this.layout(this.flowCaret); + } + } + + deleteBlocks () { + ScratchJr.storyStart('Scripts.prototype.deleteBlocks'); + ScriptsPane.cleanCarets(); + ScratchAudio.sndFX('cut.wav'); + if (this.dragList.length > 0) { + ScratchJr.runtime.stopThreadBlock(this.dragList[0].findFirst()); + } + for (var i = 0; i < this.dragList.length; i++) { + var b = this.dragList[i]; + if (b.blocktype == undefined) { + continue; + } + b.div.parentNode.removeChild(b.div); + } + } + + recreateStrip (list) { + var res = []; + var b = null; + var loops = ['repeat']; + for (var i = 0; i < list.length; i++) { + if (!BlockSpecs.defs[list[i][0]]) { + continue; + } + switch (list[i][0]) { + case 'say': + list[i][1] = unescape(list[i][1]); + break; + case 'gotopage': + var n = ScratchJr.stage.pages.indexOf(this.spr.page); + if ((list[i][1] - 1) == n) { + list[i][1] = ((n + 1) % ScratchJr.stage.pages.length) + 1; + } + break; + case 'playusersnd': + if (this.spr.sounds.length <= list[i][1]) { + list[i][0] = 'playsnd'; + list[i][1] = this.spr.sounds[0]; + } + break; + case 'playsnd': + var snd = this.spr.sounds.indexOf(list[i][1]); + if (snd < 0) { + list[i][0] = 'playsnd'; + list[i][1] = this.spr.sounds[0]; + } + break; + } + var cb = this.recreateBlock(list[i]); + res.push(cb); + if (loops.indexOf(cb.blocktype) > -1) { + var strip = this.recreateStrip(list[i][4]); + if (strip.length > 0) { + cb.inside = strip[0]; + strip[0].prev = cb; + } + cb.redrawRepeat(); + } + if (b) { + cb.prev = b; + b.next = cb; + } + b = cb; + } + if (res.length > 0) { + this.layout(res[0]); + } + return res; } - return res; -}; -///////////////////////////////// -// Load -//////////////////////////////// + ///////////////////////////////// + // Load + //////////////////////////////// -Scripts.prototype.recreateBlock = function (data) { - var op = data[0]; - var val = data[1] == 'null' ? null : data[1]; - var dx = data[2]; - var dy = data[3]; - var spec = BlockSpecs.defs[op].concat(); - if (val != null) { - spec.splice(4, 1, val); + recreateBlock (data) { + var op = data[0]; + var val = data[1] == 'null' ? null : data[1]; + var dx = data[2]; + var dy = data[3]; + var spec = BlockSpecs.defs[op].concat(); + if (val != null) { + spec.splice(4, 1, val); + } + var bbx = new Block(spec, false, scaleMultiplier); + setProps(bbx.div.style, { + position: 'absolute', + left: '0px', + top: '0px' + }); + bbx.moveBlock(dx * scaleMultiplier, dy * scaleMultiplier); + this.sc.appendChild(bbx.div); + bbx.update(this.spr); + return bbx; } - var bbx = new Block(spec, false, scaleMultiplier); - setProps(bbx.div.style, { - position: 'absolute', - left: '0px', - top: '0px' - }); - bbx.moveBlock(dx * scaleMultiplier, dy * scaleMultiplier); - this.sc.appendChild(bbx.div); - bbx.update(this.spr); - return bbx; -}; +} diff --git a/src/editor/ui/Scroll.js b/src/editor/ui/Scroll.js index fe1a4b2..179ad74 100644 --- a/src/editor/ui/Scroll.js +++ b/src/editor/ui/Scroll.js @@ -2,481 +2,486 @@ // Scrolling Pane //////////////////////////////////////////////// -var Scroll = function (div, id, w, h, cfcn, ofcn) { - this.hasHorizontal = true; - this.hasVertical = true; - this.arrowDistance = 6; - this.aleft = undefined; - this.aright = undefined; - this.aup = undefined; - this.adown = undefined; - this.contents = newDiv(div, 0, 0, w, h, {}); - this.contents.setAttribute('id', id); - this.contents.owner = this; - this.addArrows(div, w, h); - this.getContent = cfcn; - this.getObjects = ofcn; - div.scroll = this; // for now; -}; +import Events from '../../utils/Events'; +import {newDiv, newHTML, CSSTransition3D, isTablet, setCanvasSize} from '../../utils/lib'; -Scroll.prototype.update = function () { - this.adjustCanvas(); - this.refresh(); - this.bounceBack(); -}; - -///////////////////////////////////////////////////////////// -// Arrows -//////////////////////////////////////////////////////////// - -Scroll.prototype.addArrows = function (sc, w, h) { - this.aleft = newHTML('div', 'leftarrow', sc); - this.aleft.style.height = h + 'px'; - var larrow = newHTML('span', undefined, this.aleft); - larrow.style.top = Math.floor((h - larrow.offsetHeight) / 2) + 'px'; - - this.aright = newHTML('div', 'rightarrow', sc); - this.aright.style.height = h + 'px'; - var rarrow = newHTML('span', undefined, this.aright); - rarrow.style.top = Math.floor((h - rarrow.offsetHeight) / 2) + 'px'; - - this.aup = newHTML('div', 'toparrow', sc); - this.adown = newHTML('div', 'bottomarrow', sc); - newHTML('div', 'halign up', this.aup); - newHTML('div', 'halign down', this.adown); - - var me = this; - if (isTablet) { - this.aup.ontouchstart = function (e) { - me.scrolldown(e); - }; - } else { - this.aup.onmousedown = function (e) { - me.scrolldown(e); - }; +export default class Scroll { + constructor (div, id, w, h, cfcn, ofcn) { + this.hasHorizontal = true; + this.hasVertical = true; + this.arrowDistance = 6; + this.aleft = undefined; + this.aright = undefined; + this.aup = undefined; + this.adown = undefined; + this.contents = newDiv(div, 0, 0, w, h, {}); + this.contents.setAttribute('id', id); + this.contents.owner = this; + this.addArrows(div, w, h); + this.getContent = cfcn; + this.getObjects = ofcn; + div.scroll = this; // for now; } - if (isTablet) { - this.adown.ontouchstart = function (e) { - me.scrollup(e); - }; - } else { - this.adown.onmousedown = function (e) { - me.scrollup(e); - }; + update () { + this.adjustCanvas(); + this.refresh(); + this.bounceBack(); } - if (isTablet) { - this.aleft.ontouchstart = function (e) { - me.scrollright(e); - }; - } else { - this.aleft.onmousedown = function (e) { - me.scrollright(e); - }; - } + ///////////////////////////////////////////////////////////// + // Arrows + //////////////////////////////////////////////////////////// - if (isTablet) { - this.aright.ontouchstart = function (e) { - me.scrollleft(e); - }; - } else { - this.aright.onmousedown = function (e) { - me.scrollleft(e); - }; - } + addArrows (sc, w, h) { + this.aleft = newHTML('div', 'leftarrow', sc); + this.aleft.style.height = h + 'px'; + var larrow = newHTML('span', undefined, this.aleft); + larrow.style.top = Math.floor((h - larrow.offsetHeight) / 2) + 'px'; -}; + this.aright = newHTML('div', 'rightarrow', sc); + this.aright.style.height = h + 'px'; + var rarrow = newHTML('span', undefined, this.aright); + rarrow.style.top = Math.floor((h - rarrow.offsetHeight) / 2) + 'px'; -///////////////////////////////////////////////////////////// -// Scrolling -//////////////////////////////////////////////////////////// + this.aup = newHTML('div', 'toparrow', sc); + this.adown = newHTML('div', 'bottomarrow', sc); + newHTML('div', 'halign up', this.aup); + newHTML('div', 'halign down', this.adown); -Scroll.prototype.repositionArrows = function (h) { - this.aleft.style.height = h + 'px'; - this.aleft.childNodes[0].style.top = Math.floor((h - this.aleft.childNodes[0].offsetHeight) / 2) + 'px'; - this.aright.style.height = h + 'px'; - this.aright.childNodes[0].style.top = Math.floor((h - this.aright.childNodes[0].offsetHeight) / 2) + 'px'; -}; - -Scroll.prototype.getAdjustment = function (rect) { // rect of the dragg block canvas - var d = this.contents.parentNode; // scripts - var w = d.offsetWidth; - var h = d.offsetHeight; - if ((rect.x > 0) && (rect.y > 0)) { - return 'topleft'; - } - if (((rect.x + rect.width) < w) && ((rect.y + rect.height) < h)) { - return 'bottomright'; - } - if ((rect.x > 0) && ((rect.y + rect.height) < h)) { - return 'bottomleft'; - } - if (((rect.x + rect.width) < w) && (rect.y > 0)) { - return 'topright'; - } - if ((rect.x + rect.width) < w) { - return 'right'; - } - if ((rect.y + rect.height) < h) { - return 'down'; - } - if (rect.y > 0) { - return 'up'; - } - if (rect.x > 0) { - return 'left'; - } - return 'none'; -}; - -Scroll.prototype.bounceBack = function () { - var owner = this; - var p = this.contents; // scriptscontainer - var bc = this.getContent(); // blockcanvas - var valx = bc.left; - var valy = bc.top; - var h = p.offsetHeight; - var w = p.offsetWidth; - var rect = { - x: valx, - y: valy, - width: bc.offsetWidth, - height: bc.offsetHeight - }; - var transition = { - duration: 0.5, - transition: 'ease-out', - style: {}, - onComplete: function () { - owner.refresh(); + var me = this; + if (isTablet) { + this.aup.ontouchstart = function (e) { + me.scrolldown(e); + }; + } else { + this.aup.onmousedown = function (e) { + me.scrolldown(e); + }; } - }; - switch (this.getAdjustment(rect)) { - case 'topright': - transition.style.left = (this.hasHorizontal ? (w - rect.width) : 0) + 'px'; - transition.style.top = '0px'; - CSSTransition3D(bc, transition); - break; - case 'bottomright': - transition.style.left = (this.hasHorizontal ? (w - rect.width) : 0) + 'px'; - transition.style.top = (this.hasVertical ? h - rect.height : 0) + 'px'; - CSSTransition3D(bc, transition); - break; - case 'topleft': - transition.style.top = '0px'; - transition.style.left = '0px'; - CSSTransition3D(bc, transition); - break; - case 'bottomleft': - transition.style.top = (this.hasVertical ? h - rect.height : 0) + 'px'; - transition.style.left = '0px'; - CSSTransition3D(bc, transition); - break; - case 'right': - transition.style.top = valy + 'px'; - transition.style.left = (this.hasHorizontal ? (w - rect.width) : 0) + 'px'; - CSSTransition3D(bc, transition); - break; - case 'left': - if (this.hasHorizontal) { - transition.style.top = valy + 'px'; + + if (isTablet) { + this.adown.ontouchstart = function (e) { + me.scrollup(e); + }; + } else { + this.adown.onmousedown = function (e) { + me.scrollup(e); + }; + } + + if (isTablet) { + this.aleft.ontouchstart = function (e) { + me.scrollright(e); + }; + } else { + this.aleft.onmousedown = function (e) { + me.scrollright(e); + }; + } + + if (isTablet) { + this.aright.ontouchstart = function (e) { + me.scrollleft(e); + }; + } else { + this.aright.onmousedown = function (e) { + me.scrollleft(e); + }; + } + + } + + ///////////////////////////////////////////////////////////// + // Scrolling + //////////////////////////////////////////////////////////// + + repositionArrows (h) { + this.aleft.style.height = h + 'px'; + this.aleft.childNodes[0].style.top = Math.floor((h - this.aleft.childNodes[0].offsetHeight) / 2) + 'px'; + this.aright.style.height = h + 'px'; + this.aright.childNodes[0].style.top = Math.floor((h - this.aright.childNodes[0].offsetHeight) / 2) + 'px'; + } + + getAdjustment (rect) { // rect of the dragg block canvas + var d = this.contents.parentNode; // scripts + var w = d.offsetWidth; + var h = d.offsetHeight; + if ((rect.x > 0) && (rect.y > 0)) { + return 'topleft'; + } + if (((rect.x + rect.width) < w) && ((rect.y + rect.height) < h)) { + return 'bottomright'; + } + if ((rect.x > 0) && ((rect.y + rect.height) < h)) { + return 'bottomleft'; + } + if (((rect.x + rect.width) < w) && (rect.y > 0)) { + return 'topright'; + } + if ((rect.x + rect.width) < w) { + return 'right'; + } + if ((rect.y + rect.height) < h) { + return 'down'; + } + if (rect.y > 0) { + return 'up'; + } + if (rect.x > 0) { + return 'left'; + } + return 'none'; + } + + bounceBack () { + var owner = this; + var p = this.contents; // scriptscontainer + var bc = this.getContent(); // blockcanvas + var valx = bc.left; + var valy = bc.top; + var h = p.offsetHeight; + var w = p.offsetWidth; + var rect = { + x: valx, + y: valy, + width: bc.offsetWidth, + height: bc.offsetHeight + }; + var transition = { + duration: 0.5, + transition: 'ease-out', + style: {}, + onComplete: function () { + owner.refresh(); + } + }; + switch (this.getAdjustment(rect)) { + case 'topright': + transition.style.left = (this.hasHorizontal ? (w - rect.width) : 0) + 'px'; + transition.style.top = '0px'; + CSSTransition3D(bc, transition); + break; + case 'bottomright': + transition.style.left = (this.hasHorizontal ? (w - rect.width) : 0) + 'px'; + transition.style.top = (this.hasVertical ? h - rect.height : 0) + 'px'; + CSSTransition3D(bc, transition); + break; + case 'topleft': + transition.style.top = '0px'; transition.style.left = '0px'; CSSTransition3D(bc, transition); - } - break; - case 'down': - transition.style.top = (h - rect.height) + 'px'; - transition.style.left = valx + 'px'; - CSSTransition3D(bc, transition); - break; - case 'up': - if (this.hasVertical) { - transition.style.top = '0px'; + break; + case 'bottomleft': + transition.style.top = (this.hasVertical ? h - rect.height : 0) + 'px'; + transition.style.left = '0px'; + CSSTransition3D(bc, transition); + break; + case 'right': + transition.style.top = valy + 'px'; + transition.style.left = (this.hasHorizontal ? (w - rect.width) : 0) + 'px'; + CSSTransition3D(bc, transition); + break; + case 'left': + if (this.hasHorizontal) { + transition.style.top = valy + 'px'; + transition.style.left = '0px'; + CSSTransition3D(bc, transition); + } + break; + case 'down': + transition.style.top = (h - rect.height) + 'px'; transition.style.left = valx + 'px'; CSSTransition3D(bc, transition); + break; + case 'up': + if (this.hasVertical) { + transition.style.top = '0px'; + transition.style.left = valx + 'px'; + CSSTransition3D(bc, transition); + } + break; } - break; } -}; -///////////////////////////////////////////////////////////// -// Refreshing -//////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////// + // Refreshing + //////////////////////////////////////////////////////////// -Scroll.prototype.refresh = function () { - var p = this.contents; // scriptscontainer - var bc = this.getContent(); // blockcanvas - var w = p.offsetWidth; - var h = p.offsetHeight; - var you; - var needleft = 'hidden'; - var needright = 'hidden'; - var needup = 'hidden'; - var needdown = 'hidden'; - var allblocks = this.getObjects(); - for (var i = 0; i < allblocks.length; i++) { - you = allblocks[i].div; - if (you == null) { - continue; - } - if (!you.owner) { - continue; - } - if (you.style.visibility == 'hidden') { - continue; - } - if (you.left + bc.left < 0) { - needleft = 'visible'; - } - if ((you.left + you.offsetWidth + bc.left) > w) { - needright = 'visible'; - } - if (you.top + bc.top + 10 < 0) { - needup = 'visible'; - } - if ((you.top + you.offsetHeight + bc.top) > h) { - needdown = 'visible'; + refresh () { + var p = this.contents; // scriptscontainer + var bc = this.getContent(); // blockcanvas + var w = p.offsetWidth; + var h = p.offsetHeight; + var you; + var needleft = 'hidden'; + var needright = 'hidden'; + var needup = 'hidden'; + var needdown = 'hidden'; + var allblocks = this.getObjects(); + for (var i = 0; i < allblocks.length; i++) { + you = allblocks[i].div; + if (you == null) { + continue; + } + if (!you.owner) { + continue; + } + if (you.style.visibility == 'hidden') { + continue; + } + if (you.left + bc.left < 0) { + needleft = 'visible'; + } + if ((you.left + you.offsetWidth + bc.left) > w) { + needright = 'visible'; + } + if (you.top + bc.top + 10 < 0) { + needup = 'visible'; + } + if ((you.top + you.offsetHeight + bc.top) > h) { + needdown = 'visible'; + } } + this.aleft.style.visibility = needleft; + this.aright.style.visibility = needright; + this.aup.style.visibility = needup; + this.adown.style.visibility = needdown; } - this.aleft.style.visibility = needleft; - this.aright.style.visibility = needright; - this.aup.style.visibility = needup; - this.adown.style.visibility = needdown; -}; -Scroll.prototype.adjustCanvas = function () { - var bc = this.getContent(); // blockcanvas - var p = this.contents; // scriptscontainer - var w = p.offsetWidth; - var h = p.offsetHeight; - var ow = bc.offsetWidth; - var oh = bc.offsetHeight; - var you; - var minx = 99999; - var maxwidth = 0; - var miny = 99999; - var maxheight = 0; - var padding = 0; - var allblocks = this.getObjects(); - for (var i = 0; i < allblocks.length; i++) { - you = allblocks[i].div; - if (you == null) { - continue; + adjustCanvas () { + var bc = this.getContent(); // blockcanvas + var p = this.contents; // scriptscontainer + var w = p.offsetWidth; + var h = p.offsetHeight; + var ow = bc.offsetWidth; + var oh = bc.offsetHeight; + var you; + var minx = 99999; + var maxwidth = 0; + var miny = 99999; + var maxheight = 0; + var padding = 0; + var allblocks = this.getObjects(); + for (var i = 0; i < allblocks.length; i++) { + you = allblocks[i].div; + if (you == null) { + continue; + } + if (!you.owner) { + continue; + } + if (you.style.visibility == 'hidden') { + continue; + } + if (you.left < minx) { + minx = you.left; + } + if ((you.left + you.offsetWidth + padding) > maxwidth) { + maxwidth = you.left + you.offsetWidth + padding; + } + if (you.top < miny) { + miny = you.top; + } + if ((you.top + you.offsetHeight + 20) > maxheight) { + maxheight = you.top + you.offsetHeight + 20; + } } - if (!you.owner) { - continue; + if (minx < 0) { + minx -= padding; + minx += bc.left; + w -= minx; + } else { + minx = 0; } - if (you.style.visibility == 'hidden') { - continue; + if (miny < 0) { + miny -= 20; + miny += bc.top; + h -= miny; + } else { + miny = 0; } - if (you.left < minx) { - minx = you.left; + if ((maxwidth - minx) > w) { + w = Math.round(maxwidth - minx); } - if ((you.left + you.offsetWidth + padding) > maxwidth) { - maxwidth = you.left + you.offsetWidth + padding; + if ((maxheight - miny) > h) { + h = Math.round(maxheight - miny); } - if (you.top < miny) { - miny = you.top; + if ((ow != w) || (oh != h)) { + setCanvasSize(bc, w, h); } - if ((you.top + you.offsetHeight + 20) > maxheight) { - maxheight = you.top + you.offsetHeight + 20; + if ((minx < 0) || (miny < 0)) { + this.moveBlocks(-minx, -miny); + Events.move3D(bc, minx, miny); } } - if (minx < 0) { - minx -= padding; - minx += bc.left; - w -= minx; - } else { - minx = 0; - } - if (miny < 0) { - miny -= 20; - miny += bc.top; - h -= miny; - } else { - miny = 0; - } - if ((maxwidth - minx) > w) { - w = Math.round(maxwidth - minx); - } - if ((maxheight - miny) > h) { - h = Math.round(maxheight - miny); - } - if ((ow != w) || (oh != h)) { - setCanvasSize(bc, w, h); - } - if ((minx < 0) || (miny < 0)) { - this.moveBlocks(-minx, -miny); - Events.move3D(bc, minx, miny); - } -}; -Scroll.prototype.moveBlocks = function (dx, dy) { - var allblocks = this.getObjects(); - for (var i = 0; i < allblocks.length; i++) { - var b = allblocks[i]; - b.moveBlock(b.div.left + dx, b.div.top + dy); - } -}; - -///////////////////////////////////////////////////////////// -// Scrolling -//////////////////////////////////////////////////////////// - -Scroll.prototype.scrolldown = function (e) { - if (isTablet && e.touches && (e.touches.length > 1)) { - return; - } - e.preventDefault(); - e.stopPropagation(); - var owner = this; - var p = this.contents; - var sc = this.getContent(); - var h = p.offsetHeight; - var valy = sc.top + h; - if (valy > 0) { - valy = 0; - } - valy = Math.round(valy); - var transition = { - duration: 0.5, - transition: 'ease-out', - style: { - top: valy + 'px' - }, - onComplete: function () { - owner.refresh(); + moveBlocks (dx, dy) { + var allblocks = this.getObjects(); + for (var i = 0; i < allblocks.length; i++) { + var b = allblocks[i]; + b.moveBlock(b.div.left + dx, b.div.top + dy); } - }; - CSSTransition3D(sc, transition); -}; + } -Scroll.prototype.scrollup = function (e) { - if (isTablet && e.touches && (e.touches.length > 1)) { - return; - } - e.preventDefault(); - e.stopPropagation(); - var owner = this; - var p = this.contents; - var sc = this.getContent(); - var h = p.offsetHeight; - var valy = sc.top - h; - if ((valy + sc.offsetHeight) < h) { - valy = h - sc.offsetHeight; - } - valy = Math.round(valy); - var transition = { - duration: 0.5, - transition: 'ease-out', - style: { - top: valy + 'px' - }, - onComplete: function () { - owner.refresh(); + ///////////////////////////////////////////////////////////// + // Scrolling + //////////////////////////////////////////////////////////// + + scrolldown (e) { + if (isTablet && e.touches && (e.touches.length > 1)) { + return; } - }; - CSSTransition3D(sc, transition); -}; - -Scroll.prototype.scrollright = function (e) { - if (isTablet && e.touches && (e.touches.length > 1)) { - return; - } - e.preventDefault(); - e.stopPropagation(); - var owner = this; - var p = this.contents; - var sc = this.getContent(); - var w = p.offsetWidth; - var valx = sc.left + w; - if (valx > 0) { - valx = 0; - } - valx = Math.round(valx); - var transition = { - duration: 0.5, - transition: 'ease-out', - style: { - left: valx + 'px' - }, - onComplete: function () { - owner.refresh(); + e.preventDefault(); + e.stopPropagation(); + var owner = this; + var p = this.contents; + var sc = this.getContent(); + var h = p.offsetHeight; + var valy = sc.top + h; + if (valy > 0) { + valy = 0; } - }; - CSSTransition3D(sc, transition); -}; + valy = Math.round(valy); + var transition = { + duration: 0.5, + transition: 'ease-out', + style: { + top: valy + 'px' + }, + onComplete: function () { + owner.refresh(); + } + }; + CSSTransition3D(sc, transition); + } -Scroll.prototype.scrollleft = function (e) { - if (isTablet && e.touches && (e.touches.length > 1)) { - return; - } - e.preventDefault(); - e.stopPropagation(); - var owner = this; - var p = this.contents; - var sc = this.getContent(); - var w = p.offsetWidth; - var valx = sc.left - w; - if ((valx + sc.offsetWidth) < w) { - valx = w - sc.offsetWidth; - } - valx = Math.round(valx); - var transition = { - duration: 0.5, - transition: 'ease-out', - style: { - left: valx + 'px' - }, - onComplete: function () { - owner.refresh(); + scrollup (e) { + if (isTablet && e.touches && (e.touches.length > 1)) { + return; } - }; - CSSTransition3D(sc, transition); -}; - -Scroll.prototype.fitToScreen = function () { - var p = this.contents; - var sc = this.getContent(); - var valx = sc.left; - var valy = sc.top; - var h = p.offsetHeight; - var w = p.offsetWidth; - var rect = { - x: valx, - y: valy, - width: sc.offsetWidth, - height: sc.offsetHeight - }; - switch (this.getAdjustment(rect)) { - case 'topright': - valx = w - rect.width; - valy = 0; - break; - case 'bottomright': - valx = w - rect.width; - valy = h - rect.height; - break; - case 'topleft': - valx = 0; valy = 0; - break; - case 'bottomleft': - valy = h - rect.height; - valx = 0; - break; - case 'right': - valx = w - rect.width; - break; - case 'left': - valx = 0; - break; - case 'down': - valy = h - rect.height; - break; - case 'up': - valy = 0; - break; + e.preventDefault(); + e.stopPropagation(); + var owner = this; + var p = this.contents; + var sc = this.getContent(); + var h = p.offsetHeight; + var valy = sc.top - h; + if ((valy + sc.offsetHeight) < h) { + valy = h - sc.offsetHeight; + } + valy = Math.round(valy); + var transition = { + duration: 0.5, + transition: 'ease-out', + style: { + top: valy + 'px' + }, + onComplete: function () { + owner.refresh(); + } + }; + CSSTransition3D(sc, transition); } - Events.move3D(sc, valx, valy); -}; + + scrollright (e) { + if (isTablet && e.touches && (e.touches.length > 1)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + var owner = this; + var p = this.contents; + var sc = this.getContent(); + var w = p.offsetWidth; + var valx = sc.left + w; + if (valx > 0) { + valx = 0; + } + valx = Math.round(valx); + var transition = { + duration: 0.5, + transition: 'ease-out', + style: { + left: valx + 'px' + }, + onComplete: function () { + owner.refresh(); + } + }; + CSSTransition3D(sc, transition); + } + + scrollleft (e) { + if (isTablet && e.touches && (e.touches.length > 1)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + var owner = this; + var p = this.contents; + var sc = this.getContent(); + var w = p.offsetWidth; + var valx = sc.left - w; + if ((valx + sc.offsetWidth) < w) { + valx = w - sc.offsetWidth; + } + valx = Math.round(valx); + var transition = { + duration: 0.5, + transition: 'ease-out', + style: { + left: valx + 'px' + }, + onComplete: function () { + owner.refresh(); + } + }; + CSSTransition3D(sc, transition); + } + + fitToScreen () { + var p = this.contents; + var sc = this.getContent(); + var valx = sc.left; + var valy = sc.top; + var h = p.offsetHeight; + var w = p.offsetWidth; + var rect = { + x: valx, + y: valy, + width: sc.offsetWidth, + height: sc.offsetHeight + }; + switch (this.getAdjustment(rect)) { + case 'topright': + valx = w - rect.width; + valy = 0; + break; + case 'bottomright': + valx = w - rect.width; + valy = h - rect.height; + break; + case 'topleft': + valx = 0; valy = 0; + break; + case 'bottomleft': + valy = h - rect.height; + valx = 0; + break; + case 'right': + valx = w - rect.width; + break; + case 'left': + valx = 0; + break; + case 'down': + valy = h - rect.height; + break; + case 'up': + valy = 0; + break; + } + Events.move3D(sc, valx, valy); + } +}