From 2a58a466f38921b9a86210fdbd97bb1550f89a02 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:05:28 +0100 Subject: [PATCH 1/5] Update libscratchcpp to latest master --- libscratchcpp | 2 +- test/mocks/enginemock.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libscratchcpp b/libscratchcpp index bf24e61..a2b247d 160000 --- a/libscratchcpp +++ b/libscratchcpp @@ -1 +1 @@ -Subproject commit bf24e610c052a5393b49738affb1674ba0cd3466 +Subproject commit a2b247d8631f6fc147df19a5a71a8c8da95eb496 diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index f9b3674..f54d8c1 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -27,6 +27,7 @@ class EngineMock : public IEngine MOCK_METHOD(void, initClone, (std::shared_ptr), (override)); MOCK_METHOD(void, deinitClone, (std::shared_ptr), (override)); + MOCK_METHOD(void, step, (), (override)); MOCK_METHOD(void, run, (), (override)); MOCK_METHOD(void, runEventLoop, (), (override)); MOCK_METHOD(void, stopEventLoop, (), (override)); From d5a6cb332854824de844191263f6d370ce3c6e0b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:22:15 +0100 Subject: [PATCH 2/5] Prepare ProjectLoader for running projects in main thread --- src/projectloader.cpp | 90 +++-------------------- src/projectloader.h | 9 +-- test/projectloader/projectloader_test.cpp | 44 +++-------- 3 files changed, 21 insertions(+), 122 deletions(-) diff --git a/src/projectloader.cpp b/src/projectloader.cpp index 68941d7..bc9541d 100644 --- a/src/projectloader.cpp +++ b/src/projectloader.cpp @@ -12,11 +12,6 @@ using namespace scratchcpprender; using namespace libscratchcpp; -void runEventLoop(IEngine *engine) -{ - engine->runEventLoop(); -} - ProjectLoader::ProjectLoader(QObject *parent) : QObject(parent) { @@ -33,12 +28,6 @@ ProjectLoader::ProjectLoader(QObject *parent) : }); initTimer(); - - // Update refresh rate when primary screen changes - connect(qApp, &QApplication::primaryScreenChanged, this, [this]() { - killTimer(m_timerId); - initTimer(); - }); } ProjectLoader::~ProjectLoader() @@ -48,11 +37,6 @@ ProjectLoader::~ProjectLoader() if (m_loadThread.isRunning()) m_loadThread.waitForFinished(); - if (m_engine && m_eventLoopEnabled) { - m_engine->stopEventLoop(); - m_eventLoop.waitForFinished(); - } - for (SpriteModel *sprite : m_sprites) sprite->deleteLater(); } @@ -72,11 +56,6 @@ void ProjectLoader::setFileName(const QString &newFileName) m_fileName = newFileName; - if (m_engine) { - m_engine->stopEventLoop(); - m_eventLoop.waitForFinished(); - } - m_project.setScratchVersion(ScratchVersion::Scratch3); m_project.setFileName(m_fileName.toStdString()); m_loadStatus = false; @@ -164,17 +143,8 @@ void ProjectLoader::timerEvent(QTimerEvent *event) if (m_loadThread.isRunning()) return; - auto stageRenderedTarget = m_stage.renderedTarget(); - - if (stageRenderedTarget) - stageRenderedTarget->updateProperties(); - - for (auto sprite : m_sprites) { - auto renderedTarget = sprite->renderedTarget(); - - if (renderedTarget) - renderedTarget->updateProperties(); - } + if (m_engine) + m_engine->step(); event->accept(); } @@ -213,7 +183,7 @@ void ProjectLoader::load() m_engine->setCloneLimit(m_cloneLimit); m_engine->setSpriteFencingEnabled(m_spriteFencing); - auto handler = std::bind(&ProjectLoader::emitTick, this); + auto handler = std::bind(&ProjectLoader::redraw, this); m_engine->setRedrawHandler(std::function(handler)); // Load targets @@ -240,12 +210,6 @@ void ProjectLoader::load() return; } - // Run event loop - m_engine->setSpriteFencingEnabled(false); - - if (m_eventLoopEnabled) - m_eventLoop = QtConcurrent::run(&runEventLoop, m_engine); - m_engineMutex.unlock(); emit loadStatusChanged(); @@ -257,28 +221,15 @@ void ProjectLoader::load() void ProjectLoader::initTimer() { - QScreen *screen = qApp->primaryScreen(); - - if (screen) - m_timerId = startTimer(1000 / screen->refreshRate()); + m_timerId = startTimer(1000 / m_fps); } -void ProjectLoader::emitTick() +void ProjectLoader::redraw() { if (m_loadThread.isRunning()) m_loadThread.waitForFinished(); - auto stageRenderedTarget = m_stage.renderedTarget(); - - if (stageRenderedTarget) - stageRenderedTarget->loadProperties(); - - for (auto sprite : m_sprites) { - auto renderedTarget = sprite->renderedTarget(); - - if (renderedTarget) - renderedTarget->loadProperties(); - } + // TODO: Call beforeRedraw() of targets } double ProjectLoader::fps() const @@ -299,6 +250,9 @@ void ProjectLoader::setFps(double newFps) } else m_fps = newFps; + killTimer(m_timerId); + initTimer(); + m_engineMutex.unlock(); emit fpsChanged(); } @@ -405,32 +359,6 @@ void ProjectLoader::setSpriteFencing(bool newSpriteFencing) emit spriteFencingChanged(); } -bool ProjectLoader::eventLoopEnabled() const -{ - return m_eventLoopEnabled; -} - -void ProjectLoader::setEventLoopEnabled(bool newEventLoopEnabled) -{ - if (m_eventLoopEnabled == newEventLoopEnabled) - return; - - m_eventLoopEnabled = newEventLoopEnabled; - m_engineMutex.lock(); - - if (m_engine) { - if (m_eventLoopEnabled) - m_eventLoop = QtConcurrent::run(&runEventLoop, m_engine); - else { - m_engine->stopEventLoop(); - m_eventLoop.waitForFinished(); - } - } - - m_engineMutex.unlock(); - emit eventLoopEnabledChanged(); -} - unsigned int ProjectLoader::downloadedAssets() const { return m_downloadedAssets; diff --git a/src/projectloader.h b/src/projectloader.h index 7917b43..ad36e2c 100644 --- a/src/projectloader.h +++ b/src/projectloader.h @@ -29,7 +29,6 @@ class ProjectLoader : public QObject Q_PROPERTY(unsigned int stageHeight READ stageHeight WRITE setStageHeight NOTIFY stageHeightChanged) Q_PROPERTY(int cloneLimit READ cloneLimit WRITE setCloneLimit NOTIFY cloneLimitChanged) Q_PROPERTY(bool spriteFencing READ spriteFencing WRITE setSpriteFencing NOTIFY spriteFencingChanged) - Q_PROPERTY(bool eventLoopEnabled READ eventLoopEnabled WRITE setEventLoopEnabled NOTIFY eventLoopEnabledChanged) Q_PROPERTY(unsigned int downloadedAssets READ downloadedAssets NOTIFY downloadedAssetsChanged) Q_PROPERTY(unsigned int assetCount READ assetCount NOTIFY assetCountChanged) @@ -71,9 +70,6 @@ class ProjectLoader : public QObject bool spriteFencing() const; void setSpriteFencing(bool newSpriteFencing); - bool eventLoopEnabled() const; - void setEventLoopEnabled(bool newEventLoopEnabled); - unsigned int downloadedAssets() const; unsigned int assetCount() const; @@ -91,7 +87,6 @@ class ProjectLoader : public QObject void stageHeightChanged(); void cloneLimitChanged(); void spriteFencingChanged(); - void eventLoopEnabledChanged(); void downloadedAssetsChanged(); void assetCountChanged(); @@ -102,7 +97,7 @@ class ProjectLoader : public QObject static void callLoad(ProjectLoader *loader); void load(); void initTimer(); - void emitTick(); + void redraw(); int m_timerId = -1; QString m_fileName; @@ -113,14 +108,12 @@ class ProjectLoader : public QObject bool m_loadStatus = false; StageModel m_stage; QList m_sprites; - QFuture m_eventLoop; double m_fps = 30; bool m_turboMode = false; unsigned int m_stageWidth = 480; unsigned int m_stageHeight = 360; int m_cloneLimit = 300; bool m_spriteFencing = true; - bool m_eventLoopEnabled = true; std::atomic m_downloadedAssets = 0; std::atomic m_assetCount = 0; std::atomic m_stopLoading = false; diff --git a/test/projectloader/projectloader_test.cpp b/test/projectloader/projectloader_test.cpp index d5883ab..8f78cc1 100644 --- a/test/projectloader/projectloader_test.cpp +++ b/test/projectloader/projectloader_test.cpp @@ -17,7 +17,6 @@ class ProjectLoaderTest : public testing::Test { static const std::chrono::milliseconds timeout(5000); auto startTime = std::chrono::steady_clock::now(); - loader->setEventLoopEnabled(false); QSignalSpy fileNameSpy(loader, &ProjectLoader::fileNameChanged); QSignalSpy loadStatusSpy(loader, &ProjectLoader::loadStatusChanged); QSignalSpy loadingFinishedSpy(loader, &ProjectLoader::loadingFinished); @@ -89,10 +88,20 @@ TEST_F(ProjectLoaderTest, StartStop) loader.stop(); } +TEST_F(ProjectLoaderTest, TimerEvent) +{ + ProjectLoader loader; + EngineMock engine; + loader.setEngine(&engine); + QTimerEvent event(0); + + EXPECT_CALL(engine, step()); + QCoreApplication::sendEvent(&loader, &event); +} + TEST_F(ProjectLoaderTest, Fps) { ProjectLoader loader; - loader.setEventLoopEnabled(false); EngineMock engine; loader.setEngine(&engine); ASSERT_EQ(loader.fps(), 30); @@ -114,7 +123,6 @@ TEST_F(ProjectLoaderTest, Fps) TEST_F(ProjectLoaderTest, TurboMode) { ProjectLoader loader; - loader.setEventLoopEnabled(false); EngineMock engine; loader.setEngine(&engine); ASSERT_FALSE(loader.turboMode()); @@ -135,7 +143,6 @@ TEST_F(ProjectLoaderTest, TurboMode) TEST_F(ProjectLoaderTest, StageWidth) { ProjectLoader loader; - loader.setEventLoopEnabled(false); EngineMock engine; loader.setEngine(&engine); ASSERT_EQ(loader.stageWidth(), 480); @@ -150,7 +157,6 @@ TEST_F(ProjectLoaderTest, StageWidth) TEST_F(ProjectLoaderTest, StageHeight) { ProjectLoader loader; - loader.setEventLoopEnabled(false); EngineMock engine; loader.setEngine(&engine); ASSERT_EQ(loader.stageHeight(), 360); @@ -165,7 +171,6 @@ TEST_F(ProjectLoaderTest, StageHeight) TEST_F(ProjectLoaderTest, CloneLimit) { ProjectLoader loader; - loader.setEventLoopEnabled(false); EngineMock engine; loader.setEngine(&engine); ASSERT_EQ(loader.cloneLimit(), 300); @@ -187,7 +192,6 @@ TEST_F(ProjectLoaderTest, CloneLimit) TEST_F(ProjectLoaderTest, SpriteFencing) { ProjectLoader loader; - loader.setEventLoopEnabled(false); EngineMock engine; loader.setEngine(&engine); ASSERT_TRUE(loader.spriteFencing()); @@ -204,29 +208,3 @@ TEST_F(ProjectLoaderTest, SpriteFencing) ASSERT_EQ(spy.count(), 1); ASSERT_TRUE(loader.spriteFencing()); } - -TEST_F(ProjectLoaderTest, EventLoopEnabled) -{ - ProjectLoader loader; - EngineMock engine; - loader.setEngine(&engine); - ASSERT_TRUE(loader.eventLoopEnabled()); - - EXPECT_CALL(engine, stopEventLoop()); - QSignalSpy spy(&loader, SIGNAL(eventLoopEnabledChanged())); - loader.setEventLoopEnabled(false); - ASSERT_EQ(spy.count(), 1); - ASSERT_FALSE(loader.eventLoopEnabled()); - - EXPECT_CALL(engine, runEventLoop()); - spy.clear(); - loader.setEventLoopEnabled(true); - ASSERT_EQ(spy.count(), 1); - ASSERT_TRUE(loader.eventLoopEnabled()); - - EXPECT_CALL(engine, stopEventLoop()); - spy.clear(); - loader.setEventLoopEnabled(false); - ASSERT_EQ(spy.count(), 1); - ASSERT_FALSE(loader.eventLoopEnabled()); -} From fd161f39abfeabee8f78d2efbd4b37090011107b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:12:50 +0100 Subject: [PATCH 3/5] Refactor RenderedTarget for singlethreaded loop --- src/ProjectPlayer.qml | 1 + src/irenderedtarget.h | 23 +- src/projectloader.cpp | 12 +- src/renderedtarget.cpp | 321 ++++++++++--------- src/renderedtarget.h | 34 ++- src/spritemodel.cpp | 25 +- src/spritemodel.h | 1 + src/stagemodel.cpp | 15 +- src/stagemodel.h | 2 + test/mocks/renderedtargetmock.h | 12 +- test/renderedtarget/renderedtarget_test.cpp | 323 ++++++-------------- test/svg_result.png | Bin 467 -> 436 bytes test/target_models/spritemodel_test.cpp | 95 ++++-- test/target_models/stagemodel_test.cpp | 33 +- 14 files changed, 456 insertions(+), 441 deletions(-) diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 6bba808..4d93bb6 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -44,6 +44,7 @@ ProjectScene { else failedToLoad(); } + onStageChanged: stage.loadCostume(); } function start() { diff --git a/src/irenderedtarget.h b/src/irenderedtarget.h index bc91264..e2f2793 100644 --- a/src/irenderedtarget.h +++ b/src/irenderedtarget.h @@ -3,18 +3,11 @@ #pragma once #include +#include class QBuffer; class QNanoPainter; - -namespace libscratchcpp -{ - -class Costume; -class IEngine; -class Target; - -} // namespace libscratchcpp +class QOpenGLContext; namespace scratchcpprender { @@ -33,9 +26,17 @@ class IRenderedTarget : public QNanoQuickItem virtual ~IRenderedTarget() { } - virtual void loadProperties() = 0; + virtual void updateVisibility(bool visible) = 0; + virtual void updateX(double x) = 0; + virtual void updateY(double y) = 0; + virtual void updateSize(double size) = 0; + virtual void updateDirection(double direction) = 0; + virtual void updateRotationStyle(libscratchcpp::Sprite::RotationStyle style) = 0; + virtual void updateLayerOrder(int layerOrder) = 0; + virtual void loadCostume(libscratchcpp::Costume *costume) = 0; - virtual void updateProperties() = 0; + + virtual void beforeRedraw() = 0; virtual libscratchcpp::IEngine *engine() const = 0; virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0; diff --git a/src/projectloader.cpp b/src/projectloader.cpp index bc9541d..ca5640c 100644 --- a/src/projectloader.cpp +++ b/src/projectloader.cpp @@ -229,7 +229,17 @@ void ProjectLoader::redraw() if (m_loadThread.isRunning()) m_loadThread.waitForFinished(); - // TODO: Call beforeRedraw() of targets + auto stage = m_stage.renderedTarget(); + + if (stage) + stage->beforeRedraw(); + + for (auto sprite : m_sprites) { + auto renderedTarget = sprite->renderedTarget(); + + if (renderedTarget) + renderedTarget->beforeRedraw(); + } } double ProjectLoader::fps() const diff --git a/src/renderedtarget.cpp b/src/renderedtarget.cpp index b963e5b..fe71aec 100644 --- a/src/renderedtarget.cpp +++ b/src/renderedtarget.cpp @@ -40,113 +40,113 @@ RenderedTarget::RenderedTarget(QNanoQuickItem *parent) : } } -void RenderedTarget::loadProperties() +void RenderedTarget::updateVisibility(bool visible) { - Q_ASSERT(!(m_spriteModel && m_stageModel)); - if (!m_engine || !m_costume) + if (visible == isVisible()) return; - if (m_spriteModel) { - mutex.lock(); - Sprite *sprite = m_spriteModel->sprite(); + setVisible(visible); + calculatePos(); +} - // Visibility - m_visible = sprite->visible(); +void RenderedTarget::updateX(double x) +{ + if (x == m_x) + return; - m_size = sprite->size() / 100; - updateCostumeData(); + m_x = x; + calculatePos(); +} - if (m_visible) { - // Direction - switch (sprite->rotationStyle()) { - case Sprite::RotationStyle::AllAround: - m_rotation = sprite->direction() - 90; - m_newMirrorHorizontally = false; +void RenderedTarget::updateY(double y) +{ + if (y == m_y) + return; - break; + m_y = y; + calculatePos(); +} - case Sprite::RotationStyle::LeftRight: { - m_rotation = 0; - m_newMirrorHorizontally = (sprite->direction() < 0); +void RenderedTarget::updateSize(double size) +{ + if (size / 100 == m_size) + return; - break; - } + m_size = size / 100; + calculateSize(); + calculatePos(); +} - case Sprite::RotationStyle::DoNotRotate: - m_rotation = 0; - m_newMirrorHorizontally = false; - break; - } +void RenderedTarget::updateDirection(double direction) +{ + if (direction == m_direction) + return; - // Coordinates - double clampedSize = std::min(m_size, m_maxSize); - 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(); + m_direction = direction; + calculateRotation(); - // Layer - m_z = sprite->layerOrder(); - } + if (m_rotationStyle == libscratchcpp::Sprite::RotationStyle::LeftRight) + calculatePos(); +} - mutex.unlock(); - } else if (m_stageModel) { - updateCostumeData(); - 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(); - } +void RenderedTarget::updateRotationStyle(libscratchcpp::Sprite::RotationStyle style) +{ + if (style == m_rotationStyle) + return; + + m_rotationStyle = style; + calculateRotation(); + calculatePos(); +} + +void RenderedTarget::updateLayerOrder(int layerOrder) +{ + setZ(layerOrder); } void RenderedTarget::loadCostume(Costume *costume) { - if (!costume) + if (!costume || costume == m_costume) return; m_costumeMutex.lock(); - m_loadCostume = true; - m_costumeChanged = (costume != m_costume); m_costume = costume; - m_costumeMutex.unlock(); -} -void RenderedTarget::updateProperties() -{ - mutex.lock(); - setVisible(m_visible); + if (m_costume->dataFormat() == "svg") { + m_svgRenderer.load(QByteArray::fromRawData(static_cast(m_costume->data()), m_costume->dataSize())); + QRectF rect = m_svgRenderer.viewBoxF(); + m_costumeWidth = rect.width(); + m_costumeHeight = rect.height(); + } else { + m_bitmapBuffer.open(QBuffer::WriteOnly); + m_bitmapBuffer.write(static_cast(m_costume->data()), m_costume->dataSize()); + m_bitmapBuffer.close(); + m_bitmapUniqueKey = QString::fromStdString(m_costume->id()); + const char *format; - if (m_visible) { - if (m_imageChanged) { - update(); - m_imageChanged = false; + { + QImageReader reader(&m_bitmapBuffer); + format = reader.format(); } - setX(m_x); - setY(m_y); - setZ(m_z); - setWidth(m_width); - setHeight(m_height); - setRotation(m_rotation); - setTransformOriginPoint(QPointF(m_originX, m_originY)); - Q_ASSERT(m_maxSize > 0); - - if (!m_stageModel && (m_size > m_maxSize) && (m_maxSize != 0)) - setScale(m_size / m_maxSize); - else - setScale(1); - - if (m_newMirrorHorizontally != m_mirrorHorizontally) { - m_mirrorHorizontally = m_newMirrorHorizontally; - emit mirrorHorizontallyChanged(); - } + m_bitmapBuffer.close(); + m_costumeBitmap.load(&m_bitmapBuffer, format); + QSize size = m_costumeBitmap.size(); + m_costumeWidth = std::max(0, size.width()); + m_costumeHeight = std::max(0, size.height()); + m_bitmapBuffer.close(); } - mutex.unlock(); + m_costumeMutex.unlock(); + + calculateSize(); + calculatePos(); +} + +void RenderedTarget::beforeRedraw() +{ + setWidth(m_width); + setHeight(m_height); } IEngine *RenderedTarget::engine() const @@ -174,6 +174,14 @@ void RenderedTarget::setStageModel(StageModel *newStageModel) return; m_stageModel = newStageModel; + + if (m_stageModel) { + Stage *stage = m_stageModel->stage(); + + if (stage) + loadCostume(stage->currentCostume().get()); + } + emit stageModelChanged(); } @@ -188,6 +196,21 @@ void RenderedTarget::setSpriteModel(SpriteModel *newSpriteModel) return; m_spriteModel = newSpriteModel; + + if (m_spriteModel) { + Sprite *sprite = m_spriteModel->sprite(); + + if (sprite) { + loadCostume(sprite->currentCostume().get()); + updateVisibility(sprite->visible()); + updateX(sprite->x()); + updateY(sprite->y()); + updateSize(sprite->size()); + updateDirection(sprite->direction()); + updateRotationStyle(sprite->rotationStyle()); + updateLayerOrder(sprite->layerOrder()); + } + } emit spriteModelChanged(); } @@ -228,6 +251,8 @@ void RenderedTarget::setStageScale(double newStageScale) return; m_stageScale = newStageScale; + calculateSize(); + calculatePos(); emit stageScaleChanged(); } @@ -305,57 +330,6 @@ void RenderedTarget::mouseMoveEvent(QMouseEvent *event) } } -void RenderedTarget::updateCostumeData() -{ - // Costume - m_costumeMutex.lock(); - - if (m_loadCostume) { - m_loadCostume = false; - m_imageChanged = true; - - if (m_costumeChanged) { - m_costumeChanged = false; - assert(m_costume); - - if (m_costume->dataFormat() == "svg") - m_svgRenderer.load(QByteArray::fromRawData(static_cast(m_costume->data()), m_costume->dataSize())); - } - } - - m_costumeMutex.unlock(); - doLoadCostume(); -} - -void RenderedTarget::doLoadCostume() -{ - m_costumeMutex.lock(); - - if (!m_costume) { - m_costumeMutex.unlock(); - return; - } - - Target *target = scratchTarget(); - - if (m_costume->dataFormat() == "svg") { - QRectF rect = m_svgRenderer.viewBoxF(); - calculateSize(target, rect.width(), rect.height()); - } else { - m_bitmapBuffer.open(QBuffer::WriteOnly); - m_bitmapBuffer.write(static_cast(m_costume->data()), m_costume->dataSize()); - m_bitmapBuffer.close(); - m_bitmapUniqueKey = QString::fromStdString(m_costume->id()); - - QImageReader reader(&m_bitmapBuffer); - QSize size = reader.size(); - calculateSize(target, std::max(0, size.width()), std::max(0, size.height())); - m_bitmapBuffer.close(); - } - - m_costumeMutex.unlock(); -} - void RenderedTarget::paintSvg(QNanoPainter *painter) { Q_ASSERT(painter); @@ -469,22 +443,89 @@ bool RenderedTarget::contains(const QPointF &point) const return *it == intPoint; } -void RenderedTarget::calculateSize(Target *target, double costumeWidth, double costumeHeight) +void RenderedTarget::calculatePos() +{ + if (!m_costume || !m_engine) + return; + + if (m_spriteModel) { + if (isVisible()) { + double stageWidth = m_engine->stageWidth(); + double stageHeight = m_engine->stageHeight(); + setX(m_stageScale * (stageWidth / 2 + m_x - m_costume->rotationCenterX() * m_clampedSize / m_costume->bitmapResolution() * (m_mirrorHorizontally ? -1 : 1))); + setY(m_stageScale * (stageHeight / 2 - m_y - m_costume->rotationCenterY() * m_clampedSize / m_costume->bitmapResolution())); + qreal originX = m_costume->rotationCenterX() * m_clampedSize * m_stageScale / m_costume->bitmapResolution(); + qreal originY = m_costume->rotationCenterY() * m_clampedSize * m_stageScale / m_costume->bitmapResolution(); + setTransformOriginPoint(QPointF(originX, originY)); + } + } else { + double stageWidth = m_engine->stageWidth(); + double stageHeight = m_engine->stageHeight(); + setX(m_stageScale * (stageWidth / 2 - m_costume->rotationCenterX() / m_costume->bitmapResolution())); + setY(m_stageScale * (stageHeight / 2 - m_costume->rotationCenterY() / m_costume->bitmapResolution())); + qreal originX = m_costume->rotationCenterX() / m_costume->bitmapResolution(); + qreal originY = m_costume->rotationCenterY() / m_costume->bitmapResolution(); + setTransformOriginPoint(QPointF(originX, originY)); + } +} + +void RenderedTarget::calculateRotation() +{ + if (isVisible()) { + // Direction + bool oldMirrorHorizontally = m_mirrorHorizontally; + + switch (m_rotationStyle) { + case Sprite::RotationStyle::AllAround: + setRotation(m_direction - 90); + m_mirrorHorizontally = (false); + + break; + + case Sprite::RotationStyle::LeftRight: { + setRotation(0); + m_mirrorHorizontally = (m_direction < 0); + + break; + } + + case Sprite::RotationStyle::DoNotRotate: + setRotation(0); + m_mirrorHorizontally = false; + break; + } + + if (m_mirrorHorizontally != oldMirrorHorizontally) + emit mirrorHorizontallyChanged(); + } +} + +void RenderedTarget::calculateSize() { if (m_costume) { double bitmapRes = m_costume->bitmapResolution(); - 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 * m_stageScale / bitmapRes; - m_height = costumeHeight * clampedSize * m_stageScale / bitmapRes; + if (m_costumeWidth == 0 || m_costumeHeight == 0) + m_maxSize = 1; + else + m_maxSize = std::min(m_maximumWidth / (m_costumeWidth * m_stageScale), m_maximumHeight / (m_costumeHeight * m_stageScale)); + + if (m_spriteModel) { + m_clampedSize = std::min(m_size, m_maxSize); + m_width = m_costumeWidth * m_clampedSize * m_stageScale / bitmapRes; + m_height = m_height = m_costumeHeight * m_clampedSize * m_stageScale / bitmapRes; } else { - m_width = costumeWidth * m_stageScale / bitmapRes; - m_height = costumeHeight * m_stageScale / bitmapRes; + m_width = m_costumeWidth * m_stageScale / bitmapRes; + m_height = m_height = m_costumeHeight * m_stageScale / bitmapRes; } } + + Q_ASSERT(m_maxSize > 0); + + if (!m_stageModel && (m_size > m_maxSize) && (m_maxSize != 0)) + setScale(m_size / m_maxSize); + else + setScale(1); } void RenderedTarget::handleSceneMouseMove(qreal x, qreal y) diff --git a/src/renderedtarget.h b/src/renderedtarget.h index f75d6e2..f006188 100644 --- a/src/renderedtarget.h +++ b/src/renderedtarget.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "irenderedtarget.h" @@ -30,9 +31,17 @@ class RenderedTarget : public IRenderedTarget public: RenderedTarget(QNanoQuickItem *parent = nullptr); - Q_INVOKABLE void loadProperties() override; + void updateVisibility(bool visible) override; + void updateX(double x) override; + void updateY(double y) override; + void updateSize(double size) override; + void updateDirection(double direction) override; + void updateRotationStyle(libscratchcpp::Sprite::RotationStyle style) override; + void updateLayerOrder(int layerOrder) override; + void loadCostume(libscratchcpp::Costume *costume) override; - Q_INVOKABLE void updateProperties() override; + + void beforeRedraw() override; libscratchcpp::IEngine *engine() const override; void setEngine(libscratchcpp::IEngine *newEngine) override; @@ -90,9 +99,9 @@ class RenderedTarget : public IRenderedTarget void mouseMoveEvent(QMouseEvent *event) override; private: - void updateCostumeData(); - void doLoadCostume(); - void calculateSize(libscratchcpp::Target *target, double costumeWidth, double costumeHeight); + void calculatePos(); + void calculateRotation(); + void calculateSize(); void handleSceneMouseMove(qreal x, qreal y); libscratchcpp::IEngine *m_engine = nullptr; @@ -101,26 +110,23 @@ class RenderedTarget : public IRenderedTarget SpriteModel *m_spriteModel = nullptr; SceneMouseArea *m_mouseArea = nullptr; QSvgRenderer m_svgRenderer; + QImage m_costumeBitmap; QBuffer m_bitmapBuffer; QString m_bitmapUniqueKey; QMutex m_costumeMutex; QMutex mutex; - bool m_loadCostume = false; - bool m_costumeChanged = false; - bool m_imageChanged = false; - bool m_visible = true; double m_size = 1; + double m_clampedSize = 1; double m_maxSize = 1; + unsigned int m_costumeWidth = 0; + unsigned int m_costumeHeight = 0; double m_x = 0; double m_y = 0; - double m_z = 0; double m_width = 0; double m_height = 0; - double m_rotation = 0; + double m_direction = 90; + libscratchcpp::Sprite::RotationStyle m_rotationStyle = libscratchcpp::Sprite::RotationStyle::AllAround; bool m_mirrorHorizontally = false; - 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(); diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index 01ae317..0618ac0 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -17,9 +17,6 @@ SpriteModel::SpriteModel(QObject *parent) : void SpriteModel::init(libscratchcpp::Sprite *sprite) { m_sprite = sprite; - - if (m_renderedTarget && sprite) - m_renderedTarget->loadCostume(sprite->currentCostume().get()); } void SpriteModel::deinitClone() @@ -38,26 +35,44 @@ void SpriteModel::onCostumeChanged(libscratchcpp::Costume *costume) void SpriteModel::onVisibleChanged(bool visible) { + if (m_renderedTarget) + m_renderedTarget->updateVisibility(visible); } void SpriteModel::onXChanged(double x) { + if (m_renderedTarget) + m_renderedTarget->updateX(x); } void SpriteModel::onYChanged(double y) { + if (m_renderedTarget) + m_renderedTarget->updateY(y); } void SpriteModel::onSizeChanged(double size) { + if (m_renderedTarget) + m_renderedTarget->updateSize(size); } void SpriteModel::onDirectionChanged(double direction) { + if (m_renderedTarget) + m_renderedTarget->updateDirection(direction); } void SpriteModel::onRotationStyleChanged(libscratchcpp::Sprite::RotationStyle rotationStyle) { + if (m_renderedTarget) + m_renderedTarget->updateRotationStyle(rotationStyle); +} + +void SpriteModel::onLayerOrderChanged(int layerOrder) +{ + if (m_renderedTarget) + m_renderedTarget->updateLayerOrder(layerOrder); } void SpriteModel::onGraphicsEffectChanged(libscratchcpp::IGraphicsEffect *effect, double value) @@ -89,10 +104,6 @@ void SpriteModel::setRenderedTarget(IRenderedTarget *newRenderedTarget) return; m_renderedTarget = newRenderedTarget; - - if (m_renderedTarget && m_sprite) - m_renderedTarget->loadCostume(m_sprite->currentCostume().get()); - emit renderedTargetChanged(); } diff --git a/src/spritemodel.h b/src/spritemodel.h index 26f70b4..2703f8f 100644 --- a/src/spritemodel.h +++ b/src/spritemodel.h @@ -37,6 +37,7 @@ class SpriteModel void onSizeChanged(double size) override; void onDirectionChanged(double direction) override; void onRotationStyleChanged(libscratchcpp::Sprite::RotationStyle rotationStyle) override; + void onLayerOrderChanged(int layerOrder) override; void onGraphicsEffectChanged(libscratchcpp::IGraphicsEffect *effect, double value) override; void onGraphicsEffectsCleared() override; diff --git a/src/stagemodel.cpp b/src/stagemodel.cpp index 37a58d6..14521a9 100644 --- a/src/stagemodel.cpp +++ b/src/stagemodel.cpp @@ -15,9 +15,6 @@ StageModel::StageModel(QObject *parent) : void StageModel::init(libscratchcpp::Stage *stage) { m_stage = stage; - - if (m_renderedTarget && stage) - m_renderedTarget->loadCostume(stage->currentCostume().get()); } void StageModel::onCostumeChanged(libscratchcpp::Costume *costume) @@ -46,6 +43,14 @@ void StageModel::onGraphicsEffectsCleared() { } +void StageModel::loadCostume() +{ + if (m_renderedTarget && m_stage) { + if (m_stage) + m_renderedTarget->loadCostume(m_stage->currentCostume().get()); + } +} + libscratchcpp::Stage *StageModel::stage() const { return m_stage; @@ -62,9 +67,7 @@ void StageModel::setRenderedTarget(IRenderedTarget *newRenderedTarget) return; m_renderedTarget = newRenderedTarget; - - if (m_renderedTarget && m_stage) - m_renderedTarget->loadCostume(m_stage->currentCostume().get()); + loadCostume(); emit renderedTargetChanged(); } diff --git a/src/stagemodel.h b/src/stagemodel.h index fe3822f..51a7066 100644 --- a/src/stagemodel.h +++ b/src/stagemodel.h @@ -33,6 +33,8 @@ class StageModel void onGraphicsEffectChanged(libscratchcpp::IGraphicsEffect *effect, double value) override; void onGraphicsEffectsCleared() override; + Q_INVOKABLE void loadCostume(); + libscratchcpp::Stage *stage() const; IRenderedTarget *renderedTarget() const; diff --git a/test/mocks/renderedtargetmock.h b/test/mocks/renderedtargetmock.h index 49efcb6..addfd42 100644 --- a/test/mocks/renderedtargetmock.h +++ b/test/mocks/renderedtargetmock.h @@ -12,9 +12,17 @@ namespace scratchcpprender class RenderedTargetMock : public IRenderedTarget { public: - MOCK_METHOD(void, loadProperties, (), (override)); + MOCK_METHOD(void, updateVisibility, (bool), (override)); + MOCK_METHOD(void, updateX, (double), (override)); + MOCK_METHOD(void, updateY, (double), (override)); + MOCK_METHOD(void, updateSize, (double), (override)); + MOCK_METHOD(void, updateDirection, (double), (override)); + MOCK_METHOD(void, updateRotationStyle, (libscratchcpp::Sprite::RotationStyle), (override)); + MOCK_METHOD(void, updateLayerOrder, (int), (override)); + + MOCK_METHOD(void, beforeRedraw, (), (override)); + MOCK_METHOD(void, loadCostume, (libscratchcpp::Costume *), (override)); - MOCK_METHOD(void, updateProperties, (), (override)); MOCK_METHOD(libscratchcpp::IEngine *, engine, (), (const, override)); MOCK_METHOD(void, setEngine, (libscratchcpp::IEngine *), (override)); diff --git a/test/renderedtarget/renderedtarget_test.cpp b/test/renderedtarget/renderedtarget_test.cpp index 22b9fb7..047f7e3 100644 --- a/test/renderedtarget/renderedtarget_test.cpp +++ b/test/renderedtarget/renderedtarget_test.cpp @@ -48,9 +48,10 @@ TEST_F(RenderedTargetTest, Constructors) ASSERT_EQ(target2.parentItem(), &target1); } -TEST_F(RenderedTargetTest, LoadAndUpdateProperties) +TEST_F(RenderedTargetTest, UpdateMethods) { - RenderedTarget target; + RenderedTarget parent; // a parent item is needed for setVisible() to work + RenderedTarget target(&parent); QSignalSpy mirrorHorizontallySpy(&target, &RenderedTarget::mirrorHorizontallyChanged); // Stage @@ -63,38 +64,21 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties) costume.setData(costumeData.size(), static_cast(costumeData.data())); costume.setRotationCenterX(-23); costume.setRotationCenterY(72); + costume.setBitmapResolution(2.5); EngineMock engine; - target.loadCostume(&costume); target.setEngine(&engine); - target.setWidth(14.3); - target.setHeight(5.8); - target.setX(64.5); - target.setY(-43.7); - 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)); - target.loadProperties(); - ASSERT_EQ(target.width(), 14.3); - ASSERT_EQ(target.height(), 5.8); - ASSERT_EQ(target.x(), 64.5); - ASSERT_EQ(target.y(), -43.7); - ASSERT_EQ(target.z(), 2.5); - ASSERT_EQ(target.rotation(), -78.05); - ASSERT_EQ(target.transformOriginPoint(), QPointF(3.4, 9.7)); - - target.updateProperties(); - ASSERT_EQ(target.width(), 14); - ASSERT_EQ(target.height(), 21); - ASSERT_EQ(target.x(), 1032.5); - ASSERT_EQ(target.y(), 183.75); + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.loadCostume(&costume); + target.beforeRedraw(); + ASSERT_EQ(target.width(), 1.6); + ASSERT_EQ(target.height(), 2.4); + ASSERT_EQ(target.x(), 249.2); + ASSERT_EQ(target.y(), 151.2); ASSERT_EQ(target.z(), 0); ASSERT_EQ(target.rotation(), 0); - ASSERT_EQ(target.transformOriginPoint(), QPointF(-23, 72)); + ASSERT_EQ(target.transformOriginPoint(), QPointF(-9.2, 28.8)); target.setStageModel(nullptr); ASSERT_TRUE(mirrorHorizontallySpy.empty()); @@ -110,74 +94,93 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties) sprite.setLayerOrder(3); SpriteModel spriteModel; sprite.setInterface(&spriteModel); + + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); target.setSpriteModel(&spriteModel); + target.beforeRedraw(); - target.setWidth(14.3); - target.setHeight(5.8); - target.setX(64.5); - target.setY(-43.7); - 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)); - target.loadProperties(); - ASSERT_EQ(target.width(), 14.3); - ASSERT_EQ(target.height(), 5.8); - ASSERT_EQ(target.x(), 64.5); - ASSERT_EQ(target.y(), -43.7); - ASSERT_EQ(target.z(), 2.5); - ASSERT_EQ(target.rotation(), -78.05); - ASSERT_EQ(target.transformOriginPoint(), QPointF(3.4, 9.7)); - - target.updateProperties(); - 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(std::round(target.width() * 100) / 100, 2.3); + ASSERT_EQ(std::round(target.height() * 100) / 100, 3.46); + ASSERT_EQ(std::round(target.x() * 100) / 100, 185.31); + ASSERT_EQ(std::round(target.y() * 100) / 100, 16.77); ASSERT_EQ(target.z(), 3); ASSERT_EQ(target.rotation(), -157.16); - ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -173.19); - ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 542.17); + ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -13.25); + ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 41.47); ASSERT_TRUE(mirrorHorizontallySpy.empty()); - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249)); - sprite.setRotationStyle(Sprite::RotationStyle::LeftRight); - target.loadProperties(); - ASSERT_EQ(target.mirrorHorizontally(), false); - ASSERT_EQ(target.rotation(), -157.16); + // Visibility + target.updateVisibility(false); + ASSERT_FALSE(target.isVisible()); + + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateVisibility(true); + ASSERT_TRUE(target.isVisible()); + + // X + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateX(12.5); + ASSERT_EQ(std::round(target.x() * 100) / 100, 265.75); + ASSERT_EQ(std::round(target.y() * 100) / 100, 16.77); + + // Y + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateY(-76.09); + ASSERT_EQ(std::round(target.x() * 100) / 100, 265.75); + ASSERT_EQ(std::round(target.y() * 100) / 100, 214.62); + + // Size + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateSize(56.2); + target.beforeRedraw(); + ASSERT_EQ(std::round(target.width() * 100) / 100, 0.9); + ASSERT_EQ(std::round(target.height() * 100) / 100, 1.35); + ASSERT_EQ(std::round(target.x() * 100) / 100, 257.67); + ASSERT_EQ(std::round(target.y() * 100) / 100, 239.9); + ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -5.17); + ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 16.19); + + // Direction + target.updateDirection(123.8); + ASSERT_EQ(target.rotation(), 33.8); ASSERT_TRUE(mirrorHorizontallySpy.empty()); - target.updateProperties(); - ASSERT_EQ(target.mirrorHorizontally(), true); + // Rotation style + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateRotationStyle(Sprite::RotationStyle::LeftRight); + ASSERT_EQ(target.mirrorHorizontally(), false); ASSERT_EQ(target.rotation(), 0); - ASSERT_EQ(mirrorHorizontallySpy.count(), 1); + ASSERT_EQ(std::round(target.x() * 100) / 100, 257.67); + ASSERT_EQ(std::round(target.y() * 100) / 100, 239.9); + ASSERT_TRUE(mirrorHorizontallySpy.empty()); - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249)); - sprite.setDirection(134.89); - target.loadProperties(); + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateDirection(-15); ASSERT_EQ(target.mirrorHorizontally(), true); ASSERT_EQ(target.rotation(), 0); + ASSERT_EQ(std::round(target.x() * 100) / 100, 247.33); + ASSERT_EQ(std::round(target.y() * 100) / 100, 239.9); ASSERT_EQ(mirrorHorizontallySpy.count(), 1); - target.updateProperties(); - ASSERT_EQ(target.mirrorHorizontally(), false); - ASSERT_EQ(target.rotation(), 0); - ASSERT_EQ(mirrorHorizontallySpy.count(), 2); - - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249)); - sprite.setRotationStyle(Sprite::RotationStyle::DoNotRotate); - target.loadProperties(); + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateDirection(134.89); ASSERT_EQ(target.mirrorHorizontally(), false); ASSERT_EQ(target.rotation(), 0); + ASSERT_EQ(std::round(target.x() * 100) / 100, 257.67); + ASSERT_EQ(std::round(target.y() * 100) / 100, 239.9); ASSERT_EQ(mirrorHorizontallySpy.count(), 2); - target.updateProperties(); + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + target.updateRotationStyle(Sprite::RotationStyle::DoNotRotate); ASSERT_EQ(target.mirrorHorizontally(), false); ASSERT_EQ(target.rotation(), 0); ASSERT_EQ(mirrorHorizontallySpy.count(), 2); @@ -191,57 +194,12 @@ TEST_F(RenderedTargetTest, LoadJpegCostume) costume.setBitmapResolution(3); RenderedTarget target; - target.loadCostume(&costume); ASSERT_FALSE(target.isSvg()); ASSERT_FALSE(target.bitmapBuffer()->isOpen()); target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); - - EngineMock engine; - SpriteModel spriteModel; - Sprite sprite; - sprite.setSize(196.5); - spriteModel.init(&sprite); - - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - target.setEngine(&engine); - target.setSpriteModel(&spriteModel); - target.loadProperties(); - ASSERT_FALSE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_EQ(target.bitmapBuffer()->readAll().toStdString(), str); - ASSERT_EQ(target.bitmapUniqueKey().toStdString(), costume.id()); - target.bitmapBuffer()->close(); - target.bitmapBuffer()->open(QBuffer::WriteOnly); // clear the buffer - target.bitmapBuffer()->close(); - - StageModel stageModel; - Stage stage; - - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - target.setSpriteModel(nullptr); - target.setStageModel(&stageModel); - target.loadProperties(); - ASSERT_FALSE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_EQ(target.bitmapBuffer()->readAll().toStdString(), str); - ASSERT_EQ(target.bitmapUniqueKey().toStdString(), costume.id()); - target.bitmapBuffer()->close(); - - target.updateProperties(); - ASSERT_FALSE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); ASSERT_EQ(target.bitmapBuffer()->readAll().toStdString(), str); ASSERT_EQ(target.bitmapUniqueKey().toStdString(), costume.id()); - target.bitmapBuffer()->close(); } TEST_F(RenderedTargetTest, LoadPngCostume) @@ -252,57 +210,12 @@ TEST_F(RenderedTargetTest, LoadPngCostume) costume.setBitmapResolution(3); RenderedTarget target; - target.loadCostume(&costume); ASSERT_FALSE(target.isSvg()); ASSERT_FALSE(target.bitmapBuffer()->isOpen()); target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); - - EngineMock engine; - SpriteModel spriteModel; - Sprite sprite; - sprite.setSize(196.5); - spriteModel.init(&sprite); - - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - target.setEngine(&engine); - target.setSpriteModel(&spriteModel); - target.loadProperties(); - ASSERT_FALSE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_EQ(target.bitmapBuffer()->readAll().toStdString(), str); - ASSERT_EQ(target.bitmapUniqueKey().toStdString(), costume.id()); - target.bitmapBuffer()->close(); - target.bitmapBuffer()->open(QBuffer::WriteOnly); // clear the buffer - target.bitmapBuffer()->close(); - - StageModel stageModel; - Stage stage; - - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - target.setSpriteModel(nullptr); - target.setStageModel(&stageModel); - target.loadProperties(); - ASSERT_FALSE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_EQ(target.bitmapBuffer()->readAll().toStdString(), str); - ASSERT_EQ(target.bitmapUniqueKey().toStdString(), costume.id()); - target.bitmapBuffer()->close(); - - target.updateProperties(); - ASSERT_FALSE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); ASSERT_EQ(target.bitmapBuffer()->readAll().toStdString(), str); ASSERT_EQ(target.bitmapUniqueKey().toStdString(), costume.id()); - target.bitmapBuffer()->close(); } TEST_F(RenderedTargetTest, LoadSvgCostume) @@ -341,27 +254,12 @@ TEST_F(RenderedTargetTest, LoadSvgCostume) RenderedTarget target; target.setEngine(&engine); - target.setSpriteModel(&model); - - target.loadCostume(costume.get()); - ASSERT_TRUE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - target.loadProperties(); - ASSERT_TRUE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); - - target.updateProperties(); + target.setSpriteModel(&model); + target.loadCostume(costume.get()); + target.beforeRedraw(); ASSERT_TRUE(target.isSvg()); ASSERT_FALSE(target.bitmapBuffer()->isOpen()); target.bitmapBuffer()->open(QBuffer::ReadOnly); @@ -369,8 +267,8 @@ TEST_F(RenderedTargetTest, LoadSvgCostume) ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); target.bitmapBuffer()->close(); - ASSERT_EQ(std::round(target.width() * 100) / 100, maxWidth); - ASSERT_EQ(std::round(target.height() * 100) / 100, maxHeight); + ASSERT_EQ(std::round(target.width() * 100) / 100, 1548.09); + ASSERT_EQ(std::round(target.height() * 100) / 100, 1548.09); ASSERT_EQ(target.scale(), 1); ASSERT_EQ(std::round(target.x() * 100) / 100, 11126.36); ASSERT_EQ(std::round(target.y() * 100) / 100, -6593.27); @@ -378,42 +276,18 @@ TEST_F(RenderedTargetTest, LoadSvgCostume) ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 6837.42); // Test scale limit - sprite.setSize(maxSize * 250); + EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); + target.updateSize(maxSize * 250); target.setStageScale(3.89); - target.loadCostume(costume.get()); - ASSERT_TRUE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); - - EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - target.loadProperties(); - ASSERT_TRUE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); - - target.updateProperties(); - ASSERT_TRUE(target.isSvg()); - ASSERT_FALSE(target.bitmapBuffer()->isOpen()); - target.bitmapBuffer()->open(QBuffer::ReadOnly); - ASSERT_TRUE(target.bitmapBuffer()->readAll().toStdString().empty()); - ASSERT_TRUE(target.bitmapUniqueKey().toStdString().empty()); - target.bitmapBuffer()->close(); - - 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, 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); + ASSERT_EQ(std::round(target.width() * 100) / 100, 1548.09); + ASSERT_EQ(std::round(target.height() * 100) / 100, 1548.09); + ASSERT_EQ(std::round(target.scale() * 100) / 100, 9.19); + ASSERT_EQ(std::round(target.x() * 100) / 100, 12595.73); + ASSERT_EQ(std::round(target.y() * 100) / 100, -6286.52); + ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -11468.8); + ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 7236.27); } TEST_F(RenderedTargetTest, PaintSvg) @@ -436,8 +310,7 @@ TEST_F(RenderedTargetTest, PaintSvg) target.setEngine(&engine); target.setSpriteModel(&model); target.loadCostume(&costume); - target.loadProperties(); - target.updateProperties(); + target.beforeRedraw(); // Create OpenGL context QOpenGLContext context; diff --git a/test/svg_result.png b/test/svg_result.png index 1131518a0203c99bb7e0c2c11ada21f1862de890..86dceff10b5fba97df5cda66951d8429d7fb04ca 100644 GIT binary patch delta 388 zcmcc2yoGs!Wqq2bi(^Q|oVT|Fy$(BwI9y!$Iv{3QcEQX=zb~wx7_}my_oS@NbMpx; zadAv9&qy#ZF#NX4t=jhe`f}g%7IfP6oQT7UAR{J1Wi{ch%zrdco(ptWn$kf_Bc6 zf-1|to{&1cYt7>PhV@m#M#uKY-M-BOq+YmcOv*5lTqXKWfUWW2+D#tqtD8ip3q#zw;hf@<&&TUG{IGe{g6ZgMt%-+rS$sL}Dhc+^ z&gC!46ygL<=*|tTaOCUT{zg`7`u^77S1kt$)Kxy-i@Tca@3E=Oi$U9_Zo-oQ|A4O? zlYaClPV#@qwAYAB$L*4#$0mC&gCiG=Pkgv;@Ib)%y~6f7?kfsSDzcuSP*8bW#=7M| VKo^_Q(g+41@O1TaS?83{1OS%jve5tl diff --git a/test/target_models/spritemodel_test.cpp b/test/target_models/spritemodel_test.cpp index f51bd5a..9027eb5 100644 --- a/test/target_models/spritemodel_test.cpp +++ b/test/target_models/spritemodel_test.cpp @@ -23,37 +23,98 @@ TEST(SpriteModelTest, Init) Sprite sprite; model.init(&sprite); ASSERT_EQ(model.sprite(), &sprite); +} + +TEST(SpriteModelTest, OnCostumeChanged) +{ + SpriteModel model; - auto c1 = std::make_shared("", "", ""); - auto c2 = std::make_shared("", "", ""); - auto c3 = std::make_shared("", "", ""); - sprite.addCostume(c1); - sprite.addCostume(c2); - sprite.addCostume(c3); - sprite.setCostumeIndex(1); + Costume costume("", "", ""); RenderedTargetMock renderedTarget; - QSignalSpy spy(&model, &SpriteModel::renderedTargetChanged); - EXPECT_CALL(renderedTarget, loadCostume(c2.get())); model.setRenderedTarget(&renderedTarget); - ASSERT_EQ(spy.count(), 1); - ASSERT_EQ(model.renderedTarget(), &renderedTarget); - EXPECT_CALL(renderedTarget, loadCostume(c2.get())); - model.init(&sprite); + EXPECT_CALL(renderedTarget, loadCostume(&costume)); + model.onCostumeChanged(&costume); } -TEST(SpriteModelTest, OnCostumeChanged) +TEST(SpriteModelTest, OnVisibleChanged) +{ + SpriteModel model; + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, updateVisibility(true)); + model.onVisibleChanged(true); + + EXPECT_CALL(renderedTarget, updateVisibility(false)); + model.onVisibleChanged(false); +} + +TEST(SpriteModelTest, OnXChanged) { SpriteModel model; + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); - Costume costume("", "", ""); + EXPECT_CALL(renderedTarget, updateX(32.4)); + model.onXChanged(32.4); +} +TEST(SpriteModelTest, OnYChanged) +{ + SpriteModel model; RenderedTargetMock renderedTarget; model.setRenderedTarget(&renderedTarget); - EXPECT_CALL(renderedTarget, loadCostume(&costume)); - model.onCostumeChanged(&costume); + EXPECT_CALL(renderedTarget, updateY(-46.1)); + model.onYChanged(-46.1); +} + +TEST(SpriteModelTest, OnSizeChanged) +{ + SpriteModel model; + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, updateSize(65.2)); + model.onSizeChanged(65.2); +} + +TEST(SpriteModelTest, OnDirectionChanged) +{ + SpriteModel model; + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, updateDirection(-5.4)); + model.onDirectionChanged(-5.4); +} + +TEST(SpriteModelTest, OnRotationStyleChanged) +{ + SpriteModel model; + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, updateRotationStyle(Sprite::RotationStyle::AllAround)); + model.onRotationStyleChanged(Sprite::RotationStyle::AllAround); + + EXPECT_CALL(renderedTarget, updateRotationStyle(Sprite::RotationStyle::LeftRight)); + model.onRotationStyleChanged(Sprite::RotationStyle::LeftRight); + + EXPECT_CALL(renderedTarget, updateRotationStyle(Sprite::RotationStyle::DoNotRotate)); + model.onRotationStyleChanged(Sprite::RotationStyle::DoNotRotate); +} + +TEST(SpriteModelTest, OnLayerOrderChanged) +{ + SpriteModel model; + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, updateLayerOrder(7)); + model.onLayerOrderChanged(7); } TEST(SpriteModelTest, RenderedTarget) diff --git a/test/target_models/stagemodel_test.cpp b/test/target_models/stagemodel_test.cpp index 8bf78bc..822e344 100644 --- a/test/target_models/stagemodel_test.cpp +++ b/test/target_models/stagemodel_test.cpp @@ -23,24 +23,6 @@ TEST(StageModelTest, Init) Stage stage; model.init(&stage); ASSERT_EQ(model.stage(), &stage); - - auto c1 = std::make_shared("", "", ""); - auto c2 = std::make_shared("", "", ""); - auto c3 = std::make_shared("", "", ""); - stage.addCostume(c1); - stage.addCostume(c2); - stage.addCostume(c3); - stage.setCostumeIndex(1); - - RenderedTargetMock renderedTarget; - QSignalSpy spy(&model, &StageModel::renderedTargetChanged); - EXPECT_CALL(renderedTarget, loadCostume(c2.get())); - model.setRenderedTarget(&renderedTarget); - ASSERT_EQ(spy.count(), 1); - ASSERT_EQ(model.renderedTarget(), &renderedTarget); - - EXPECT_CALL(renderedTarget, loadCostume(c2.get())); - model.init(&stage); } TEST(StageModelTest, OnCostumeChanged) @@ -60,10 +42,25 @@ TEST(StageModelTest, RenderedTarget) { StageModel model; ASSERT_EQ(model.renderedTarget(), nullptr); + Stage stage; + model.init(&stage); + + auto c1 = std::make_shared("", "", ""); + auto c2 = std::make_shared("", "", ""); + auto c3 = std::make_shared("", "", ""); + stage.addCostume(c1); + stage.addCostume(c2); + stage.addCostume(c3); + stage.setCostumeIndex(1); RenderedTargetMock renderedTarget; QSignalSpy spy(&model, &StageModel::renderedTargetChanged); + EXPECT_CALL(renderedTarget, loadCostume(c2.get())); model.setRenderedTarget(&renderedTarget); ASSERT_EQ(spy.count(), 1); ASSERT_EQ(model.renderedTarget(), &renderedTarget); + + EXPECT_CALL(renderedTarget, loadCostume(c3.get())); + stage.setCostumeIndex(2); + model.loadCostume(); } From 230c53dab4dc08050da3e5a9655a10285e2b60e0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:38:46 +0100 Subject: [PATCH 4/5] Force basic render loop --- src/targetpainter.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/targetpainter.cpp b/src/targetpainter.cpp index 83c368d..c91c3e9 100644 --- a/src/targetpainter.cpp +++ b/src/targetpainter.cpp @@ -19,6 +19,11 @@ TargetPainter::~TargetPainter() void TargetPainter::paint(QNanoPainter *painter) { + if (QThread::currentThread() != qApp->thread()) { + qFatal("Error: Rendering must happen in the GUI thread to work correctly. Please disable threaded render loop using qputenv(\"QSG_RENDER_LOOP\", \"basic\") before constructing your " + "application object."); + } + m_target->lockCostume(); double width = m_target->width(); double height = m_target->height(); From 3858f4247fa32d8e8295bc0baa3229743e1ac598 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:43:45 +0100 Subject: [PATCH 5/5] README: Add a note about render loop type --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index c26e41a..4d8a5dc 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,13 @@ Button { } ``` +Please note that the ScratchCPP renderer only works with the basic scene graph render loop. +Qt 6 uses the threaded render loop by default, so you'll have to disable it by calling this +before constructing your application object: +```cpp +qputenv("QSG_RENDER_LOOP", "basic"); +``` +

(back to top)