wow thats a lot of changes
This commit is contained in:
68
editor.html
68
editor.html
@@ -307,6 +307,46 @@
|
|||||||
</value>
|
</value>
|
||||||
</block>
|
</block>
|
||||||
<sep gap="50"></sep>
|
<sep gap="50"></sep>
|
||||||
|
<block type="display_text_as_sprite">
|
||||||
|
<value name="TEXT">
|
||||||
|
<shadow type="text">
|
||||||
|
<field name="TEXT">Hello World!</field>
|
||||||
|
</shadow>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
<block type="clear_text_sprite"></block>
|
||||||
|
<block type="set_text_property">
|
||||||
|
<value name="VALUE">
|
||||||
|
<shadow type="text">
|
||||||
|
<field name="TEXT">Arial</field>
|
||||||
|
</shadow>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
<sep gap="50"></sep>
|
||||||
|
|
||||||
|
<block type="set_color_effect">
|
||||||
|
<value name="COLOR">
|
||||||
|
<shadow type="colour_picker">
|
||||||
|
<field name="COLOUR">#ff0000</field>
|
||||||
|
</shadow>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
<block type="set_effect">
|
||||||
|
<value name="VALUE">
|
||||||
|
<shadow type="math_number">
|
||||||
|
<field name="NUM">25</field>
|
||||||
|
</shadow>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
<block type="change_effect">
|
||||||
|
<value name="VALUE">
|
||||||
|
<shadow type="math_number">
|
||||||
|
<field name="NUM">25</field>
|
||||||
|
</shadow>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
<block type="clear_effects"></block>
|
||||||
|
|
||||||
<block type="switch_costume">
|
<block type="switch_costume">
|
||||||
<value name="COSTUME">
|
<value name="COSTUME">
|
||||||
<shadow type="text">
|
<shadow type="text">
|
||||||
@@ -442,6 +482,28 @@
|
|||||||
<block type="window_size"></block>
|
<block type="window_size"></block>
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
|
<category name="Sensing" colour="#4CBFE6">
|
||||||
|
<block type="touching"></block>
|
||||||
|
<block type="touching_color"></block>
|
||||||
|
<block type="mouse_down"></block>
|
||||||
|
<sep gap="50"></sep>
|
||||||
|
<block type="distance_to"></block>
|
||||||
|
<sep gap="50"></sep>
|
||||||
|
<block type="ask_and_wait">
|
||||||
|
<value name="QUESTION">
|
||||||
|
<shadow type="text">
|
||||||
|
<field name="TEXT">What's your name?</field>
|
||||||
|
</shadow>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
<block type="answer"></block>
|
||||||
|
<sep gap="50"></sep>
|
||||||
|
<block type="current"></block>
|
||||||
|
<block type="days_since_2000"></block>
|
||||||
|
<block type="username"></block>
|
||||||
|
<block type="loudness"></block>
|
||||||
|
</category>
|
||||||
|
|
||||||
<category name="Lists" colour="#e35340">
|
<category name="Lists" colour="#e35340">
|
||||||
<block type="lists_create_with">
|
<block type="lists_create_with">
|
||||||
<mutation items="2"></mutation>
|
<mutation items="2"></mutation>
|
||||||
@@ -554,6 +616,12 @@
|
|||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category name="Variables" colour="#FF8C1A" custom="GLOBAL_VARIABLES"></category>
|
<category name="Variables" colour="#FF8C1A" custom="GLOBAL_VARIABLES"></category>
|
||||||
|
|
||||||
|
<category name="Variable Tools" colour="#FF8C1A">
|
||||||
|
<block type="show_variable"></block>
|
||||||
|
<block type="hide_variable"></block>
|
||||||
|
<block type="move_variable_to"></block>
|
||||||
|
</category>
|
||||||
</xml>
|
</xml>
|
||||||
|
|
||||||
<script type="module" src="src/scripts/editor.js"></script>
|
<script type="module" src="src/scripts/editor.js"></script>
|
||||||
|
|||||||
101
ext.js
101
ext.js
@@ -1,101 +0,0 @@
|
|||||||
class TextSpriteExtension {
|
|
||||||
id = "textSprite";
|
|
||||||
|
|
||||||
registerCategory() {
|
|
||||||
return {
|
|
||||||
name: "Text Sprite",
|
|
||||||
color: "#8E44AD",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
registerBlocks() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: "statement",
|
|
||||||
id: "createTextSprite",
|
|
||||||
text: "create text sprite [text] font [font] size [size] color [color]",
|
|
||||||
tooltip: "Create a new sprite rendered from text",
|
|
||||||
fields: {
|
|
||||||
text: { kind: "value", type: "String", default: "Hello!" },
|
|
||||||
font: {
|
|
||||||
kind: "menu",
|
|
||||||
items: [
|
|
||||||
"Arial",
|
|
||||||
"Verdana",
|
|
||||||
"Courier New",
|
|
||||||
"Times New Roman",
|
|
||||||
"Comic Sans MS",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
size: { kind: "value", type: "Number", default: "48" },
|
|
||||||
color: { kind: "value", type: "String", default: "#000000" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCode() {
|
|
||||||
return {
|
|
||||||
createTextSprite: (inputs) => {
|
|
||||||
if (!window.app || !window.PIXI) return;
|
|
||||||
|
|
||||||
const text = String(inputs.text ?? "");
|
|
||||||
const font = inputs.font || "Arial";
|
|
||||||
const size = Number(inputs.size || 48);
|
|
||||||
const color = String(inputs.color || "#000000");
|
|
||||||
|
|
||||||
/* ---------- Canvas ---------- */
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
|
|
||||||
ctx.font = `${size}px ${font}`;
|
|
||||||
const metrics = ctx.measureText(text);
|
|
||||||
|
|
||||||
canvas.width = Math.ceil(metrics.width) + 20;
|
|
||||||
canvas.height = size + 20;
|
|
||||||
|
|
||||||
ctx.font = `${size}px ${font}`;
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.textBaseline = "top";
|
|
||||||
ctx.fillText(text, 10, 10);
|
|
||||||
|
|
||||||
/* ---------- PIXI Texture ---------- */
|
|
||||||
const baseTexture = new PIXI.BaseTexture(canvas);
|
|
||||||
const texture = new PIXI.Texture(baseTexture);
|
|
||||||
baseTexture.update();
|
|
||||||
|
|
||||||
/* ---------- Create Sprite (same as addSprite) ---------- */
|
|
||||||
const pixiSprite = new PIXI.Sprite(texture);
|
|
||||||
pixiSprite.anchor.set(0.5);
|
|
||||||
pixiSprite.x = 0;
|
|
||||||
pixiSprite.y = 0;
|
|
||||||
pixiSprite.scale._parentScaleEvent = pixiSprite;
|
|
||||||
|
|
||||||
app.stage.addChild(pixiSprite);
|
|
||||||
|
|
||||||
const id = "text-sprite-" + Date.now();
|
|
||||||
|
|
||||||
const spriteData = {
|
|
||||||
id,
|
|
||||||
pixiSprite,
|
|
||||||
code: "",
|
|
||||||
costumes: [{ name: "text", texture }],
|
|
||||||
sounds: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
sprites.push(spriteData);
|
|
||||||
|
|
||||||
// Register globally
|
|
||||||
if (!window.projectCostumes.includes("text")) {
|
|
||||||
window.projectCostumes.push("text");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select it
|
|
||||||
setActiveSprite(spriteData);
|
|
||||||
renderSpritesList(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerExtension(TextSpriteExtension);
|
|
||||||
44
package-lock.json
generated
44
package-lock.json
generated
@@ -8,6 +8,8 @@
|
|||||||
"name": "rarry-vite",
|
"name": "rarry-vite",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blockly/field-colour": "^6.0.11",
|
||||||
|
"@blockly/field-colour-hsv-sliders": "^6.0.11",
|
||||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||||
"blockly": "^12.2.0",
|
"blockly": "^12.2.0",
|
||||||
@@ -33,6 +35,48 @@
|
|||||||
"lru-cache": "^10.4.3"
|
"lru-cache": "^10.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@blockly/field-colour": {
|
||||||
|
"version": "6.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-6.0.11.tgz",
|
||||||
|
"integrity": "sha512-UUTwH3DMt1RslKWQIGAlN5cvGynoQ9mpWPKJGI4erG92wAQWKsLAh5ldVsv9TxO2EfNwpSmQ0/5JqTdSMMqdfA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@blockly/field-grid-dropdown": "^6.0.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"blockly": "^12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@blockly/field-colour-hsv-sliders": {
|
||||||
|
"version": "6.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blockly/field-colour-hsv-sliders/-/field-colour-hsv-sliders-6.0.11.tgz",
|
||||||
|
"integrity": "sha512-l8sZezbdWIuIqqmhUacsuOlXDo/Un3YxbE7SSejx6Apb+5h0GAfQY9kxWVlHolan80CH2aLxFjkNZwiVkax6Kw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@blockly/field-colour": "^6.0.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"blockly": "^12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@blockly/field-grid-dropdown": {
|
||||||
|
"version": "6.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blockly/field-grid-dropdown/-/field-grid-dropdown-6.0.9.tgz",
|
||||||
|
"integrity": "sha512-YuPdS7ZhJKoVagPxbEwUPr5Gq14SnqNk6XD064mYGiH1OYO7LrtljESXG6cnnvQjhKkitiRjZ1ye20MzG3I6FQ==",
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.17.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"blockly": "^12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@blockly/plugin-strict-connection-checker": {
|
"node_modules/@blockly/plugin-strict-connection-checker": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@blockly/plugin-strict-connection-checker/-/plugin-strict-connection-checker-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@blockly/plugin-strict-connection-checker/-/plugin-strict-connection-checker-6.0.1.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
"vite": "^7.0.4"
|
"vite": "^7.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blockly/field-colour": "^6.0.11",
|
||||||
|
"@blockly/field-colour-hsv-sliders": "^6.0.11",
|
||||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||||
"blockly": "^12.2.0",
|
"blockly": "^12.2.0",
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ Blockly.Blocks["every_seconds"] = {
|
|||||||
init: function () {
|
init: function () {
|
||||||
this.appendDummyInput()
|
this.appendDummyInput()
|
||||||
.appendField("every")
|
.appendField("every")
|
||||||
.appendField(new Blockly.FieldNumber(2, 0.1), "SECONDS")
|
.appendField(new Blockly.FieldNumber(2, 0.01), "SECONDS")
|
||||||
.appendField("seconds");
|
.appendField("seconds");
|
||||||
this.appendStatementInput("DO").setCheck("default");
|
this.appendStatementInput("DO").setCheck("default");
|
||||||
this.setStyle("events_blocks");
|
this.setStyle("events_blocks");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as Blockly from "blockly";
|
import * as Blockly from "blockly";
|
||||||
import * as BlocklyJS from "blockly/javascript";
|
import * as BlocklyJS from "blockly/javascript";
|
||||||
|
import {FieldColourHsvSliders} from '@blockly/field-colour-hsv-sliders';
|
||||||
|
|
||||||
function getAvailableCostumes() {
|
function getAvailableCostumes() {
|
||||||
if (window.projectCostumes && window.projectCostumes.length > 0) {
|
if (window.projectCostumes && window.projectCostumes.length > 0) {
|
||||||
@@ -216,3 +217,95 @@ BlocklyJS.javascriptGenerator.forBlock["switch_backdrop"] = function (block) {
|
|||||||
const name = block.getFieldValue("BACKDROP_NAME");
|
const name = block.getFieldValue("BACKDROP_NAME");
|
||||||
return `setBackdropByName('${name}');\n`;
|
return `setBackdropByName('${name}');\n`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Color/Tint effect block - with HSV color picker
|
||||||
|
Blockly.Blocks["set_color_effect"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("set color effect to")
|
||||||
|
.appendField(new FieldColourHsvSliders("#ff0000"), "COLOR");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setInputsInline(true);
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["set_color_effect"] = function (block, generator) {
|
||||||
|
const color = block.getFieldValue("COLOR") || "#000000";
|
||||||
|
return `setSpriteEffect('tint', '${color}');\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set effect by amount
|
||||||
|
Blockly.Blocks["set_effect"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("set")
|
||||||
|
.appendField(new Blockly.FieldDropdown([
|
||||||
|
["color", "color"],
|
||||||
|
["brightness", "brightness"],
|
||||||
|
["ghost", "ghost"],
|
||||||
|
["pixelate", "pixelate"],
|
||||||
|
["mosaic (WIP)", "mosaic"],
|
||||||
|
["whirl", "whirl"],
|
||||||
|
["fisheye", "fisheye"],
|
||||||
|
]), "EFFECT")
|
||||||
|
.appendField("effect to");
|
||||||
|
this.appendValueInput("VALUE")
|
||||||
|
.setCheck("Number");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setInputsInline(true);
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["set_effect"] = function (block, generator) {
|
||||||
|
const effect = block.getFieldValue("EFFECT");
|
||||||
|
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC) || "0";
|
||||||
|
return `setSpriteEffect('${effect}', ${value});\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change effect by amount
|
||||||
|
Blockly.Blocks["change_effect"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("change")
|
||||||
|
.appendField(new Blockly.FieldDropdown([
|
||||||
|
["color", "color"],
|
||||||
|
["brightness", "brightness"],
|
||||||
|
["ghost", "ghost"],
|
||||||
|
["pixelate", "pixelate"],
|
||||||
|
["mosaic (WIP)", "mosaic"],
|
||||||
|
["whirl", "whirl"],
|
||||||
|
["fisheye", "fisheye"],
|
||||||
|
]), "EFFECT")
|
||||||
|
.appendField("effect by");
|
||||||
|
this.appendValueInput("VALUE")
|
||||||
|
.setCheck("Number");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setInputsInline(true);
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["change_effect"] = function (block, generator) {
|
||||||
|
const effect = block.getFieldValue("EFFECT");
|
||||||
|
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC) || "0";
|
||||||
|
return `changeSpriteEffect('${effect}', ${value});\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear all effects
|
||||||
|
Blockly.Blocks["clear_effects"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("clear graphic effects");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["clear_effects"] = function () {
|
||||||
|
return `clearSpriteEffects();\n`;
|
||||||
|
};
|
||||||
90
src/blocks/monitors.js
Normal file
90
src/blocks/monitors.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import * as Blockly from "blockly";
|
||||||
|
import * as BlocklyJS from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks["show_variable"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("show variable")
|
||||||
|
.appendField(new Blockly.FieldDropdown(this.getVariables), "VAR");
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("at x:")
|
||||||
|
.appendField(new Blockly.FieldNumber(10), "X")
|
||||||
|
.appendField("y:")
|
||||||
|
.appendField(new Blockly.FieldNumber(10), "Y");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setColour("#FF8C1A");
|
||||||
|
this.setInputsInline(true);
|
||||||
|
},
|
||||||
|
getVariables: function() {
|
||||||
|
const variables = window.projectVariables || {};
|
||||||
|
const varNames = Object.keys(variables);
|
||||||
|
if (varNames.length === 0) {
|
||||||
|
return [["no variables", ""]];
|
||||||
|
}
|
||||||
|
return varNames.map(name => [name, name]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["show_variable"] = function (block) {
|
||||||
|
const varName = block.getFieldValue("VAR");
|
||||||
|
const x = block.getFieldValue("X") || 10;
|
||||||
|
const y = block.getFieldValue("Y") || 10;
|
||||||
|
return `showVariableMonitor("${varName}", ${x}, ${y});\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
Blockly.Blocks["hide_variable"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("hide variable")
|
||||||
|
.appendField(new Blockly.FieldDropdown(this.getVariables), "VAR");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setColour("#FF8C1A");
|
||||||
|
},
|
||||||
|
getVariables: function() {
|
||||||
|
const variables = window.projectVariables || {};
|
||||||
|
const varNames = Object.keys(variables);
|
||||||
|
if (varNames.length === 0) {
|
||||||
|
return [["no variables", ""]];
|
||||||
|
}
|
||||||
|
return varNames.map(name => [name, name]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["hide_variable"] = function (block) {
|
||||||
|
const varName = block.getFieldValue("VAR");
|
||||||
|
return `hideVariableMonitor("${varName}");\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Block to MOVE the variable monitor to a position
|
||||||
|
Blockly.Blocks["move_variable_to"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("move variable")
|
||||||
|
.appendField(new Blockly.FieldDropdown(this.getVariables), "VAR")
|
||||||
|
.appendField("to x:")
|
||||||
|
.appendField(new Blockly.FieldNumber(10), "X")
|
||||||
|
.appendField("y:")
|
||||||
|
.appendField(new Blockly.FieldNumber(10), "Y");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setColour("#FF8C1A");
|
||||||
|
},
|
||||||
|
getVariables: function() {
|
||||||
|
const variables = window.projectVariables || {};
|
||||||
|
const varNames = Object.keys(variables);
|
||||||
|
if (varNames.length === 0) {
|
||||||
|
return [["no variables", ""]];
|
||||||
|
}
|
||||||
|
return varNames.map(name => [name, name]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["move_variable_to"] = function (block) {
|
||||||
|
const varName = block.getFieldValue("VAR");
|
||||||
|
const x = block.getFieldValue("X") || 10;
|
||||||
|
const y = block.getFieldValue("Y") || 10;
|
||||||
|
console.log('Generating move_variable_to code:', varName, x, y);
|
||||||
|
return `moveVariableMonitor("${varName}", ${x}, ${y});\n`;
|
||||||
|
};
|
||||||
278
src/blocks/sensing.js
Normal file
278
src/blocks/sensing.js
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import * as Blockly from "blockly";
|
||||||
|
import * as BlocklyJS from "blockly/javascript";
|
||||||
|
import {FieldColourHsvSliders} from '@blockly/field-colour-hsv-sliders';
|
||||||
|
|
||||||
|
// Function to get available sprites for dropdown
|
||||||
|
function getAvailableSprites() {
|
||||||
|
if (window.sprites && window.sprites.length > 0) {
|
||||||
|
return window.sprites.map(sprite => [sprite.id, sprite.id]);
|
||||||
|
}
|
||||||
|
return [["no sprites", ""]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touching block
|
||||||
|
Blockly.Blocks["touching"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput("MAIN")
|
||||||
|
.appendField("touching")
|
||||||
|
.appendField(
|
||||||
|
new Blockly.FieldDropdown([
|
||||||
|
["mouse pointer", "mouse"],
|
||||||
|
["edge", "edge"],
|
||||||
|
["sprite", "sprite"],
|
||||||
|
]),
|
||||||
|
"TARGET"
|
||||||
|
);
|
||||||
|
this.setOutput(true, "Boolean");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Check if sprite is touching something");
|
||||||
|
},
|
||||||
|
onchange: function(e) {
|
||||||
|
if (e.type === Blockly.Events.BLOCK_FIELD_INTERMEDIATE_CHANGE) return;
|
||||||
|
if (!this.workspace) return;
|
||||||
|
|
||||||
|
const target = this.getFieldValue('TARGET');
|
||||||
|
const spriteDropdown = this.getField('SPRITE_ID');
|
||||||
|
const mainInput = this.getInput('MAIN');
|
||||||
|
|
||||||
|
if (target === 'sprite') {
|
||||||
|
if (!spriteDropdown) {
|
||||||
|
mainInput.appendField("named");
|
||||||
|
mainInput.appendField(new Blockly.FieldDropdown(
|
||||||
|
function() {
|
||||||
|
return getAvailableSprites();
|
||||||
|
}
|
||||||
|
), "SPRITE_ID");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (spriteDropdown) {
|
||||||
|
// Remove the dropdown first
|
||||||
|
mainInput.removeField('SPRITE_ID');
|
||||||
|
|
||||||
|
// Find and remove the "named" label
|
||||||
|
const fields = mainInput.fieldRow;
|
||||||
|
for (let i = fields.length - 1; i >= 0; i--) {
|
||||||
|
const field = fields[i];
|
||||||
|
if (field.getText && field.getText() === "named") {
|
||||||
|
mainInput.removeField(fields[i].name, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["touching"] = function (block, generator) {
|
||||||
|
const target = block.getFieldValue("TARGET");
|
||||||
|
|
||||||
|
if (target === "mouse") {
|
||||||
|
return [`isTouchingMouse()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
} else if (target === "edge") {
|
||||||
|
return [`isTouchingEdge()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
} else if (target === "sprite") {
|
||||||
|
const spriteId = block.getFieldValue("SPRITE_ID") || "";
|
||||||
|
return [`isTouchingSprite("${spriteId}")`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["false", BlocklyJS.Order.ATOMIC];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mouse down block
|
||||||
|
Blockly.Blocks["mouse_down"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("mouse down?");
|
||||||
|
this.setOutput(true, "Boolean");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Check if mouse button is pressed");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["mouse_down"] = function () {
|
||||||
|
return [`isMouseDown()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Touching color block with full slider control
|
||||||
|
Blockly.Blocks["touching_color"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("touching color")
|
||||||
|
.appendField(new FieldColourHsvSliders("#ff0000"), "COLOR");
|
||||||
|
this.setOutput(true, "Boolean");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Check if sprite is touching a specific color");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["touching_color"] = function (block) {
|
||||||
|
const color = block.getFieldValue("COLOR");
|
||||||
|
return [`isTouchingColor("${color}")`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Distance to block - FIXED VERSION
|
||||||
|
Blockly.Blocks["distance_to"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput("MAIN")
|
||||||
|
.appendField("distance to")
|
||||||
|
.appendField(
|
||||||
|
new Blockly.FieldDropdown([
|
||||||
|
["mouse pointer", "mouse"],
|
||||||
|
["sprite", "sprite"],
|
||||||
|
]),
|
||||||
|
"TARGET"
|
||||||
|
);
|
||||||
|
this.setOutput(true, "Number");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Get distance to target");
|
||||||
|
},
|
||||||
|
onchange: function(e) {
|
||||||
|
if (e.type === Blockly.Events.BLOCK_FIELD_INTERMEDIATE_CHANGE) return;
|
||||||
|
if (!this.workspace) return;
|
||||||
|
|
||||||
|
const target = this.getFieldValue('TARGET');
|
||||||
|
const spriteDropdown = this.getField('SPRITE_ID');
|
||||||
|
const mainInput = this.getInput('MAIN');
|
||||||
|
|
||||||
|
if (target === 'sprite') {
|
||||||
|
if (!spriteDropdown) {
|
||||||
|
mainInput.appendField("named");
|
||||||
|
mainInput.appendField(new Blockly.FieldDropdown(
|
||||||
|
function() {
|
||||||
|
return getAvailableSprites();
|
||||||
|
}
|
||||||
|
), "SPRITE_ID");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (spriteDropdown) {
|
||||||
|
// Remove the dropdown first
|
||||||
|
mainInput.removeField('SPRITE_ID');
|
||||||
|
|
||||||
|
// Find and remove the "named" label
|
||||||
|
const fields = mainInput.fieldRow;
|
||||||
|
for (let i = fields.length - 1; i >= 0; i--) {
|
||||||
|
const field = fields[i];
|
||||||
|
if (field.getText && field.getText() === "named") {
|
||||||
|
mainInput.removeField(fields[i].name, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["distance_to"] = function (block, generator) {
|
||||||
|
const target = block.getFieldValue("TARGET");
|
||||||
|
|
||||||
|
if (target === "mouse") {
|
||||||
|
return [`distanceToMouse()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
} else if (target === "sprite") {
|
||||||
|
const spriteId = block.getFieldValue("SPRITE_ID") || "";
|
||||||
|
return [`distanceToSprite("${spriteId}")`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["0", BlocklyJS.Order.ATOMIC];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Answer (for ask and wait)
|
||||||
|
Blockly.Blocks["answer"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("answer");
|
||||||
|
this.setOutput(true, "String");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("The answer from the last 'ask and wait' prompt");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["answer"] = function () {
|
||||||
|
return [`getAnswer()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ask and wait
|
||||||
|
Blockly.Blocks["ask_and_wait"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendValueInput("QUESTION")
|
||||||
|
.setCheck("String")
|
||||||
|
.appendField("ask")
|
||||||
|
.appendField("and wait");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Ask a question and wait for user input");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["ask_and_wait"] = function (block, generator) {
|
||||||
|
const question = generator.valueToCode(block, "QUESTION", BlocklyJS.Order.ATOMIC) || '"What\'s your name?"';
|
||||||
|
return `await askAndWait(${question});\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Username
|
||||||
|
Blockly.Blocks["username"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("username");
|
||||||
|
this.setOutput(true, "String");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Get the current username");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["username"] = function () {
|
||||||
|
return [`getUsername()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loudness
|
||||||
|
Blockly.Blocks["loudness"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("loudness");
|
||||||
|
this.setOutput(true, "Number");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Microphone loudness (0-100)");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["loudness"] = function () {
|
||||||
|
return [`getLoudness()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Current date/time
|
||||||
|
Blockly.Blocks["current"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("current")
|
||||||
|
.appendField(
|
||||||
|
new Blockly.FieldDropdown([
|
||||||
|
["year", "year"],
|
||||||
|
["month", "month"],
|
||||||
|
["date", "date"],
|
||||||
|
["day of week", "dayofweek"],
|
||||||
|
["hour", "hour"],
|
||||||
|
["minute", "minute"],
|
||||||
|
["second", "second"],
|
||||||
|
]),
|
||||||
|
"UNIT"
|
||||||
|
);
|
||||||
|
this.setOutput(true, "Number");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Get current date/time value");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["current"] = function (block) {
|
||||||
|
const unit = block.getFieldValue("UNIT");
|
||||||
|
return [`getCurrent("${unit}")`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Days since 2000
|
||||||
|
Blockly.Blocks["days_since_2000"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("days since 2000");
|
||||||
|
this.setOutput(true, "Number");
|
||||||
|
this.setColour("#4CBFE6");
|
||||||
|
this.setTooltip("Number of days since January 1, 2000");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["days_since_2000"] = function () {
|
||||||
|
return [`getDaysSince2000()`, BlocklyJS.Order.FUNCTION_CALL];
|
||||||
|
};
|
||||||
77
src/blocks/text_rendering.js
Normal file
77
src/blocks/text_rendering.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import * as Blockly from "blockly";
|
||||||
|
import * as BlocklyJS from "blockly/javascript";
|
||||||
|
|
||||||
|
Blockly.Blocks["display_text_as_sprite"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendValueInput("TEXT")
|
||||||
|
.setCheck("String")
|
||||||
|
.appendField("display text");
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("font size")
|
||||||
|
.appendField(new Blockly.FieldNumber(32, 1, 500), "SIZE");
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("color")
|
||||||
|
.appendField(new Blockly.FieldTextInput("#ffffff"), "COLOR"); // CHANGED: Use FieldTextInput instead
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
this.setTooltip("Display text as the sprite");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["display_text_as_sprite"] = function (
|
||||||
|
block,
|
||||||
|
generator
|
||||||
|
) {
|
||||||
|
const text = generator.valueToCode(block, "TEXT", BlocklyJS.Order.ATOMIC) || '""';
|
||||||
|
const size = block.getFieldValue("SIZE");
|
||||||
|
const color = block.getFieldValue("COLOR");
|
||||||
|
|
||||||
|
return `displayTextAsSprite(${text}, ${size}, "${color}");\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
Blockly.Blocks["clear_text_sprite"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput().appendField("clear text sprite");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
this.setTooltip("Remove text and restore original sprite");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["clear_text_sprite"] = function () {
|
||||||
|
return `clearTextSprite();\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
Blockly.Blocks["set_text_property"] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendValueInput("VALUE")
|
||||||
|
.appendField("set text")
|
||||||
|
.appendField(
|
||||||
|
new Blockly.FieldDropdown([
|
||||||
|
["font", "font"],
|
||||||
|
["alignment", "align"],
|
||||||
|
["bold", "bold"],
|
||||||
|
["italic", "italic"],
|
||||||
|
["outline color", "stroke"],
|
||||||
|
["outline thickness", "strokeThickness"],
|
||||||
|
]),
|
||||||
|
"PROPERTY"
|
||||||
|
)
|
||||||
|
.appendField("to");
|
||||||
|
this.setPreviousStatement(true, "default");
|
||||||
|
this.setNextStatement(true, "default");
|
||||||
|
this.setStyle("looks_blocks");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocklyJS.javascriptGenerator.forBlock["set_text_property"] = function (
|
||||||
|
block,
|
||||||
|
generator
|
||||||
|
) {
|
||||||
|
const property = block.getFieldValue("PROPERTY");
|
||||||
|
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC) || '""';
|
||||||
|
|
||||||
|
return `setTextProperty("${property}", ${value});\n`;
|
||||||
|
};
|
||||||
172
src/functions/monitors.js
Normal file
172
src/functions/monitors.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import * as PIXI from "pixi.js-legacy";
|
||||||
|
|
||||||
|
const monitors = [];
|
||||||
|
const MONITOR_PADDING = 8;
|
||||||
|
const MONITOR_FONT_SIZE = 14;
|
||||||
|
const MONITOR_MIN_WIDTH = 80;
|
||||||
|
const MONITOR_BG_COLOR = 0xFF8C1A; // Orange for variables
|
||||||
|
const MONITOR_TEXT_COLOR = 0xFFFFFF;
|
||||||
|
|
||||||
|
export class Monitor {
|
||||||
|
constructor(app, type, name, getValue, x = 10, y = 10) {
|
||||||
|
this.app = app;
|
||||||
|
this.type = type; // 'variable', 'timer', 'answer', etc.
|
||||||
|
this.name = name;
|
||||||
|
this.label = name;
|
||||||
|
this.getValue = getValue;
|
||||||
|
this.visible = true;
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
|
this.container = new PIXI.Container();
|
||||||
|
this.container.x = x;
|
||||||
|
this.container.y = y;
|
||||||
|
this.container.interactive = true;
|
||||||
|
this.container.buttonMode = true;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
this.background = new PIXI.Graphics();
|
||||||
|
this.container.addChild(this.background);
|
||||||
|
|
||||||
|
// Label text
|
||||||
|
this.labelText = new PIXI.Text(name, {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: MONITOR_FONT_SIZE,
|
||||||
|
fill: MONITOR_TEXT_COLOR,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
});
|
||||||
|
this.labelText.x = MONITOR_PADDING;
|
||||||
|
this.labelText.y = MONITOR_PADDING;
|
||||||
|
this.container.addChild(this.labelText);
|
||||||
|
|
||||||
|
// Value text
|
||||||
|
this.valueText = new PIXI.Text('0', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: MONITOR_FONT_SIZE + 2,
|
||||||
|
fill: MONITOR_TEXT_COLOR
|
||||||
|
});
|
||||||
|
this.valueText.x = MONITOR_PADDING;
|
||||||
|
this.valueText.y = MONITOR_PADDING + MONITOR_FONT_SIZE + 4;
|
||||||
|
this.container.addChild(this.valueText);
|
||||||
|
|
||||||
|
// Make draggable
|
||||||
|
this.container.on('pointerdown', this.onDragStart.bind(this));
|
||||||
|
this.container.on('pointerup', this.onDragEnd.bind(this));
|
||||||
|
this.container.on('pointerupoutside', this.onDragEnd.bind(this));
|
||||||
|
this.container.on('pointermove', this.onDragMove.bind(this));
|
||||||
|
|
||||||
|
this.app.stage.addChild(this.container);
|
||||||
|
this.updateDisplay();
|
||||||
|
|
||||||
|
monitors.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragStart(event) {
|
||||||
|
this.dragging = true;
|
||||||
|
this.dragData = event.data;
|
||||||
|
const pos = this.dragData.getLocalPosition(this.app.stage);
|
||||||
|
this.dragOffset = {
|
||||||
|
x: pos.x - this.container.x,
|
||||||
|
y: pos.y - this.container.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragEnd() {
|
||||||
|
this.dragging = false;
|
||||||
|
this.dragData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragMove() {
|
||||||
|
if (this.dragging && this.dragData) {
|
||||||
|
const pos = this.dragData.getLocalPosition(this.app.stage);
|
||||||
|
this.container.x = pos.x - this.dragOffset.x;
|
||||||
|
this.container.y = pos.y - this.dragOffset.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDisplay() {
|
||||||
|
const value = this.getValue();
|
||||||
|
this.valueText.text = String(value);
|
||||||
|
|
||||||
|
// Resize background
|
||||||
|
const width = Math.max(
|
||||||
|
MONITOR_MIN_WIDTH,
|
||||||
|
Math.max(this.labelText.width, this.valueText.width) + MONITOR_PADDING * 2
|
||||||
|
);
|
||||||
|
const height = this.labelText.height + this.valueText.height + MONITOR_PADDING * 3;
|
||||||
|
|
||||||
|
this.background.clear();
|
||||||
|
this.background.beginFill(MONITOR_BG_COLOR);
|
||||||
|
this.background.drawRoundedRect(0, 0, width, height, 8);
|
||||||
|
this.background.endFill();
|
||||||
|
this.background.lineStyle(2, 0x000000, 0.2);
|
||||||
|
this.background.drawRoundedRect(0, 0, width, height, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisible(visible) {
|
||||||
|
this.visible = visible;
|
||||||
|
this.container.visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.container.destroy({ children: true });
|
||||||
|
const index = monitors.indexOf(this);
|
||||||
|
if (index > -1) {
|
||||||
|
monitors.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
name: this.name,
|
||||||
|
x: this.container.x,
|
||||||
|
y: this.container.y,
|
||||||
|
visible: this.visible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAllMonitors() {
|
||||||
|
monitors.forEach(monitor => monitor.updateDisplay());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMonitor(type, name) {
|
||||||
|
return monitors.find(m => m.type === type && m.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMonitor(app, type, name, getValue, x, y) {
|
||||||
|
// Check if monitor already exists
|
||||||
|
const existing = getMonitor(type, name);
|
||||||
|
if (existing) {
|
||||||
|
existing.setVisible(true);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Monitor(app, type, name, getValue, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeMonitor(type, name) {
|
||||||
|
const monitor = getMonitor(type, name);
|
||||||
|
if (monitor) {
|
||||||
|
monitor.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllMonitors() {
|
||||||
|
return monitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAllMonitors() {
|
||||||
|
monitors.forEach(m => m.destroy());
|
||||||
|
monitors.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadMonitors(app, monitorsData, valueGetters) {
|
||||||
|
monitorsData.forEach(data => {
|
||||||
|
const getter = valueGetters[data.type]?.[data.name];
|
||||||
|
if (getter) {
|
||||||
|
const monitor = new Monitor(app, data.type, data.name, getter, data.x, data.y);
|
||||||
|
monitor.setVisible(data.visible);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -437,5 +437,741 @@ export function runCodeWithFunctions({
|
|||||||
if (spriteData.currentBubble) spriteData.currentBubble.visible = bool;
|
if (spriteData.currentBubble) spriteData.currentBubble.visible = bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function displayTextAsSprite(text, fontSize, color) {
|
||||||
|
// Store original texture if not already stored
|
||||||
|
if (!spriteData.originalTexture) {
|
||||||
|
spriteData.originalTexture = sprite.texture;
|
||||||
|
spriteData.originalAnchor = { x: sprite.anchor.x, y: sprite.anchor.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old text if it exists
|
||||||
|
if (spriteData.textSprite) {
|
||||||
|
sprite.removeChild(spriteData.textSprite);
|
||||||
|
spriteData.textSprite.destroy();
|
||||||
|
spriteData.textSprite = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update text style
|
||||||
|
if (!spriteData.textStyle) {
|
||||||
|
spriteData.textStyle = {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: fontSize || 32,
|
||||||
|
fill: color || '#ffffff',
|
||||||
|
align: 'center',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
spriteData.textStyle.fontSize = fontSize || spriteData.textStyle.fontSize;
|
||||||
|
spriteData.textStyle.fill = color || spriteData.textStyle.fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create PIXI Text
|
||||||
|
const pixiText = new PIXI.Text(String(text), spriteData.textStyle);
|
||||||
|
pixiText.anchor.set(0.5);
|
||||||
|
|
||||||
|
// Hide the original sprite texture
|
||||||
|
sprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
|
||||||
|
// Add text as child
|
||||||
|
sprite.addChild(pixiText);
|
||||||
|
spriteData.textSprite = pixiText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTextSprite() {
|
||||||
|
// Remove text sprite
|
||||||
|
if (spriteData.textSprite) {
|
||||||
|
sprite.removeChild(spriteData.textSprite);
|
||||||
|
spriteData.textSprite.destroy();
|
||||||
|
spriteData.textSprite = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original texture
|
||||||
|
if (spriteData.originalTexture) {
|
||||||
|
sprite.texture = spriteData.originalTexture;
|
||||||
|
sprite.anchor.set(spriteData.originalAnchor.x, spriteData.originalAnchor.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear stored style
|
||||||
|
spriteData.textStyle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTextProperty(property, value) {
|
||||||
|
if (!spriteData.textStyle) {
|
||||||
|
spriteData.textStyle = {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: 32,
|
||||||
|
fill: '#ffffff',
|
||||||
|
align: 'center',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (property) {
|
||||||
|
case 'font':
|
||||||
|
spriteData.textStyle.fontFamily = String(value);
|
||||||
|
break;
|
||||||
|
case 'align':
|
||||||
|
spriteData.textStyle.align = String(value);
|
||||||
|
break;
|
||||||
|
case 'bold':
|
||||||
|
spriteData.textStyle.fontWeight = value ? 'bold' : 'normal';
|
||||||
|
break;
|
||||||
|
case 'italic':
|
||||||
|
spriteData.textStyle.fontStyle = value ? 'italic' : 'normal';
|
||||||
|
break;
|
||||||
|
case 'stroke':
|
||||||
|
spriteData.textStyle.stroke = String(value);
|
||||||
|
break;
|
||||||
|
case 'strokeThickness':
|
||||||
|
spriteData.textStyle.strokeThickness = Number(value) || 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing text sprite if it exists
|
||||||
|
if (spriteData.textSprite) {
|
||||||
|
spriteData.textSprite.style = new PIXI.TextStyle(spriteData.textStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touching mouse pointer
|
||||||
|
function isTouchingMouse() {
|
||||||
|
const mouse = renderer.events.pointer.global;
|
||||||
|
const bounds = sprite.getBounds();
|
||||||
|
return bounds.contains(mouse.x, mouse.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touching edge
|
||||||
|
function isTouchingEdge() {
|
||||||
|
const bounds = sprite.getBounds();
|
||||||
|
const stageWidth = renderer.width;
|
||||||
|
const stageHeight = renderer.height;
|
||||||
|
|
||||||
|
return (
|
||||||
|
bounds.left <= 0 ||
|
||||||
|
bounds.right >= stageWidth ||
|
||||||
|
bounds.top <= 0 ||
|
||||||
|
bounds.bottom >= stageHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touching another sprite
|
||||||
|
function isTouchingSprite(spriteName) {
|
||||||
|
const targetSpriteData = window.sprites?.find(s => s.id === spriteName);
|
||||||
|
if (!targetSpriteData) return false;
|
||||||
|
|
||||||
|
const targetSprite = targetSpriteData.pixiSprite;
|
||||||
|
const bounds1 = sprite.getBounds();
|
||||||
|
const bounds2 = targetSprite.getBounds();
|
||||||
|
|
||||||
|
return bounds1.intersects(bounds2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse down
|
||||||
|
function isMouseDown() {
|
||||||
|
return Object.values(mouseButtonsPressed).some((pressed) => pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touching color (simplified - checks if sprite overlaps with any pixel of that color on stage)
|
||||||
|
// Touching color (improved version)
|
||||||
|
function isTouchingColor(hexColor) {
|
||||||
|
// Convert hex to RGB
|
||||||
|
const color = parseInt(hexColor.replace('#', ''), 16);
|
||||||
|
const targetR = (color >> 16) & 0xFF;
|
||||||
|
const targetG = (color >> 8) & 0xFF;
|
||||||
|
const targetB = color & 0xFF;
|
||||||
|
|
||||||
|
// Get sprite bounds in screen coordinates
|
||||||
|
const bounds = sprite.getBounds();
|
||||||
|
|
||||||
|
// Sample points around the sprite (not just inside)
|
||||||
|
const samplePoints = [];
|
||||||
|
const step = 5; // Sample every 5 pixels
|
||||||
|
|
||||||
|
for (let x = bounds.left; x < bounds.right; x += step) {
|
||||||
|
for (let y = bounds.top; y < bounds.bottom; y += step) {
|
||||||
|
samplePoints.push({ x: Math.floor(x), y: Math.floor(y) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add edge points
|
||||||
|
for (let x = bounds.left; x < bounds.right; x += step) {
|
||||||
|
samplePoints.push({ x: Math.floor(x), y: Math.floor(bounds.top) });
|
||||||
|
samplePoints.push({ x: Math.floor(x), y: Math.floor(bounds.bottom) });
|
||||||
|
}
|
||||||
|
for (let y = bounds.top; y < bounds.bottom; y += step) {
|
||||||
|
samplePoints.push({ x: Math.floor(bounds.left), y: Math.floor(y) });
|
||||||
|
samplePoints.push({ x: Math.floor(bounds.right), y: Math.floor(y) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract pixels from the entire stage
|
||||||
|
try {
|
||||||
|
const renderTexture = PIXI.RenderTexture.create({
|
||||||
|
width: renderer.width,
|
||||||
|
height: renderer.height
|
||||||
|
});
|
||||||
|
|
||||||
|
renderer.render(stage, { renderTexture });
|
||||||
|
|
||||||
|
const pixels = renderer.extract.pixels(renderTexture);
|
||||||
|
const width = renderer.width;
|
||||||
|
|
||||||
|
// Check sampled points
|
||||||
|
for (const point of samplePoints) {
|
||||||
|
const x = point.x;
|
||||||
|
const y = point.y;
|
||||||
|
|
||||||
|
if (x < 0 || x >= width || y < 0 || y >= renderer.height) continue;
|
||||||
|
|
||||||
|
const index = (y * width + x) * 4;
|
||||||
|
const pixelR = pixels[index];
|
||||||
|
const pixelG = pixels[index + 1];
|
||||||
|
const pixelB = pixels[index + 2];
|
||||||
|
const pixelA = pixels[index + 3];
|
||||||
|
|
||||||
|
// Skip transparent pixels
|
||||||
|
if (pixelA < 128) continue;
|
||||||
|
|
||||||
|
// Check if color matches (with tolerance of 20)
|
||||||
|
const tolerance = 20;
|
||||||
|
if (Math.abs(pixelR - targetR) <= tolerance &&
|
||||||
|
Math.abs(pixelG - targetG) <= tolerance &&
|
||||||
|
Math.abs(pixelB - targetB) <= tolerance) {
|
||||||
|
renderTexture.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTexture.destroy();
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error checking color:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance to mouse
|
||||||
|
function distanceToMouse() {
|
||||||
|
const mouse = renderer.events.pointer.global;
|
||||||
|
const spriteX = sprite.x + renderer.width / 2;
|
||||||
|
const spriteY = sprite.y + renderer.height / 2;
|
||||||
|
|
||||||
|
const dx = mouse.x - spriteX;
|
||||||
|
const dy = mouse.y - spriteY;
|
||||||
|
|
||||||
|
return Math.sqrt(dx * dx + dy * dy) / stage.scale.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance to sprite
|
||||||
|
function distanceToSprite(spriteName) {
|
||||||
|
const targetSpriteData = window.sprites?.find(s => s.id === spriteName);
|
||||||
|
if (!targetSpriteData) return 0;
|
||||||
|
|
||||||
|
const targetSprite = targetSpriteData.pixiSprite;
|
||||||
|
const dx = targetSprite.x - sprite.x;
|
||||||
|
const dy = targetSprite.y - sprite.y;
|
||||||
|
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask and wait
|
||||||
|
let lastAnswer = "";
|
||||||
|
|
||||||
|
async function askAndWait(question) {
|
||||||
|
lastAnswer = prompt(String(question)) || "";
|
||||||
|
return lastAnswer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAnswer() {
|
||||||
|
return lastAnswer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Username
|
||||||
|
function getUsername() {
|
||||||
|
// Try to get from localStorage or return "user"
|
||||||
|
return localStorage.getItem("username") || "user";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loudness (placeholder - would need microphone access)
|
||||||
|
function getLoudness() {
|
||||||
|
return 0; // Would need Web Audio API implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current date/time
|
||||||
|
function getCurrent(unit) {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
switch (unit) {
|
||||||
|
case "year":
|
||||||
|
return now.getFullYear();
|
||||||
|
case "month":
|
||||||
|
return now.getMonth() + 1; // 1-12
|
||||||
|
case "date":
|
||||||
|
return now.getDate();
|
||||||
|
case "dayofweek":
|
||||||
|
return now.getDay() + 1; // 1-7 (1 = Sunday)
|
||||||
|
case "hour":
|
||||||
|
return now.getHours();
|
||||||
|
case "minute":
|
||||||
|
return now.getMinutes();
|
||||||
|
case "second":
|
||||||
|
return now.getSeconds();
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Days since 2000
|
||||||
|
function getDaysSince2000() {
|
||||||
|
const now = new Date();
|
||||||
|
const year2000 = new Date(2000, 0, 1);
|
||||||
|
const diff = now.getTime() - year2000.getTime();
|
||||||
|
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showVariableMonitor(varName, x, y) {
|
||||||
|
console.log('=== showVariableMonitor called ===');
|
||||||
|
console.log('Variable:', varName, 'Position:', x, y);
|
||||||
|
|
||||||
|
const existing = window.getMonitor?.('variable', varName);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
console.log('Monitor already exists, moving to:', x, y);
|
||||||
|
existing.setVisible(true);
|
||||||
|
// FLIP Y coordinate: negate it to match stage coordinates
|
||||||
|
existing.container.x = x !== undefined ? x : 10;
|
||||||
|
existing.container.y = y !== undefined ? -y : -10;
|
||||||
|
} else {
|
||||||
|
console.log('Creating new monitor at:', x, y);
|
||||||
|
const newMonitor = window.createMonitor?.(
|
||||||
|
window.app,
|
||||||
|
'variable',
|
||||||
|
varName,
|
||||||
|
() => window.projectVariables[varName],
|
||||||
|
x !== undefined ? x : 10,
|
||||||
|
y !== undefined ? -y : -10 // FLIP Y here too
|
||||||
|
);
|
||||||
|
console.log('Monitor created:', !!newMonitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideVariableMonitor(varName) {
|
||||||
|
window.removeMonitor?.('variable', varName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoVariableMonitor(varName) {
|
||||||
|
console.log('=== gotoVariableMonitor called ===');
|
||||||
|
console.log('Looking for variable:', varName);
|
||||||
|
|
||||||
|
const monitors = window.getAllMonitors?.() || [];
|
||||||
|
const monitor = monitors.find(m => m.type === 'variable' && m.name === varName);
|
||||||
|
|
||||||
|
if (monitor) {
|
||||||
|
console.log('✓ Monitor found!');
|
||||||
|
console.log('Monitor container.x:', monitor.container.x);
|
||||||
|
console.log('Monitor container.y:', monitor.container.y);
|
||||||
|
console.log('Stage position:', { x: stage.x, y: stage.y });
|
||||||
|
console.log('Stage scale:', { x: stage.scale.x, y: stage.scale.y });
|
||||||
|
console.log('Renderer size:', { width: renderer.width, height: renderer.height });
|
||||||
|
console.log('App stage size:', { width: app.stageWidth, height: app.stageHeight });
|
||||||
|
console.log('Current sprite position:', { x: sprite.x, y: sprite.y });
|
||||||
|
|
||||||
|
// The monitor is a direct child of app.stage
|
||||||
|
// Sprites use coordinates where (0,0) is center
|
||||||
|
// So we can directly use the monitor's position
|
||||||
|
sprite.x = monitor.container.x;
|
||||||
|
sprite.y = monitor.container.y;
|
||||||
|
|
||||||
|
console.log('New sprite position:', { x: sprite.x, y: sprite.y });
|
||||||
|
} else {
|
||||||
|
console.log('✗ Monitor NOT found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveVariableMonitor(varName, x, y) {
|
||||||
|
console.log('=== moveVariableMonitor called ===');
|
||||||
|
console.log('Variable:', varName, 'New position:', x, y);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const monitor = window.getMonitor?.('variable', varName);
|
||||||
|
console.log('Monitor found (after timeout):', !!monitor);
|
||||||
|
|
||||||
|
if (monitor && monitor.container) {
|
||||||
|
console.log('Current position:', monitor.container.x, monitor.container.y);
|
||||||
|
monitor.container.x = x;
|
||||||
|
monitor.container.y = -y; // FLIP Y coordinate
|
||||||
|
console.log('New position set to:', monitor.container.x, monitor.container.y);
|
||||||
|
} else {
|
||||||
|
console.log('Monitor or container not found!');
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprite effects
|
||||||
|
function setSpriteEffect(effect, value) {
|
||||||
|
if (!spriteData.effects) {
|
||||||
|
spriteData.effects = {
|
||||||
|
color: 0,
|
||||||
|
brightness: 0,
|
||||||
|
ghost: 0,
|
||||||
|
pixelate: 0,
|
||||||
|
mosaic: 0,
|
||||||
|
whirl: 0,
|
||||||
|
fisheye: 0,
|
||||||
|
tint: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effect === 'tint') {
|
||||||
|
// For color effect with hex color picker
|
||||||
|
spriteData.effects.tint = value;
|
||||||
|
if (value && typeof value === 'string') {
|
||||||
|
const color = parseInt(value.replace('#', ''), 16);
|
||||||
|
sprite.tint = color;
|
||||||
|
} else {
|
||||||
|
sprite.tint = 0xFFFFFF;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spriteData.effects[effect] = Number(value) || 0;
|
||||||
|
applySpriteEffects();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSpriteEffect(effect, value) {
|
||||||
|
if (!spriteData.effects) {
|
||||||
|
spriteData.effects = {
|
||||||
|
color: 0,
|
||||||
|
brightness: 0,
|
||||||
|
ghost: 0,
|
||||||
|
pixelate: 0,
|
||||||
|
mosaic: 0,
|
||||||
|
whirl: 0,
|
||||||
|
fisheye: 0,
|
||||||
|
tint: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteData.effects[effect] = (spriteData.effects[effect] || 0) + (Number(value) || 0);
|
||||||
|
applySpriteEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSpriteEffects() {
|
||||||
|
spriteData.effects = {
|
||||||
|
color: 0,
|
||||||
|
brightness: 0,
|
||||||
|
ghost: 0,
|
||||||
|
pixelate: 0,
|
||||||
|
mosaic: 0,
|
||||||
|
whirl: 0,
|
||||||
|
fisheye: 0,
|
||||||
|
tint: null
|
||||||
|
};
|
||||||
|
sprite.tint = 0xFFFFFF;
|
||||||
|
sprite.alpha = 1;
|
||||||
|
sprite.filters = null;
|
||||||
|
applySpriteEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySpriteEffects() {
|
||||||
|
if (!spriteData.effects) return;
|
||||||
|
|
||||||
|
const effects = spriteData.effects;
|
||||||
|
const filters = [];
|
||||||
|
|
||||||
|
// Ghost effect (transparency) - 0 to 100
|
||||||
|
sprite.alpha = Math.max(0, Math.min(1, 1 - (effects.ghost / 100)));
|
||||||
|
|
||||||
|
// Color effect (hue shift) - 0 to 200
|
||||||
|
if (effects.color !== 0 && !effects.tint) {
|
||||||
|
const hue = (effects.color % 200) / 200; // 0 to 1
|
||||||
|
const rgb = hsvToRgb(hue, 1, 1);
|
||||||
|
sprite.tint = (rgb.r << 16) | (rgb.g << 8) | rgb.b;
|
||||||
|
} else if (!effects.tint) {
|
||||||
|
sprite.tint = 0xFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brightness effect using ColorMatrixFilter
|
||||||
|
if (effects.brightness !== 0) {
|
||||||
|
const brightnessFilter = new PIXI.filters.ColorMatrixFilter();
|
||||||
|
brightnessFilter.brightness(1 + (effects.brightness / 100), false);
|
||||||
|
filters.push(brightnessFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pixelate effect with custom shader
|
||||||
|
if (effects.pixelate !== 0) {
|
||||||
|
const pixelSize = Math.max(1, Math.abs(effects.pixelate / 10));
|
||||||
|
const pixelateFilter = createPixelateFilter(pixelSize);
|
||||||
|
filters.push(pixelateFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mosaic effect (creates a tiled/blocky pattern)
|
||||||
|
if (effects.mosaic !== 0) {
|
||||||
|
const mosaicSize = Math.max(1, Math.abs(effects.mosaic / 3));
|
||||||
|
const mosaicFilter = createMosaicFilter(mosaicSize);
|
||||||
|
filters.push(mosaicFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whirl/Twist effect
|
||||||
|
if (effects.whirl !== 0) {
|
||||||
|
const whirlFilter = createWhirlFilter(effects.whirl);
|
||||||
|
filters.push(whirlFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fisheye effect
|
||||||
|
if (effects.fisheye !== 0) {
|
||||||
|
const fisheyeFilter = createFisheyeFilter(effects.fisheye);
|
||||||
|
filters.push(fisheyeFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply all filters
|
||||||
|
sprite.filters = filters.length > 0 ? filters : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pixelate filter with custom shader
|
||||||
|
function createPixelateFilter(pixelSize) {
|
||||||
|
const fragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform vec2 dimensions;
|
||||||
|
uniform float pixelSize;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = vTextureCoord * dimensions;
|
||||||
|
vec2 pixelated = floor(coord / pixelSize) * pixelSize;
|
||||||
|
vec2 uv = pixelated / dimensions;
|
||||||
|
gl_FragColor = texture2D(uSampler, uv);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filter = new PIXI.Filter(null, fragmentShader, {
|
||||||
|
dimensions: [sprite.texture.width, sprite.texture.height],
|
||||||
|
pixelSize: pixelSize
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create whirl/twist filter with custom shader
|
||||||
|
function createWhirlFilter(amount) {
|
||||||
|
const radius = 0.5;
|
||||||
|
const angle = amount * 0.05;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform float angle;
|
||||||
|
uniform float radius;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = vTextureCoord - 0.5;
|
||||||
|
float dist = length(coord);
|
||||||
|
|
||||||
|
if (dist < radius) {
|
||||||
|
float percent = (radius - dist) / radius;
|
||||||
|
float theta = percent * percent * angle;
|
||||||
|
float s = sin(theta);
|
||||||
|
float c = cos(theta);
|
||||||
|
coord = vec2(
|
||||||
|
coord.x * c - coord.y * s,
|
||||||
|
coord.x * s + coord.y * c
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
coord += 0.5;
|
||||||
|
gl_FragColor = texture2D(uSampler, coord);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filter = new PIXI.Filter(null, fragmentShader, {
|
||||||
|
angle: angle,
|
||||||
|
radius: radius
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fisheye filter with custom shader
|
||||||
|
function createFisheyeFilter(amount) {
|
||||||
|
const strength = amount / 100;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform float strength;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = vTextureCoord - 0.5;
|
||||||
|
float dist = length(coord);
|
||||||
|
|
||||||
|
if (dist > 0.0) {
|
||||||
|
float distortion = 1.0 + dist * strength;
|
||||||
|
coord = coord / distortion;
|
||||||
|
}
|
||||||
|
|
||||||
|
coord += 0.5;
|
||||||
|
|
||||||
|
if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0) {
|
||||||
|
gl_FragColor = vec4(0.0);
|
||||||
|
} else {
|
||||||
|
gl_FragColor = texture2D(uSampler, coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filter = new PIXI.Filter(null, fragmentShader, {
|
||||||
|
strength: strength
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mosaic filter - creates a repeating/mirrored tile pattern
|
||||||
|
function createMosaicFilter(size) {
|
||||||
|
const fragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform vec2 dimensions;
|
||||||
|
uniform float size;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = vTextureCoord * dimensions;
|
||||||
|
|
||||||
|
// Create repeating tiles by using modulo
|
||||||
|
vec2 tileCoord = mod(coord, size * 2.0);
|
||||||
|
|
||||||
|
// Mirror alternating tiles for a kaleidoscope effect
|
||||||
|
vec2 tile = floor(coord / (size * 2.0));
|
||||||
|
if (mod(tile.x, 2.0) > 0.5) {
|
||||||
|
tileCoord.x = size * 2.0 - tileCoord.x;
|
||||||
|
}
|
||||||
|
if (mod(tile.y, 2.0) > 0.5) {
|
||||||
|
tileCoord.y = size * 2.0 - tileCoord.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 uv = tileCoord / dimensions;
|
||||||
|
gl_FragColor = texture2D(uSampler, uv);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filter = new PIXI.Filter(null, fragmentShader, {
|
||||||
|
dimensions: [sprite.texture.width, sprite.texture.height],
|
||||||
|
size: size
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert HSV to RGB
|
||||||
|
function hsvToRgb(h, s, v) {
|
||||||
|
let r, g, b;
|
||||||
|
const i = Math.floor(h * 6);
|
||||||
|
const f = h * 6 - i;
|
||||||
|
const p = v * (1 - s);
|
||||||
|
const q = v * (1 - f * s);
|
||||||
|
const t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0: r = v; g = t; b = p; break;
|
||||||
|
case 1: r = q; g = v; b = p; break;
|
||||||
|
case 2: r = p; g = v; b = t; break;
|
||||||
|
case 3: r = p; g = q; b = v; break;
|
||||||
|
case 4: r = t; g = p; b = v; break;
|
||||||
|
case 5: r = v; g = p; b = q; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: Math.round(r * 255),
|
||||||
|
g: Math.round(g * 255),
|
||||||
|
b: Math.round(b * 255)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create whirl/twist filter with custom shader
|
||||||
|
function createWhirlFilter(amount) {
|
||||||
|
const radius = 0.5;
|
||||||
|
const angle = amount * 0.05; // Scale down the effect
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform float angle;
|
||||||
|
uniform float radius;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = vTextureCoord - 0.5;
|
||||||
|
float dist = length(coord);
|
||||||
|
|
||||||
|
if (dist < radius) {
|
||||||
|
float percent = (radius - dist) / radius;
|
||||||
|
float theta = percent * percent * angle;
|
||||||
|
float s = sin(theta);
|
||||||
|
float c = cos(theta);
|
||||||
|
coord = vec2(
|
||||||
|
coord.x * c - coord.y * s,
|
||||||
|
coord.x * s + coord.y * c
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
coord += 0.5;
|
||||||
|
gl_FragColor = texture2D(uSampler, coord);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filter = new PIXI.Filter(null, fragmentShader, {
|
||||||
|
angle: angle,
|
||||||
|
radius: radius
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fisheye filter with custom shader
|
||||||
|
function createFisheyeFilter(amount) {
|
||||||
|
const strength = amount / 100;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
uniform float strength;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = vTextureCoord - 0.5;
|
||||||
|
float dist = length(coord);
|
||||||
|
|
||||||
|
if (dist > 0.0) {
|
||||||
|
float distortion = 1.0 + dist * strength;
|
||||||
|
coord = coord / distortion;
|
||||||
|
}
|
||||||
|
|
||||||
|
coord += 0.5;
|
||||||
|
|
||||||
|
if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0) {
|
||||||
|
gl_FragColor = vec4(0.0);
|
||||||
|
} else {
|
||||||
|
gl_FragColor = texture2D(uSampler, coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filter = new PIXI.Filter(null, fragmentShader, {
|
||||||
|
strength: strength
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
eval(code);
|
eval(code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,20 @@ import {
|
|||||||
} from "../functions/extensionManager.js";
|
} from "../functions/extensionManager.js";
|
||||||
import { Thread } from "../functions/threads.js";
|
import { Thread } from "../functions/threads.js";
|
||||||
import { runCodeWithFunctions } from "../functions/runCode.js";
|
import { runCodeWithFunctions } from "../functions/runCode.js";
|
||||||
|
import {
|
||||||
|
createMonitor,
|
||||||
|
removeMonitor,
|
||||||
|
updateAllMonitors,
|
||||||
|
getAllMonitors,
|
||||||
|
clearAllMonitors,
|
||||||
|
loadMonitors,
|
||||||
|
getMonitor
|
||||||
|
} from "../functions/monitors.js";
|
||||||
|
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
BlocklyJS.javascriptGenerator.addReservedWords(
|
BlocklyJS.javascriptGenerator.addReservedWords(
|
||||||
"whenFlagClicked,moveSteps,getAngle,getMousePosition,sayMessage,waitOneFrame,wait,switchCostume,setSize,setAngle,projectTime,isKeyPressed,isMouseButtonPressed,getCostumeSize,getSpriteScale,_startTween,startTween,soundProperties,setSoundProperty,playSound,stopSound,stopAllSounds,isMouseTouchingSprite,setPenStatus,setPenColor,setPenColorHex,setPenSize,clearPen,Thread,fastExecution,BUBBLE_TEXTSTYLE,sprite,renderer,stage,costumeMap,soundMap,stopped,code,penGraphics,runningScripts,findOrFilterItem,registerEvent,triggerCustomEvent,hideSprite,showSprite,MyFunctions"
|
"whenFlagClicked,moveSteps,getAngle,getMousePosition,sayMessage,waitOneFrame,wait,switchCostume,setSize,setAngle,projectTime,isKeyPressed,isMouseButtonPressed,getCostumeSize,getSpriteScale,_startTween,startTween,soundProperties,setSoundProperty,playSound,stopSound,stopAllSounds,isMouseTouchingSprite,setPenStatus,setPenColor,setPenColorHex,setPenSize,clearPen,Thread,fastExecution,BUBBLE_TEXTSTYLE,sprite,renderer,stage,costumeMap,soundMap,stopped,code,penGraphics,runningScripts,findOrFilterItem,registerEvent,triggerCustomEvent,hideSprite,showSprite,MyFunctions,displayTextAsSprite,clearTextSprite,setTextProperty,isTouchingMouse,isTouchingEdge,isTouchingSprite,isMouseDown,isTouchingColor,distanceToMouse,distanceToSprite,askAndWait,getAnswer,getUsername,getLoudness,getCurrent,getDaysSince2000,showVariableMonitor,hideVariableMonitor,gotoVariableMonitor,moveVariableMonitor,setSpriteEffect,changeSpriteEffect,clearSpriteEffects"
|
||||||
);
|
);
|
||||||
|
|
||||||
import.meta.glob("../blocks/**/*.js", { eager: true });
|
import.meta.glob("../blocks/**/*.js", { eager: true });
|
||||||
@@ -40,6 +49,9 @@ let currentRoom = null;
|
|||||||
let amHost = false;
|
let amHost = false;
|
||||||
let invitesEnabled = true;
|
let invitesEnabled = true;
|
||||||
let connectedUsers = [];
|
let connectedUsers = [];
|
||||||
|
let mouseX = 0;
|
||||||
|
let mouseY = 0;
|
||||||
|
let mouseCoordsFrozen = false;
|
||||||
|
|
||||||
const wrapper = document.getElementById("stage-wrapper");
|
const wrapper = document.getElementById("stage-wrapper");
|
||||||
const stageContainer = document.getElementById("stage");
|
const stageContainer = document.getElementById("stage");
|
||||||
@@ -94,8 +106,10 @@ function createPenGraphics() {
|
|||||||
}
|
}
|
||||||
createPenGraphics();
|
createPenGraphics();
|
||||||
|
|
||||||
export let projectVariables = {};
|
window.projectVariables = {};
|
||||||
export let sprites = [];
|
export const projectVariables = window.projectVariables;
|
||||||
|
window.sprites = [];
|
||||||
|
export const sprites = window.sprites;
|
||||||
export let activeSprite = null;
|
export let activeSprite = null;
|
||||||
window.projectSounds = [];
|
window.projectSounds = [];
|
||||||
window.projectCostumes = ["default"];
|
window.projectCostumes = ["default"];
|
||||||
@@ -186,6 +200,13 @@ workspace.registerToolboxCategoryCallback("GLOBAL_VARIABLES", function (_) {
|
|||||||
varField.setAttribute("name", "VAR");
|
varField.setAttribute("name", "VAR");
|
||||||
varField.textContent = name;
|
varField.textContent = name;
|
||||||
get.appendChild(varField);
|
get.appendChild(varField);
|
||||||
|
|
||||||
|
// ADD THIS: Add checkbox for showing monitor
|
||||||
|
const checkbox = Blockly.utils.xml.createElement("field");
|
||||||
|
checkbox.setAttribute("name", "CHECKBOX");
|
||||||
|
checkbox.textContent = "FALSE";
|
||||||
|
get.appendChild(checkbox);
|
||||||
|
|
||||||
xmlList.push(get);
|
xmlList.push(get);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +448,10 @@ function renderSpriteInfo() {
|
|||||||
const infoEl = document.getElementById("sprite-info");
|
const infoEl = document.getElementById("sprite-info");
|
||||||
|
|
||||||
if (!activeSprite) {
|
if (!activeSprite) {
|
||||||
infoEl.innerHTML = "<p>Select a sprite to see its info.</p>";
|
infoEl.innerHTML = `
|
||||||
|
<p>Select a sprite to see its info.</p>
|
||||||
|
<p>Mouse: ${mouseX}, ${mouseY} ${mouseCoordsFrozen ? '🔒' : ''}</p>
|
||||||
|
`;
|
||||||
} else {
|
} else {
|
||||||
const sprite = activeSprite.pixiSprite;
|
const sprite = activeSprite.pixiSprite;
|
||||||
|
|
||||||
@@ -436,6 +460,7 @@ function renderSpriteInfo() {
|
|||||||
<p>${Math.round(sprite.angle)}º</p>
|
<p>${Math.round(sprite.angle)}º</p>
|
||||||
<p>size: ${Math.round(((sprite.scale.x + sprite.scale.y) / 2) * 100)}</p>
|
<p>size: ${Math.round(((sprite.scale.x + sprite.scale.y) / 2) * 100)}</p>
|
||||||
<p><i class="fa-solid fa-${sprite.visible ? "eye" : "eye-slash"}"></i></p>
|
<p><i class="fa-solid fa-${sprite.visible ? "eye" : "eye-slash"}"></i></p>
|
||||||
|
<p>Mouse: ${mouseX}, ${mouseY} ${mouseCoordsFrozen ? '🔒' : ''}</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1024,7 +1049,20 @@ function stopAllScripts() {
|
|||||||
clearTimeout(spriteData.sayTimeout);
|
clearTimeout(spriteData.sayTimeout);
|
||||||
spriteData.sayTimeout = null;
|
spriteData.sayTimeout = null;
|
||||||
}
|
}
|
||||||
|
if (spriteData.textSprite) {
|
||||||
|
const sprite = spriteData.pixiSprite;
|
||||||
|
sprite.removeChild(spriteData.textSprite);
|
||||||
|
spriteData.textSprite.destroy();
|
||||||
|
spriteData.textSprite = null;
|
||||||
|
|
||||||
|
// Restore original texture
|
||||||
|
if (spriteData.originalTexture) {
|
||||||
|
sprite.texture = spriteData.originalTexture;
|
||||||
|
sprite.anchor.set(spriteData.originalAnchor.x, spriteData.originalAnchor.y);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateAllMonitors();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runCode() {
|
async function runCode() {
|
||||||
@@ -1103,6 +1141,11 @@ async function runCode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add ticker for updating monitors
|
||||||
|
app.ticker.add(() => {
|
||||||
|
updateAllMonitors();
|
||||||
|
});
|
||||||
|
|
||||||
app.view.addEventListener("click", () => {
|
app.view.addEventListener("click", () => {
|
||||||
for (const entry of eventRegistry.stageClick) {
|
for (const entry of eventRegistry.stageClick) {
|
||||||
entry.cb();
|
entry.cb();
|
||||||
@@ -1211,6 +1254,7 @@ export async function getProject() {
|
|||||||
backdrops: backdropsData,
|
backdrops: backdropsData,
|
||||||
currentBackdrop: currentBackdrop,
|
currentBackdrop: currentBackdrop,
|
||||||
projectName: projectName,
|
projectName: projectName,
|
||||||
|
monitors: getAllMonitors().map(m => m.toJSON()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1223,6 +1267,7 @@ async function saveProject() {
|
|||||||
backdrops: [], // ADD THIS
|
backdrops: [], // ADD THIS
|
||||||
currentBackdrop: currentBackdrop, // ADD THIS
|
currentBackdrop: currentBackdrop, // ADD THIS
|
||||||
projectName: projectName,
|
projectName: projectName,
|
||||||
|
monitors: getAllMonitors().map(m => m.toJSON()),
|
||||||
};
|
};
|
||||||
const toUint8Array = base64 =>
|
const toUint8Array = base64 =>
|
||||||
Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
||||||
@@ -1516,14 +1561,20 @@ async function handleProjectData(data) {
|
|||||||
for (const child of app.stage.removeChildren()) {
|
for (const child of app.stage.removeChildren()) {
|
||||||
if (child.destroy) child.destroy({ children: true });
|
if (child.destroy) child.destroy({ children: true });
|
||||||
}
|
}
|
||||||
sprites = [];
|
sprites.length = 0;
|
||||||
|
window.sprites = sprites;
|
||||||
|
|
||||||
if (!Array.isArray(data.sprites)) {
|
if (!Array.isArray(data.sprites)) {
|
||||||
window.alert("No valid sprites found in file.");
|
window.alert("No valid sprites found in file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.variables) projectVariables = data.variables;
|
if (data.variables) {
|
||||||
|
for (const key in projectVariables) {
|
||||||
|
delete projectVariables[key];
|
||||||
|
}
|
||||||
|
Object.assign(projectVariables, data.variables);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset arrays
|
// Reset arrays
|
||||||
window.projectCostumes = ["default"];
|
window.projectCostumes = ["default"];
|
||||||
@@ -1678,6 +1729,27 @@ async function handleProjectData(data) {
|
|||||||
// IMPORTANT: Update toolbox AFTER everything is loaded
|
// IMPORTANT: Update toolbox AFTER everything is loaded
|
||||||
workspace.updateToolbox(document.getElementById('toolbox'));
|
workspace.updateToolbox(document.getElementById('toolbox'));
|
||||||
|
|
||||||
|
if (Array.isArray(data.monitors)) {
|
||||||
|
clearAllMonitors();
|
||||||
|
|
||||||
|
const valueGetters = {
|
||||||
|
variable: {},
|
||||||
|
timer: {
|
||||||
|
'timer': () => projectTime()
|
||||||
|
},
|
||||||
|
answer: {
|
||||||
|
'answer': () => window.lastAnswer || ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add getters for all variables
|
||||||
|
Object.keys(projectVariables).forEach(varName => {
|
||||||
|
valueGetters.variable[varName] = () => projectVariables[varName];
|
||||||
|
});
|
||||||
|
|
||||||
|
loadMonitors(app, data.monitors, valueGetters);
|
||||||
|
}
|
||||||
|
|
||||||
// Force refresh all blocks with dropdowns to show correct values
|
// Force refresh all blocks with dropdowns to show correct values
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
workspace.getAllBlocks(false).forEach(block => {
|
workspace.getAllBlocks(false).forEach(block => {
|
||||||
@@ -1876,6 +1948,29 @@ window.addEventListener("resize", () => {
|
|||||||
resizeCanvas();
|
resizeCanvas();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Track mouse position relative to stage
|
||||||
|
app.view.addEventListener("mousemove", (e) => {
|
||||||
|
if (mouseCoordsFrozen) return;
|
||||||
|
|
||||||
|
const rect = app.view.getBoundingClientRect();
|
||||||
|
const mouseScreenX = e.clientX - rect.left;
|
||||||
|
const mouseScreenY = e.clientY - rect.top;
|
||||||
|
|
||||||
|
// Convert to stage coordinates
|
||||||
|
mouseX = Math.round((mouseScreenX - app.stage.x) / app.stage.scale.x);
|
||||||
|
mouseY = -Math.round((mouseScreenY - app.stage.y) / app.stage.scale.y);
|
||||||
|
|
||||||
|
renderSpriteInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle freeze on spacebar
|
||||||
|
window.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "h" && !e.repeat) {
|
||||||
|
mouseCoordsFrozen = !mouseCoordsFrozen;
|
||||||
|
renderSpriteInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function isXmlEmpty(input = "") {
|
function isXmlEmpty(input = "") {
|
||||||
input = input.trim();
|
input = input.trim();
|
||||||
return (
|
return (
|
||||||
@@ -2741,3 +2836,16 @@ workspace.addChangeListener(event => {
|
|||||||
workspace.updateAllFunctionCalls = () => {
|
workspace.updateAllFunctionCalls = () => {
|
||||||
updateAllFunctionCalls(workspace);
|
updateAllFunctionCalls(workspace);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
window.projectVariables = projectVariables;
|
||||||
|
window.sprites = sprites;
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
window.app = app;
|
||||||
|
window.projectVariables = projectVariables;
|
||||||
|
window.sprites = sprites;
|
||||||
|
window.createMonitor = createMonitor;
|
||||||
|
window.removeMonitor = removeMonitor;
|
||||||
|
window.getAllMonitors = getAllMonitors;
|
||||||
|
window.getMonitor = getMonitor;
|
||||||
Reference in New Issue
Block a user