wow thats a lot of changes
This commit is contained in:
68
editor.html
68
editor.html
@@ -307,6 +307,46 @@
|
||||
</value>
|
||||
</block>
|
||||
<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">
|
||||
<value name="COSTUME">
|
||||
<shadow type="text">
|
||||
@@ -442,6 +482,28 @@
|
||||
<block type="window_size"></block>
|
||||
</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">
|
||||
<block type="lists_create_with">
|
||||
<mutation items="2"></mutation>
|
||||
@@ -554,6 +616,12 @@
|
||||
</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>
|
||||
|
||||
<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",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@blockly/field-colour": "^6.0.11",
|
||||
"@blockly/field-colour-hsv-sliders": "^6.0.11",
|
||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||
"blockly": "^12.2.0",
|
||||
@@ -33,6 +35,48 @@
|
||||
"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": {
|
||||
"version": "6.0.1",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockly/field-colour": "^6.0.11",
|
||||
"@blockly/field-colour-hsv-sliders": "^6.0.11",
|
||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||
"blockly": "^12.2.0",
|
||||
|
||||
@@ -117,7 +117,7 @@ Blockly.Blocks["every_seconds"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("every")
|
||||
.appendField(new Blockly.FieldNumber(2, 0.1), "SECONDS")
|
||||
.appendField(new Blockly.FieldNumber(2, 0.01), "SECONDS")
|
||||
.appendField("seconds");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
import {FieldColourHsvSliders} from '@blockly/field-colour-hsv-sliders';
|
||||
|
||||
function getAvailableCostumes() {
|
||||
if (window.projectCostumes && window.projectCostumes.length > 0) {
|
||||
@@ -216,3 +217,95 @@ BlocklyJS.javascriptGenerator.forBlock["switch_backdrop"] = function (block) {
|
||||
const name = block.getFieldValue("BACKDROP_NAME");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,20 @@ import {
|
||||
} from "../functions/extensionManager.js";
|
||||
import { Thread } from "../functions/threads.js";
|
||||
import { runCodeWithFunctions } from "../functions/runCode.js";
|
||||
import {
|
||||
createMonitor,
|
||||
removeMonitor,
|
||||
updateAllMonitors,
|
||||
getAllMonitors,
|
||||
clearAllMonitors,
|
||||
loadMonitors,
|
||||
getMonitor
|
||||
} from "../functions/monitors.js";
|
||||
|
||||
import config from "../config";
|
||||
|
||||
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 });
|
||||
@@ -40,6 +49,9 @@ let currentRoom = null;
|
||||
let amHost = false;
|
||||
let invitesEnabled = true;
|
||||
let connectedUsers = [];
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
let mouseCoordsFrozen = false;
|
||||
|
||||
const wrapper = document.getElementById("stage-wrapper");
|
||||
const stageContainer = document.getElementById("stage");
|
||||
@@ -94,8 +106,10 @@ function createPenGraphics() {
|
||||
}
|
||||
createPenGraphics();
|
||||
|
||||
export let projectVariables = {};
|
||||
export let sprites = [];
|
||||
window.projectVariables = {};
|
||||
export const projectVariables = window.projectVariables;
|
||||
window.sprites = [];
|
||||
export const sprites = window.sprites;
|
||||
export let activeSprite = null;
|
||||
window.projectSounds = [];
|
||||
window.projectCostumes = ["default"];
|
||||
@@ -186,6 +200,13 @@ workspace.registerToolboxCategoryCallback("GLOBAL_VARIABLES", function (_) {
|
||||
varField.setAttribute("name", "VAR");
|
||||
varField.textContent = name;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -427,7 +448,10 @@ function renderSpriteInfo() {
|
||||
const infoEl = document.getElementById("sprite-info");
|
||||
|
||||
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 {
|
||||
const sprite = activeSprite.pixiSprite;
|
||||
|
||||
@@ -436,6 +460,7 @@ function renderSpriteInfo() {
|
||||
<p>${Math.round(sprite.angle)}º</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>Mouse: ${mouseX}, ${mouseY} ${mouseCoordsFrozen ? '🔒' : ''}</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1024,8 +1049,21 @@ function stopAllScripts() {
|
||||
clearTimeout(spriteData.sayTimeout);
|
||||
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() {
|
||||
stopAllScripts();
|
||||
@@ -1103,6 +1141,11 @@ async function runCode() {
|
||||
}
|
||||
}
|
||||
|
||||
// Add ticker for updating monitors
|
||||
app.ticker.add(() => {
|
||||
updateAllMonitors();
|
||||
});
|
||||
|
||||
app.view.addEventListener("click", () => {
|
||||
for (const entry of eventRegistry.stageClick) {
|
||||
entry.cb();
|
||||
@@ -1211,6 +1254,7 @@ export async function getProject() {
|
||||
backdrops: backdropsData,
|
||||
currentBackdrop: currentBackdrop,
|
||||
projectName: projectName,
|
||||
monitors: getAllMonitors().map(m => m.toJSON()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1223,6 +1267,7 @@ async function saveProject() {
|
||||
backdrops: [], // ADD THIS
|
||||
currentBackdrop: currentBackdrop, // ADD THIS
|
||||
projectName: projectName,
|
||||
monitors: getAllMonitors().map(m => m.toJSON()),
|
||||
};
|
||||
const toUint8Array = base64 =>
|
||||
Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
||||
@@ -1516,14 +1561,20 @@ async function handleProjectData(data) {
|
||||
for (const child of app.stage.removeChildren()) {
|
||||
if (child.destroy) child.destroy({ children: true });
|
||||
}
|
||||
sprites = [];
|
||||
sprites.length = 0;
|
||||
window.sprites = sprites;
|
||||
|
||||
if (!Array.isArray(data.sprites)) {
|
||||
window.alert("No valid sprites found in file.");
|
||||
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
|
||||
window.projectCostumes = ["default"];
|
||||
@@ -1678,6 +1729,27 @@ async function handleProjectData(data) {
|
||||
// IMPORTANT: Update toolbox AFTER everything is loaded
|
||||
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
|
||||
setTimeout(() => {
|
||||
workspace.getAllBlocks(false).forEach(block => {
|
||||
@@ -1876,6 +1948,29 @@ window.addEventListener("resize", () => {
|
||||
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 = "") {
|
||||
input = input.trim();
|
||||
return (
|
||||
@@ -2741,3 +2836,16 @@ workspace.addChangeListener(event => {
|
||||
workspace.updateAllFunctionCalls = () => {
|
||||
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