diff --git a/ScratchCPPGui/ProjectPlayer.qml b/ScratchCPPGui/ProjectPlayer.qml index 4942929..7c72788 100644 --- a/ScratchCPPGui/ProjectPlayer.qml +++ b/ScratchCPPGui/ProjectPlayer.qml @@ -7,6 +7,8 @@ import ScratchCPPGui ProjectScene { property string fileName + property int stageWidth: 480 + property int stageHeight: 360 property alias fps: loader.fps property alias turboMode: loader.turboMode property alias cloneLimit: loader.cloneLimit @@ -21,6 +23,7 @@ ProjectScene { id: root clip: true engine: loader.engine + stageScale: (stageWidth == 0 || stageHeight == 0) ? 1 : Math.min(width / stageWidth, height / stageHeight) onFileNameChanged: priv.loading = true; QtObject { @@ -31,8 +34,8 @@ ProjectScene { ProjectLoader { id: loader fileName: root.fileName - stageWidth: parent.width - stageHeight: parent.height + stageWidth: root.stageWidth + stageHeight: root.stageHeight onLoadingFinished: { priv.loading = false; @@ -48,6 +51,7 @@ ProjectScene { engine: loader.engine stageModel: loader.stage mouseArea: sceneMouseArea + stageScale: root.stageScale onStageModelChanged: stageModel.renderedTarget = this } @@ -68,6 +72,7 @@ ProjectScene { engine: loader.engine spriteModel: modelData mouseArea: sceneMouseArea + stageScale: root.stageScale transform: Scale { xScale: mirrorHorizontally ? -1 : 1 } Component.onCompleted: modelData.renderedTarget = this } diff --git a/ScratchCPPGui/irenderedtarget.h b/ScratchCPPGui/irenderedtarget.h index 3d2ff17..4e52ca1 100644 --- a/ScratchCPPGui/irenderedtarget.h +++ b/ScratchCPPGui/irenderedtarget.h @@ -51,6 +51,9 @@ class IRenderedTarget : public QNanoQuickItem virtual SceneMouseArea *mouseArea() const = 0; virtual void setMouseArea(SceneMouseArea *newMouseArea) = 0; + virtual double stageScale() const = 0; + virtual void setStageScale(double scale) = 0; + virtual qreal width() const = 0; virtual void setWidth(qreal width) = 0; diff --git a/ScratchCPPGui/projectscene.cpp b/ScratchCPPGui/projectscene.cpp index 5a0ddc8..57d5df4 100644 --- a/ScratchCPPGui/projectscene.cpp +++ b/ScratchCPPGui/projectscene.cpp @@ -37,11 +37,26 @@ void ProjectScene::setEngine(IEngine *newEngine) emit engineChanged(); } +double ProjectScene::stageScale() const +{ + return m_stageScale; +} + +void ProjectScene::setStageScale(double newStageScale) +{ + if (qFuzzyCompare(m_stageScale, newStageScale)) + return; + + m_stageScale = newStageScale; + Q_ASSERT(m_stageScale > 0); + emit stageScaleChanged(); +} + void ProjectScene::handleMouseMove(qreal x, qreal y) { if (m_engine) { - m_engine->setMouseX(x - m_engine->stageWidth() / 2.0); - m_engine->setMouseY(-y + m_engine->stageHeight() / 2.0); + m_engine->setMouseX(x / m_stageScale - m_engine->stageWidth() / 2.0); + m_engine->setMouseY(-y / m_stageScale + m_engine->stageHeight() / 2.0); } } diff --git a/ScratchCPPGui/projectscene.h b/ScratchCPPGui/projectscene.h index 74fa311..7247524 100644 --- a/ScratchCPPGui/projectscene.h +++ b/ScratchCPPGui/projectscene.h @@ -19,6 +19,7 @@ class ProjectScene : public QQuickItem Q_OBJECT QML_ELEMENT Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged) + Q_PROPERTY(double stageScale READ stageScale WRITE setStageScale NOTIFY stageScaleChanged) public: ProjectScene(QQuickItem *parent = nullptr); @@ -26,6 +27,9 @@ class ProjectScene : public QQuickItem libscratchcpp::IEngine *engine() const; void setEngine(libscratchcpp::IEngine *newEngine); + double stageScale() const; + void setStageScale(double newStageScale); + Q_INVOKABLE void handleMouseMove(qreal x, qreal y); Q_INVOKABLE void handleMousePress(); Q_INVOKABLE void handleMouseRelease(); @@ -35,11 +39,13 @@ class ProjectScene : public QQuickItem signals: void engineChanged(); + void stageScaleChanged(); private: void installKeyHandler(QQuickWindow *window); libscratchcpp::IEngine *m_engine = nullptr; + double m_stageScale = 1; KeyEventHandler *m_keyHandler = nullptr; }; diff --git a/ScratchCPPGui/renderedtarget.cpp b/ScratchCPPGui/renderedtarget.cpp index 4b8c6a8..81ef0e7 100644 --- a/ScratchCPPGui/renderedtarget.cpp +++ b/ScratchCPPGui/renderedtarget.cpp @@ -80,10 +80,12 @@ void RenderedTarget::loadProperties() // Coordinates double clampedSize = std::min(m_size, m_maxSize); - m_x = static_cast(m_engine->stageWidth()) / 2 + sprite->x() - m_costume->rotationCenterX() * clampedSize / m_costume->bitmapResolution() * (m_newMirrorHorizontally ? -1 : 1); - m_y = static_cast(m_engine->stageHeight()) / 2 - sprite->y() - m_costume->rotationCenterY() * clampedSize / m_costume->bitmapResolution(); - m_originX = m_costume->rotationCenterX() * clampedSize / m_costume->bitmapResolution(); - m_originY = m_costume->rotationCenterY() * clampedSize / m_costume->bitmapResolution(); + double stageWidth = m_engine->stageWidth(); + double stageHeight = m_engine->stageHeight(); + m_x = m_stageScale * (stageWidth / 2 + sprite->x() - m_costume->rotationCenterX() * clampedSize / m_costume->bitmapResolution() * (m_newMirrorHorizontally ? -1 : 1)); + m_y = m_stageScale * (stageHeight / 2 - sprite->y() - m_costume->rotationCenterY() * clampedSize / m_costume->bitmapResolution()); + m_originX = m_costume->rotationCenterX() * clampedSize * m_stageScale / m_costume->bitmapResolution(); + m_originY = m_costume->rotationCenterY() * clampedSize * m_stageScale / m_costume->bitmapResolution(); // Layer m_z = sprite->layerOrder(); @@ -92,8 +94,10 @@ void RenderedTarget::loadProperties() mutex.unlock(); } else if (m_stageModel) { updateCostumeData(); - m_x = static_cast(m_engine->stageWidth()) / 2 - m_costume->rotationCenterX() / m_costume->bitmapResolution(); - m_y = static_cast(m_engine->stageHeight()) / 2 - m_costume->rotationCenterY() / m_costume->bitmapResolution(); + double stageWidth = m_engine->stageWidth(); + double stageHeight = m_engine->stageHeight(); + m_x = m_stageScale * (stageWidth / 2 - m_costume->rotationCenterX() / m_costume->bitmapResolution()); + m_y = m_stageScale * (stageHeight / 2 - m_costume->rotationCenterY() / m_costume->bitmapResolution()); m_originX = m_costume->rotationCenterX() / m_costume->bitmapResolution(); m_originY = m_costume->rotationCenterY() / m_costume->bitmapResolution(); } @@ -213,6 +217,21 @@ void RenderedTarget::setMouseArea(SceneMouseArea *newMouseArea) emit mouseAreaChanged(); } +double RenderedTarget::stageScale() const +{ + return m_stageScale; +} + +void RenderedTarget::setStageScale(double newStageScale) +{ + if (qFuzzyCompare(m_stageScale, newStageScale)) + return; + + m_stageScale = newStageScale; + Q_ASSERT(m_stageScale > 0); + emit stageScaleChanged(); +} + qreal RenderedTarget::width() const { return QNanoQuickItem::width(); @@ -436,16 +455,16 @@ void RenderedTarget::calculateSize(Target *target, double costumeWidth, double c { if (m_costume) { double bitmapRes = m_costume->bitmapResolution(); - m_maxSize = std::min(m_maximumWidth / costumeWidth, m_maximumHeight / costumeHeight); + m_maxSize = std::min(m_maximumWidth / (costumeWidth * m_stageScale), m_maximumHeight / (costumeHeight * m_stageScale)); Sprite *sprite = dynamic_cast(target); if (sprite) { double clampedSize = std::min(m_size, m_maxSize); - m_width = costumeWidth * clampedSize / bitmapRes; - m_height = costumeHeight * clampedSize / bitmapRes; + m_width = costumeWidth * clampedSize * m_stageScale / bitmapRes; + m_height = costumeHeight * clampedSize * m_stageScale / bitmapRes; } else { - m_width = costumeWidth / bitmapRes; - m_height = costumeHeight / bitmapRes; + m_width = costumeWidth * m_stageScale / bitmapRes; + m_height = costumeHeight * m_stageScale / bitmapRes; } } } @@ -458,8 +477,8 @@ void RenderedTarget::handleSceneMouseMove(qreal x, qreal y) Q_ASSERT(m_spriteModel && m_spriteModel->sprite()); Q_ASSERT(m_engine); Sprite *sprite = m_spriteModel->sprite(); - sprite->setX(x - m_engine->stageWidth() / 2.0 - m_dragDeltaX); - sprite->setY(-y + m_engine->stageHeight() / 2.0 - m_dragDeltaY); + sprite->setX(x / m_stageScale - m_engine->stageWidth() / 2.0 - m_dragDeltaX); + sprite->setY(-y / m_stageScale + m_engine->stageHeight() / 2.0 - m_dragDeltaY); } } diff --git a/ScratchCPPGui/renderedtarget.h b/ScratchCPPGui/renderedtarget.h index d2e4d3e..7a4d595 100644 --- a/ScratchCPPGui/renderedtarget.h +++ b/ScratchCPPGui/renderedtarget.h @@ -25,6 +25,7 @@ class RenderedTarget : public IRenderedTarget Q_PROPERTY(SpriteModel *spriteModel READ spriteModel WRITE setSpriteModel NOTIFY spriteModelChanged) Q_PROPERTY(bool mirrorHorizontally READ mirrorHorizontally NOTIFY mirrorHorizontallyChanged) Q_PROPERTY(SceneMouseArea *mouseArea READ mouseArea WRITE setMouseArea NOTIFY mouseAreaChanged) + Q_PROPERTY(double stageScale READ stageScale WRITE setStageScale NOTIFY stageScaleChanged) public: RenderedTarget(QNanoQuickItem *parent = nullptr); @@ -47,6 +48,9 @@ class RenderedTarget : public IRenderedTarget SceneMouseArea *mouseArea() const override; void setMouseArea(SceneMouseArea *newMouseArea) override; + double stageScale() const override; + void setStageScale(double newStageScale) override; + qreal width() const override; void setWidth(qreal width) override; @@ -77,6 +81,7 @@ class RenderedTarget : public IRenderedTarget void spriteModelChanged(); void mouseAreaChanged(); void mirrorHorizontallyChanged(); + void stageScaleChanged(); protected: QNanoQuickItemPainter *createItemPainter() const override; @@ -116,6 +121,7 @@ class RenderedTarget : public IRenderedTarget bool m_newMirrorHorizontally = false; double m_originX = 0; double m_originY = 0; + double m_stageScale = 1; qreal m_maximumWidth = std::numeric_limits::infinity(); qreal m_maximumHeight = std::numeric_limits::infinity(); std::vector m_hullPoints; diff --git a/test/mocks/renderedtargetmock.h b/test/mocks/renderedtargetmock.h index 75ce8ee..822da9b 100644 --- a/test/mocks/renderedtargetmock.h +++ b/test/mocks/renderedtargetmock.h @@ -30,6 +30,9 @@ class RenderedTargetMock : public IRenderedTarget MOCK_METHOD(SceneMouseArea *, mouseArea, (), (const, override)); MOCK_METHOD(void, setMouseArea, (SceneMouseArea *), (override)); + MOCK_METHOD(double, stageScale, (), (const, override)); + MOCK_METHOD(void, setStageScale, (double), (override)); + MOCK_METHOD(qreal, width, (), (const, override)); MOCK_METHOD(void, setWidth, (qreal), (override)); diff --git a/test/projectscene/projectscene_test.cpp b/test/projectscene/projectscene_test.cpp index 9fc12a5..e7dfafb 100644 --- a/test/projectscene/projectscene_test.cpp +++ b/test/projectscene/projectscene_test.cpp @@ -8,7 +8,7 @@ using namespace scratchcppgui; using ::testing::Return; -TEST(ProjectScene, Engine) +TEST(ProjectSceneTest, Engine) { ProjectScene scene; ASSERT_EQ(scene.engine(), nullptr); @@ -18,20 +18,30 @@ TEST(ProjectScene, Engine) ASSERT_EQ(scene.engine(), &engine); } -TEST(ProjectScene, HandleMouseMove) +TEST(ProjectSceneTest, StageScale) +{ + ProjectScene scene; + ASSERT_EQ(scene.stageScale(), 1); + + scene.setStageScale(5.79); + ASSERT_EQ(scene.stageScale(), 5.79); +} + +TEST(ProjectSceneTest, HandleMouseMove) { ProjectScene scene; EngineMock engine; scene.setEngine(&engine); + scene.setStageScale(2.5); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(600)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(400)); - EXPECT_CALL(engine, setMouseX(-253.1)); - EXPECT_CALL(engine, setMouseY(216.7)); + EXPECT_CALL(engine, setMouseX(-281.24)); + EXPECT_CALL(engine, setMouseY(206.68)); scene.handleMouseMove(46.9, -16.7); } -TEST(ProjectScene, HandleMousePress) +TEST(ProjectSceneTest, HandleMousePress) { ProjectScene scene; EngineMock engine; @@ -44,7 +54,7 @@ TEST(ProjectScene, HandleMousePress) scene.handleMousePress(); } -TEST(ProjectScene, HandleMouseRelease) +TEST(ProjectSceneTest, HandleMouseRelease) { ProjectScene scene; EngineMock engine; @@ -57,7 +67,7 @@ TEST(ProjectScene, HandleMouseRelease) scene.handleMouseRelease(); } -TEST(ProjectScene, HandleKeyPressAndRelease) +TEST(ProjectSceneTest, HandleKeyPressAndRelease) { static const std::unordered_map SPECIAL_KEY_MAP = { { Qt::Key_Space, KeyEvent::Type::Space }, { Qt::Key_Left, KeyEvent::Type::Left }, { Qt::Key_Up, KeyEvent::Type::Up }, { Qt::Key_Right, KeyEvent::Type::Right }, diff --git a/test/renderedtarget/renderedtarget_test.cpp b/test/renderedtarget/renderedtarget_test.cpp index fbaf355..9db26ba 100644 --- a/test/renderedtarget/renderedtarget_test.cpp +++ b/test/renderedtarget/renderedtarget_test.cpp @@ -74,6 +74,7 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties) target.setZ(2.5); target.setRotation(-78.05); target.setTransformOriginPoint(QPointF(3.4, 9.7)); + target.setStageScale(3.5); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249)); @@ -87,10 +88,10 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties) ASSERT_EQ(target.transformOriginPoint(), QPointF(3.4, 9.7)); target.updateProperties(); - ASSERT_EQ(target.width(), 4); - ASSERT_EQ(target.height(), 6); - ASSERT_EQ(target.x(), 295); - ASSERT_EQ(target.y(), 52.5); + ASSERT_EQ(target.width(), 14); + ASSERT_EQ(target.height(), 21); + ASSERT_EQ(target.x(), 1032.5); + ASSERT_EQ(target.y(), 183.75); ASSERT_EQ(target.z(), 0); ASSERT_EQ(target.rotation(), 0); ASSERT_EQ(target.transformOriginPoint(), QPointF(-23, 72)); @@ -118,6 +119,7 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties) target.setZ(2.5); target.setRotation(-78.05); target.setTransformOriginPoint(QPointF(3.4, 9.7)); + target.setStageScale(5.23); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249)); @@ -131,14 +133,14 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties) ASSERT_EQ(target.transformOriginPoint(), QPointF(3.4, 9.7)); target.updateProperties(); - ASSERT_EQ(target.width(), 5.7592); - ASSERT_EQ(target.height(), 8.6388); - ASSERT_EQ(std::round(target.x() * 100) / 100, 237.18); - ASSERT_EQ(std::round(target.y() * 100) / 100, -100.93); + ASSERT_EQ(std::round(target.width() * 100) / 100, 30.12); + ASSERT_EQ(std::round(target.height() * 100) / 100, 45.18); + ASSERT_EQ(std::round(target.x() * 100) / 100, 1240.43); + ASSERT_EQ(std::round(target.y() * 100) / 100, -527.84); ASSERT_EQ(target.z(), 3); ASSERT_EQ(target.rotation(), -157.16); - ASSERT_EQ(target.transformOriginPoint().x(), -33.1154); - ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 103.67); + ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -173.19); + ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 542.17); ASSERT_TRUE(mirrorHorizontallySpy.empty()); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544)); @@ -377,6 +379,7 @@ TEST_F(RenderedTargetTest, LoadSvgCostume) // Test scale limit sprite.setSize(maxSize * 250); + target.setStageScale(3.89); target.loadCostume(costume.get()); ASSERT_TRUE(target.isSvg()); @@ -406,9 +409,9 @@ TEST_F(RenderedTargetTest, LoadSvgCostume) ASSERT_EQ(std::round(target.width() * 100) / 100, maxWidth); ASSERT_EQ(std::round(target.height() * 100) / 100, maxHeight); - ASSERT_EQ(std::round(target.scale() * 100) / 100, 2.5); - ASSERT_EQ(std::round(target.x() * 100) / 100, 11126.36); - ASSERT_EQ(std::round(target.y() * 100) / 100, -6593.27); + ASSERT_EQ(std::round(target.scale() * 100) / 100, 9.73); + ASSERT_EQ(std::round(target.x() * 100) / 100, 11963.59); + ASSERT_EQ(std::round(target.y() * 100) / 100, -5887.67); ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -10836.66); ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 6837.42); } @@ -603,3 +606,12 @@ TEST_F(RenderedTargetTest, MouseArea) target.setMouseArea(&mouseArea); ASSERT_EQ(target.mouseArea(), &mouseArea); } + +TEST_F(RenderedTargetTest, StageScale) +{ + RenderedTarget target; + ASSERT_EQ(target.stageScale(), 1); + + target.setStageScale(6.4); + ASSERT_EQ(target.stageScale(), 6.4); +}