From 127767ffc99e867ff9c44a0c56ea2907e4834a61 Mon Sep 17 00:00:00 2001 From: aomwankhede Date: Fri, 1 Mar 2024 14:17:04 +0530 Subject: [PATCH 1/2] Added glide functionality with and without pencil --- package-lock.json | 27 +- package.json | 1 + src/components/BlockCategories/Motion.jsx | 17 +- src/components/Canvas.jsx | 302 ++++++++++++++++------ src/components/Canvas/Pencil.jsx | 13 + src/components/InitializeBlockly.jsx | 5 +- src/features/motionSlice.js | 129 +++++---- 7 files changed, 318 insertions(+), 176 deletions(-) create mode 100644 src/components/Canvas/Pencil.jsx diff --git a/package-lock.json b/package-lock.json index e382f60..3f97274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "blockly": "^10.2.1", "country-flag-emoji": "^1.0.3", "emoji-dictionary": "^1.0.11", + "immer": "^10.0.3", "re-resizable": "^6.9.11", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -4533,6 +4534,15 @@ } } }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", @@ -8001,9 +8011,9 @@ } }, "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -16851,6 +16861,11 @@ "reselect": "^4.1.8" }, "dependencies": { + "immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + }, "redux-thunk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", @@ -19491,9 +19506,9 @@ } }, "immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==" }, "import-fresh": { "version": "3.3.0", diff --git a/package.json b/package.json index 11a12dd..3a39f05 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "blockly": "^10.2.1", "country-flag-emoji": "^1.0.3", "emoji-dictionary": "^1.0.11", + "immer": "^10.0.3", "re-resizable": "^6.9.11", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/BlockCategories/Motion.jsx b/src/components/BlockCategories/Motion.jsx index f8222c6..751a44b 100644 --- a/src/components/BlockCategories/Motion.jsx +++ b/src/components/BlockCategories/Motion.jsx @@ -102,18 +102,6 @@ Blockly.Blocks["go_to_menu"] = { }, }; -// javascriptGenerator['go_to_menu'] = function(block) { -// const destination = block.getFieldValue('DESTINATION'); -// let code = ''; -// if (destination === 'mouse_pointer') { -// // Generate code to dispatch the moveSpriteToMousePointer thunk -// code = 'store.dispatch(moveSpriteToMousePointer());\n'; -// } - -// console.log(code); -// return code; -// } - Blockly.Blocks["go_to_xy"] = { init: function () { this.appendDummyInput() @@ -362,9 +350,8 @@ javascriptGenerator["glidesecstoxy"] = function (block) { const sec = block.getFieldValue("SECS"); const xCoord = block.getFieldValue("X_COORD"); const yCoord = block.getFieldValue("Y_COORD"); - const code = `store.dispatch(glideSecsXY(${xCoord}, ${yCoord}, ${sec})); -`; - console.log(code); + const code = `store.dispatch(glideSecsXY(${xCoord}, ${yCoord}, ${sec}));`; + console.log("Hello Code",code); return code; }; diff --git a/src/components/Canvas.jsx b/src/components/Canvas.jsx index 4b68b94..22613b5 100644 --- a/src/components/Canvas.jsx +++ b/src/components/Canvas.jsx @@ -1,29 +1,32 @@ -import React, { useEffect, useState } from 'react'; -import { Card } from '@mui/material'; -import Draggable from 'react-draggable'; -import { Resizable } from 're-resizable'; -import Blockly from 'blockly'; -import 'blockly/javascript'; -import GenerateCodeBox from './GenerateCodeBox'; - -import {javascriptGenerator} from 'blockly/javascript'; -import { useSelector, useDispatch } from 'react-redux'; -import { spriteClickedEvent,flagClickedEvent} from './BlockCategories/Events'; -import { whenSpriteClicked } from '../features/eventSlice'; -import { whenFlagClicked } from '../features/eventSlice'; -import { whenKeyPressed } from '../features/eventSlice'; // keypress +import React, { useEffect, useState } from "react"; +import { Card } from "@mui/material"; +import Draggable from "react-draggable"; +import { Resizable } from "re-resizable"; +import Blockly from "blockly"; +import "blockly/javascript"; +import GenerateCodeBox from "./GenerateCodeBox"; +import { useRef } from "react"; +import { javascriptGenerator } from "blockly/javascript"; +import { useSelector, useDispatch } from "react-redux"; +import { spriteClickedEvent, flagClickedEvent } from "./BlockCategories/Events"; +import { whenSpriteClicked } from "../features/eventSlice"; +import { whenFlagClicked } from "../features/eventSlice"; +import { whenKeyPressed } from "../features/eventSlice"; // keypress + +import { glideSecsXY, done } from "../features/motionSlice"; // Import Image from src // import Demo from '../Images/trial_sprite_nobkg.png' // Import the button components -import FlagButton from './Canvas/FlagButton'; -import StopButton from './Canvas/StopButton'; -import UndoButton from './Canvas/UndoButton'; -import RedoButton from './Canvas/RedoButton'; -import ZoomIn from './Canvas/ZoomIn'; -import ZoomOut from './Canvas/ZoomOut'; -import FullScreen from './Canvas/FullScreen'; +import FlagButton from "./Canvas/FlagButton"; +import StopButton from "./Canvas/StopButton"; +import UndoButton from "./Canvas/UndoButton"; +import RedoButton from "./Canvas/RedoButton"; +import ZoomIn from "./Canvas/ZoomIn"; +import ZoomOut from "./Canvas/ZoomOut"; +import FullScreen from "./Canvas/FullScreen"; +import Pencil from "./Canvas/Pencil"; //Start of key press const useKeyPress = (targetKey, callback) => { @@ -34,99 +37,238 @@ const useKeyPress = (targetKey, callback) => { }; useEffect(() => { - window.addEventListener('keydown', handleKeyDown); + window.addEventListener("keydown", handleKeyDown); return () => { - window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener("keydown", handleKeyDown); }; }, [targetKey, callback]); }; - //End of key press - +//End of key press const Canvas = () => { const { position, angle } = useSelector((state) => ({ position: state.motion.position, angle: state.motion.angle, })); - - const language = useSelector(state => state.language); // Language + const glideEndPosn = useSelector((state) => state.motion.glideEndPosn); + const glideStartPosn = useSelector((state) => state.motion.position); + const glideClicked = useSelector((state) => state.motion.glideClicked); + const language = useSelector((state) => state.language); // Language const dispatch = useDispatch(); //dispatch fore event click - - // const { position, angle } = useSelector((state) => ({ - // position: state.motion.position, - // angle: state.motion.angle, - // })); - - - // useEffect(() => { - // const spriteElement = document.getElementById('sprite'); - // if (spriteElement) { - // spriteElement.style.transform = `translate(${position.x}px, ${position.y}px) rotate(${angle}deg)`; - // } - // }, [position, angle]); - - + const [start, setStart] = useState({ x: -1, y: -1 }); + const [end, setEnd] = useState({ x: -1, y: -1 }); + const [arr, setArr] = useState([]); const [imageSize, setImageSize] = useState(100); // useState for zooming in-out const maxImageSize = 200; // Maximum limit for image size const minImageSize = 100; // Minimum limit for image size - useEffect(() => { - const spriteElement = document.getElementById('sprite'); + const spriteElement = document.getElementById("sprite"); if (spriteElement) { - spriteElement.style.transform = `translate(${position.x}px, ${position.y}px) rotate(${angle}deg)`; + spriteElement.style.transform = `translate(${position?.x}px, ${position?.y}px) rotate(${angle}deg)`; + } + }, [position, angle]); + + useEffect(() => { + if (arr.length > 0) { + let c = 0; + const startX = arr[c].x; + const startY = arr[c].y; + const targetX = arr[arr.length-1].x; + const targetY = arr[arr.length-1].y; + const sec = start.x != -1 ? glideEndPosn.sec : glideEndPosn.sec; + console.log("Second:",sec) + if (!glideClicked) { + return; + } + + let startTime; + const updatePosition = () => { + const currentTime = Date.now(); + const elapsedTime = (currentTime - startTime) / 1000; // convert to seconds + if(c>=arr.length){ + dispatch(done()); + return; + } + if (elapsedTime >= sec) { + + dispatch(glideSecsXY(targetX, targetY, sec)); + dispatch(done()); + } else { + const ratio = elapsedTime / sec; + let newX = arr[c].x; + let newY = arr[c].y; + c++; + dispatch(glideSecsXY(newX, newY, sec)); + requestAnimationFrame(updatePosition); + } + }; + + startTime = Date.now(); + requestAnimationFrame(updatePosition); + } else { + let c = 0; + const startX = start.x != -1 ? start.x : glideStartPosn.x; + const startY = start.y != -1 ? start.y : glideStartPosn.y; + const targetX = end.x != -1 ? end.x : glideEndPosn.x; + const targetY = end.y != -1 ? end.y : glideEndPosn.y; + const sec = start.x != -1 ? glideEndPosn.sec : glideEndPosn.sec; + console.log(startX, startY, targetX, targetY, sec); + if (!glideClicked) { + return; + } + let startTime; + const updatePosition = () => { + const currentTime = Date.now(); + const elapsedTime = (currentTime - startTime) / 1000; // convert to seconds + + if (elapsedTime >= sec) { + dispatch(glideSecsXY(targetX, targetY, sec)); + dispatch(done()); + } else { + const ratio = elapsedTime / sec; + let newX = startX + (targetX - startX) * ratio; + let newY = startY + (targetY - startY) * ratio; + dispatch(glideSecsXY(newX, newY, sec)); + requestAnimationFrame(updatePosition); + } + }; + + startTime = Date.now(); + requestAnimationFrame(updatePosition); } - }, [position, angle]); + return () => { + // Clean up if needed + setStart({ x: -1, y: -1 }); + setEnd({ x: -1, y: -1 }); + // setArr([]); + }; + }, [glideClicked]); + const canvasRef = useRef(null); + const [isDrawing, setIsDrawing] = useState(false); + const [isPencilActive, setIsPencilActive] = useState(false); + + const startDrawing = (e) => { + if (!isPencilActive) return; + setIsDrawing(true); + const canvas = canvasRef.current; + const context = canvas.getContext("2d"); + const rect = canvas.getBoundingClientRect(); // Get the bounding rectangle of the canvas + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + setStart({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + setArr([...arr, { x: e.clientX - rect.left, y: e.clientY - rect.top }]); + context.moveTo(e.clientX - rect.left, e.clientY - rect.top); // Adjust coordinates using the bounding rectangle + }; + + const draw = (e) => { + if (!isDrawing || !isPencilActive) return; // Check if drawing and pencil are active + // if (!isDrawing) return; + const canvas = canvasRef.current; + const context = canvas.getContext("2d"); + const rect = canvas.getBoundingClientRect(); // Get the bounding rectangle of the canvas + // setEnd({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + setArr([...arr, { x: e.clientX - rect.left, y: e.clientY - rect.top }]); + context.lineTo(e.clientX - rect.left, e.clientY - rect.top); // Adjust coordinates using the bounding rectangle + context.stroke(); + }; + + const stopDrawing = () => { + setIsDrawing(false); + }; + + const handlePencilClick = () => { + // Add code to handle pencil click + if(isPencilActive){setArr([])} + setIsPencilActive(!isPencilActive); + }; return ( - -

Canvas

- + +

Canvas

+ -
+
-
-
+
+ +
+ {}} /> {}} /> - {}} /> - {}} /> - {}} /> - {}} /> -
-
- {}} /> - {}} /> - {}} /> -
+ {}} /> + {}} /> + {}} /> + + + {}} /> + {}} /> + {}} />
- ); }; -export default Canvas; \ No newline at end of file +export default Canvas; diff --git a/src/components/Canvas/Pencil.jsx b/src/components/Canvas/Pencil.jsx new file mode 100644 index 0000000..cd01b44 --- /dev/null +++ b/src/components/Canvas/Pencil.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + import { IconButton } from '@mui/material'; + import DrawIcon from '@mui/icons-material/Draw'; + + const Pencil = ({ onClick,isActive }) => { + return ( + + + + ); + }; + + export default Pencil; \ No newline at end of file diff --git a/src/components/InitializeBlockly.jsx b/src/components/InitializeBlockly.jsx index 3eef614..2d2ca98 100644 --- a/src/components/InitializeBlockly.jsx +++ b/src/components/InitializeBlockly.jsx @@ -57,11 +57,8 @@ const InitializeBlockly = (toolboxXml) => { var codeToExecute = generateCodeForBlock(clickedBlock); const codeString = store.getState().code.codeString; - // if (codeToExecute !== codeString) { - // store.dispatch(setCodeString(codeToExecute)); - // } store.dispatch(setCodeString(codeToExecute)); - console.log("Executing block code:", codeToExecute); + console.log("Executing block code:",codeToExecute); try { await eval(`(async () => { ${codeToExecute} })();`); console.log("executed"); diff --git a/src/features/motionSlice.js b/src/features/motionSlice.js index e3ff5ca..0d512e2 100644 --- a/src/features/motionSlice.js +++ b/src/features/motionSlice.js @@ -1,10 +1,13 @@ import { createSlice } from "@reduxjs/toolkit"; import { useSelector } from "react-redux"; - +import { createAsyncThunk } from "@reduxjs/toolkit"; // Initial state of the sprite const initialState = { position: { x: 150, y: 100 }, // Assuming default position angle: 0, + glideClicked: false, + glideStartPosn: { x: -1, y: -1 }, + glideEndPosn: { x: -1, y: -1, sec: 0 }, }; // Create the slice @@ -13,31 +16,37 @@ export const motionSlice = createSlice({ initialState, reducers: { moveSteps: { - reducer: (state, action) => { - const { rightSteps, upSteps } = action.payload; - const angleInRadians = (state.angle * Math.PI) / 180; - let newX = state.position.x + rightSteps * Math.cos(angleInRadians) - upSteps * Math.sin(angleInRadians); - let newY = state.position.y + rightSteps * Math.sin(angleInRadians) + upSteps * Math.cos(angleInRadians); - - const spriteElement = document.getElementById('sprite'); - const canvasElement = document.getElementsByClassName("highlighted")[1]; - - if(newX >= 300 ){ - newX = 300; - } - if(newX <= -100 ){ - newX = -100; - } - if(newY >= 300 ){ - newY = 300; - } - if(newY <= -100 ){ - newY = -100; - } - state.position.x = newX; - state.position.y = newY; - }, - prepare: (rightSteps, upSteps) => ({ payload: { rightSteps, upSteps } }) + reducer: (state, action) => { + const { rightSteps, upSteps } = action.payload; + const angleInRadians = (state.angle * Math.PI) / 180; + let newX = + state.position.x + + rightSteps * Math.cos(angleInRadians) - + upSteps * Math.sin(angleInRadians); + let newY = + state.position.y + + rightSteps * Math.sin(angleInRadians) + + upSteps * Math.cos(angleInRadians); + + const spriteElement = document.getElementById("sprite"); + const canvasElement = document.getElementsByClassName("highlighted")[1]; + + if (newX >= 300) { + newX = 300; + } + if (newX <= -100) { + newX = -100; + } + if (newY >= 300) { + newY = 300; + } + if (newY <= -100) { + newY = -100; + } + state.position.x = newX; + state.position.y = newY; + }, + prepare: (rightSteps, upSteps) => ({ payload: { rightSteps, upSteps } }), }, setX: { reducer: (state, action) => { @@ -108,7 +117,7 @@ export const motionSlice = createSlice({ reducer: (state, action) => { if (action.payload.angle == -1) { let clientX, clientY; - + const move = (event) => { clientX = event.clientX; clientY = event.clientY; @@ -121,8 +130,8 @@ export const motionSlice = createSlice({ const getCursorPosition = (event) => { clientX = event.clientX; clientY = event.clientY; - move({clientX,clientY}); - } + move({ clientX, clientY }); + }; window.addEventListener("onmousemove", getCursorPosition); } else { state.angle = action.payload.angle % 360; // Ensure the angle stays within 0 to 359 degrees @@ -152,13 +161,24 @@ export const motionSlice = createSlice({ } }, }, - // glideSecsXY: { - // reducer: (state, action) => { - // state.position.x = action.payload.x; - // state.position.y = action.payload.y; - // }, - // prepare: (x, y) => ({ payload: { x, y } }) - // } + glideSecsXY: { + reducer: (state, action) => { + state.glideStartPosn = state.position; + state.glideEndPosn = action.payload; + if (state.glideClicked == false) { + state.glideClicked = true; + return; + } + state.position = action.payload; + }, + prepare: (x, y, sec) => ({ payload: { x, y, sec } }), + }, + done: { + reducer: (state, action) => { + state.glideClicked = false; + console.log("Made false") + }, + }, }, }); @@ -178,6 +198,8 @@ export const { changeX, changeY, ifOnEdgeBounce, + glideSecsXY, + done } = motionSlice.actions; // export default motionSlice.reducer; @@ -210,41 +232,6 @@ export const moveSpriteToMousePointer = () => (dispatch) => { }; }; -export const glideSecsXY = (x, y, time) => (dispatch) => { - const spritePosition = useSelector((state) => state.motionSlice.position); - console.log("Sprite Position:", spritePosition); - const startX = spritePosition.x; - const startY = spritePosition.y; - const distanceX = x - startX; - const distanceY = y - startY; - const steps = time / 10; - let currentStep = 0; - - const intervalId = setInterval(() => { - currentStep++; - const newX = startX + (distanceX * currentStep) / steps; - const newY = startY + (distanceY * currentStep) / steps; - dispatch(glideSecsXY(newX, newY)); - - if (currentStep >= steps) { - clearInterval(intervalId); - dispatch(goToXY(x, y)); - } - }, 10); -}; -// name: "Motion", -// initialState, -// reducers: { -// moveSprite: { -// reducer: (state, action) => { -// state.position.x += action.payload.rightSteps; -// state.position.y += action.payload.upSteps; -// }, -// prepare: (rightSteps, upSteps) => ({ payload: { rightSteps, upSteps } }) -// }, -// }, -// }); - // Export the action and reducer export const { moveSprite } = motionSlice.actions; export default motionSlice.reducer; From ca0b2ff4b6d8bd5169055af696b6dd12080e1e85 Mon Sep 17 00:00:00 2001 From: aomwankhede Date: Mon, 18 Mar 2024 01:16:37 +0530 Subject: [PATCH 2/2] Added time dependence to glide-pencil --- src/components/Canvas.jsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/Canvas.jsx b/src/components/Canvas.jsx index 22613b5..fb17ecd 100644 --- a/src/components/Canvas.jsx +++ b/src/components/Canvas.jsx @@ -73,12 +73,9 @@ const Canvas = () => { useEffect(() => { if (arr.length > 0) { let c = 0; - const startX = arr[c].x; - const startY = arr[c].y; const targetX = arr[arr.length-1].x; const targetY = arr[arr.length-1].y; const sec = start.x != -1 ? glideEndPosn.sec : glideEndPosn.sec; - console.log("Second:",sec) if (!glideClicked) { return; } @@ -101,7 +98,10 @@ const Canvas = () => { let newY = arr[c].y; c++; dispatch(glideSecsXY(newX, newY, sec)); - requestAnimationFrame(updatePosition); + setTimeout(() => { + c++; + requestAnimationFrame(updatePosition); + }, (sec/arr.length)*2000); } }; @@ -142,7 +142,6 @@ const Canvas = () => { // Clean up if needed setStart({ x: -1, y: -1 }); setEnd({ x: -1, y: -1 }); - // setArr([]); }; }, [glideClicked]); const canvasRef = useRef(null); @@ -158,7 +157,7 @@ const Canvas = () => { context.clearRect(0, 0, canvas.width, canvas.height); context.beginPath(); setStart({ x: e.clientX - rect.left, y: e.clientY - rect.top }); - setArr([...arr, { x: e.clientX - rect.left, y: e.clientY - rect.top }]); + setArr([...arr, { x: e.clientX - rect.left - 100, y: e.clientY - rect.top - 150 }]); context.moveTo(e.clientX - rect.left, e.clientY - rect.top); // Adjust coordinates using the bounding rectangle }; @@ -169,7 +168,7 @@ const Canvas = () => { const context = canvas.getContext("2d"); const rect = canvas.getBoundingClientRect(); // Get the bounding rectangle of the canvas // setEnd({ x: e.clientX - rect.left, y: e.clientY - rect.top }); - setArr([...arr, { x: e.clientX - rect.left, y: e.clientY - rect.top }]); + setArr([...arr, { x: e.clientX - rect.left - 100, y: e.clientY - rect.top - 150 }]); context.lineTo(e.clientX - rect.left, e.clientY - rect.top); // Adjust coordinates using the bounding rectangle context.stroke(); };