diff --git a/cypress/e2e/DoenetML/tagSpecific/image.cy.js b/cypress/e2e/DoenetML/tagSpecific/image.cy.js index 42e3626831..9b4d04079f 100644 --- a/cypress/e2e/DoenetML/tagSpecific/image.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/image.cy.js @@ -484,6 +484,60 @@ describe('Image Tag Tests', function () { }); + it('rotate image in graph', () => { + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + + + + +

Rotate 1: $image1.rotate

+

Change rotate 1

+

Change rotate 1a

+ + + + `}, "*"); + }); + + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + // Is there a way to test the rotation of the image in the graph? + + cy.get("#\\/pRotate1").should('contain.text', 'Rotate 1: 0.785') + + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/image1"].stateValues.rotate).eq(Math.PI / 4) + }); + + cy.log("change rotate") + + cy.get('#\\/rotate1 textarea').type("{end}{shift+home}{backspace}3pi/4{enter}", { force: true }) + + cy.get("#\\/pRotate1").should('contain.text', 'Rotate 1: 2.356') + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/image1"].stateValues.rotate).eq(3 * Math.PI / 4) + }); + + cy.get('#\\/rotate1a textarea').type("{end}{shift+home}{backspace}-pi{enter}", { force: true }) + + cy.get("#\\/pRotate1").should('contain.text', 'Rotate 1: -3.14159') + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/image1"].stateValues.rotate).eq(-Math.PI) + }); + + + }); + }) diff --git a/src/Core/components/Image.js b/src/Core/components/Image.js index 394d400897..4fa5020f46 100644 --- a/src/Core/components/Image.js +++ b/src/Core/components/Image.js @@ -107,6 +107,14 @@ export default class Image extends BlockComponent { validValues: ["upperright", "upperleft", "lowerright", "lowerleft", "top", "bottom", "left", "right", "center"] } + attributes.rotate = { + createComponentOfType: "number", + createStateVariable: "rotate", + defaultValue: 0, + public: true, + forRenderer: true, + } + attributes.styleNumber.defaultValue = 0; return attributes; diff --git a/src/Viewer/renderers/image.jsx b/src/Viewer/renderers/image.jsx index feb09d28dc..cea4c6f6ff 100644 --- a/src/Viewer/renderers/image.jsx +++ b/src/Viewer/renderers/image.jsx @@ -29,6 +29,9 @@ export default React.memo(function Image(props) { let currentOffset = useRef(null); + let rotationTransform = useRef(null); + let lastRotate = useRef(SVs.rotate); + const urlOrSource = (SVs.cid ? url : SVs.source) || ""; let onChangeVisibility = isVisible => { @@ -70,7 +73,7 @@ export default React.memo(function Image(props) { visible: !SVs.hidden, fixed, layer: 10 * SVs.layer + 0, - highlight: !fixed + highlight: !fixed, }; @@ -137,6 +140,33 @@ export default React.memo(function Image(props) { let newImageJXG = board.create('image', [urlOrSource, offset, [width, height]], jsxImageAttributes); + // tranformation code copied from jsxgraph documentation: + // https://jsxgraph.uni-bayreuth.de/wiki/index.php?title=Images#The_JavaScript_code_5 + var tOff = board.create('transform', [ + function () { + return -newImageJXG.X() - newImageJXG.W() * 0.5; + }, function () { + return -newImageJXG.Y() - newImageJXG.H() * 0.5; + } + ], { type: 'translate' }); + var tOffInverse = board.create('transform', [ + function () { + return newImageJXG.X() + newImageJXG.W() * 0.5; + }, function () { + return newImageJXG.Y() + newImageJXG.H() * 0.5; + } + ], { type: 'translate' }); + var tRot = board.create('transform', [ + SVs.rotate + ], { type: 'rotate' }); + + + tOff.bindTo(newImageJXG); // Shift image to origin + tRot.bindTo(newImageJXG); // Rotate + tOffInverse.bindTo(newImageJXG); // Shift image back + + rotationTransform.current = tRot; + lastRotate.current = SVs.rotate; newImageJXG.on('down', function (e) { pointerAtDown.current = [e.x, e.y]; @@ -214,6 +244,9 @@ export default React.memo(function Image(props) { previousPositionFromAnchor.current = SVs.positionFromAnchor; currentSize.current = [width, height]; + // need fullUpdate to get initial rotation in case image was from a blob + imageJXG.current.fullUpdate(); + } if (board) { @@ -289,6 +322,12 @@ export default React.memo(function Image(props) { currentSize.current = [width, height]; } + if (SVs.rotate != lastRotate.current) { + rotationTransform.current.setMatrix(board, "rotate", [SVs.rotate]); + lastRotate.current = SVs.rotate; + } + + if (SVs.positionFromAnchor !== previousPositionFromAnchor.current || sizeChanged) { let offset; if (SVs.positionFromAnchor === "center") {