mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-27 00:12:57 -05:00
1e3907e922
Added formatMessage to the name attribute for: * music * pen * translate * video motion Skipped speak extension - trademarked name Skipped extensions that are not yet localized at all.
757 lines
30 KiB
JavaScript
757 lines
30 KiB
JavaScript
const ArgumentType = require('../../extension-support/argument-type');
|
|
const BlockType = require('../../extension-support/block-type');
|
|
const Cast = require('../../util/cast');
|
|
const Clone = require('../../util/clone');
|
|
const Color = require('../../util/color');
|
|
const formatMessage = require('format-message');
|
|
const MathUtil = require('../../util/math-util');
|
|
const RenderedTarget = require('../../sprites/rendered-target');
|
|
const log = require('../../util/log');
|
|
const StageLayering = require('../../engine/stage-layering');
|
|
|
|
/**
|
|
* Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
|
|
* @type {string}
|
|
*/
|
|
// eslint-disable-next-line max-len
|
|
const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+cGVuLWljb248L3RpdGxlPjxnIHN0cm9rZT0iIzU3NUU3NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik04Ljc1MyAzNC42MDJsLTQuMjUgMS43OCAxLjc4My00LjIzN2MxLjIxOC0yLjg5MiAyLjkwNy01LjQyMyA1LjAzLTcuNTM4TDMxLjA2NiA0LjkzYy44NDYtLjg0MiAyLjY1LS40MSA0LjAzMi45NjcgMS4zOCAxLjM3NSAxLjgxNiAzLjE3My45NyA0LjAxNUwxNi4zMTggMjkuNTljLTIuMTIzIDIuMTE2LTQuNjY0IDMuOC03LjU2NSA1LjAxMiIgZmlsbD0iI0ZGRiIvPjxwYXRoIGQ9Ik0yOS40MSA2LjExcy00LjQ1LTIuMzc4LTguMjAyIDUuNzcyYy0xLjczNCAzLjc2Ni00LjM1IDEuNTQ2LTQuMzUgMS41NDYiLz48cGF0aCBkPSJNMzYuNDIgOC44MjVjMCAuNDYzLS4xNC44NzMtLjQzMiAxLjE2NGwtOS4zMzUgOS4zYy4yODItLjI5LjQxLS42NjguNDEtMS4xMiAwLS44NzQtLjUwNy0xLjk2My0xLjQwNi0yLjg2OC0xLjM2Mi0xLjM1OC0zLjE0Ny0xLjgtNC4wMDItLjk5TDMwLjk5IDUuMDFjLjg0NC0uODQgMi42NS0uNDEgNC4wMzUuOTYuODk4LjkwNCAxLjM5NiAxLjk4MiAxLjM5NiAyLjg1NU0xMC41MTUgMzMuNzc0Yy0uNTczLjMwMi0xLjE1Ny41Ny0xLjc2NC44M0w0LjUgMzYuMzgybDEuNzg2LTQuMjM1Yy4yNTgtLjYwNC41My0xLjE4Ni44MzMtMS43NTcuNjkuMTgzIDEuNDQ4LjYyNSAyLjEwOCAxLjI4Mi42Ni42NTggMS4xMDIgMS40MTIgMS4yODcgMi4xMDIiIGZpbGw9IiM0Qzk3RkYiLz48cGF0aCBkPSJNMzYuNDk4IDguNzQ4YzAgLjQ2NC0uMTQuODc0LS40MzMgMS4xNjVsLTE5Ljc0MiAxOS42OGMtMi4xMyAyLjExLTQuNjczIDMuNzkzLTcuNTcyIDUuMDFMNC41IDM2LjM4bC45NzQtMi4zMTYgMS45MjUtLjgwOGMyLjg5OC0xLjIxOCA1LjQ0LTIuOSA3LjU3LTUuMDFsMTkuNzQzLTE5LjY4Yy4yOTItLjI5Mi40MzItLjcwMi40MzItMS4xNjUgMC0uNjQ2LS4yNy0xLjQtLjc4LTIuMTIyLjI1LjE3Mi41LjM3Ny43MzcuNjE0Ljg5OC45MDUgMS4zOTYgMS45ODMgMS4zOTYgMi44NTYiIGZpbGw9IiM1NzVFNzUiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0xOC40NSAxMi44M2MwIC41LS40MDQuOTA1LS45MDQuOTA1cy0uOTA1LS40MDUtLjkwNS0uOTA0YzAtLjUuNDA3LS45MDMuOTA2LS45MDMuNSAwIC45MDQuNDA0LjkwNC45MDR6IiBmaWxsPSIjNTc1RTc1Ii8+PC9nPjwvc3ZnPg==';
|
|
|
|
/**
|
|
* Enum for pen color parameter values.
|
|
* @readonly
|
|
* @enum {string}
|
|
*/
|
|
const ColorParam = {
|
|
COLOR: 'color',
|
|
SATURATION: 'saturation',
|
|
BRIGHTNESS: 'brightness',
|
|
TRANSPARENCY: 'transparency'
|
|
};
|
|
|
|
/**
|
|
* @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} color - the current color (hue) 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
|
|
*/
|
|
class Scratch3PenBlocks {
|
|
constructor (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._onTargetCreated = this._onTargetCreated.bind(this);
|
|
this._onTargetMoved = this._onTargetMoved.bind(this);
|
|
|
|
runtime.on('targetWasCreated', this._onTargetCreated);
|
|
}
|
|
|
|
/**
|
|
* The default pen state, to be used when a target has no existing pen state.
|
|
* @type {PenState}
|
|
*/
|
|
static get DEFAULT_PEN_STATE () {
|
|
return {
|
|
penDown: false,
|
|
color: 66.66,
|
|
saturation: 100,
|
|
brightness: 100,
|
|
transparency: 0,
|
|
_shade: 50, // Used only for legacy `change shade by` blocks
|
|
penAttributes: {
|
|
color4f: [0, 0, 1, 1],
|
|
diameter: 1
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* The minimum and maximum allowed pen size.
|
|
* The maximum is twice the diagonal of the stage, so that even an
|
|
* off-stage sprite can fill it.
|
|
* @type {{min: number, max: number}}
|
|
*/
|
|
static get PEN_SIZE_RANGE () {
|
|
return {min: 1, max: 1200};
|
|
}
|
|
|
|
/**
|
|
* The key to load & store a target's pen-related state.
|
|
* @type {string}
|
|
*/
|
|
static get STATE_KEY () {
|
|
return '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
|
|
*/
|
|
_clampPenSize (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
|
|
*/
|
|
_getPenLayerID () {
|
|
if (this._penSkinId < 0 && this.runtime.renderer) {
|
|
this._penSkinId = this.runtime.renderer.createPenSkin();
|
|
this._penDrawableId = this.runtime.renderer.createDrawable(StageLayering.PEN_LAYER);
|
|
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
|
|
*/
|
|
_getPenState (target) {
|
|
let penState = target.getCustomState(Scratch3PenBlocks.STATE_KEY);
|
|
if (!penState) {
|
|
penState = Clone.simple(Scratch3PenBlocks.DEFAULT_PEN_STATE);
|
|
target.setCustomState(Scratch3PenBlocks.STATE_KEY, penState);
|
|
}
|
|
return penState;
|
|
}
|
|
|
|
/**
|
|
* When a pen-using Target is cloned, clone the pen state.
|
|
* @param {Target} newTarget - the newly created target.
|
|
* @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.
|
|
* @listens Runtime#event:targetWasCreated
|
|
* @private
|
|
*/
|
|
_onTargetCreated (newTarget, sourceTarget) {
|
|
if (sourceTarget) {
|
|
const penState = sourceTarget.getCustomState(Scratch3PenBlocks.STATE_KEY);
|
|
if (penState) {
|
|
newTarget.setCustomState(Scratch3PenBlocks.STATE_KEY, Clone.simple(penState));
|
|
if (penState.penDown) {
|
|
newTarget.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param {boolean} isForce - whether the movement was forced.
|
|
* @private
|
|
*/
|
|
_onTargetMoved (target, oldX, oldY, isForce) {
|
|
// Only move the pen if the movement isn't forced (ie. dragged).
|
|
if (!isForce) {
|
|
const penSkinId = this._getPenLayerID();
|
|
if (penSkinId >= 0) {
|
|
const penState = this._getPenState(target);
|
|
this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y);
|
|
this.runtime.requestRedraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap a color input into the range (0,100).
|
|
* @param {number} value - the value to be wrapped.
|
|
* @returns {number} the wrapped value.
|
|
* @private
|
|
*/
|
|
_wrapColor (value) {
|
|
return MathUtil.wrapClamp(value, 0, 100);
|
|
}
|
|
|
|
/**
|
|
* Initialize color parameters menu with localized strings
|
|
* @returns {array} of the localized text and values for each menu element
|
|
* @private
|
|
*/
|
|
_initColorParam () {
|
|
return [
|
|
{
|
|
text: formatMessage({
|
|
id: 'pen.colorMenu.color',
|
|
default: 'color',
|
|
description: 'label for color element in color picker for pen extension'
|
|
}),
|
|
value: ColorParam.COLOR
|
|
},
|
|
{
|
|
text: formatMessage({
|
|
id: 'pen.colorMenu.saturation',
|
|
default: 'saturation',
|
|
description: 'label for saturation element in color picker for pen extension'
|
|
}),
|
|
value: ColorParam.SATURATION
|
|
},
|
|
{
|
|
text: formatMessage({
|
|
id: 'pen.colorMenu.brightness',
|
|
default: 'brightness',
|
|
description: 'label for brightness element in color picker for pen extension'
|
|
}),
|
|
value: ColorParam.BRIGHTNESS
|
|
},
|
|
{
|
|
text: formatMessage({
|
|
id: 'pen.colorMenu.transparency',
|
|
default: 'transparency',
|
|
description: 'label for transparency element in color picker for pen extension'
|
|
}),
|
|
value: ColorParam.TRANSPARENCY
|
|
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Clamp a pen color parameter to the range (0,100).
|
|
* @param {number} value - the value to be clamped.
|
|
* @returns {number} the clamped value.
|
|
* @private
|
|
*/
|
|
_clampColorParam (value) {
|
|
return MathUtil.clamp(value, 0, 100);
|
|
}
|
|
|
|
/**
|
|
* Convert an alpha value to a pen transparency value.
|
|
* Alpha ranges from 0 to 1, where 0 is transparent and 1 is opaque.
|
|
* Transparency ranges from 0 to 100, where 0 is opaque and 100 is transparent.
|
|
* @param {number} alpha - the input alpha value.
|
|
* @returns {number} the transparency value.
|
|
* @private
|
|
*/
|
|
_alphaToTransparency (alpha) {
|
|
return (1.0 - alpha) * 100.0;
|
|
}
|
|
|
|
/**
|
|
* Convert a pen transparency value to an alpha value.
|
|
* Alpha ranges from 0 to 1, where 0 is transparent and 1 is opaque.
|
|
* Transparency ranges from 0 to 100, where 0 is opaque and 100 is transparent.
|
|
* @param {number} transparency - the input transparency value.
|
|
* @returns {number} the alpha value.
|
|
* @private
|
|
*/
|
|
_transparencyToAlpha (transparency) {
|
|
return 1.0 - (transparency / 100.0);
|
|
}
|
|
|
|
/**
|
|
* @returns {object} metadata for this extension and its blocks.
|
|
*/
|
|
getInfo () {
|
|
return {
|
|
id: 'pen',
|
|
name: formatMessage({
|
|
id: 'pen.categoryName',
|
|
default: 'Pen',
|
|
description: 'Label for the pen extension category'
|
|
}),
|
|
blockIconURI: blockIconURI,
|
|
blocks: [
|
|
{
|
|
opcode: 'clear',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.clear',
|
|
default: 'erase all',
|
|
description: 'erase all pen trails and stamps'
|
|
})
|
|
},
|
|
{
|
|
opcode: 'stamp',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.stamp',
|
|
default: 'stamp',
|
|
description: 'render current costume on the background'
|
|
})
|
|
},
|
|
{
|
|
opcode: 'penDown',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.penDown',
|
|
default: 'pen down',
|
|
description: 'start leaving a trail when the sprite moves'
|
|
})
|
|
},
|
|
{
|
|
opcode: 'penUp',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.penUp',
|
|
default: 'pen up',
|
|
description: 'stop leaving a trail behind the sprite'
|
|
})
|
|
},
|
|
{
|
|
opcode: 'setPenColorToColor',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.setColor',
|
|
default: 'set pen color to [COLOR]',
|
|
description: 'set the pen color to a particular (RGB) value'
|
|
}),
|
|
arguments: {
|
|
COLOR: {
|
|
type: ArgumentType.COLOR
|
|
}
|
|
}
|
|
},
|
|
{
|
|
opcode: 'changePenColorParamBy',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.changeColorParam',
|
|
default: 'change pen [COLOR_PARAM] by [VALUE]',
|
|
description: 'change the state of a pen color parameter'
|
|
}),
|
|
arguments: {
|
|
COLOR_PARAM: {
|
|
type: ArgumentType.STRING,
|
|
menu: 'colorParam',
|
|
defaultValue: ColorParam.COLOR
|
|
},
|
|
VALUE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 10
|
|
}
|
|
}
|
|
},
|
|
{
|
|
opcode: 'setPenColorParamTo',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.setColorParam',
|
|
default: 'set pen [COLOR_PARAM] to [VALUE]',
|
|
description: 'set the state for a pen color parameter e.g. saturation'
|
|
}),
|
|
arguments: {
|
|
COLOR_PARAM: {
|
|
type: ArgumentType.STRING,
|
|
menu: 'colorParam',
|
|
defaultValue: ColorParam.COLOR
|
|
},
|
|
VALUE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 50
|
|
}
|
|
}
|
|
},
|
|
{
|
|
opcode: 'changePenSizeBy',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.changeSize',
|
|
default: 'change pen size by [SIZE]',
|
|
description: 'change the diameter of the trail left by a sprite'
|
|
}),
|
|
arguments: {
|
|
SIZE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 1
|
|
}
|
|
}
|
|
},
|
|
{
|
|
opcode: 'setPenSizeTo',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.setSize',
|
|
default: 'set pen size to [SIZE]',
|
|
description: 'set the diameter of a trail left by a sprite'
|
|
}),
|
|
arguments: {
|
|
SIZE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 1
|
|
}
|
|
}
|
|
},
|
|
/* Legacy blocks, should not be shown in flyout */
|
|
{
|
|
opcode: 'setPenShadeToNumber',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.setShade',
|
|
default: 'set pen shade to [SHADE]',
|
|
description: 'legacy pen blocks - set pen shade'
|
|
}),
|
|
arguments: {
|
|
SHADE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 1
|
|
}
|
|
},
|
|
hideFromPalette: true
|
|
},
|
|
{
|
|
opcode: 'changePenShadeBy',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.changeShade',
|
|
default: 'change pen shade by [SHADE]',
|
|
description: 'legacy pen blocks - change pen shade'
|
|
}),
|
|
arguments: {
|
|
SHADE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 1
|
|
}
|
|
},
|
|
hideFromPalette: true
|
|
},
|
|
{
|
|
opcode: 'setPenHueToNumber',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.setHue',
|
|
default: 'set pen color to [HUE]',
|
|
description: 'legacy pen blocks - set pen color to number'
|
|
}),
|
|
arguments: {
|
|
HUE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 1
|
|
}
|
|
},
|
|
hideFromPalette: true
|
|
},
|
|
{
|
|
opcode: 'changePenHueBy',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.changeHue',
|
|
default: 'change pen color by [HUE]',
|
|
description: 'legacy pen blocks - change pen color'
|
|
}),
|
|
arguments: {
|
|
HUE: {
|
|
type: ArgumentType.NUMBER,
|
|
defaultValue: 1
|
|
}
|
|
},
|
|
hideFromPalette: true
|
|
}
|
|
],
|
|
menus: {
|
|
colorParam: this._initColorParam()
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The pen "clear" block clears the pen layer's contents.
|
|
*/
|
|
clear () {
|
|
const 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.
|
|
*/
|
|
stamp (args, util) {
|
|
const penSkinId = this._getPenLayerID();
|
|
if (penSkinId >= 0) {
|
|
const 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.
|
|
*/
|
|
penDown (args, util) {
|
|
const target = util.target;
|
|
const penState = this._getPenState(target);
|
|
|
|
if (!penState.penDown) {
|
|
penState.penDown = true;
|
|
target.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
|
|
}
|
|
|
|
const 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.
|
|
*/
|
|
penUp (args, util) {
|
|
const target = util.target;
|
|
const 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.
|
|
* The transparency is reset to 0.
|
|
* @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.
|
|
*/
|
|
setPenColorToColor (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
const rgb = Cast.toRgbColorObject(args.COLOR);
|
|
const hsv = Color.rgbToHsv(rgb);
|
|
penState.color = (hsv.h / 360) * 100;
|
|
penState.saturation = hsv.s * 100;
|
|
penState.brightness = hsv.v * 100;
|
|
if (rgb.hasOwnProperty('a')) {
|
|
penState.transparency = 100 * (1 - (rgb.a / 255.0));
|
|
} else {
|
|
penState.transparency = 0;
|
|
}
|
|
|
|
// Set the legacy "shade" value the same way scratch 2 did.
|
|
penState._shade = penState.brightness / 2;
|
|
|
|
this._updatePenColor(penState);
|
|
}
|
|
|
|
/**
|
|
* Update the cached color from the color, saturation, brightness and transparency values
|
|
* in the provided PenState object.
|
|
* @param {PenState} penState - the pen state to update.
|
|
* @private
|
|
*/
|
|
_updatePenColor (penState) {
|
|
const rgb = Color.hsvToRgb({
|
|
h: penState.color * 360 / 100,
|
|
s: penState.saturation / 100,
|
|
v: penState.brightness / 100
|
|
});
|
|
penState.penAttributes.color4f[0] = rgb.r / 255.0;
|
|
penState.penAttributes.color4f[1] = rgb.g / 255.0;
|
|
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
|
penState.penAttributes.color4f[3] = this._transparencyToAlpha(penState.transparency);
|
|
}
|
|
|
|
/**
|
|
* Set or change a single color parameter on the pen state, and update the pen color.
|
|
* @param {ColorParam} param - the name of the color parameter to set or change.
|
|
* @param {number} value - the value to set or change the param by.
|
|
* @param {PenState} penState - the pen state to update.
|
|
* @param {boolean} change - if true change param by value, if false set param to value.
|
|
* @private
|
|
*/
|
|
_setOrChangeColorParam (param, value, penState, change) {
|
|
switch (param) {
|
|
case ColorParam.COLOR:
|
|
penState.color = this._wrapColor(value + (change ? penState.color : 0));
|
|
break;
|
|
case ColorParam.SATURATION:
|
|
penState.saturation = this._clampColorParam(value + (change ? penState.saturation : 0));
|
|
break;
|
|
case ColorParam.BRIGHTNESS:
|
|
penState.brightness = this._clampColorParam(value + (change ? penState.brightness : 0));
|
|
break;
|
|
case ColorParam.TRANSPARENCY:
|
|
penState.transparency = this._clampColorParam(value + (change ? penState.transparency : 0));
|
|
break;
|
|
default:
|
|
log.warn(`Tried to set or change unknown color parameter: ${param}`);
|
|
}
|
|
this._updatePenColor(penState);
|
|
}
|
|
|
|
/**
|
|
* The "change pen {ColorParam} by {number}" block changes one of the pen's color parameters
|
|
* by a given amound.
|
|
* @param {object} args - the block arguments.
|
|
* @property {ColorParam} COLOR_PARAM - the name of the selected color parameter.
|
|
* @property {number} VALUE - the amount to change the selected parameter by.
|
|
* @param {object} util - utility object provided by the runtime.
|
|
*/
|
|
changePenColorParamBy (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
this._setOrChangeColorParam(args.COLOR_PARAM, Cast.toNumber(args.VALUE), penState, true);
|
|
}
|
|
|
|
/**
|
|
* The "set pen {ColorParam} to {number}" block sets one of the pen's color parameters
|
|
* to a given amound.
|
|
* @param {object} args - the block arguments.
|
|
* @property {ColorParam} COLOR_PARAM - the name of the selected color parameter.
|
|
* @property {number} VALUE - the amount to set the selected parameter to.
|
|
* @param {object} util - utility object provided by the runtime.
|
|
*/
|
|
setPenColorParamTo (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
this._setOrChangeColorParam(args.COLOR_PARAM, Cast.toNumber(args.VALUE), penState, false);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
changePenSizeBy (args, util) {
|
|
const 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.
|
|
*/
|
|
setPenSizeTo (args, util) {
|
|
const penAttributes = this._getPenState(util.target).penAttributes;
|
|
penAttributes.diameter = this._clampPenSize(Cast.toNumber(args.SIZE));
|
|
}
|
|
|
|
/* LEGACY OPCODES */
|
|
/**
|
|
* Scratch 2 "hue" param is equivelant to twice the new "color" param.
|
|
* @param {object} args - the block arguments.
|
|
* @property {number} HUE - the amount to set the hue to.
|
|
* @param {object} util - utility object provided by the runtime.
|
|
*/
|
|
setPenHueToNumber (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
const hueValue = Cast.toNumber(args.HUE);
|
|
const colorValue = hueValue / 2;
|
|
this._setOrChangeColorParam(ColorParam.COLOR, colorValue, penState, false);
|
|
|
|
this._legacyUpdatePenColor(penState);
|
|
}
|
|
|
|
/**
|
|
* Scratch 2 "hue" param is equivelant to twice the new "color" param.
|
|
* @param {object} args - the block arguments.
|
|
* @property {number} HUE - the amount of desired hue change.
|
|
* @param {object} util - utility object provided by the runtime.
|
|
*/
|
|
changePenHueBy (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
const hueChange = Cast.toNumber(args.HUE);
|
|
const colorChange = hueChange / 2;
|
|
this._setOrChangeColorParam(ColorParam.COLOR, colorChange, penState, true);
|
|
|
|
this._legacyUpdatePenColor(penState);
|
|
}
|
|
|
|
/**
|
|
* Use legacy "set shade" code to calculate RGB value for shade,
|
|
* then convert back to HSV and store those components.
|
|
* It is important to also track the given shade in penState._shade
|
|
* because it cannot be accurately backed out of the new HSV later.
|
|
* @param {object} args - the block arguments.
|
|
* @property {number} SHADE - the amount to set the shade to.
|
|
* @param {object} util - utility object provided by the runtime.
|
|
*/
|
|
setPenShadeToNumber (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
let newShade = Cast.toNumber(args.SHADE);
|
|
|
|
// Wrap clamp the new shade value the way scratch 2 did.
|
|
newShade = newShade % 200;
|
|
if (newShade < 0) newShade += 200;
|
|
|
|
// And store the shade that was used to compute this new color for later use.
|
|
penState._shade = newShade;
|
|
|
|
this._legacyUpdatePenColor(penState);
|
|
}
|
|
|
|
/**
|
|
* Because "shade" cannot be backed out of hsv consistently, use the previously
|
|
* stored penState._shade to make the shade change.
|
|
* @param {object} args - the block arguments.
|
|
* @property {number} SHADE - the amount of desired shade change.
|
|
* @param {object} util - utility object provided by the runtime.
|
|
*/
|
|
changePenShadeBy (args, util) {
|
|
const penState = this._getPenState(util.target);
|
|
const shadeChange = Cast.toNumber(args.SHADE);
|
|
this.setPenShadeToNumber({SHADE: penState._shade + shadeChange}, util);
|
|
}
|
|
|
|
/**
|
|
* Update the pen state's color from its hue & shade values, Scratch 2.0 style.
|
|
* @param {object} penState - update the HSV & RGB values in this pen state from its hue & shade values.
|
|
* @private
|
|
*/
|
|
_legacyUpdatePenColor (penState) {
|
|
// Create the new color in RGB using the scratch 2 "shade" model
|
|
let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1});
|
|
const 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);
|
|
}
|
|
|
|
// Update the pen state according to new color
|
|
const hsv = Color.rgbToHsv(rgb);
|
|
penState.color = 100 * hsv.h / 360;
|
|
penState.saturation = 100 * hsv.s;
|
|
penState.brightness = 100 * hsv.v;
|
|
|
|
this._updatePenColor(penState);
|
|
}
|
|
}
|
|
|
|
module.exports = Scratch3PenBlocks;
|