mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 15:02:52 -05:00
Merge branch 'develop' of https://github.com/LLK/scratch-vm into sound
This commit is contained in:
commit
2d8491123e
26 changed files with 1102 additions and 418 deletions
|
@ -1,7 +1,4 @@
|
|||
build/*
|
||||
dist.js
|
||||
coverage/*
|
||||
dist/*
|
||||
node_modules/*
|
||||
playground/*
|
||||
vm.js
|
||||
vm.min.js
|
||||
coverage/*
|
||||
|
|
5
.gitattributes
vendored
5
.gitattributes
vendored
|
@ -5,8 +5,10 @@
|
|||
# People who (for example) rsync between Windows and Linux need this.
|
||||
|
||||
# File types which we know are binary
|
||||
*.sb2 binary
|
||||
|
||||
# Prefer LF for most file types
|
||||
*.css text eol=lf
|
||||
*.frag text eol=lf
|
||||
*.htm text eol=lf
|
||||
*.html text eol=lf
|
||||
|
@ -17,13 +19,16 @@
|
|||
*.md text eol=lf
|
||||
*.vert text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.yml text eol=lf
|
||||
|
||||
# Prefer LF for these files
|
||||
.editorconfig text eol=lf
|
||||
.eslintignore text eol=lf
|
||||
.eslintrc text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.gitmodules text eol=lf
|
||||
.npmignore text eol=lf
|
||||
LICENSE text eol=lf
|
||||
Makefile text eol=lf
|
||||
README text eol=lf
|
||||
|
|
13
.github/CONTRIBUTING.md
vendored
Normal file
13
.github/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
## Contributing
|
||||
The development of Scratch is an ongoing process, and we love to have people in the Scratch and open source communities help us along the way.
|
||||
|
||||
If you're interested in contributing, please take a look at the [issues](https://github.com/LLK/scratch-vm/issues) on this repository.
|
||||
Two great ways of helping are by identifying bugs and documenting them as issues, or fixing issues and creating pull requests. When looking for bugs to fix, please look for the ["Help Wanted" label](https://github.com/LLK/scratch-vm/issues?q=label%3A%22help+wanted%22). Bugs with this label have been specifically set aside for Open Source contributors. Issues without the label can also be worked on but we ask that you comment on the issue prior to starting work. When submitting pull requests please be patient -- it can take a while to find time to review them. The organization and class structures can't be radically changed without significant coordination and collaboration from the Scratch Team, so these types of changes should be avoided.
|
||||
|
||||
It's been said that the Scratch Team spends about one hour of design discussion for every pixel in Scratch, but some think that estimate is a little low. While we welcome suggestions for new features in our [suggestions forum](https://scratch.mit.edu/discuss/1/) (especially ones that come with mockups), we are unlikely to accept PRs with new features that haven't been thought through and discussed as a group. Why? Because we have a strong belief in the value of keeping things simple for new users. To learn more about our design philosophy, see [the Scratch Developers page](https://scratch.mit.edu/developers), or [this paper](http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf).
|
||||
|
||||
Beyond this repo, there are also some other resources that you might want to take a look at:
|
||||
* [Community Guidelines](https://github.com/LLK/scratch-www/wiki/Community-Guidelines) (we find it important to maintain a constructive and welcoming community, just like on Scratch)
|
||||
* [Open Source forum](https://scratch.mit.edu/discuss/49/) on Scratch
|
||||
* [Suggestions forum](https://scratch.mit.edu/discuss/1/) on Scratch
|
||||
* [Bugs & Glitches forum](https://scratch.mit.edu/discuss/3/) on Scratch
|
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,15 +1,15 @@
|
|||
### Expected behavior
|
||||
### Expected Behavior
|
||||
|
||||
_Please describe what should happen_
|
||||
|
||||
### Actual behavior
|
||||
### Actual Behavior
|
||||
|
||||
_Describe what actually happens_
|
||||
|
||||
### Steps to reproduce
|
||||
### Steps to Reproduce
|
||||
|
||||
_Explain what someone needs to do in order to see what's described in *Actual behavior* above_
|
||||
|
||||
### Operating system and browser
|
||||
### Operating System and Browser
|
||||
|
||||
_e.g. Mac OS 10.11.6 Safari 10.0_
|
||||
|
|
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,7 +1,15 @@
|
|||
### Proposed changes
|
||||
### Resolves
|
||||
|
||||
_What Github issue does this resolve (please include link)?_
|
||||
|
||||
### Proposed Changes
|
||||
|
||||
_Describe what this Pull Request does_
|
||||
|
||||
### Reason for changes
|
||||
### Reason for Changes
|
||||
|
||||
_Explain why these changes should be made. Please include an issue # if applicable._
|
||||
_Explain why these changes should be made_
|
||||
|
||||
### Test Coverage
|
||||
|
||||
_Please show how you have added tests to cover your changes_
|
||||
|
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -8,11 +8,16 @@ npm-*
|
|||
# Testing
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/dist.js
|
||||
/vm.js
|
||||
/vm.min.js
|
||||
|
||||
# IDEA
|
||||
/.idea
|
||||
|
||||
# Build
|
||||
/dist
|
||||
/playground/assets
|
||||
/playground/media
|
||||
/playground/scratch-vm.js
|
||||
/playground/scratch-vm.js.map
|
||||
/playground/vendor.js
|
||||
/playground/vm.js
|
||||
/playground/vendor.js.map
|
||||
/playground/zenburn.css
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
## Contributing
|
||||
The development of scratch-vm is an ongoing process,
|
||||
and we love to have people in the Scratch and open source communities help us along the way.
|
||||
|
||||
If you're interested in contributing, please take a look at the
|
||||
[issues](https://github.com/LLK/scratch-vm/issues) on this repository.
|
||||
Two great ways of helping are by identifying bugs and documenting them as issues,
|
||||
or fixing issues and creating pull requests. When submitting pull requests please be patient
|
||||
-- it can take a while to find time to review them.
|
||||
The organization and class structures can't be radically changed without significant coordination
|
||||
and collaboration from the Scratch Team, so these types of changes should be avoided.
|
||||
|
||||
It's been said that the Scratch Team spends about one hour of design discussion for every pixel in Scratch,
|
||||
but some think that estimate is a little low. While we welcome suggestions for new features in our
|
||||
[suggestions forum](https://scratch.mit.edu/discuss/1/) (especially ones that come with mockups), we are unlikely to accept PRs with
|
||||
new features that haven't been thought through and discussed as a group. Why? Because we have a strong belief
|
||||
in the value of keeping things simple for new users. To learn more about our design philosophy,
|
||||
see [the Scratch Developers page](https://scratch.mit.edu/developers), or
|
||||
[this paper](http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf).
|
||||
|
||||
Beyond this repo, there are also some other resources that you might want to take a look at:
|
||||
* [Community Guidelines](https://github.com/LLK/scratch-www/wiki/Community-Guidelines) (we find it important to maintain a constructive and welcoming community, just like on Scratch)
|
||||
* [Open Source forum](https://scratch.mit.edu/discuss/49/) on Scratch
|
||||
* [Suggestions forum](https://scratch.mit.edu/discuss/1/) on Scratch
|
||||
* [Bugs & Glitches forum](https://scratch.mit.edu/discuss/3/) on Scratch
|
|
@ -9,7 +9,7 @@
|
|||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/LLK/scratch-vm.git"
|
||||
},
|
||||
"main": "./dist.js",
|
||||
"main": "./dist/node/scratch-vm.js",
|
||||
"scripts": {
|
||||
"build": "./node_modules/.bin/webpack --progress --colors --bail",
|
||||
"coverage": "./node_modules/.bin/tap ./test/{unit,integration}/*.js --coverage --coverage-report=lcov",
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<!-- FPS counter, Syntax highlighter, Blocks, Renderer -->
|
||||
<script src="./vendor.js"></script>
|
||||
<!-- VM Worker -->
|
||||
<script src="./vm.js"></script>
|
||||
<script src="./scratch-vm.js"></script>
|
||||
<!-- Playground -->
|
||||
<script src="./playground.js"></script>
|
||||
<script>
|
||||
|
|
336
src/blocks/scratch3_pen.js
Normal file
336
src/blocks/scratch3_pen.js
Normal file
|
@ -0,0 +1,336 @@
|
|||
var Cast = require('../util/cast');
|
||||
var Clone = require('../util/clone');
|
||||
var Color = require('../util/color');
|
||||
var MathUtil = require('../util/math-util');
|
||||
var RenderedTarget = require('../sprites/rendered-target');
|
||||
|
||||
/**
|
||||
* @typedef {object} PenState - the pen state associated with a particular target.
|
||||
* @property {Boolean} penDown - tracks whether the pen should draw for this target.
|
||||
* @property {number} hue - the current hue of the pen.
|
||||
* @property {number} shade - the current shade of the pen.
|
||||
* @property {PenAttributes} penAttributes - cached pen attributes for the renderer. This is the authoritative value for
|
||||
* diameter but not for pen color.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Host for the Pen-related blocks in Scratch 3.0
|
||||
* @param {Runtime} runtime - the runtime instantiating this block package.
|
||||
* @constructor
|
||||
*/
|
||||
var Scratch3PenBlocks = function (runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
|
||||
/**
|
||||
* The ID of the renderer Drawable corresponding to the pen layer.
|
||||
* @type {int}
|
||||
* @private
|
||||
*/
|
||||
this._penDrawableId = -1;
|
||||
|
||||
/**
|
||||
* The ID of the renderer Skin corresponding to the pen layer.
|
||||
* @type {int}
|
||||
* @private
|
||||
*/
|
||||
this._penSkinId = -1;
|
||||
|
||||
this._onTargetMoved = this._onTargetMoved.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* The default pen state, to be used when a target has no existing pen state.
|
||||
* @type {PenState}
|
||||
*/
|
||||
Scratch3PenBlocks.DEFAULT_PEN_STATE = {
|
||||
penDown: false,
|
||||
hue: 120,
|
||||
shade: 50,
|
||||
penAttributes: {
|
||||
color4f: [0, 0, 1, 1],
|
||||
diameter: 1
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Place the pen layer in front of the backdrop but behind everything else.
|
||||
* We should probably handle this somewhere else... somewhere central that knows about pen, backdrop, video, etc.
|
||||
* Maybe it should be in the GUI?
|
||||
* @type {int}
|
||||
*/
|
||||
Scratch3PenBlocks.PEN_ORDER = 1;
|
||||
|
||||
/**
|
||||
* The minimum and maximum allowed pen size.
|
||||
* @type {{min: number, max: number}}
|
||||
*/
|
||||
Scratch3PenBlocks.PEN_SIZE_RANGE = {min: 1, max: 255};
|
||||
|
||||
/**
|
||||
* The key to load & store a target's pen-related state.
|
||||
* @type {string}
|
||||
*/
|
||||
Scratch3PenBlocks.STATE_KEY = 'Scratch.pen';
|
||||
|
||||
/**
|
||||
* Clamp a pen size value to the range allowed by the pen.
|
||||
* @param {number} requestedSize - the requested pen size.
|
||||
* @returns {number} the clamped size.
|
||||
* @private
|
||||
*/
|
||||
Scratch3PenBlocks.prototype._clampPenSize = function (requestedSize) {
|
||||
return MathUtil.clamp(requestedSize, Scratch3PenBlocks.PEN_SIZE_RANGE.min, Scratch3PenBlocks.PEN_SIZE_RANGE.max);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the renderer "Skin" corresponding to the pen layer. If the pen Skin doesn't yet exist, create it.
|
||||
* @returns {int} the Skin ID of the pen layer, or -1 on failure.
|
||||
* @private
|
||||
*/
|
||||
Scratch3PenBlocks.prototype._getPenLayerID = function () {
|
||||
if (this._penSkinId < 0 && this.runtime.renderer) {
|
||||
this._penSkinId = this.runtime.renderer.createPenSkin();
|
||||
this._penDrawableId = this.runtime.renderer.createDrawable();
|
||||
this.runtime.renderer.setDrawableOrder(this._penDrawableId, Scratch3PenBlocks.PEN_ORDER);
|
||||
this.runtime.renderer.updateDrawableProperties(this._penDrawableId, {skinId: this._penSkinId});
|
||||
}
|
||||
return this._penSkinId;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Target} target - collect pen state for this target. Probably, but not necessarily, a RenderedTarget.
|
||||
* @returns {PenState} the mutable pen state associated with that target. This will be created if necessary.
|
||||
* @private
|
||||
*/
|
||||
Scratch3PenBlocks.prototype._getPenState = function (target) {
|
||||
var penState = target.getCustomState(Scratch3PenBlocks.STATE_KEY);
|
||||
if (!penState) {
|
||||
penState = Clone.simple(Scratch3PenBlocks.DEFAULT_PEN_STATE);
|
||||
target.setCustomState(Scratch3PenBlocks.STATE_KEY, penState);
|
||||
}
|
||||
return penState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a target which has moved. This only fires when the pen is down.
|
||||
* @param {RenderedTarget} target - the target which has moved.
|
||||
* @param {number} oldX - the previous X position.
|
||||
* @param {number} oldY - the previous Y position.
|
||||
* @private
|
||||
*/
|
||||
Scratch3PenBlocks.prototype._onTargetMoved = function (target, oldX, oldY) {
|
||||
var penSkinId = this._getPenLayerID();
|
||||
if (penSkinId >= 0) {
|
||||
var penState = this._getPenState(target);
|
||||
this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y);
|
||||
this.runtime.requestRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cached RGB color from the hue & shade values in the provided PenState object.
|
||||
* @param {PenState} penState - the pen state to update.
|
||||
* @private
|
||||
*/
|
||||
Scratch3PenBlocks.prototype._updatePenColor = function (penState) {
|
||||
var rgb = Color.hsvToRgb({h: penState.hue * 180 / 100, s: 1, v: 1});
|
||||
var shade = (penState.shade > 100) ? 200 - penState.shade : penState.shade;
|
||||
if (shade < 50) {
|
||||
rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60);
|
||||
} else {
|
||||
rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60);
|
||||
}
|
||||
penState.penAttributes.color4f[0] = rgb.r / 255.0;
|
||||
penState.penAttributes.color4f[1] = rgb.g / 255.0;
|
||||
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a pen hue or shade values to the range [0,200).
|
||||
* @param {number} value - the pen hue or shade value to the proper range.
|
||||
* @returns {number} the wrapped value.
|
||||
* @private
|
||||
*/
|
||||
Scratch3PenBlocks.prototype._wrapHueOrShade = function (value) {
|
||||
value = value % 200;
|
||||
if (value < 0) value += 200;
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.getPrimitives = function () {
|
||||
return {
|
||||
pen_clear: this.clear,
|
||||
pen_stamp: this.stamp,
|
||||
pen_pendown: this.penDown,
|
||||
pen_penup: this.penUp,
|
||||
pen_setpencolortocolor: this.setPenColorToColor,
|
||||
pen_changepencolorby: this.changePenHueBy,
|
||||
pen_setpencolortonum: this.setPenHueToNumber,
|
||||
pen_changepenshadeby: this.changePenShadeBy,
|
||||
pen_setpenshadeto: this.setPenShadeToNumber,
|
||||
pen_changepensizeby: this.changePenSizeBy,
|
||||
pen_setpensizeto: this.setPenSizeTo
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "clear" block clears the pen layer's contents.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.clear = function () {
|
||||
var penSkinId = this._getPenLayerID();
|
||||
if (penSkinId >= 0) {
|
||||
this.runtime.renderer.penClear(penSkinId);
|
||||
this.runtime.requestRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "stamp" block stamps the current drawable's image onto the pen layer.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.stamp = function (args, util) {
|
||||
var penSkinId = this._getPenLayerID();
|
||||
if (penSkinId >= 0) {
|
||||
var target = util.target;
|
||||
this.runtime.renderer.penStamp(penSkinId, target.drawableID);
|
||||
this.runtime.requestRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "pen down" block causes the target to leave pen trails on future motion.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.penDown = function (args, util) {
|
||||
var target = util.target;
|
||||
var penState = this._getPenState(target);
|
||||
|
||||
if (!penState.penDown) {
|
||||
penState.penDown = true;
|
||||
target.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
|
||||
}
|
||||
|
||||
var penSkinId = this._getPenLayerID();
|
||||
if (penSkinId >= 0) {
|
||||
this.runtime.renderer.penPoint(penSkinId, penState.penAttributes, target.x, target.y);
|
||||
this.runtime.requestRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "pen up" block stops the target from leaving pen trails.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.penUp = function (args, util) {
|
||||
var target = util.target;
|
||||
var penState = this._getPenState(target);
|
||||
|
||||
if (penState.penDown) {
|
||||
penState.penDown = false;
|
||||
target.removeListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "set pen color to {color}" block sets the pen to a particular RGB color.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {int} COLOR - the color to set, expressed as a 24-bit RGB value (0xRRGGBB).
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.setPenColorToColor = function (args, util) {
|
||||
var penState = this._getPenState(util.target);
|
||||
var rgb = Cast.toRgbColorObject(args.COLOR);
|
||||
var hsv = Color.rgbToHsv(rgb);
|
||||
|
||||
penState.hue = 200 * hsv.h / 360;
|
||||
penState.shade = 50 * hsv.v;
|
||||
penState.penAttributes.color4f[0] = rgb.r / 255.0;
|
||||
penState.penAttributes.color4f[1] = rgb.g / 255.0;
|
||||
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
||||
if (rgb.hasOwnProperty('a')) { // Will there always be an 'a'?
|
||||
penState.penAttributes.color4f[3] = rgb.a / 255.0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "change pen color by {number}" block rotates the hue of the pen by the given amount.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} COLOR - the amount of desired hue rotation.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.changePenHueBy = function (args, util) {
|
||||
var penState = this._getPenState(util.target);
|
||||
penState.hue = this._wrapHueOrShade(penState.hue + Cast.toNumber(args.COLOR));
|
||||
this._updatePenColor(penState);
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "set pen color to {number}" block sets the hue of the pen.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} COLOR - the desired hue.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.setPenHueToNumber = function (args, util) {
|
||||
var penState = this._getPenState(util.target);
|
||||
penState.hue = this._wrapHueOrShade(Cast.toNumber(args.COLOR));
|
||||
this._updatePenColor(penState);
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "change pen shade by {number}" block changes the "shade" of the pen, related to the HSV value.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} SHADE - the amount of desired shade change.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.changePenShadeBy = function (args, util) {
|
||||
var penState = this._getPenState(util.target);
|
||||
penState.shade = this._wrapHueOrShade(penState.shade + Cast.toNumber(args.SHADE));
|
||||
this._updatePenColor(penState);
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "set pen shade to {number}" block sets the "shade" of the pen, related to the HSV value.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} SHADE - the amount of desired shade change.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.setPenShadeToNumber = function (args, util) {
|
||||
var penState = this._getPenState(util.target);
|
||||
penState.shade = this._wrapHueOrShade(Cast.toNumber(args.SHADE));
|
||||
this._updatePenColor(penState);
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "change pen size by {number}" block changes the pen size by the given amount.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} SIZE - the amount of desired size change.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.changePenSizeBy = function (args, util) {
|
||||
var penAttributes = this._getPenState(util.target).penAttributes;
|
||||
penAttributes.diameter = this._clampPenSize(penAttributes.diameter + Cast.toNumber(args.SIZE));
|
||||
};
|
||||
|
||||
/**
|
||||
* The pen "set pen size to {number}" block sets the pen size to the given amount.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} SIZE - the amount of desired size change.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
Scratch3PenBlocks.prototype.setPenSizeTo = function (args, util) {
|
||||
var penAttributes = this._getPenState(util.target).penAttributes;
|
||||
penAttributes.diameter = this._clampPenSize(Cast.toNumber(args.SIZE));
|
||||
};
|
||||
|
||||
module.exports = Scratch3PenBlocks;
|
|
@ -15,6 +15,7 @@ var defaultBlockPackages = {
|
|||
scratch3_looks: require('../blocks/scratch3_looks'),
|
||||
scratch3_motion: require('../blocks/scratch3_motion'),
|
||||
scratch3_operators: require('../blocks/scratch3_operators'),
|
||||
scratch3_pen: require('../blocks/scratch3_pen'),
|
||||
scratch3_sound: require('../blocks/scratch3_sound'),
|
||||
scratch3_sensing: require('../blocks/scratch3_sensing'),
|
||||
scratch3_data: require('../blocks/scratch3_data'),
|
||||
|
|
|
@ -131,12 +131,17 @@ Sequencer.prototype.stepThread = function (thread) {
|
|||
// If no next block has been found at this point, look on the stack.
|
||||
while (!thread.peekStack()) {
|
||||
thread.popStack();
|
||||
|
||||
if (thread.stack.length === 0) {
|
||||
// No more stack to run!
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
return;
|
||||
}
|
||||
if (thread.peekStackFrame().isLoop) {
|
||||
|
||||
var stackFrame = thread.peekStackFrame();
|
||||
isWarpMode = stackFrame.warpMode;
|
||||
|
||||
if (stackFrame.isLoop) {
|
||||
// The current level of the stack is marked as a loop.
|
||||
// Return to yield for the frame/tick in general.
|
||||
// Unless we're in warp mode - then only return if the
|
||||
|
@ -151,7 +156,7 @@ Sequencer.prototype.stepThread = function (thread) {
|
|||
// since loops need to be re-executed.
|
||||
continue;
|
||||
}
|
||||
} else if (thread.peekStackFrame().waitingReporter) {
|
||||
} else if (stackFrame.waitingReporter) {
|
||||
// This level of the stack was waiting for a value.
|
||||
// This means a reporter has just returned - so don't go
|
||||
// to the next block for this level of the stack.
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
var EventEmitter = require('events');
|
||||
var util = require('util');
|
||||
|
||||
var Blocks = require('./blocks');
|
||||
var Variable = require('../engine/variable');
|
||||
var List = require('../engine/list');
|
||||
|
@ -14,6 +17,8 @@ var uid = require('../util/uid');
|
|||
* @constructor
|
||||
*/
|
||||
var Target = function (blocks) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (!blocks) {
|
||||
blocks = new Blocks(this);
|
||||
}
|
||||
|
@ -39,8 +44,20 @@ var Target = function (blocks) {
|
|||
* @type {Object.<string,*>}
|
||||
*/
|
||||
this.lists = {};
|
||||
/**
|
||||
* Dictionary of custom state for this target.
|
||||
* This can be used to store target-specific custom state for blocks which need it.
|
||||
* TODO: do we want to persist this in SB3 files?
|
||||
* @type {Object.<string,*>}
|
||||
*/
|
||||
this._customState = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter
|
||||
*/
|
||||
util.inherits(Target, EventEmitter);
|
||||
|
||||
/**
|
||||
* Called when the project receives a "green flag."
|
||||
* @abstract
|
||||
|
@ -112,10 +129,30 @@ Target.prototype.lookupOrCreateList = function (name) {
|
|||
*/
|
||||
Target.prototype.postSpriteInfo = function () {};
|
||||
|
||||
/**
|
||||
* Retrieve custom state associated with this target and the provided state ID.
|
||||
* @param {string} stateId - specify which piece of state to retrieve.
|
||||
* @returns {*} the associated state, if any was found.
|
||||
*/
|
||||
Target.prototype.getCustomState = function (stateId) {
|
||||
return this._customState[stateId];
|
||||
};
|
||||
|
||||
/**
|
||||
* Store custom state associated with this target and the provided state ID.
|
||||
* @param {string} stateId - specify which piece of state to store on this target.
|
||||
* @param {*} newValue - the state value to store.
|
||||
*/
|
||||
Target.prototype.setCustomState = function (stateId, newValue) {
|
||||
this._customState[stateId] = newValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Call to destroy a target.
|
||||
* @abstract
|
||||
*/
|
||||
Target.prototype.dispose = function () {};
|
||||
Target.prototype.dispose = function () {
|
||||
this._customState = {};
|
||||
};
|
||||
|
||||
module.exports = Target;
|
||||
|
|
|
@ -167,8 +167,8 @@ var parseScripts = function (scripts, blocks) {
|
|||
// Adjust script coordinates to account for
|
||||
// larger block size in scratch-blocks.
|
||||
// @todo: Determine more precisely the right formulas here.
|
||||
parsedBlockList[0].x = scriptX * 1.1;
|
||||
parsedBlockList[0].y = scriptY * 1.1;
|
||||
parsedBlockList[0].x = scriptX * 1.5;
|
||||
parsedBlockList[0].y = scriptY * 2.2;
|
||||
parsedBlockList[0].topLevel = true;
|
||||
parsedBlockList[0].parent = null;
|
||||
}
|
||||
|
|
|
@ -563,7 +563,7 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'setPenShadeTo:': {
|
||||
opcode: 'pen_changepenshadeby',
|
||||
opcode: 'pen_setpenshadeto',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
|
338
src/index.js
338
src/index.js
|
@ -1,339 +1,3 @@
|
|||
var EventEmitter = require('events');
|
||||
var util = require('util');
|
||||
|
||||
var Runtime = require('./engine/runtime');
|
||||
var sb2import = require('./import/sb2import');
|
||||
|
||||
/**
|
||||
* Handles connections between blocks, stage, and extensions.
|
||||
* @constructor
|
||||
*/
|
||||
var VirtualMachine = function () {
|
||||
var instance = this;
|
||||
// Bind event emitter and runtime to VM instance
|
||||
EventEmitter.call(instance);
|
||||
/**
|
||||
* VM runtime, to store blocks, I/O devices, sprites/targets, etc.
|
||||
* @type {!Runtime}
|
||||
*/
|
||||
instance.runtime = new Runtime();
|
||||
/**
|
||||
* The "currently editing"/selected target ID for the VM.
|
||||
* Block events from any Blockly workspace are routed to this target.
|
||||
* @type {!string}
|
||||
*/
|
||||
instance.editingTarget = null;
|
||||
// Runtime emits are passed along as VM emits.
|
||||
instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (glowData) {
|
||||
instance.emit(Runtime.SCRIPT_GLOW_ON, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (glowData) {
|
||||
instance.emit(Runtime.SCRIPT_GLOW_OFF, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (glowData) {
|
||||
instance.emit(Runtime.BLOCK_GLOW_ON, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (glowData) {
|
||||
instance.emit(Runtime.BLOCK_GLOW_OFF, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.PROJECT_RUN_START, function () {
|
||||
instance.emit(Runtime.PROJECT_RUN_START);
|
||||
});
|
||||
instance.runtime.on(Runtime.PROJECT_RUN_STOP, function () {
|
||||
instance.emit(Runtime.PROJECT_RUN_STOP);
|
||||
});
|
||||
instance.runtime.on(Runtime.VISUAL_REPORT, function (visualReport) {
|
||||
instance.emit(Runtime.VISUAL_REPORT, visualReport);
|
||||
});
|
||||
instance.runtime.on(Runtime.SPRITE_INFO_REPORT, function (spriteInfo) {
|
||||
instance.emit(Runtime.SPRITE_INFO_REPORT, spriteInfo);
|
||||
});
|
||||
|
||||
this.blockListener = this.blockListener.bind(this);
|
||||
this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter
|
||||
*/
|
||||
util.inherits(VirtualMachine, EventEmitter);
|
||||
|
||||
/**
|
||||
* Start running the VM - do this before anything else.
|
||||
*/
|
||||
VirtualMachine.prototype.start = function () {
|
||||
this.runtime.start();
|
||||
};
|
||||
|
||||
/**
|
||||
* "Green flag" handler - start all threads starting with a green flag.
|
||||
*/
|
||||
VirtualMachine.prototype.greenFlag = function () {
|
||||
this.runtime.greenFlag();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the VM is in "turbo mode."
|
||||
* When true, loops don't yield to redraw.
|
||||
* @param {Boolean} turboModeOn Whether turbo mode should be set.
|
||||
*/
|
||||
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
|
||||
this.runtime.turboMode = !!turboModeOn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the VM is in 2.0 "compatibility mode."
|
||||
* When true, ticks go at 2.0 speed (30 TPS).
|
||||
* @param {Boolean} compatibilityModeOn Whether compatibility mode is set.
|
||||
*/
|
||||
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
|
||||
this.runtime.setCompatibilityMode(!!compatibilityModeOn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop all threads and running activities.
|
||||
*/
|
||||
VirtualMachine.prototype.stopAll = function () {
|
||||
this.runtime.stopAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear out current running project data.
|
||||
*/
|
||||
VirtualMachine.prototype.clear = function () {
|
||||
this.runtime.dispose();
|
||||
this.editingTarget = null;
|
||||
this.emitTargetsUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get data for playground. Data comes back in an emitted event.
|
||||
*/
|
||||
VirtualMachine.prototype.getPlaygroundData = function () {
|
||||
var instance = this;
|
||||
// Only send back thread data for the current editingTarget.
|
||||
var threadData = this.runtime.threads.filter(function (thread) {
|
||||
return thread.target === instance.editingTarget;
|
||||
});
|
||||
// Remove the target key, since it's a circular reference.
|
||||
var filteredThreadData = JSON.stringify(threadData, function (key, value) {
|
||||
if (key === 'target') return;
|
||||
return value;
|
||||
}, 2);
|
||||
this.emit('playgroundData', {
|
||||
blocks: this.editingTarget.blocks,
|
||||
threads: filteredThreadData
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Post I/O data to the virtual devices.
|
||||
* @param {?string} device Name of virtual I/O device.
|
||||
* @param {Object} data Any data object to post to the I/O device.
|
||||
*/
|
||||
VirtualMachine.prototype.postIOData = function (device, data) {
|
||||
if (this.runtime.ioDevices[device]) {
|
||||
this.runtime.ioDevices[device].postData(data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a project from a Scratch 2.0 JSON representation.
|
||||
* @param {?string} json JSON string representing the project.
|
||||
*/
|
||||
VirtualMachine.prototype.loadProject = function (json) {
|
||||
this.clear();
|
||||
// @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
|
||||
sb2import(json, this.runtime);
|
||||
// Select the first target for editing, e.g., the first sprite.
|
||||
this.editingTarget = this.runtime.targets[1];
|
||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||
this.emitTargetsUpdate();
|
||||
this.emitWorkspaceUpdate();
|
||||
this.runtime.setEditingTarget(this.editingTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
|
||||
* @param {?string} json JSON string representing the sprite.
|
||||
*/
|
||||
VirtualMachine.prototype.addSprite2 = function (json) {
|
||||
// Select new sprite.
|
||||
this.editingTarget = sb2import(json, this.runtime, true);
|
||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||
this.emitTargetsUpdate();
|
||||
this.emitWorkspaceUpdate();
|
||||
this.runtime.setEditingTarget(this.editingTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a costume to the current editing target.
|
||||
* @param {!Object} costumeObject Object representing the costume.
|
||||
*/
|
||||
VirtualMachine.prototype.addCostume = function (costumeObject) {
|
||||
this.editingTarget.sprite.costumes.push(costumeObject);
|
||||
// Switch to the costume.
|
||||
this.editingTarget.setCostume(
|
||||
this.editingTarget.sprite.costumes.length - 1
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a backdrop to the stage.
|
||||
* @param {!Object} backdropObject Object representing the backdrop.
|
||||
*/
|
||||
VirtualMachine.prototype.addBackdrop = function (backdropObject) {
|
||||
var stage = this.runtime.getTargetForStage();
|
||||
stage.sprite.costumes.push(backdropObject);
|
||||
// Switch to the backdrop.
|
||||
stage.setCostume(stage.sprite.costumes.length - 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a sprite.
|
||||
* @param {string} targetId ID of a target whose sprite to rename.
|
||||
* @param {string} newName New name of the sprite.
|
||||
*/
|
||||
VirtualMachine.prototype.renameSprite = function (targetId, newName) {
|
||||
var target = this.runtime.getTargetById(targetId);
|
||||
if (target) {
|
||||
if (!target.isSprite()) {
|
||||
throw new Error('Cannot rename non-sprite targets.');
|
||||
}
|
||||
var sprite = target.sprite;
|
||||
if (!sprite) {
|
||||
throw new Error('No sprite associated with this target.');
|
||||
}
|
||||
sprite.name = newName;
|
||||
this.emitTargetsUpdate();
|
||||
} else {
|
||||
throw new Error('No target with the provided id.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a sprite and all its clones.
|
||||
* @param {string} targetId ID of a target whose sprite to delete.
|
||||
*/
|
||||
VirtualMachine.prototype.deleteSprite = function (targetId) {
|
||||
var target = this.runtime.getTargetById(targetId);
|
||||
if (target) {
|
||||
if (!target.isSprite()) {
|
||||
throw new Error('Cannot delete non-sprite targets.');
|
||||
}
|
||||
var sprite = target.sprite;
|
||||
if (!sprite) {
|
||||
throw new Error('No sprite associated with this target.');
|
||||
}
|
||||
var currentEditingTarget = this.editingTarget;
|
||||
for (var i = 0; i < sprite.clones.length; i++) {
|
||||
var clone = sprite.clones[i];
|
||||
this.runtime.stopForTarget(sprite.clones[i]);
|
||||
this.runtime.disposeTarget(sprite.clones[i]);
|
||||
// Ensure editing target is switched if we are deleting it.
|
||||
if (clone === currentEditingTarget) {
|
||||
this.setEditingTarget(this.runtime.targets[0].id);
|
||||
}
|
||||
}
|
||||
// Sprite object should be deleted by GC.
|
||||
this.emitTargetsUpdate();
|
||||
} else {
|
||||
throw new Error('No target with the provided id.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the renderer for the VM/runtime
|
||||
* @param {!RenderWebGL} renderer The renderer to attach
|
||||
*/
|
||||
VirtualMachine.prototype.attachRenderer = function (renderer) {
|
||||
this.runtime.attachRenderer(renderer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the audio engine for the VM/runtime
|
||||
* @param {!AudioEngine} audioEngine The audio engine to attach
|
||||
*/
|
||||
VirtualMachine.prototype.attachAudioEngine = function (audioEngine) {
|
||||
this.runtime.attachAudioEngine(audioEngine);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a Blockly event for the current editing target.
|
||||
* @param {!Blockly.Event} e Any Blockly event.
|
||||
*/
|
||||
VirtualMachine.prototype.blockListener = function (e) {
|
||||
if (this.editingTarget) {
|
||||
this.editingTarget.blocks.blocklyListen(e, this.runtime);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a Blockly event for the flyout.
|
||||
* @param {!Blockly.Event} e Any Blockly event.
|
||||
*/
|
||||
VirtualMachine.prototype.flyoutBlockListener = function (e) {
|
||||
this.runtime.flyoutBlocks.blocklyListen(e, this.runtime);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set an editing target. An editor UI can use this function to switch
|
||||
* between editing different targets, sprites, etc.
|
||||
* After switching the editing target, the VM may emit updates
|
||||
* to the list of targets and any attached workspace blocks
|
||||
* (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
|
||||
* @param {string} targetId Id of target to set as editing.
|
||||
*/
|
||||
VirtualMachine.prototype.setEditingTarget = function (targetId) {
|
||||
// Has the target id changed? If not, exit.
|
||||
if (targetId === this.editingTarget.id) {
|
||||
return;
|
||||
}
|
||||
var target = this.runtime.getTargetById(targetId);
|
||||
if (target) {
|
||||
this.editingTarget = target;
|
||||
// Emit appropriate UI updates.
|
||||
this.emitTargetsUpdate();
|
||||
this.emitWorkspaceUpdate();
|
||||
this.runtime.setEditingTarget(target);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit metadata about available targets.
|
||||
* An editor UI could use this to display a list of targets and show
|
||||
* the currently editing one.
|
||||
*/
|
||||
VirtualMachine.prototype.emitTargetsUpdate = function () {
|
||||
this.emit('targetsUpdate', {
|
||||
// [[target id, human readable target name], ...].
|
||||
targetList: this.runtime.targets.filter(function (target) {
|
||||
// Don't report clones.
|
||||
return !target.hasOwnProperty('isOriginal') || target.isOriginal;
|
||||
}).map(function (target) {
|
||||
return target.toJSON();
|
||||
}),
|
||||
// Currently editing target id.
|
||||
editingTarget: this.editingTarget ? this.editingTarget.id : null
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an Blockly/scratch-blocks compatible XML representation
|
||||
* of the current editing target's blocks.
|
||||
*/
|
||||
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
|
||||
this.emit('workspaceUpdate', {
|
||||
xml: this.editingTarget.blocks.toXML()
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Post/edit sprite info for the current editing target.
|
||||
* @param {object} data An object with sprite info data to set.
|
||||
*/
|
||||
VirtualMachine.prototype.postSpriteInfo = function (data) {
|
||||
this.editingTarget.postSpriteInfo(data);
|
||||
};
|
||||
var VirtualMachine = require('./virtual-machine');
|
||||
|
||||
module.exports = VirtualMachine;
|
||||
|
|
|
@ -137,6 +137,12 @@ RenderedTarget.prototype.size = 100;
|
|||
*/
|
||||
RenderedTarget.prototype.currentCostume = 0;
|
||||
|
||||
/**
|
||||
* Event which fires when a target moves.
|
||||
* @type {string}
|
||||
*/
|
||||
RenderedTarget.EVENT_TARGET_MOVED = 'TARGET_MOVED';
|
||||
|
||||
/**
|
||||
* Rotation style for "all around"/spinning.
|
||||
* @enum
|
||||
|
@ -184,6 +190,8 @@ RenderedTarget.prototype.setXY = function (x, y) {
|
|||
if (this.isStage) {
|
||||
return;
|
||||
}
|
||||
var oldX = this.x;
|
||||
var oldY = this.y;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
if (this.renderer) {
|
||||
|
@ -194,6 +202,7 @@ RenderedTarget.prototype.setXY = function (x, y) {
|
|||
this.runtime.requestRedraw();
|
||||
}
|
||||
}
|
||||
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY);
|
||||
this.runtime.spriteInfoReport(this);
|
||||
};
|
||||
|
||||
|
@ -370,14 +379,21 @@ RenderedTarget.prototype.setCostume = function (index) {
|
|||
);
|
||||
if (this.renderer) {
|
||||
var costume = this.sprite.costumes[this.currentCostume];
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
var drawableProperties = {
|
||||
skin: costume.skin,
|
||||
costumeResolution: costume.bitmapResolution,
|
||||
rotationCenter: [
|
||||
costume.rotationCenterX / costume.bitmapResolution,
|
||||
costume.rotationCenterY / costume.bitmapResolution
|
||||
]
|
||||
});
|
||||
costumeResolution: costume.bitmapResolution
|
||||
};
|
||||
if (
|
||||
typeof costume.rotationCenterX !== 'undefined' &&
|
||||
typeof costume.rotationCenterY !== 'undefined'
|
||||
) {
|
||||
var scale = costume.bitmapResolution || 1;
|
||||
drawableProperties.rotationCenter = [
|
||||
costume.rotationCenterX / scale,
|
||||
costume.rotationCenterY / scale
|
||||
];
|
||||
}
|
||||
this.renderer.updateDrawableProperties(this.drawableID, drawableProperties);
|
||||
if (this.visible) {
|
||||
this.runtime.requestRedraw();
|
||||
}
|
||||
|
@ -462,7 +478,7 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () {
|
|||
if (this.renderer) {
|
||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||
var costume = this.sprite.costumes[this.currentCostume];
|
||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||
var props = {
|
||||
position: [this.x, this.y],
|
||||
direction: renderedDirectionScale.direction,
|
||||
scale: renderedDirectionScale.scale,
|
||||
|
@ -473,7 +489,11 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () {
|
|||
costume.rotationCenterX / costume.bitmapResolution,
|
||||
costume.rotationCenterY / costume.bitmapResolution
|
||||
]
|
||||
});
|
||||
};
|
||||
for (var effectID in this.effects) {
|
||||
props[effectID] = this.effects[effectID];
|
||||
}
|
||||
this.renderer.updateDrawableProperties(this.drawableID, props);
|
||||
if (this.visible) {
|
||||
this.runtime.requestRedraw();
|
||||
}
|
||||
|
|
|
@ -66,18 +66,28 @@ Cast.toString = function (value) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Cast any Scratch argument to an RGB color object to be used for the renderer.
|
||||
* @param {*} value Value to convert to RGB color object.
|
||||
* Cast any Scratch argument to an RGB color array to be used for the renderer.
|
||||
* @param {*} value Value to convert to RGB color array.
|
||||
* @return {Array.<number>} [r,g,b], values between 0-255.
|
||||
*/
|
||||
Cast.toRgbColorList = function (value) {
|
||||
var color = Cast.toRgbColorObject(value);
|
||||
return [color.r, color.g, color.b];
|
||||
};
|
||||
|
||||
/**
|
||||
* Cast any Scratch argument to an RGB color object to be used for the renderer.
|
||||
* @param {*} value Value to convert to RGB color object.
|
||||
* @return {RGBOject} [r,g,b], values between 0-255.
|
||||
*/
|
||||
Cast.toRgbColorObject = function (value) {
|
||||
var color;
|
||||
if (typeof value === 'string' && value.substring(0, 1) === '#') {
|
||||
color = Color.hexToRgb(value);
|
||||
} else {
|
||||
color = Color.decimalToRgb(Cast.toNumber(value));
|
||||
}
|
||||
return [color.r, color.g, color.b];
|
||||
return color;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
17
src/util/clone.js
Normal file
17
src/util/clone.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Methods for cloning JavaScript objects.
|
||||
* @type {object}
|
||||
*/
|
||||
var Clone = {};
|
||||
|
||||
/**
|
||||
* Deep-clone a "simple" object: one which can be fully expressed with JSON.
|
||||
* Non-JSON values, such as functions, will be stripped from the clone.
|
||||
* @param {object} original - the object to be cloned.
|
||||
* @returns {object} a deep clone of the original object.
|
||||
*/
|
||||
Clone.simple = function (original) {
|
||||
return JSON.parse(JSON.stringify(original));
|
||||
};
|
||||
|
||||
module.exports = Clone;
|
|
@ -1,5 +1,25 @@
|
|||
var Color = function () {};
|
||||
|
||||
/**
|
||||
* @typedef {object} RGBObject - An object representing a color in RGB format.
|
||||
* @property {number} r - the red component, in the range [0, 255].
|
||||
* @property {number} g - the green component, in the range [0, 255].
|
||||
* @property {number} b - the blue component, in the range [0, 255].
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} HSVObject - An object representing a color in HSV format.
|
||||
* @property {number} h - hue, in the range [0-359).
|
||||
* @property {number} s - saturation, in the range [0,1].
|
||||
* @property {number} v - value, in the range [0,1].
|
||||
*/
|
||||
|
||||
/** @type {RGBObject} */
|
||||
Color.RGB_BLACK = {r: 0, g: 0, b: 0};
|
||||
|
||||
/** @type {RGBObject} */
|
||||
Color.RGB_WHITE = {r: 255, g: 255, b: 255};
|
||||
|
||||
/**
|
||||
* Convert a Scratch decimal color to a hex string, #RRGGBB.
|
||||
* @param {number} decimal RGB color as a decimal.
|
||||
|
@ -17,13 +37,14 @@ Color.decimalToHex = function (decimal) {
|
|||
/**
|
||||
* Convert a Scratch decimal color to an RGB color object.
|
||||
* @param {number} decimal RGB color as decimal.
|
||||
* @returns {Object} {r: R, g: G, b: B}, values between 0-255
|
||||
* @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
||||
*/
|
||||
Color.decimalToRgb = function (decimal) {
|
||||
var a = (decimal >> 24) & 0xFF;
|
||||
var r = (decimal >> 16) & 0xFF;
|
||||
var g = (decimal >> 8) & 0xFF;
|
||||
var b = decimal & 0xFF;
|
||||
return {r: r, g: g, b: b};
|
||||
return {r: r, g: g, b: b, a: a > 0 ? a : 255};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -31,7 +52,7 @@ Color.decimalToRgb = function (decimal) {
|
|||
* CC-BY-SA Tim Down:
|
||||
* https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
||||
* @param {!string} hex Hex representation of the color.
|
||||
* @return {Object} {r: R, g: G, b: B}, 0-255, or null.
|
||||
* @return {RGBObject} null on failure, or rgb: {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
||||
*/
|
||||
Color.hexToRgb = function (hex) {
|
||||
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
|
@ -48,7 +69,7 @@ Color.hexToRgb = function (hex) {
|
|||
|
||||
/**
|
||||
* Convert an RGB color object to a hex color.
|
||||
* @param {Object} rgb {r: R, g: G, b: B}, values between 0-255.
|
||||
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
||||
* @return {!string} Hex representation of the color.
|
||||
*/
|
||||
Color.rgbToHex = function (rgb) {
|
||||
|
@ -57,7 +78,7 @@ Color.rgbToHex = function (rgb) {
|
|||
|
||||
/**
|
||||
* Convert an RGB color object to a Scratch decimal color.
|
||||
* @param {Object} rgb {r: R, g: G, b: B}, values between 0-255.
|
||||
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
||||
* @return {!number} Number representing the color.
|
||||
*/
|
||||
Color.rgbToDecimal = function (rgb) {
|
||||
|
@ -73,4 +94,109 @@ Color.hexToDecimal = function (hex) {
|
|||
return Color.rgbToDecimal(Color.hexToRgb(hex));
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert an HSV color to RGB format.
|
||||
* @param {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]}
|
||||
* @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
||||
*/
|
||||
Color.hsvToRgb = function (hsv) {
|
||||
var h = hsv.h % 360;
|
||||
if (h < 0) h += 360;
|
||||
var s = Math.max(0, Math.min(hsv.s, 1));
|
||||
var v = Math.max(0, Math.min(hsv.v, 1));
|
||||
|
||||
var i = Math.floor(h / 60);
|
||||
var f = (h / 60) - i;
|
||||
var p = v * (1 - s);
|
||||
var q = v * (1 - (s * f));
|
||||
var t = v * (1 - (s * (1 - f)));
|
||||
|
||||
var r;
|
||||
var g;
|
||||
var b;
|
||||
|
||||
switch (i) {
|
||||
default:
|
||||
case 0:
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
case 5:
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
r: Math.floor(r * 255),
|
||||
g: Math.floor(g * 255),
|
||||
b: Math.floor(b * 255)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert an RGB color to HSV format.
|
||||
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
||||
* @return {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]}
|
||||
*/
|
||||
Color.rgbToHsv = function (rgb) {
|
||||
var r = rgb.r / 255;
|
||||
var g = rgb.g / 255;
|
||||
var b = rgb.b / 255;
|
||||
var x = Math.min(Math.min(r, g), b);
|
||||
var v = Math.max(Math.max(r, g), b);
|
||||
|
||||
// For grays, hue will be arbitrarily reported as zero. Otherwise, calculate
|
||||
var h = 0;
|
||||
var s = 0;
|
||||
if (x !== v) {
|
||||
var f = (r === x) ? g - b : ((g === x) ? b - r : r - g);
|
||||
var i = (r === x) ? 3 : ((g === x) ? 5 : 1);
|
||||
h = ((i - (f / (v - x))) * 60) % 360;
|
||||
s = (v - x) / v;
|
||||
}
|
||||
|
||||
return {h: h, s: s, v: v};
|
||||
};
|
||||
|
||||
/**
|
||||
* Linear interpolation between rgb0 and rgb1.
|
||||
* @param {RGBObject} rgb0 - the color corresponding to fraction1 <= 0.
|
||||
* @param {RGBObject} rgb1 - the color corresponding to fraction1 >= 1.
|
||||
* @param {number} fraction1 - the interpolation parameter. If this is 0.5, for example, mix the two colors equally.
|
||||
* @return {RGBObject} the interpolated color.
|
||||
*/
|
||||
Color.mixRgb = function (rgb0, rgb1, fraction1) {
|
||||
if (fraction1 <= 0) return rgb0;
|
||||
if (fraction1 >= 1) return rgb1;
|
||||
var fraction0 = 1 - fraction1;
|
||||
return {
|
||||
r: (fraction0 * rgb0.r) + (fraction1 * rgb1.r),
|
||||
g: (fraction0 * rgb0.g) + (fraction1 * rgb1.g),
|
||||
b: (fraction0 * rgb0.b) + (fraction1 * rgb1.b)
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Color;
|
||||
|
|
339
src/virtual-machine.js
Normal file
339
src/virtual-machine.js
Normal file
|
@ -0,0 +1,339 @@
|
|||
var EventEmitter = require('events');
|
||||
var util = require('util');
|
||||
|
||||
var Runtime = require('./engine/runtime');
|
||||
var sb2import = require('./import/sb2import');
|
||||
|
||||
/**
|
||||
* Handles connections between blocks, stage, and extensions.
|
||||
* @constructor
|
||||
*/
|
||||
var VirtualMachine = function () {
|
||||
var instance = this;
|
||||
// Bind event emitter and runtime to VM instance
|
||||
EventEmitter.call(instance);
|
||||
/**
|
||||
* VM runtime, to store blocks, I/O devices, sprites/targets, etc.
|
||||
* @type {!Runtime}
|
||||
*/
|
||||
instance.runtime = new Runtime();
|
||||
/**
|
||||
* The "currently editing"/selected target ID for the VM.
|
||||
* Block events from any Blockly workspace are routed to this target.
|
||||
* @type {!string}
|
||||
*/
|
||||
instance.editingTarget = null;
|
||||
// Runtime emits are passed along as VM emits.
|
||||
instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (glowData) {
|
||||
instance.emit(Runtime.SCRIPT_GLOW_ON, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (glowData) {
|
||||
instance.emit(Runtime.SCRIPT_GLOW_OFF, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (glowData) {
|
||||
instance.emit(Runtime.BLOCK_GLOW_ON, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (glowData) {
|
||||
instance.emit(Runtime.BLOCK_GLOW_OFF, glowData);
|
||||
});
|
||||
instance.runtime.on(Runtime.PROJECT_RUN_START, function () {
|
||||
instance.emit(Runtime.PROJECT_RUN_START);
|
||||
});
|
||||
instance.runtime.on(Runtime.PROJECT_RUN_STOP, function () {
|
||||
instance.emit(Runtime.PROJECT_RUN_STOP);
|
||||
});
|
||||
instance.runtime.on(Runtime.VISUAL_REPORT, function (visualReport) {
|
||||
instance.emit(Runtime.VISUAL_REPORT, visualReport);
|
||||
});
|
||||
instance.runtime.on(Runtime.SPRITE_INFO_REPORT, function (spriteInfo) {
|
||||
instance.emit(Runtime.SPRITE_INFO_REPORT, spriteInfo);
|
||||
});
|
||||
|
||||
this.blockListener = this.blockListener.bind(this);
|
||||
this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter
|
||||
*/
|
||||
util.inherits(VirtualMachine, EventEmitter);
|
||||
|
||||
/**
|
||||
* Start running the VM - do this before anything else.
|
||||
*/
|
||||
VirtualMachine.prototype.start = function () {
|
||||
this.runtime.start();
|
||||
};
|
||||
|
||||
/**
|
||||
* "Green flag" handler - start all threads starting with a green flag.
|
||||
*/
|
||||
VirtualMachine.prototype.greenFlag = function () {
|
||||
this.runtime.greenFlag();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the VM is in "turbo mode."
|
||||
* When true, loops don't yield to redraw.
|
||||
* @param {Boolean} turboModeOn Whether turbo mode should be set.
|
||||
*/
|
||||
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
|
||||
this.runtime.turboMode = !!turboModeOn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the VM is in 2.0 "compatibility mode."
|
||||
* When true, ticks go at 2.0 speed (30 TPS).
|
||||
* @param {Boolean} compatibilityModeOn Whether compatibility mode is set.
|
||||
*/
|
||||
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
|
||||
this.runtime.setCompatibilityMode(!!compatibilityModeOn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop all threads and running activities.
|
||||
*/
|
||||
VirtualMachine.prototype.stopAll = function () {
|
||||
this.runtime.stopAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear out current running project data.
|
||||
*/
|
||||
VirtualMachine.prototype.clear = function () {
|
||||
this.runtime.dispose();
|
||||
this.editingTarget = null;
|
||||
this.emitTargetsUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get data for playground. Data comes back in an emitted event.
|
||||
*/
|
||||
VirtualMachine.prototype.getPlaygroundData = function () {
|
||||
var instance = this;
|
||||
// Only send back thread data for the current editingTarget.
|
||||
var threadData = this.runtime.threads.filter(function (thread) {
|
||||
return thread.target === instance.editingTarget;
|
||||
});
|
||||
// Remove the target key, since it's a circular reference.
|
||||
var filteredThreadData = JSON.stringify(threadData, function (key, value) {
|
||||
if (key === 'target') return;
|
||||
return value;
|
||||
}, 2);
|
||||
this.emit('playgroundData', {
|
||||
blocks: this.editingTarget.blocks,
|
||||
threads: filteredThreadData
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Post I/O data to the virtual devices.
|
||||
* @param {?string} device Name of virtual I/O device.
|
||||
* @param {Object} data Any data object to post to the I/O device.
|
||||
*/
|
||||
VirtualMachine.prototype.postIOData = function (device, data) {
|
||||
if (this.runtime.ioDevices[device]) {
|
||||
this.runtime.ioDevices[device].postData(data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a project from a Scratch 2.0 JSON representation.
|
||||
* @param {?string} json JSON string representing the project.
|
||||
*/
|
||||
VirtualMachine.prototype.loadProject = function (json) {
|
||||
this.clear();
|
||||
// @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
|
||||
sb2import(json, this.runtime);
|
||||
// Select the first target for editing, e.g., the first sprite.
|
||||
this.editingTarget = this.runtime.targets[1];
|
||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||
this.emitTargetsUpdate();
|
||||
this.emitWorkspaceUpdate();
|
||||
this.runtime.setEditingTarget(this.editingTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
|
||||
* @param {?string} json JSON string representing the sprite.
|
||||
*/
|
||||
VirtualMachine.prototype.addSprite2 = function (json) {
|
||||
// Select new sprite.
|
||||
this.editingTarget = sb2import(json, this.runtime, true);
|
||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||
this.emitTargetsUpdate();
|
||||
this.emitWorkspaceUpdate();
|
||||
this.runtime.setEditingTarget(this.editingTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a costume to the current editing target.
|
||||
* @param {!Object} costumeObject Object representing the costume.
|
||||
*/
|
||||
VirtualMachine.prototype.addCostume = function (costumeObject) {
|
||||
this.editingTarget.sprite.costumes.push(costumeObject);
|
||||
// Switch to the costume.
|
||||
this.editingTarget.setCostume(
|
||||
this.editingTarget.sprite.costumes.length - 1
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a backdrop to the stage.
|
||||
* @param {!Object} backdropObject Object representing the backdrop.
|
||||
*/
|
||||
VirtualMachine.prototype.addBackdrop = function (backdropObject) {
|
||||
var stage = this.runtime.getTargetForStage();
|
||||
stage.sprite.costumes.push(backdropObject);
|
||||
// Switch to the backdrop.
|
||||
stage.setCostume(stage.sprite.costumes.length - 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a sprite.
|
||||
* @param {string} targetId ID of a target whose sprite to rename.
|
||||
* @param {string} newName New name of the sprite.
|
||||
*/
|
||||
VirtualMachine.prototype.renameSprite = function (targetId, newName) {
|
||||
var target = this.runtime.getTargetById(targetId);
|
||||
if (target) {
|
||||
if (!target.isSprite()) {
|
||||
throw new Error('Cannot rename non-sprite targets.');
|
||||
}
|
||||
var sprite = target.sprite;
|
||||
if (!sprite) {
|
||||
throw new Error('No sprite associated with this target.');
|
||||
}
|
||||
sprite.name = newName;
|
||||
this.emitTargetsUpdate();
|
||||
} else {
|
||||
throw new Error('No target with the provided id.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a sprite and all its clones.
|
||||
* @param {string} targetId ID of a target whose sprite to delete.
|
||||
*/
|
||||
VirtualMachine.prototype.deleteSprite = function (targetId) {
|
||||
var target = this.runtime.getTargetById(targetId);
|
||||
if (target) {
|
||||
if (!target.isSprite()) {
|
||||
throw new Error('Cannot delete non-sprite targets.');
|
||||
}
|
||||
var sprite = target.sprite;
|
||||
if (!sprite) {
|
||||
throw new Error('No sprite associated with this target.');
|
||||
}
|
||||
var currentEditingTarget = this.editingTarget;
|
||||
for (var i = 0; i < sprite.clones.length; i++) {
|
||||
var clone = sprite.clones[i];
|
||||
this.runtime.stopForTarget(sprite.clones[i]);
|
||||
this.runtime.disposeTarget(sprite.clones[i]);
|
||||
// Ensure editing target is switched if we are deleting it.
|
||||
if (clone === currentEditingTarget) {
|
||||
this.setEditingTarget(this.runtime.targets[0].id);
|
||||
}
|
||||
}
|
||||
// Sprite object should be deleted by GC.
|
||||
this.emitTargetsUpdate();
|
||||
} else {
|
||||
throw new Error('No target with the provided id.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the renderer for the VM/runtime
|
||||
* @param {!RenderWebGL} renderer The renderer to attach
|
||||
*/
|
||||
VirtualMachine.prototype.attachRenderer = function (renderer) {
|
||||
this.runtime.attachRenderer(renderer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the audio engine for the VM/runtime
|
||||
* @param {!AudioEngine} audioEngine The audio engine to attach
|
||||
*/
|
||||
VirtualMachine.prototype.attachAudioEngine = function (audioEngine) {
|
||||
this.runtime.attachAudioEngine(audioEngine);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a Blockly event for the current editing target.
|
||||
* @param {!Blockly.Event} e Any Blockly event.
|
||||
*/
|
||||
VirtualMachine.prototype.blockListener = function (e) {
|
||||
if (this.editingTarget) {
|
||||
this.editingTarget.blocks.blocklyListen(e, this.runtime);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a Blockly event for the flyout.
|
||||
* @param {!Blockly.Event} e Any Blockly event.
|
||||
*/
|
||||
VirtualMachine.prototype.flyoutBlockListener = function (e) {
|
||||
this.runtime.flyoutBlocks.blocklyListen(e, this.runtime);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set an editing target. An editor UI can use this function to switch
|
||||
* between editing different targets, sprites, etc.
|
||||
* After switching the editing target, the VM may emit updates
|
||||
* to the list of targets and any attached workspace blocks
|
||||
* (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
|
||||
* @param {string} targetId Id of target to set as editing.
|
||||
*/
|
||||
VirtualMachine.prototype.setEditingTarget = function (targetId) {
|
||||
// Has the target id changed? If not, exit.
|
||||
if (targetId === this.editingTarget.id) {
|
||||
return;
|
||||
}
|
||||
var target = this.runtime.getTargetById(targetId);
|
||||
if (target) {
|
||||
this.editingTarget = target;
|
||||
// Emit appropriate UI updates.
|
||||
this.emitTargetsUpdate();
|
||||
this.emitWorkspaceUpdate();
|
||||
this.runtime.setEditingTarget(target);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit metadata about available targets.
|
||||
* An editor UI could use this to display a list of targets and show
|
||||
* the currently editing one.
|
||||
*/
|
||||
VirtualMachine.prototype.emitTargetsUpdate = function () {
|
||||
this.emit('targetsUpdate', {
|
||||
// [[target id, human readable target name], ...].
|
||||
targetList: this.runtime.targets.filter(function (target) {
|
||||
// Don't report clones.
|
||||
return !target.hasOwnProperty('isOriginal') || target.isOriginal;
|
||||
}).map(function (target) {
|
||||
return target.toJSON();
|
||||
}),
|
||||
// Currently editing target id.
|
||||
editingTarget: this.editingTarget ? this.editingTarget.id : null
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an Blockly/scratch-blocks compatible XML representation
|
||||
* of the current editing target's blocks.
|
||||
*/
|
||||
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
|
||||
this.emit('workspaceUpdate', {
|
||||
xml: this.editingTarget.blocks.toXML()
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Post/edit sprite info for the current editing target.
|
||||
* @param {object} data An object with sprite info data to set.
|
||||
*/
|
||||
VirtualMachine.prototype.postSpriteInfo = function (data) {
|
||||
this.editingTarget.postSpriteInfo(data);
|
||||
};
|
||||
|
||||
module.exports = VirtualMachine;
|
BIN
test/fixtures/pen.sb2
vendored
Normal file
BIN
test/fixtures/pen.sb2
vendored
Normal file
Binary file not shown.
34
test/integration/pen.js
Normal file
34
test/integration/pen.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
var path = require('path');
|
||||
var test = require('tap').test;
|
||||
var extract = require('../fixtures/extract');
|
||||
var VirtualMachine = require('../../src/index');
|
||||
|
||||
var uri = path.resolve(__dirname, '../fixtures/pen.sb2');
|
||||
var project = extract(uri);
|
||||
|
||||
test('pen', function (t) {
|
||||
var vm = new VirtualMachine();
|
||||
|
||||
// Evaluate playground data and exit
|
||||
vm.on('playgroundData', function () {
|
||||
// @todo Additional tests
|
||||
t.end();
|
||||
process.nextTick(process.exit);
|
||||
});
|
||||
|
||||
// Start VM, load project, and run
|
||||
t.doesNotThrow(function () {
|
||||
vm.start();
|
||||
vm.clear();
|
||||
vm.setCompatibilityMode(false);
|
||||
vm.setTurboMode(false);
|
||||
vm.loadProject(project);
|
||||
vm.greenFlag();
|
||||
});
|
||||
|
||||
// After two seconds, get playground data and stop
|
||||
setTimeout(function () {
|
||||
vm.getPlaygroundData();
|
||||
vm.stopAll();
|
||||
}, 2000);
|
||||
});
|
|
@ -91,6 +91,25 @@ test('toRbgColorList', function (t) {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('toRbgColorObject', function (t) {
|
||||
// Hex (minimal, see "color" util tests)
|
||||
t.deepEqual(cast.toRgbColorObject('#000'), {r: 0, g: 0, b: 0});
|
||||
t.deepEqual(cast.toRgbColorObject('#000000'), {r: 0, g: 0, b: 0});
|
||||
t.deepEqual(cast.toRgbColorObject('#fff'), {r: 255, g: 255, b: 255});
|
||||
t.deepEqual(cast.toRgbColorObject('#ffffff'), {r: 255, g: 255, b: 255});
|
||||
|
||||
// Decimal (minimal, see "color" util tests)
|
||||
t.deepEqual(cast.toRgbColorObject(0), {a: 255, r: 0, g: 0, b: 0});
|
||||
t.deepEqual(cast.toRgbColorObject(1), {a: 255, r: 0, g: 0, b: 1});
|
||||
t.deepEqual(cast.toRgbColorObject(16777215), {a: 255, r: 255, g: 255, b: 255});
|
||||
t.deepEqual(cast.toRgbColorObject('0x80010203'), {a: 128, r: 1, g: 2, b: 3});
|
||||
|
||||
// Malformed
|
||||
t.deepEqual(cast.toRgbColorObject('ffffff'), {a: 255, r: 0, g: 0, b: 0});
|
||||
t.deepEqual(cast.toRgbColorObject('foobar'), {a: 255, r: 0, g: 0, b: 0});
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('compare', function (t) {
|
||||
// Numeric
|
||||
t.strictEqual(cast.compare(0, 0), 0);
|
||||
|
|
|
@ -1,6 +1,42 @@
|
|||
var test = require('tap').test;
|
||||
var color = require('../../src/util/color');
|
||||
|
||||
/**
|
||||
* Assert that two HSV colors are similar to each other, within a tolerance.
|
||||
* @param {Test} t - the Tap test object.
|
||||
* @param {HSVObject} actual - the first HSV color to compare.
|
||||
* @param {HSVObject} expected - the other HSV color to compare.
|
||||
*/
|
||||
var hsvSimilar = function (t, actual, expected) {
|
||||
if ((Math.abs(actual.h - expected.h) >= 1) ||
|
||||
(Math.abs(actual.s - expected.s) >= 0.01) ||
|
||||
(Math.abs(actual.v - expected.v) >= 0.01)
|
||||
) {
|
||||
t.fail('HSV colors not similar enough', {
|
||||
actual: actual,
|
||||
expected: expected
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Assert that two RGB colors are similar to each other, within a tolerance.
|
||||
* @param {Test} t - the Tap test object.
|
||||
* @param {RGBObject} actual - the first RGB color to compare.
|
||||
* @param {RGBObject} expected - the other RGB color to compare.
|
||||
*/
|
||||
var rgbSimilar = function (t, actual, expected) {
|
||||
if ((Math.abs(actual.r - expected.r) >= 1) ||
|
||||
(Math.abs(actual.g - expected.g) >= 1) ||
|
||||
(Math.abs(actual.b - expected.b) >= 1)
|
||||
) {
|
||||
t.fail('RGB colors not similar enough', {
|
||||
actual: actual,
|
||||
expected: expected
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
test('decimalToHex', function (t) {
|
||||
t.strictEqual(color.decimalToHex(0), '#000000');
|
||||
t.strictEqual(color.decimalToHex(1), '#000001');
|
||||
|
@ -11,11 +47,11 @@ test('decimalToHex', function (t) {
|
|||
});
|
||||
|
||||
test('decimalToRgb', function (t) {
|
||||
t.deepEqual(color.decimalToRgb(0), {r: 0, g: 0, b: 0});
|
||||
t.deepEqual(color.decimalToRgb(1), {r: 0, g: 0, b: 1});
|
||||
t.deepEqual(color.decimalToRgb(16777215), {r: 255, g: 255, b: 255});
|
||||
t.deepEqual(color.decimalToRgb(-16777215), {r: 0, g: 0, b: 1});
|
||||
t.deepEqual(color.decimalToRgb(99999999), {r: 245, g: 224, b: 255});
|
||||
t.deepEqual(color.decimalToRgb(0), {a: 255, r: 0, g: 0, b: 0});
|
||||
t.deepEqual(color.decimalToRgb(1), {a: 255, r: 0, g: 0, b: 1});
|
||||
t.deepEqual(color.decimalToRgb(16777215), {a: 255, r: 255, g: 255, b: 255});
|
||||
t.deepEqual(color.decimalToRgb(-16777215), {a: 255, r: 0, g: 0, b: 1});
|
||||
t.deepEqual(color.decimalToRgb(99999999), {a: 5, r: 245, g: 224, b: 255});
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -60,3 +96,37 @@ test('hexToDecimal', function (t) {
|
|||
t.strictEqual(color.hexToDecimal('#00ffaa'), 65450);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('hsvToRgb', function (t) {
|
||||
rgbSimilar(t, color.hsvToRgb({h: 0, s: 0, v: 0}), {r: 0, g: 0, b: 0});
|
||||
rgbSimilar(t, color.hsvToRgb({h: 123, s: 0.1234, v: 0}), {r: 0, g: 0, b: 0});
|
||||
rgbSimilar(t, color.hsvToRgb({h: 0, s: 0, v: 1}), {r: 255, g: 255, b: 255});
|
||||
rgbSimilar(t, color.hsvToRgb({h: 321, s: 0, v: 1}), {r: 255, g: 255, b: 255});
|
||||
rgbSimilar(t, color.hsvToRgb({h: 0, s: 1, v: 1}), {r: 255, g: 0, b: 0});
|
||||
rgbSimilar(t, color.hsvToRgb({h: 120, s: 1, v: 1}), {r: 0, g: 255, b: 0});
|
||||
rgbSimilar(t, color.hsvToRgb({h: 240, s: 1, v: 1}), {r: 0, g: 0, b: 255});
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('rgbToHsv', function (t) {
|
||||
hsvSimilar(t, color.rgbToHsv({r: 0, g: 0, b: 0}), {h: 0, s: 0, v: 0});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 64, g: 64, b: 64}), {h: 0, s: 0, v: 0.25});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 128, g: 128, b: 128}), {h: 0, s: 0, v: 0.5});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 192, g: 192, b: 192}), {h: 0, s: 0, v: 0.75});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 255, g: 255, b: 255}), {h: 0, s: 0, v: 1});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 255, g: 0, b: 0}), {h: 0, s: 1, v: 1});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 0, g: 255, b: 0}), {h: 120, s: 1, v: 1});
|
||||
hsvSimilar(t, color.rgbToHsv({r: 0, g: 0, b: 255}), {h: 240, s: 1, v: 1});
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('mixRgb', function (t) {
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, -1), {r: 10, g: 20, b: 30});
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, 0), {r: 10, g: 20, b: 30});
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, 0.25), {r: 15, g: 25, b: 35});
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, 0.5), {r: 20, g: 30, b: 40});
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, 0.75), {r: 25, g: 35, b: 45});
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, 1), {r: 30, g: 40, b: 50});
|
||||
rgbSimilar(t, color.mixRgb({r: 10, g: 20, b: 30}, {r: 30, g: 40, b: 50}, 2), {r: 30, g: 40, b: 50});
|
||||
t.end();
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ var base = {
|
|||
host: '0.0.0.0',
|
||||
port: process.env.PORT || 8073
|
||||
},
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
|
@ -29,14 +30,15 @@ var base = {
|
|||
};
|
||||
|
||||
module.exports = [
|
||||
// Web-compatible, playground
|
||||
// Web-compatible
|
||||
defaultsDeep({}, base, {
|
||||
target: 'web',
|
||||
entry: {
|
||||
'vm': './src/index.js',
|
||||
'vm.min': './src/index.js'
|
||||
'scratch-vm': './src/index.js',
|
||||
'scratch-vm.min': './src/index.js'
|
||||
},
|
||||
output: {
|
||||
path: __dirname,
|
||||
path: path.resolve(__dirname, 'dist/web'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
|
@ -48,24 +50,25 @@ module.exports = [
|
|||
])
|
||||
}
|
||||
}),
|
||||
// Webpack-compatible
|
||||
// Node-compatible
|
||||
defaultsDeep({}, base, {
|
||||
target: 'node',
|
||||
entry: {
|
||||
dist: './src/index.js'
|
||||
'scratch-vm': './src/index.js'
|
||||
},
|
||||
|
||||
output: {
|
||||
library: 'VirtualMachine',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: __dirname,
|
||||
path: path.resolve(__dirname, 'dist/node'),
|
||||
filename: '[name].js'
|
||||
}
|
||||
}),
|
||||
// Playground
|
||||
defaultsDeep({}, base, {
|
||||
target: 'web',
|
||||
entry: {
|
||||
vm: './src/index.js',
|
||||
vendor: [
|
||||
'scratch-vm': './src/index.js',
|
||||
'vendor': [
|
||||
// FPS counter
|
||||
'stats.js/build/stats.min.js',
|
||||
// Syntax highlighter
|
||||
|
|
Loading…
Reference in a new issue