mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-28 08:52:33 -05:00
f51cf9877e
* localize the block and menu strings in the pen extension * adds .tx/config to be able to push translations to transifex * includes format-message to localize strings and extracting them. * add setLocale function to VM to allow GUI to pass in locale data. * refresh block definitions when the locale changes. ### Still to be decided For now just extracting messages from the pen extension into their own file. We’ll need to decide if each category gets its own file, or group all the strings into one resource.
744 lines
29 KiB
JavaScript
744 lines
29 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');
|
|
|
|
/**
|
|
* 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 iconURI = '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
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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}
|
|
*/
|
|
static get PEN_ORDER () {
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* The minimum and maximum allowed pen size.
|
|
* @type {{min: number, max: number}}
|
|
*/
|
|
static get PEN_SIZE_RANGE () {
|
|
return {min: 1, max: 255};
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
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
|
|
*/
|
|
_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.
|
|
* @private
|
|
*/
|
|
_onTargetMoved (target, oldX, oldY) {
|
|
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: 'Pen',
|
|
iconURI: iconURI,
|
|
blocks: [
|
|
{
|
|
opcode: 'clear',
|
|
blockType: BlockType.COMMAND,
|
|
text: formatMessage({
|
|
id: 'pen.clear',
|
|
default: 'clear',
|
|
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 hue 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 hue 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// 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 = (newShade > 100) ? 200 - newShade : newShade;
|
|
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;
|
|
|
|
// And store the shade that was used to compute this new color for later use.
|
|
penState._shade = newShade;
|
|
|
|
this._updatePenColor(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);
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = Scratch3PenBlocks;
|