soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
105 kB
/* eslint-disable no-multi-spaces */
/* eslint-disable no-invalid-this */
/* eslint-disable no-undef */
const BlockType = require('../../extension-support/block-type');
const ArgumentType = require('../../extension-support/argument-type');
const MathUtil = require('../../util/math-util');
const CanvasVar = require('./canvasData');
const uid = require('../../util/uid');
const sanitize = string => {
if (typeof string !== 'string') {
log.warn(`sanitize got unexpected type: ${typeof string}`);
string = '' + string;
}
return JSON.stringify(string).slice(1, -1);
};
const DefaultDrawImage = 'https://studio.penguinmod.com/favicon.ico';
const canvasPropInfos = [
['compositing method', 'globalCompositeOperation', [
['source over', 'source-over'],
['source in', 'source-in'],
['source out', 'source-out'],
['source atop', 'source-atop'],
['destination over', 'destination-over'],
['destination in', 'destination-in'],
['destination out', 'destination-out'],
['destination atop', 'destination-atop'],
['lighter', 'lighter'],
['copy', 'copy'],
['xor', 'xor'],
['multiply', 'multiply'],
['screen', 'screen'],
['overlay', 'overlay'],
['darken', 'darken'],
['lighten', 'lighten'],
['color dodge', 'color-dodge'],
['color burn', 'color-burn'],
['hard light', 'hard-light'],
['soft light', 'soft-light'],
['difference', 'difference'],
['exclusion', 'exclusion'],
['hue', 'hue'],
['saturation', 'saturation'],
['color', 'color'],
['luminosity', 'luminosity']
], 'source-over'],
['CSS filter', 'filter', ArgumentType.STRING, 'none'],
['font', 'font', ArgumentType.STRING, ''],
['font kerning method', 'fontKerning', [
['browser defined', 'auto'],
['font defined', 'normal'],
['none', 'none']
], 'normal'],
['font stretch', 'fontStretch', [
['ultra condensed', 'ultra-condensed'],
['extra condensed', 'extra-condensed'],
['condensed', 'condensed'],
['normal', 'normal'],
['semi expanded', 'semi-expanded'],
['expanded', 'expanded'],
['extra expanded', 'extra-expanded'],
['ultra expanded', 'ultra-expanded']
], 'normal'],
['font case sizing', 'fontVariantCaps', [
['normal', 'normal'],
['uni-case', 'unicase'],
['titling-case', 'titling-caps'],
['smaller uppercase', 'small-caps'],
['smaller cased characters', 'all-small-caps'],
['petite uppercase', 'petite-caps'],
['petite cased characters', 'all-petite-caps']
], 'normal'],
['transparency', 'globalAlpha', ArgumentType.NUMBER, '0'],
['image smoothing', 'imageSmoothingEnabled', ArgumentType.BOOLEAN, ''],
['image smoothing quality', 'imageSmoothingQuality', [
['low', 'low'],
['medium', 'medium'],
['high', 'high']
], 'low'],
['letter spacing', 'letterSpacing', ArgumentType.NUMBER, '0'],
['line cap shape', 'lineCap', [
['sharp', 'butt'],
['round', 'round'],
['square', 'square']
], 'butt'],
['line dash offset', 'lineDashOffset', ArgumentType.NUMBER, '0'],
['line join shape', 'lineJoin', [
['round', 'round'],
['beveled', 'bevel'],
['sharp', 'miter']
], 'miter'],
['line size', 'lineWidth', ArgumentType.NUMBER, '1'],
['sharp line join limit', 'miterLimit', ArgumentType.NUMBER, '10'],
['shadow blur', 'shadowBlur', ArgumentType.NUMBER, '0'],
['shadow color', 'shadowColor', ArgumentType.COLOR, null],
['shadow X offset', 'shadowOffsetX', ArgumentType.NUMBER, '0'],
['shadow Y offset', 'shadowOffsetY', ArgumentType.NUMBER, '0'],
['line color', 'strokeStyle', ArgumentType.COLOR, null],
['text horizontal alignment', 'textAlign', [
['start', 'start'],
['left', 'left'],
['center', 'center'],
['right', 'right'],
['end', 'end']
], 'start'],
['text vertical alignment', 'textBaseline', [
['top', 'top'],
['hanging', 'hanging'],
['middle', 'middle'],
['alphabetic', 'alphabetic'],
['ideographic', 'ideographic'],
['bottom', 'bottom']
], 'alphabetic'],
['text rendering optimisation', 'textRendering', [
['auto', 'auto'],
['render speed', 'optimizeSpeed'],
['legibility', 'optimizeLegibility'],
['geometric precision', 'geometricPrecision']
], 'auto'],
['word spacing', 'wordSpacing', ArgumentType.NUMBER, '0']
];
/**
* Class
* @constructor
*/
class canvas {
constructor(runtime) {
/**
* The runtime instantiating this block package.
* @type {runtime}
*/
this.runtime = runtime;
this.lastVars = [];
this.preloadedImages = {};
this.propList = [];
this.sbInfo = {};
for (const item of canvasPropInfos) {
this.propList.push(item.slice(0, 2));
const info = {
isDummy: false,
default: item[3],
type: item[2]
};
switch (item[2]) {
case ArgumentType.STRING:
info.shadow = 'text';
break;
case ArgumentType.NUMBER:
info.shadow = 'math_number';
break;
case ArgumentType.BOOLEAN:
info.check = 'Boolean';
break;
case ArgumentType.COLOR:
info.shadow = 'colour_picker';
break;
default:
info.isDummy = true;
info.options = item[2];
}
this.sbInfo[item[1]] = info;
}
this.runtime.registerVariable('canvas', CanvasVar);
this.runtime.registerSerializer(
CanvasVar.customId,
canvas => canvas.id,
(varId, target) => {
let variable = target.variables[varId];
if (!variable) {
for (const target of this.runtime.targets) {
if (target.variables[varId]) {
variable = target.variables[varId];
break;
}
}
}
return variable;
}
);
this.runtime.registerCompiledExtensionBlocks('newCanvas', this.getCompileInfo());
const updateVariables = type => {
if (type === 'canvas') {
this.runtime.vm.emitWorkspaceUpdate();
}
};
this.runtime.on('variableCreate', updateVariables);
this.runtime.on('variableChange', updateVariables);
this.runtime.on('variableDelete', updateVariables);
let infoObj = {};
Object.defineProperty(ScratchBlocks.Blocks, 'newCanvas_setProperty', {
set: block => {
this._implementSBInfo(block);
infoObj = block;
},
get: () => infoObj
});
}
orderCategoryBlocks(blocks) {
const button = blocks[0];
const varBlock = blocks[1];
const variables = [button];
delete blocks[0];
delete blocks[1];
const stage = this.runtime.getTargetForStage();
const target = this.runtime.vm.editingTarget;
const stageVars = Object.values(stage.variables)
.filter(variable => variable.type === 'canvas')
.map(variable => variable.toToolboxDefault('canvas'))
.map(xml => varBlock.replace('></block>', `>${xml}</block>`));
const privateVars = Object.values(target.variables)
.filter(variable => variable.type === 'canvas')
.map(variable => variable.toToolboxDefault('canvas'))
.map(xml => varBlock.replace('></block>', `>${xml}</block>`));
if (stageVars.length) {
variables.push(`<label text="Canvases for all sprites"></label>`);
variables.push(...stageVars);
}
if (privateVars.length && target.id !== stage.id) {
variables.push(`<label text="Canvases for this sprite"></label>`);
variables.push(...privateVars);
}
if (stageVars.length || privateVars.length) {
variables.push(...blocks);
}
return variables;
}
_implementSBInfo(block) {
const info = this.sbInfo;
block.renderInput = function(item) {
if (!item) item = this.getFieldValue('prop');
const existingInput = this.getInput('value');
const isInputCurrentlyUsed = existingInput.type !== ScratchBlocks.DUMMY_INPUT
&& !existingInput.connection.targetBlock()?.isShadow?.();
const target = info[item];
if (this.lastItem === item || (isInputCurrentlyUsed && !target.isDummy)) return;
this.removeInput('value');
if (target.isDummy) {
const inp = this.appendDummyInput('value');
const field = new ScratchBlocks.FieldDropdown(target.options);
inp.appendField(field, 'value');
field.setValue(target.default);
return;
}
const inp = this.appendValueInput('value');
inp.setCheck(target.check);
if (target.shadow && !this.isInsertionMarker()) {
const shadow = this.workspace.newBlock(target.shadow);
shadow.setShadow(true);
shadow.initSvg();
shadow.inputList[0].fieldRow[0].setValue(target.default);
inp.connection.connect(shadow.outputConnection);
shadow.render(false);
}
};
const oldInit = block.init;
block.init = function() {
oldInit.apply(this);
this.appendDummyInput('value');
const dropdownField = this.getField('prop');
dropdownField.setValidator(item => {
this.renderInput(item);
return item;
});
this.renderInput();
};
}
/**
* @returns {object} metadata for this extension and its blocks.
*/
getInfo() {
return {
id: 'newCanvas',
name: 'html canvas',
color1: '#0069c2',
isDynamic: true,
orderBlocks: this.orderCategoryBlocks.bind(this),
blocks: [
{
opcode: 'createNewCanvas',
blockType: BlockType.BUTTON,
text: 'create new canvas'
},
{
opcode: 'canvasGetter',
blockType: BlockType.REPORTER,
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
},
text: '[canvas]'
},
{
blockType: BlockType.LABEL,
text: "stylizing"
},
{
opcode: 'setSize',
text: 'set width: [width] height: [height] of [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageWidth
},
height: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageHeight
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'setProperty',
text: 'set [prop] of [canvas] to ',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
prop: {
type: ArgumentType.STRING,
menu: 'canvasProps'
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'getProperty',
text: 'get [prop] of [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
prop: {
type: ArgumentType.STRING,
menu: 'canvasProps'
}
},
blockType: BlockType.REPORTER
},
{
opcode: 'dash',
blockType: BlockType.COMMAND,
text: 'set line dash to [dashing] in [canvas]',
arguments: {
dashing: {
type: ArgumentType.STRING,
defaultValue: '[10, 10]'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
blockType: BlockType.LABEL,
text: "direct drawing"
},
{
opcode: 'clearCanvas',
text: 'clear canvas [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'clearAria',
text: 'clear area at x: [x] y: [y] with width: [width] height: [height] on [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageWidth
},
height: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageHeight
}
},
blockType: BlockType.COMMAND
},
'---',
{
opcode: 'drawText',
text: 'draw text [text] at [x] [y] onto [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
text: {
type: ArgumentType.STRING,
defaultValue: 'photos printed'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'drawTextWithCap',
text: 'draw text [text] at [x] [y] with size cap [cap] onto [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
text: {
type: ArgumentType.STRING,
defaultValue: 'photos printed'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
cap: {
type: ArgumentType.NUMBER,
defauleValue: '10'
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'outlineText',
text: 'draw text outline for [text] at [x] [y] onto [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
text: {
type: ArgumentType.STRING,
defaultValue: 'photos printed'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'outlineTextWithCap',
text: 'draw text outline for [text] at [x] [y] with size cap [cap] onto [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
text: {
type: ArgumentType.STRING,
defaultValue: 'photos printed'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
cap: {
type: ArgumentType.NUMBER,
defauleValue: '10'
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'drawRect',
text: 'draw rectangle at x: [x] y: [y] with width: [width] height: [height] on [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageWidth
},
height: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageHeight
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'outlineRect',
text: 'draw rectangle outline at x: [x] y: [y] with width: [width] height: [height] on [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageWidth
},
height: {
type: ArgumentType.NUMBER,
defaultValue: this.runtime.stageHeight
}
},
blockType: BlockType.COMMAND
},
{
opcode: 'preloadUriImage',
blockType: BlockType.COMMAND,
text: 'preload image [URI] as [NAME]',
arguments: {
URI: {
type: ArgumentType.STRING,
exemptFromNormalization: true,
defaultValue: DefaultDrawImage
},
NAME: {
type: ArgumentType.STRING,
defaultValue: "preloaded image"
}
}
},
{
opcode: 'unloadUriImage',
blockType: BlockType.COMMAND,
text: 'unload image [NAME]',
arguments: {
NAME: {
type: ArgumentType.STRING,
defaultValue: "preloaded image"
}
}
},
{
opcode: 'getWidthOfPreloaded',
blockType: BlockType.REPORTER,
text: 'get width of [name]',
arguments: {
name: {
type: ArgumentType.STRING,
defaultValue: "preloaded image"
}
}
},
{
opcode: 'getHeightOfPreloaded',
blockType: BlockType.REPORTER,
text: 'get height of [name]',
arguments: {
name: {
type: ArgumentType.STRING,
defaultValue: "preloaded image"
}
}
},
{
opcode: 'drawUriImage',
blockType: BlockType.COMMAND,
text: 'draw image [URI] at x:[X] y:[Y] onto canvas [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
URI: {
type: ArgumentType.STRING,
exemptFromNormalization: true,
defaultValue: DefaultDrawImage
},
X: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
Y: {
type: ArgumentType.NUMBER,
defaultValue: 0
}
}
},
{
opcode: 'drawUriImageWHR',
blockType: BlockType.COMMAND,
text: 'draw image [URI] at x:[X] y:[Y] width:[WIDTH] height:[HEIGHT] pointed at: [ROTATE] onto canvas [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
URI: {
type: ArgumentType.STRING,
exemptFromNormalization: true,
defaultValue: DefaultDrawImage
},
X: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
Y: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
WIDTH: {
type: ArgumentType.NUMBER,
defaultValue: 64
},
HEIGHT: {
type: ArgumentType.NUMBER,
defaultValue: 64
},
ROTATE: {
type: ArgumentType.ANGLE,
defaultValue: 90
}
}
},
{
opcode: 'drawUriImageWHCX1Y1X2Y2R',
blockType: BlockType.COMMAND,
text: 'draw image [URI] at x:[X] y:[Y] width:[WIDTH] height:[HEIGHT] cropping from x:[CROPX] y:[CROPY] width:[CROPW] height:[CROPH] pointed at: [ROTATE] onto canvas [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
},
URI: {
type: ArgumentType.STRING,
exemptFromNormalization: true,
defaultValue: DefaultDrawImage
},
X: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
Y: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
WIDTH: {
type: ArgumentType.NUMBER,
defaultValue: 64
},
HEIGHT: {
type: ArgumentType.NUMBER,
defaultValue: 64
},
CROPX: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
CROPY: {
type: ArgumentType.NUMBER,
defaultValue: 0
},
CROPW: {
type: ArgumentType.NUMBER,
defaultValue: 100
},
CROPH: {
type: ArgumentType.NUMBER,
defaultValue: 100
},
ROTATE: {
type: ArgumentType.ANGLE,
defaultValue: 90
}
}
},
{
blockType: BlockType.LABEL,
text: "path drawing"
},
{
opcode: 'beginPath',
blockType: BlockType.COMMAND,
text: 'begin path drawing on [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'moveTo',
blockType: BlockType.COMMAND,
text: 'move pen to x:[x] y:[y] on [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'lineTo',
blockType: BlockType.COMMAND,
text: 'add line going to x:[x] y:[y] on [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'arcTo',
blockType: BlockType.COMMAND,
text: 'add arc going to x:[x] y:[y] on [canvas] with control points [controlPoints] and radius [radius]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
controlPoints: {
type: ArgumentType.POLYGON,
nodes: 2
},
radius: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'addRect',
blockType: BlockType.COMMAND,
text: 'add a rectangle at x:[x] y:[y] with width:[width] height:[height] to [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: 10
},
height: {
type: ArgumentType.NUMBER,
defaultValue: 10
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'addEllipse',
blockType: BlockType.COMMAND,
text: 'add a ellipse at x:[x] y:[y] with width:[width] height:[height] pointed towards [dir] to [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: 10
},
height: {
type: ArgumentType.NUMBER,
defaultValue: 10
},
dir: {
type: ArgumentType.ANGLE,
defaultValue: 90
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'addEllipseStartStop',
blockType: BlockType.COMMAND,
text: 'add a ellipse with starting rotation [start] and ending rotation [end] at x:[x] y:[y] with width:[width] height:[height] pointed towards [dir] to [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
width: {
type: ArgumentType.NUMBER,
defaultValue: 10
},
height: {
type: ArgumentType.NUMBER,
defaultValue: 10
},
start: {
type: ArgumentType.NUMBER,
defaultValue: '0'
},
end: {
type: ArgumentType.NUMBER,
defaultValue: '360'
},
dir: {
type: ArgumentType.ANGLE,
defaultValue: 90
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'closePath',
blockType: BlockType.COMMAND,
text: 'attempt to close any open path in [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'stroke',
blockType: BlockType.COMMAND,
text: 'draw outline for current path in [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'fill',
blockType: BlockType.COMMAND,
text: 'draw fill for current path in [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
blockType: BlockType.LABEL,
text: "transforms"
},
{
opcode: 'saveTransform',
blockType: BlockType.COMMAND,
text: 'save [canvas]\'s transform',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'restoreTransform',
blockType: BlockType.COMMAND,
text: 'reset to [canvas]\'s saved transform',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'turnRotationLeft',
blockType: BlockType.COMMAND,
text: 'turn left [degrees] in [canvas]',
arguments: {
degrees: {
type: ArgumentType.NUMBER,
defaultValue: '90'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'turnRotationRight',
blockType: BlockType.COMMAND,
text: 'turn right [degrees] in [canvas]',
arguments: {
degrees: {
type: ArgumentType.NUMBER,
defaultValue: '90'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'setRotation',
blockType: BlockType.COMMAND,
text: 'set rotation to [degrees] in [canvas]',
arguments: {
degrees: {
type: ArgumentType.ANGLE,
defaultValue: '90'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'setTranslateXY',
blockType: BlockType.COMMAND,
text: 'set translation X: [x] Y: [y] on [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'changeTranslateXY',
blockType: BlockType.COMMAND,
text: 'change translation X: [x] Y: [y] on [canvas]',
arguments: {
x: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
y: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'changeTranslateX',
blockType: BlockType.COMMAND,
text: 'change X translation by [amount] on [canvas]',
arguments: {
amount: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'setTranslateX',
blockType: BlockType.COMMAND,
text: 'set X scaler to [amount] on [canvas]',
arguments: {
amount: {
type: ArgumentType.NUMBER,
defaultValue: '50'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'changeTranslateY',
blockType: BlockType.COMMAND,
text: 'change Y translation by [amount] on [canvas]',
arguments: {
amount: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'setTranslateY',
blockType: BlockType.COMMAND,
text: 'set Y translation by [amount] on [canvas]',
arguments: {
amount: {
type: ArgumentType.NUMBER,
defaultValue: '50'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'changeScaleXY',
blockType: BlockType.COMMAND,
text: 'change XY scaler by [percent]% on [canvas]',
arguments: {
percent: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'setScaleXY',
blockType: BlockType.COMMAND,
text: 'set XY scaler to [percent]% on [canvas]',
arguments: {
percent: {
type: ArgumentType.NUMBER,
defaultValue: '50'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'changeScaleX',
blockType: BlockType.COMMAND,
text: 'change X scaler by [percent]% on [canvas]',
arguments: {
percent: {
type: ArgumentType.NUMBER,
defaultValue: '10'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'setScaleX',
blockType: BlockType.COMMAND,
text: 'set X scaler to [percent]% on [canvas]',
arguments: {
percent: {
type: ArgumentType.NUMBER,
defaultValue: '50'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'changeScaleY',
blockType: BlockType.COMMAND,
text: 'change Y scaler by [percent]% on [canvas]',
arguments: {
percent: {
type: ArgumentType.NUMBER,
defaultValue: '50'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'setScaleY',
blockType: BlockType.COMMAND,
text: 'set Y scaler to [percent]% on [canvas]',
arguments: {
percent: {
type: ArgumentType.NUMBER,
defaultValue: '50'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
"---",
{
opcode: 'resetTransform',
blockType: BlockType.COMMAND,
text: 'clear transform in [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'loadTransform',
blockType: BlockType.COMMAND,
text: 'set new transform [transform] on [canvas]',
arguments: {
transform: {
type: ArgumentType.STRING,
defaultValue: '[1, 0, 0, 1, 0, 0]'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'getTransform',
blockType: BlockType.REPORTER,
text: 'get current transform in [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
blockType: BlockType.LABEL,
text: "utilizing"
},
{
opcode: 'putOntoSprite',
blockType: BlockType.COMMAND,
text: 'set this sprites costume to [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'getDataURI',
blockType: BlockType.REPORTER,
text: 'get data URL of [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'getWidthOfCanvas',
blockType: BlockType.REPORTER,
text: 'get width of [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'getHeightOfCanvas',
blockType: BlockType.REPORTER,
text: 'get height of [canvas]',
arguments: {
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
},
{
opcode: 'getDrawnWidthOfText',
blockType: BlockType.REPORTER,
text: 'get [dimension] of text [text] when drawn to [canvas]',
arguments: {
dimension: {
type: ArgumentType.STRING,
menu: 'textDimension'
},
text: {
type: ArgumentType.STRING,
defaultValue: 'bogos binted'
},
canvas: {
type: ArgumentType.STRING,
menu: 'canvas'
}
}
}
],
menus: {
textDimension: {
items: [
'width',
'height',
['bounding box left', 'actualBoundingBoxLeft'],
['bounding box right', 'actualBoundingBoxRight'],
['bounding box ascent', 'actualBoundingBoxAscent'],
['bounding box descent', 'actualBoundingBoxDescent'],
['font bounding box ascent', 'fontBoundingBoxAscent'],
['font bounding box descent', 'fontBoundingBoxDescent']
// maby add the other ones but the em ones be hella spotty
]
},
canvas: {
variableType: 'canvas'
},
canvasProps: {
items: this.propList
}
}
};
}
createNewCanvas() {
// expect the global ScratchBlocks from inside the window
ScratchBlocks.prompt('New Canvas name:', '',
(name, additionalVars, {scope}) => {
name = ScratchBlocks.Variables.validateScalarVarOrListName_(name,
ScratchBlocks.getMainWorkspace(), additionalVars, false,
'canvas', 'A Canvas named "%1" already exists.');
if (!name) return;
const target = scope
? this.runtime.getTargetForStage()
: this.runtime.vm.editingTarget;
target.createVariable(uid(), name, 'canvas');
this.runtime.vm.emitWorkspaceUpdate();
}, 'New Canvas', 'canvas');
}
/**
* This function is used for any compiled blocks in the extension if they exist.
* Data in this function is given to the IR & JS generators.
* Data must be valid otherwise errors may occur.
* @returns {object} functions that create data for compiled blocks.
*/
getCompileInfo() {
return {
ir: {
canvasGetter: (generator, block) => ({
kind: 'input',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setSize: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height')
}),
setProperty: (generator, block) => ({
kind: 'stack',
isField: !!block.fields.value,
prop: block.fields.prop.value,
value: block.fields?.value?.value ?? generator.descendInputOfBlock(block, 'value'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
getProperty: (generator, block) => ({
kind: 'input',
prop: block.fields.prop.value,
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
dash: (generator, block) => ({
kind: 'stack',
dashing: generator.descendInputOfBlock(block, 'dashing'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
clearCanvas: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
clearAria: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height')
}),
drawText: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
text: generator.descendInputOfBlock(block, 'text')
}),
drawTextWithCap: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
text: generator.descendInputOfBlock(block, 'text'),
cap: generator.descendInputOfBlock(block, 'cap')
}),
outlineText: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
text: generator.descendInputOfBlock(block, 'text')
}),
outlineTextWithCap: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
text: generator.descendInputOfBlock(block, 'text'),
cap: generator.descendInputOfBlock(block, 'cap')
}),
drawRect: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height')
}),
outlineRect: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height')
}),
preloadUriImage: (generator, block) => ({
kind: 'stack',
URI: generator.descendInputOfBlock(block, 'URI'),
NAME: generator.descendInputOfBlock(block, 'NAME')
}),
unloadUriImage: (generator, block) => ({
kind: 'stack',
NAME: generator.descendInputOfBlock(block, 'NAME')
}),
getWidthOfPreloaded: (generator, block) => ({
kind: 'input',
name: generator.descendInputOfBlock(block, 'name')
}),
getHeightOfPreloaded: (generator, block) => ({
kind: 'input',
name: generator.descendInputOfBlock(block, 'name')
}),
drawUriImage: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
URI: generator.descendInputOfBlock(block, 'URI'),
X: generator.descendInputOfBlock(block, 'X'),
Y: generator.descendInputOfBlock(block, 'Y')
}),
drawUriImageWHR: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
URI: generator.descendInputOfBlock(block, 'URI'),
X: generator.descendInputOfBlock(block, 'X'),
Y: generator.descendInputOfBlock(block, 'Y'),
WIDTH: generator.descendInputOfBlock(block, 'WIDTH'),
HEIGHT: generator.descendInputOfBlock(block, 'HEIGHT'),
ROTATE: generator.descendInputOfBlock(block, 'ROTATE')
}),
drawUriImageWHCX1Y1X2Y2R: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas'),
URI: generator.descendInputOfBlock(block, 'URI'),
X: generator.descendInputOfBlock(block, 'X'),
Y: generator.descendInputOfBlock(block, 'Y'),
WIDTH: generator.descendInputOfBlock(block, 'WIDTH'),
HEIGHT: generator.descendInputOfBlock(block, 'HEIGHT'),
CROPX: generator.descendInputOfBlock(block, 'CROPX'),
CROPY: generator.descendInputOfBlock(block, 'CROPY'),
CROPW: generator.descendInputOfBlock(block, 'CROPW'),
CROPH: generator.descendInputOfBlock(block, 'CROPH'),
ROTATE: generator.descendInputOfBlock(block, 'ROTATE')
}),
getWidthOfCanvas: (generator, block) => ({
kind: 'input',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
getHeightOfCanvas: (generator, block) => ({
kind: 'input',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
beginPath: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
moveTo: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
lineTo: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
arcTo: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
controlPoints: generator.descendInputOfBlock(block, 'controlPoints'),
radius: generator.descendInputOfBlock(block, 'radius'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
addRect: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
addEllipse: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height'),
dir: generator.descendInputOfBlock(block, 'dir'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
addEllipseStartStop: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
width: generator.descendInputOfBlock(block, 'width'),
height: generator.descendInputOfBlock(block, 'height'),
start: generator.descendInputOfBlock(block, 'start'),
end: generator.descendInputOfBlock(block, 'end'),
dir: generator.descendInputOfBlock(block, 'dir'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
stroke: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
fill: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
saveTransform: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
restoreTransform: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
turnRotationLeft: (generator, block) => ({
kind: 'stack',
degrees: generator.descendInputOfBlock(block, 'degrees'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
turnRotationRight: (generator, block) => ({
kind: 'stack',
degrees: generator.descendInputOfBlock(block, 'degrees'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setRotation: (generator, block) => ({
kind: 'stack',
degrees: generator.descendInputOfBlock(block, 'degrees'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setTranslateXY: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
changeTranslateXY: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'x'),
y: generator.descendInputOfBlock(block, 'y'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
changeTranslateX: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'amount'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setTranslateX: (generator, block) => ({
kind: 'stack',
x: generator.descendInputOfBlock(block, 'amount'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
changeTranslateY: (generator, block) => ({
kind: 'stack',
y: generator.descendInputOfBlock(block, 'amount'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setTranslateY: (generator, block) => ({
kind: 'stack',
y: generator.descendInputOfBlock(block, 'amount'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
changeScaleXY: (generator, block) => ({
kind: 'stack',
scale: generator.descendInputOfBlock(block, 'percent'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setScaleXY: (generator, block) => ({
kind: 'stack',
scale: generator.descendInputOfBlock(block, 'percent'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
changeScaleX: (generator, block) => ({
kind: 'stack',
scale: generator.descendInputOfBlock(block, 'percent'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setScaleX: (generator, block) => ({
kind: 'stack',
scale: generator.descendInputOfBlock(block, 'percent'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
changeScaleY: (generator, block) => ({
kind: 'stack',
scale: generator.descendInputOfBlock(block, 'percent'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
setScaleY: (generator, block) => ({
kind: 'stack',
scale: generator.descendInputOfBlock(block, 'percent'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
resetTransform: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
loadTransform: (generator, block) => ({
kind: 'stack',
transform: generator.descendInputOfBlock(block, 'transform'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
getTransform: (generator, block) => ({
kind: 'input',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
putOntoSprite: (generator, block) => ({
kind: 'stack',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
getDataURI: (generator, block) => ({
kind: 'input',
canvas: generator.descendVariable(block, 'canvas', 'canvas')
}),
getDrawnWidthOfText: (generator, block) => ({
kind: 'input',
prop: block.fields.dimension.value,
text: generator.descendInputOfBlock(block, 'text'),
canvas: generator.descendVariable(block, 'canvas', 'canvas')
})
},
js: {
canvasGetter: (node, compiler, {TypedInput, TYPE_UNKNOWN}) =>
new TypedInput(compiler.referenceVariable(node.canvas), TYPE_UNKNOWN),
setSize: (node, compiler) => {
console.log(node);
const canvas = compiler.referenceVariable(node.canvas);
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
compiler.source += `${canvas}.canvas.width = ${width};\n`;
compiler.source += `${canvas}.canvas.height = ${height};\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
setProperty: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const val = node.isField
? node.value
: compiler.descendInput(node.value);
compiler.source += `${ctx}.${node.prop} = `;
const target = this.sbInfo[node.prop];
switch (target.type) {
case ArgumentType.STRING:
compiler.source += val.asString();
break;
case ArgumentType.NUMBER:
compiler.source += val.asNumber();
break;
case ArgumentType.BOOLEAN:
compiler.source += val.asBoolean();
break;
case ArgumentType.COLOR:
compiler.source += val.asString();
break;
default:
compiler.source += `"${sanitize(val)}"`;
}
compiler.source += ';\n';
},
getProperty: (node, compiler, {TypedInput, TYPE_NUMBER, TYPE_STRING, TYPE_BOOLEAN, TYPE_UNKNOWN}) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
let type = TYPE_UNKNOWN;
const target = this.sbInfo[node.prop];
switch (target.type) {
case ArgumentType.STRING:
type = TYPE_STRING;
break;
case ArgumentType.NUMBER:
type = TYPE_NUMBER;
break;
case ArgumentType.BOOLEAN:
type = TYPE_BOOLEAN;
break;
case ArgumentType.COLOR:
type = TYPE_STRING;
break;
default:
type = TYPE_STRING;
}
return new TypedInput(`${ctx}.${node.prop}`, type);
},
dash: (node, compiler, {ConstantInput}) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const arrInp = compiler.descendInput(node.dashing);
const isConstant = arrInp instanceof ConstantInput;
compiler.source += `${ctx}.setLineDash(`;
if (!isConstant) compiler.source += `parseJSONSafe(`;
compiler.source += isConstant
? arrInp.constantValue
: arrInp.asUnknown();
if (!isConstant) compiler.source += ')';
compiler.source += ');\n';
},
clearCanvas: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.clearRect(0, 0, ${canvas}.canvas.width, ${canvas}.canvas.height);\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
clearAria: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
compiler.source += `${ctx}.clearRect(${x}, ${y}, ${width}, ${height});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
drawText: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const text = compiler.descendInput(node.text).asString();
compiler.source += `${ctx}.fillText(${text}, ${x}, ${y});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
drawTextWithCap: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const text = compiler.descendInput(node.text).asString();
const cap = compiler.descendInput(node.cap).asNumber();
compiler.source += `${ctx}.fillText(${text}, ${x}, ${y}, ${cap});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
outlineText: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const text = compiler.descendInput(node.text).asString();
compiler.source += `${ctx}.strokeText(${text}, ${x}, ${y});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
outlineTextWithCap: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const text = compiler.descendInput(node.text).asString();
const cap = compiler.descendInput(node.cap).asNumber();
compiler.source += `${ctx}.strokeText(${text}, ${x}, ${y}, ${cap});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
drawRect: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
compiler.source += `${ctx}.fillRect(${x}, ${y}, ${width}, ${height});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
outlineRect: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
compiler.source += `${ctx}.strokeRect(${x}, ${y}, ${width}, ${height});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
preloadUriImage: (node, compiler) => {
const allPreloaded = compiler.evaluateOnce('{}');
const preloadName = compiler.descendInput(node.NAME).asString();
const preloadUri = compiler.descendInput(node.URI).asUnknown();
compiler.source += `${allPreloaded}[${preloadName}] = yield* waitPromise(`;
compiler.source += `resolveImageURL(${preloadUri})`;
compiler.source += ');\n';
},
unloadUriImage: (node, compiler) => {
const allPreloaded = compiler.evaluateOnce('{}');
const preloadName = compiler.descendInput(node.NAME).asString();
compiler.source += `if (${allPreloaded}[${preloadName}]) {`;
compiler.source += `${allPreloaded}[${preloadName}].remove();\n`;
compiler.source += `delete ${allPreloaded}[${preloadName}];\n`;
compiler.source += '}';
},
getWidthOfPreloaded: (node, compiler, {TypedInput, TYPE_NUMBER}) => {
const allPreloaded = compiler.evaluateOnce('{}');
const preloadName = compiler.descendInput(node.name).asString();
return new TypedInput(`${allPreloaded}[${preloadName}]?.width ?? 0`, TYPE_NUMBER);
},
getHeightOfPreloaded: (node, compiler, {TypedInput, TYPE_NUMBER}) => {
const allPreloaded = compiler.evaluateOnce('{}');
const preloadName = compiler.descendInput(node.name).asString();
return new TypedInput(`${allPreloaded}[${preloadName}]?.height ?? 0`, TYPE_NUMBER);
},
drawUriImage: (node, compiler) => {
const allPreloaded = compiler.evaluateOnce('{}');
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const uri = compiler.descendInput(node.URI).asUnknown();
const x = compiler.descendInput(node.X).asNumber();
const y = compiler.descendInput(node.Y).asNumber();
compiler.source += `${ctx}.drawImage(`;
compiler.source += `${allPreloaded}[${uri}]`;
compiler.source += `? ${allPreloaded}[${uri}]`;
compiler.source += `: yield* waitPromise(resolveImageURL(${uri}))`;
compiler.source += `, ${x}, ${y});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
drawUriImageWHR: (node, compiler) => {
const allPreloaded = compiler.evaluateOnce('{}');
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const uri = compiler.descendInput(node.URI).asUnknown();
const x = compiler.descendInput(node.X).asNumber();
const y = compiler.descendInput(node.Y).asNumber();
const width = compiler.descendInput(node.WIDTH).asNumber();
const height = compiler.descendInput(node.HEIGHT).asNumber();
const dir = compiler.descendInput(node.ROTATE).asNumber();
compiler.source += `${ctx}.drawImage(`;
compiler.source += `${allPreloaded}[${uri}] ? `;
compiler.source += `${allPreloaded}[${uri}] : `;
compiler.source += `yield* waitPromise(resolveImageURL(${uri}))`;
compiler.source += `, ${x}, ${y}, ${width}, ${height}, ${dir});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
drawUriImageWHCX1Y1X2Y2R: (node, compiler) => {
const allPreloaded = compiler.evaluateOnce('{}');
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const uri = compiler.descendInput(node.URI).asUnknown();
const x = compiler.descendInput(node.X).asNumber();
const y = compiler.descendInput(node.Y).asNumber();
const width = compiler.descendInput(node.WIDTH).asNumber();
const height = compiler.descendInput(node.HEIGHT).asNumber();
const dir = compiler.descendInput(node.ROTATE).asNumber();
const cropX = compiler.descendInput(node.CROPX).asNumber();
const cropY = compiler.descendInput(node.CROPY).asNumber();
const cropWidth = compiler.descendInput(node.CROPW).asNumber();
const cropHeight = compiler.descendInput(node.CROPH).asNumber();
compiler.source += `${ctx}.drawImage(`;
compiler.source += `${allPreloaded}[${uri}] ? `;
compiler.source += `${allPreloaded}[${uri}] : `;
compiler.source += `yield* waitPromise(resolveImageURL(${uri}))`;
compiler.source += `, ${x}, ${y}, ${width}, ${height}, ${dir}, `;
compiler.source += `${cropX}, ${cropY}, ${cropWidth}, ${cropHeight});\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
getWidthOfCanvas: (node, compiler, {TYPE_NUMBER, TypedInput}) => {
const canvas = compiler.referenceVariable(node.canvas);
return new TypedInput(`${canvas}.canvas.width`, TYPE_NUMBER);
},
getHeightOfCanvas: (node, compiler, {TYPE_NUMBER, TypedInput}) => {
const canvas = compiler.referenceVariable(node.canvas);
return new TypedInput(`${canvas}.canvas.height`, TYPE_NUMBER);
},
beginPath: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.beginPath();\n`;
},
moveTo: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
compiler.source += `${ctx}.moveTo(${x}, ${y});\n`;
},
lineTo: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
compiler.source += `${ctx}.lineTo(${x}, ${y});\n`;
},
arcTo: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const controlPoints = compiler.descendInput(node.controlPoints).asUnknown();
const radius = compiler.descendInput(node.radius).asNumber();
compiler.source += `${ctx}.arcTo(${x}, ${y}, ...${controlPoints}, ${radius});\n`;
},
addRect: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
compiler.source += `${ctx}.rect(${x}, ${y}, ${width}, ${height});\n`;
},
addEllipse: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
const dir = compiler.descendInput(node.dir).asNumber();
compiler.source += `${ctx}.ellipse(${x}, ${y}, ${width}, ${height}`;
compiler.source += `, (${dir} - 90) * Math.PI / 180, 0, 2 * Math.PI);\n`;
},
addEllipseStartStop: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
const width = compiler.descendInput(node.width).asNumber();
const height = compiler.descendInput(node.height).asNumber();
const dir = compiler.descendInput(node.dir).asNumber();
const start = compiler.descendInput(node.start).asNumber();
const end = compiler.descendInput(node.end).asNumber();
compiler.source += `${ctx}.ellipse(${x}, ${y}, ${width}, ${height}, `;
compiler.source += `(${dir} - 90) * Math.PI / 180, (${start} - 90) * Math.PI / 180, (${end} - 90) * Math.PI / 180);\n`;
},
closePath: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.soource += `${ctx}.closePath()`;
},
stroke: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.stroke();\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
fill: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.fill();\n`;
compiler.source += `${canvas}.updateCanvasContentRenders();\n`;
},
saveTransform: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.save();\n`;
},
restoreTransform: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.restore();\n`;
},
turnRotationLeft: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const degrees = compiler.descendInput(node.degrees).asNumber();
compiler.source += `${ctx}.rotate(`;
compiler.source += `(${canvas}._cameraStuff.rotation -= ${degrees}) * Math.PI / 180`;
compiler.source += `);\n`;
},
turnRotationRight: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const degrees = compiler.descendInput(node.degrees).asNumber();
compiler.source += `${ctx}.rotate(`;
compiler.source += `(${canvas}._cameraStuff.rotation += ${degrees}) * Math.PI / 180`;
compiler.source += `);\n`;
},
setRotation: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const degrees = compiler.descendInput(node.degrees).asNumber();
compiler.source += `${ctx}.rotate(`;
compiler.source += `((${canvas}._cameraStuff.rotation = ${degrees}) - 90) * Math.PI / 180`;
compiler.source += `);\n`;
},
setTranslateXY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
compiler.source += `${ctx}.translate(`;
compiler.source += `${canvas}._cameraStuff.x = ${x},`;
compiler.source += `${canvas}._cameraStuff.y = ${y}`;
compiler.source += `);\n`;
},
changeTranslateXY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
const y = compiler.descendInput(node.y).asNumber();
compiler.source += `${ctx}.translate(`;
compiler.source += `${canvas}._cameraStuff.x += ${x},`;
compiler.source += `${canvas}._cameraStuff.y += ${y}`;
compiler.source += `);\n`;
},
changeTranslateX: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
compiler.source += `${ctx}.translate(`;
compiler.source += `${canvas}._cameraStuff.x += ${x},`;
compiler.source += `${canvas}._cameraStuff.y`;
compiler.source += `);\n`;
},
setTranslateX: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const x = compiler.descendInput(node.x).asNumber();
compiler.source += `${ctx}.translate(`;
compiler.source += `${canvas}._cameraStuff.x = ${x},`;
compiler.source += `${canvas}._cameraStuff.y`;
compiler.source += `);\n`;
},
changeTranslateY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const y = compiler.descendInput(node.y).asNumber();
compiler.source += `${ctx}.translate(`;
compiler.source += `${canvas}._cameraStuff.x,`;
compiler.source += `${canvas}._cameraStuff.y += ${y}`;
compiler.source += `);\n`;
},
setTranslateY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const y = compiler.descendInput(node.y).asNumber();
compiler.source += `${ctx}.translate(`;
compiler.source += `${canvas}._cameraStuff.x,`;
compiler.source += `${canvas}._cameraStuff.y = ${y}`;
compiler.source += `);\n`;
},
changeScaleXY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const scale = compiler.descendInput(node.scale).asNumber();
compiler.source += `${ctx}.scale(`;
compiler.source += `${canvas}._cameraStuff.scaleX += (${scale} / 100),`;
compiler.source += `${canvas}._cameraStuff.scaleY += (${scale} / 100)`;
compiler.source += `);\n`;
},
setScaleXY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const scale = compiler.descendInput(node.scale).asNumber();
compiler.source += `${ctx}.scale(`;
compiler.source += `${canvas}._cameraStuff.scaleX = (${scale} / 100),`;
compiler.source += `${canvas}._cameraStuff.scaleY = (${scale} / 100)`;
compiler.source += `);\n`;
},
changeScaleX: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const scale = compiler.descendInput(node.scale).asNumber();
compiler.source += `${ctx}.scale(`;
compiler.source += `${canvas}._cameraStuff.scaleX += (${scale} / 100),`;
compiler.source += `${canvas}._cameraStuff.scaleY`;
compiler.source += `);\n`;
},
setScaleX: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const scale = compiler.descendInput(node.scale).asNumber();
compiler.source += `${ctx}.scale(`;
compiler.source += `${canvas}._cameraStuff.scaleX = (${scale} / 100),`;
compiler.source += `${canvas}._cameraStuff.scaleY`;
compiler.source += `);\n`;
},
changeScaleY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const scale = compiler.descendInput(node.scale).asNumber();
compiler.source += `${ctx}.scale(`;
compiler.source += `${canvas}._cameraStuff.scaleX,`;
compiler.source += `${canvas}._cameraStuff.scaleY += (${scale} / 100)`;
compiler.source += `);\n`;
},
setScaleY: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const scale = compiler.descendInput(node.scale).asNumber();
compiler.source += `${ctx}.scale(`;
compiler.source += `${canvas}._cameraStuff.scaleX,`;
compiler.source += `${canvas}._cameraStuff.scaleY = (${scale} / 100)`;
compiler.source += `);\n`;
},
resetTransform: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
compiler.source += `${ctx}.resetTransform();\n`;
},
loadTransform: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const transform = compiler.descendInput(node.transform).asString();
compiler.source += `${ctx}.setTransform(`;
compiler.source += `parseJSONSafe(${transform})`;
compiler.source += `);\n`;
},
getTransform: (node, compiler, { TypedInput, TYPE_STRING }) => {
const canvas = compiler.referenceVariable(node.canvas);
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
let content = '(() => {';
content += `const transform = ${ctx}.getTransform(); `;
content += 'return JSON.stringify([';
content += 'transform.a, transform.b, transform.c, ';
content += 'transform.d, transform.e, transform.f';
content += '])})()';
return new TypedInput(content, TYPE_STRING);
},
putOntoSprite: (node, compiler) => {
const canvas = compiler.referenceVariable(node.canvas);
compiler.source += `${canvas}.applyCanvasToTarget(target);\n`;
},
getDataURI: (node, compiler, {TypedInput, TYPE_STRING}) => {
const canvas = compiler.referenceVariable(node.canvas);
return new TypedInput(`${canvas}.toString()`, TYPE_STRING);
},
getDrawnWidthOfText: (node, compiler, {TypedInput, TYPE_NUMBER}) => {
const canvas = compiler.referenceVariable(node.canvas);
const text = compiler.descendInput(node.text).asString();
const ctx = compiler.evaluateOnce(`${canvas}.canvas.getContext('2d')`);
const cache = compiler.evaluateOnce(`{}`);
let code = `(text => {`;
code += `let textMeasure = ${cache}[text + ${ctx}.font]`;
code += `if (!textMeasure) {`;
code += `textMeasure = ${ctx}.measureText(text);\n`;
code += `${cache}[text + ${ctx}.font] = textMeasure;\n`;
code += '}\n';
code += 'return textMeasure.';
switch (node.prop) {
case 'height':
code += `actualBoundingBoxAscent + textMeasure.actualBoundingBoxDescent`;
break;
default:
code += node.prop;
}
code += `;})(${text})`;
return new TypedInput(code, TYPE_NUMBER);
}
}
};
}
getOrCreateVariable(target, id, name) {
const stage = this.runtime.getTargetForStage();
const variable = target.variables[id] ?? stage.variables[id];
if (!variable) {
return target.createVariable(id, name);
}
return variable;
}
// display monitors
canvasGetter(args, util) {
const canvasObj = this.getOrCreateVariable(util.target, args.canvas.id, args.canvas.name);
return canvasObj;
}
getProperty(args, util) {
const canvasObj = this.getOrCreateVariable(util.target, args.canvas.id, args.canvas.name);
const ctx = canvasObj.canvas.getContext('2d');
return ctx[args.prop];
}
getDataURI(args, util) {
const canvasObj = this.getOrCreateVariable(util.target, args.canvas.id, args.canvas.name);
return canvasObj.toString();
}
getWidthOfPreloaded ({ name }) {
if (!this.preloadedImages.hasOwnProperty(name)) return 0;
return this.preloadedImages[name].width;
}
getHeightOfPreloaded ({ name }) {
if (!this.preloadedImages.hasOwnProperty(name)) return 0;
return this.preloadedImages[name].height;
}
getWidthOfCanvas({ canvas }, util) {
const canvasObj = this.getOrCreateVariable(util.target, canvas.id, canvas.name);
return canvasObj.size[0];
}
getHeightOfCanvas({ canvas }, util) {
const canvasObj = this.getOrCreateVariable(util.target, canvas.id, canvas.name);
return canvasObj.size[1];
}
}
module.exports = canvas;