diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c228c0c..19a752b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: include: - qt_version: '6.6' qt_arch: 'gcc_64' - - qt_version: '6.7' + - qt_version: '6.7.2' qt_arch: 'linux_gcc_64' runs-on: ubuntu-latest diff --git a/.github/workflows/utests.yml b/.github/workflows/utests.yml index bc84c7b..616004e 100644 --- a/.github/workflows/utests.yml +++ b/.github/workflows/utests.yml @@ -16,7 +16,7 @@ jobs: include: - qt_version: '6.6' qt_arch: 'gcc_64' - - qt_version: '6.7' + - qt_version: '6.7.2' qt_arch: 'linux_gcc_64' runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d4e5aa..892ae06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(scratchcpp-render VERSION 0.6.0 LANGUAGES CXX) +project(scratchcpp-render VERSION 0.7.0 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) diff --git a/README.md b/README.md index 670e6b9..d38feeb 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ int main(int argc, char **argv) { - [x] Clones - [x] Sprite dragging - [x] Touching sprite block -- [ ] Touching color blocks (color is touching color is not implemented yet) +- [x] Touching color blocks - [x] Pen blocks - [x] Monitors - [ ] Graphics effects (color, brightness and ghost are implemented) diff --git a/libscratchcpp b/libscratchcpp index 363bec1..bc5e5a8 160000 --- a/libscratchcpp +++ b/libscratchcpp @@ -1 +1 @@ -Subproject commit 363bec1b8658a25cb558021f09907bdf3b69f80f +Subproject commit bc5e5a8d7cb25148affaf008096dc71167e6328b diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fdf033a..b681546 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,8 +72,8 @@ qt_add_qml_module(scratchcpp-render textbubblepainter.h cputexturemanager.cpp cputexturemanager.h - blocks/penextension.cpp - blocks/penextension.h + effecttransform.cpp + effecttransform.h blocks/penblocks.cpp blocks/penblocks.h ) diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 2355484..86fa183 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -11,6 +11,8 @@ ProjectScene { readonly property string fileName: loader.fileName property int stageWidth: 480 property int stageHeight: 360 + readonly property bool running: loader.running + readonly property list unsupportedBlocks: loader.unsupportedBlocks property alias fps: loader.fps property alias turboMode: loader.turboMode property alias cloneLimit: loader.cloneLimit @@ -28,11 +30,11 @@ ProjectScene { id: root engine: loader.engine stageScale: (stageWidth == 0 || stageHeight == 0) ? 1 : Math.min(width / stageWidth, height / stageHeight) - onFileNameChanged: priv.loading = true; onLoaded: priv.loaded = true onFailedToLoad: priv.loaded = false function load(fileName) { + priv.loading = true; loader.fileName = fileName; } @@ -117,6 +119,29 @@ ProjectScene { color: priv.loading || !priv.loaded ? "transparent" : "white" clip: true + Component { + id: renderedTextBubble + + Loader { + property QtObject model: null + active: model ? model.bubbleText !== "" : false + z: model ? model.bubbleLayer : 0 + + sourceComponent: TextBubble { + type: model.bubbleType + text: model.bubbleText + target: model.renderedTarget + stageScale: root.stageScale + stageWidth: root.stageWidth + stageHeight: root.stageHeight + x: target.x + y: target.y + } + + Component.onCompleted: model = modelData + } + } + RenderedTarget { id: stageTarget engine: loader.engine @@ -127,17 +152,9 @@ ProjectScene { } Loader { - readonly property alias model: stageTarget.stageModel + readonly property alias modelData: stageTarget.stageModel active: model ? model.bubbleText !== "" : false - - sourceComponent: TextBubble { - type: model ? model.bubbleType : TextBubbleShape.Say - text: model ? model.bubbleText : "" - target: stageTarget - stageScale: root.stageScale - stageWidth: root.stageWidth - stageHeight: root.stageHeight - } + sourceComponent: renderedTextBubble } PenLayer { @@ -218,20 +235,6 @@ ProjectScene { Component.onCompleted: transform = targetItem.transform[0] } }*/ - - Loader { - readonly property alias model: targetItem.spriteModel - active: model ? model.bubbleText !== "" : false - - sourceComponent: TextBubble { - type: model ? model.bubbleType : TextBubbleShape.Say - text: model ? model.bubbleText : "" - target: targetItem - stageScale: root.stageScale - stageWidth: root.stageWidth - stageHeight: root.stageHeight - } - } } } @@ -247,6 +250,12 @@ ProjectScene { delegate: renderedSprite } + Repeater { + id: textBubbles + model: loader.sprites + delegate: renderedTextBubble + } + SceneMouseArea { id: sceneMouseArea anchors.fill: parent diff --git a/src/blocks/penblocks.cpp b/src/blocks/penblocks.cpp index 5c3e1cd..8905786 100644 --- a/src/blocks/penblocks.cpp +++ b/src/blocks/penblocks.cpp @@ -26,7 +26,12 @@ const std::unordered_map std::string PenBlocks::name() const { - return "Pen"; + return "pen"; +} + +std::string PenBlocks::description() const +{ + return "Pen blocks"; } void PenBlocks::registerBlocks(IEngine *engine) diff --git a/src/blocks/penblocks.h b/src/blocks/penblocks.h index 53f6596..0feb01a 100644 --- a/src/blocks/penblocks.h +++ b/src/blocks/penblocks.h @@ -3,7 +3,7 @@ #pragma once #include -#include +#include namespace scratchcpprender { @@ -11,7 +11,7 @@ namespace scratchcpprender class SpriteModel; class PenState; -class PenBlocks : public libscratchcpp::IBlockSection +class PenBlocks : public libscratchcpp::IExtension { public: enum Inputs @@ -25,6 +25,7 @@ class PenBlocks : public libscratchcpp::IBlockSection }; std::string name() const override; + std::string description() const override; void registerBlocks(libscratchcpp::IEngine *engine) override; diff --git a/src/blocks/penextension.cpp b/src/blocks/penextension.cpp deleted file mode 100644 index 1226d08..0000000 --- a/src/blocks/penextension.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later - -#include - -#include "penextension.h" -#include "penblocks.h" - -using namespace scratchcpprender; -using namespace libscratchcpp; - -std::string PenExtension::name() const -{ - return "pen"; -} - -std::string PenExtension::description() const -{ - return "Pen extension"; -} - -void PenExtension::registerSections(IEngine *engine) -{ - engine->registerSection(std::make_shared()); -} diff --git a/src/blocks/penextension.h b/src/blocks/penextension.h deleted file mode 100644 index 1e7363d..0000000 --- a/src/blocks/penextension.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later - -#pragma once - -#include - -namespace scratchcpprender -{ - -class PenExtension : public libscratchcpp::IExtension -{ - public: - std::string name() const override; - std::string description() const override; - - void registerSections(libscratchcpp::IEngine *engine) override; -}; - -} // namespace scratchcpprender diff --git a/src/cputexturemanager.cpp b/src/cputexturemanager.cpp index c97b37d..2174cf4 100644 --- a/src/cputexturemanager.cpp +++ b/src/cputexturemanager.cpp @@ -2,6 +2,7 @@ #include "cputexturemanager.h" #include "texture.h" +#include "effecttransform.h" using namespace scratchcpprender; @@ -51,10 +52,55 @@ const std::vector &CpuTextureManager::getTextureConvexHullPoints(const T return it->second; } -bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint) +QRgb CpuTextureManager::getPointColor(const Texture &texture, int x, int y, const std::unordered_map &effects) +{ + const int width = texture.width(); + const int height = texture.height(); + + if (!effects.empty()) { + // Get local position with effect transform + QVector2D transformedCoords; + const QVector2D localCoords(x / static_cast(width), y / static_cast(height)); + EffectTransform::transformPoint(effects, localCoords, transformedCoords); + x = transformedCoords.x() * width; + y = transformedCoords.y() * height; + } + + if ((x < 0 || x >= width) || (y < 0 || y >= height)) + return qRgba(0, 0, 0, 0); + + GLubyte *pixels = getTextureData(texture); + QRgb color = qRgba(pixels[(y * width + x) * 4], pixels[(y * width + x) * 4 + 1], pixels[(y * width + x) * 4 + 2], pixels[(y * width + x) * 4 + 3]); + + if (effects.empty()) + return color; + else + return EffectTransform::transformColor(effects, color); +} + +bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint, const std::unordered_map &effects) { // https://github.com/scratchfoundation/scratch-render/blob/7b823985bc6fe92f572cc3276a8915e550f7c5e6/src/Silhouette.js#L219-L226 - return getPointAlpha(texture, localPoint.x(), localPoint.y()) > 0; + const int width = texture.width(); + const int height = texture.height(); + int x = localPoint.x(); + int y = localPoint.y(); + + if (!effects.empty()) { + // Get local position with effect transform + QVector2D transformedCoords; + const QVector2D localCoords(x / static_cast(width), y / static_cast(height)); + EffectTransform::transformPoint(effects, localCoords, transformedCoords); + x = transformedCoords.x() * width; + y = transformedCoords.y() * height; + } + + if ((x < 0 || x >= width) || (y < 0 || y >= height)) + return false; + + GLubyte *pixels = getTextureData(texture); + QRgb color = qRgba(pixels[(y * width + x) * 4], pixels[(y * width + x) * 4 + 1], pixels[(y * width + x) * 4 + 2], pixels[(y * width + x) * 4 + 3]); + return qAlpha(color) > 0; } void CpuTextureManager::removeTexture(const Texture &texture) @@ -137,12 +183,3 @@ bool CpuTextureManager::addTexture(const Texture &texture) return true; } - -int CpuTextureManager::getPointAlpha(const Texture &texture, int x, int y) -{ - if ((x < 0 || x >= texture.width()) || (y < 0 || y >= texture.height())) - return 0; - - GLubyte *pixels = getTextureData(texture); - return pixels[(y * texture.width() + x) * 4 + 3]; -} diff --git a/src/cputexturemanager.h b/src/cputexturemanager.h index df19588..4abdb5a 100644 --- a/src/cputexturemanager.h +++ b/src/cputexturemanager.h @@ -6,6 +6,8 @@ #include #include +#include "shadermanager.h" + namespace scratchcpprender { @@ -20,13 +22,13 @@ class CpuTextureManager GLubyte *getTextureData(const Texture &texture); const std::vector &getTextureConvexHullPoints(const Texture &texture); - bool textureContainsPoint(const Texture &texture, const QPointF &localPoint); + QRgb getPointColor(const Texture &texture, int x, int y, const std::unordered_map &effects); + bool textureContainsPoint(const Texture &texture, const QPointF &localPoint, const std::unordered_map &effects); void removeTexture(const Texture &texture); private: bool addTexture(const Texture &texture); - int getPointAlpha(const Texture &texture, int x, int y); std::unordered_map m_textureData; std::unordered_map> m_convexHullPoints; diff --git a/src/effecttransform.cpp b/src/effecttransform.cpp new file mode 100644 index 0000000..4ea1b70 --- /dev/null +++ b/src/effecttransform.cpp @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include + +#include "effecttransform.h" + +using namespace scratchcpprender; + +QRgb EffectTransform::transformColor(const std::unordered_map &effectValues, QRgb color) +{ + // https://github.com/scratchfoundation/scratch-render/blob/e075e5f5ebc95dec4a2718551624ad587c56f0a6/src/EffectTransform.js#L40-L119 + // If the color is fully transparent, don't bother attempting any transformations. + if (qAlpha(color) == 0) + return color; + + QColor inOutColor = QColor::fromRgba(color); + + std::unordered_map uniforms; + ShaderManager::getUniformValuesForEffects(effectValues, uniforms); + + const bool enableColor = uniforms[ShaderManager::Effect::Color] != 0; + const bool enableBrightness = uniforms[ShaderManager::Effect::Brightness] != 0; + + if (enableColor || enableBrightness) { + // gl_FragColor.rgb /= gl_FragColor.a + epsilon; + // Here, we're dividing by the (previously pre-multiplied) alpha to ensure HSV is properly calculated + // for partially transparent pixels. + const float alpha = inOutColor.alphaF(); + + if (alpha == 0) { + inOutColor.setRed(255); + inOutColor.setGreen(255); + inOutColor.setBlue(255); + } else { + inOutColor.setRedF(inOutColor.redF() / alpha); + inOutColor.setGreenF(inOutColor.greenF() / alpha); + inOutColor.setBlueF(inOutColor.blueF() / alpha); + } + + if (enableColor) { + // vec3 hsv = convertRGB2HSV(gl_FragColor.xyz); + QColor hsv = inOutColor.toHsv(); + + // this code forces grayscale values to be slightly saturated + // so that some slight change of hue will be visible + // const float minLightness = 0.11 / 2.0; + const float minV = 0.11f / 2.0f; + // const float minSaturation = 0.09; + const float minS = 0.09f; + // if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness); + if (hsv.valueF() < minV) { + hsv.setHsvF(0.0f, 1.0f, minV); + // else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z); + } else if (hsv.saturationF() < minS) { + hsv.setHsvF(0.0f, minS, hsv.valueF()); + } + + // hsv.x = mod(hsv.x + u_color, 1.0); + // if (hsv.x < 0.0) hsv.x += 1.0; + float hue = std::fmod(uniforms[ShaderManager::Effect::Color] + hsv.hueF(), 1.0f); + + if (hue < 0.0f) + hue += 1.0f; + + hsv.setHsvF(hue, hsv.saturationF(), hsv.valueF()); + + // gl_FragColor.rgb = convertHSV2RGB(hsl); + inOutColor = hsv.toRgb(); + } + + if (enableBrightness) { + const float brightness = uniforms[ShaderManager::Effect::Brightness] * 255.0f; + // gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1)); + inOutColor.setRed(std::clamp(inOutColor.red() + brightness, 0.0f, 255.0f)); + inOutColor.setGreen(std::clamp(inOutColor.green() + brightness, 0.0f, 255.0f)); + inOutColor.setBlue(std::clamp(inOutColor.blue() + brightness, 0.0f, 255.0f)); + } + + // gl_FragColor.rgb *= gl_FragColor.a + epsilon; + // Now we're doing the reverse, premultiplying by the alpha once again. + inOutColor.setRedF(inOutColor.redF() * alpha); + inOutColor.setGreenF(inOutColor.greenF() * alpha); + inOutColor.setBlueF(inOutColor.blueF() * alpha); + + // Restore alpha + inOutColor.setAlphaF(alpha); + } + + const float ghost = uniforms[ShaderManager::Effect::Ghost]; + + if (ghost != 1) { + // gl_FragColor *= u_ghost + inOutColor.setRedF(inOutColor.redF() * ghost); + inOutColor.setGreenF(inOutColor.greenF() * ghost); + inOutColor.setBlueF(inOutColor.blueF() * ghost); + inOutColor.setAlphaF(inOutColor.alphaF() * ghost); + } + + return inOutColor.rgba(); +} + +void EffectTransform::transformPoint(const std::unordered_map &effectValues, const QVector2D &vec, QVector2D &dst) +{ + // TODO: Implement remaining effects + dst = vec; +} diff --git a/src/effecttransform.h b/src/effecttransform.h new file mode 100644 index 0000000..6992f25 --- /dev/null +++ b/src/effecttransform.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include "shadermanager.h" + +namespace scratchcpprender +{ + +class EffectTransform +{ + public: + EffectTransform() = delete; + + static QRgb transformColor(const std::unordered_map &effectValues, QRgb color); + static void transformPoint(const std::unordered_map &effectValues, const QVector2D &vec, QVector2D &dst); +}; + +} // namespace scratchcpprender diff --git a/src/graphicseffect.cpp b/src/graphicseffect.cpp index cb07d26..e78384c 100644 --- a/src/graphicseffect.cpp +++ b/src/graphicseffect.cpp @@ -4,6 +4,11 @@ using namespace scratchcpprender; +static std::unordered_map> EFFECT_RANGE = { + { ShaderManager::Effect::Ghost, { 0, 100 } }, + { ShaderManager::Effect::Brightness, { -100, 100 } } +}; + GraphicsEffect::GraphicsEffect(ShaderManager::Effect effect, const std::string &name) : m_effect(effect), m_name(name) @@ -19,3 +24,13 @@ std::string GraphicsEffect::name() const { return m_name; } + + +double GraphicsEffect::clamp(double value) const +{ + // https://github.com/scratchfoundation/scratch-vm/blob/8dbcc1fc8f8d8c4f1e40629fe8a388149d6dfd1c/src/blocks/scratch3_looks.js#L523-L538 + if(m_effect == ShaderManager::Effect::Ghost || m_effect == ShaderManager::Effect::Brightness) + return std::clamp(value, EFFECT_RANGE[m_effect].first, EFFECT_RANGE[m_effect].second); + + return value; +} diff --git a/src/graphicseffect.h b/src/graphicseffect.h index 2719fca..17a4305 100644 --- a/src/graphicseffect.h +++ b/src/graphicseffect.h @@ -17,6 +17,8 @@ class GraphicsEffect : public libscratchcpp::IGraphicsEffect ShaderManager::Effect effect() const; std::string name() const override; + double clamp(double value) const override; + private: ShaderManager::Effect m_effect = static_cast(0); std::string m_name; diff --git a/src/internal/ListMonitor.qml b/src/internal/ListMonitor.qml index 36e02d4..5195e7e 100644 --- a/src/internal/ListMonitor.qml +++ b/src/internal/ListMonitor.qml @@ -17,6 +17,8 @@ Rectangle { border.color: Qt.rgba(0.765, 0.8, 0.85, 1) radius: 5 visible: model ? model.visible : true + onWidthChanged: if(model) model.width = width + onHeightChanged: if(model) model.height = height QtObject { id: priv diff --git a/src/internal/ValueMonitor.qml b/src/internal/ValueMonitor.qml index 74dd34f..5a46723 100644 --- a/src/internal/ValueMonitor.qml +++ b/src/internal/ValueMonitor.qml @@ -15,11 +15,13 @@ Rectangle { width: layout.implicitWidth + priv.horizontalMargins * 2 height: layout.implicitHeight + priv.verticalMargins * 2 visible: model ? model.visible : true + onWidthChanged: if(model) model.width = width + onHeightChanged: if(model) model.height = height QtObject { id: priv readonly property int horizontalMargins: 9 - readonly property double verticalMargins: 2 + readonly property double verticalMargins: 2.5 readonly property color bgColor: Qt.rgba(0.9, 0.94, 1, 1) } diff --git a/src/irenderedtarget.h b/src/irenderedtarget.h index 7925cd0..14cb457 100644 --- a/src/irenderedtarget.h +++ b/src/irenderedtarget.h @@ -93,6 +93,7 @@ class IRenderedTarget : public QNanoQuickItem virtual bool touchingClones(const std::vector &clones) const = 0; virtual bool touchingColor(const libscratchcpp::Value &color) const = 0; + virtual bool touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const = 0; }; } // namespace scratchcpprender diff --git a/src/listmonitorlistmodel.cpp b/src/listmonitorlistmodel.cpp index 3cd7fd7..a055bcc 100644 --- a/src/listmonitorlistmodel.cpp +++ b/src/listmonitorlistmodel.cpp @@ -57,7 +57,7 @@ QVariant ListMonitorListModel::data(const QModelIndex &index, int role) const if (!m_list || index.row() < 0 || index.row() >= m_list->size()) return QVariant(); - return QString::fromStdString((*m_list)[index.row()].toString()); + return QString::fromStdString(libscratchcpp::Value((*m_list)[index.row()]).toString()); } QHash ListMonitorListModel::roleNames() const diff --git a/src/listmonitormodel.cpp b/src/listmonitormodel.cpp index 2a42315..2182303 100644 --- a/src/listmonitormodel.cpp +++ b/src/listmonitormodel.cpp @@ -12,8 +12,8 @@ ListMonitorModel::ListMonitorModel(QObject *parent) : { } -ListMonitorModel::ListMonitorModel(libscratchcpp::IBlockSection *section, QObject *parent) : - MonitorModel(section, parent) +ListMonitorModel::ListMonitorModel(libscratchcpp::IExtension *extension, QObject *parent) : + MonitorModel(extension, parent) { m_listModel = new ListMonitorListModel(this); } diff --git a/src/listmonitormodel.h b/src/listmonitormodel.h index 45a6e8b..4034e03 100644 --- a/src/listmonitormodel.h +++ b/src/listmonitormodel.h @@ -21,7 +21,7 @@ class ListMonitorModel : public MonitorModel public: ListMonitorModel(QObject *parent = nullptr); - ListMonitorModel(libscratchcpp::IBlockSection *section, QObject *parent = nullptr); + ListMonitorModel(libscratchcpp::IExtension *extension, QObject *parent = nullptr); void onValueChanged(const libscratchcpp::VirtualMachine *vm) override; diff --git a/src/monitormodel.cpp b/src/monitormodel.cpp index 9554276..60bd8db 100644 --- a/src/monitormodel.cpp +++ b/src/monitormodel.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "monitormodel.h" @@ -13,14 +13,14 @@ MonitorModel::MonitorModel(QObject *parent) : { } -MonitorModel::MonitorModel(libscratchcpp::IBlockSection *section, QObject *parent) : +MonitorModel::MonitorModel(libscratchcpp::IExtension *extension, QObject *parent) : QObject(parent) { - if (!section) + if (!extension) return; - // TODO: Get the color from the block section - std::string name = section->name(); + // TODO: Get the color from the extension + std::string name = extension->name(); if (name == "Motion") m_color = QColor::fromString("#4C97FF"); else if (name == "Looks") @@ -49,7 +49,7 @@ QString MonitorModel::name() const bool MonitorModel::visible() const { if (m_monitor) - return m_monitor->visible(); + return m_monitor->visible() && !m_monitor->needsAutoPosition(); else return false; } @@ -69,6 +69,18 @@ void MonitorModel::onVisibleChanged(bool visible) emit visibleChanged(); } +void MonitorModel::onXChanged(int x) +{ + emit xChanged(); + emit visibleChanged(); +} + +void MonitorModel::onYChanged(int y) +{ + emit yChanged(); + emit visibleChanged(); +} + libscratchcpp::Monitor *MonitorModel::monitor() const { return m_monitor; diff --git a/src/monitormodel.h b/src/monitormodel.h index 329a284..8ad7f73 100644 --- a/src/monitormodel.h +++ b/src/monitormodel.h @@ -10,7 +10,7 @@ namespace libscratchcpp { -class IBlockSection; +class IExtension; } @@ -43,12 +43,14 @@ class MonitorModel Q_ENUM(Type) MonitorModel(QObject *parent = nullptr); - MonitorModel(libscratchcpp::IBlockSection *section, QObject *parent = nullptr); + MonitorModel(libscratchcpp::IExtension *extension, QObject *parent = nullptr); void init(libscratchcpp::Monitor *monitor) override final; virtual void onValueChanged(const libscratchcpp::VirtualMachine *vm) override { } void onVisibleChanged(bool visible) override final; + void onXChanged(int x) override final; + void onYChanged(int y) override final; libscratchcpp::Monitor *monitor() const; diff --git a/src/mouseeventhandler.cpp b/src/mouseeventhandler.cpp index 18876ea..e1dde78 100644 --- a/src/mouseeventhandler.cpp +++ b/src/mouseeventhandler.cpp @@ -165,7 +165,7 @@ void MouseEventHandler::forwardPointEvent(QSinglePointEvent *event, QQuickItem * // contains() expects position in the item's coordinate system QPointF localPos = sprite->mapFromScene(event->scenePosition()); - if (sprite->contains(localPos)) { + if (static_cast(sprite->scratchTarget())->visible() && sprite->contains(localPos)) { hoveredItem = sprite; break; } diff --git a/src/projectloader.cpp b/src/projectloader.cpp index 3090487..d8c6fea 100644 --- a/src/projectloader.cpp +++ b/src/projectloader.cpp @@ -12,7 +12,7 @@ #include "valuemonitormodel.h" #include "listmonitormodel.h" #include "renderedtarget.h" -#include "blocks/penextension.h" +#include "blocks/penblocks.h" using namespace scratchcpprender; using namespace libscratchcpp; @@ -35,7 +35,7 @@ ProjectLoader::ProjectLoader(QObject *parent) : initTimer(); // Register pen blocks - ScratchConfiguration::registerExtension(std::make_shared()); + ScratchConfiguration::registerExtension(std::make_shared()); } ProjectLoader::~ProjectLoader() @@ -128,6 +128,11 @@ bool ProjectLoader::loadStatus() const return m_loadStatus; } +bool ProjectLoader::running() const +{ + return m_running; +} + IEngine *ProjectLoader::engine() const { if (m_loadThread.isRunning()) @@ -183,6 +188,11 @@ const QList &ProjectLoader::monitorList() const return m_monitors; } +const QStringList &ProjectLoader::unsupportedBlocks() const +{ + return m_unsupportedBlocks; +} + void ProjectLoader::start() { if (m_loadThread.isRunning()) @@ -216,9 +226,20 @@ void ProjectLoader::timerEvent(QTimerEvent *event) if (m_loadThread.isRunning()) return; - if (m_engine) + if (m_engine) { + for (Monitor *monitor : m_unpositionedMonitors) + monitor->autoPosition(m_engine->monitors()); + + m_unpositionedMonitors.clear(); + m_engine->step(); + if (m_running != m_engine->isRunning()) { + m_running = !m_running; + emit runningChanged(); + } + } + event->accept(); } @@ -229,6 +250,12 @@ void ProjectLoader::callLoad(ProjectLoader *loader) void ProjectLoader::load() { + m_unpositionedMonitors.clear(); + m_sprites.clear(); + m_clones.clear(); + emit spritesChanged(); + emit clonesChanged(); + m_loadStatus = m_project.load(); m_engineMutex.lock(); m_engine = m_project.engine().get(); @@ -240,6 +267,7 @@ void ProjectLoader::load() emit loadingFinished(); emit engineChanged(); emit spritesChanged(); + emit clonesChanged(); return; } @@ -286,9 +314,19 @@ void ProjectLoader::load() emit loadingFinished(); emit engineChanged(); emit spritesChanged(); + emit clonesChanged(); return; } + // Get unsupported blocks + const auto &unsupportedBlocks = m_engine->unsupportedBlocks(); + m_unsupportedBlocks.clear(); + + for (const std::string &opcode : unsupportedBlocks) + m_unsupportedBlocks.push_back(QString::fromStdString(opcode)); + + emit unsupportedBlocksChanged(); + m_engineMutex.unlock(); emit loadStatusChanged(); @@ -296,6 +334,7 @@ void ProjectLoader::load() emit engineChanged(); emit stageChanged(); emit spritesChanged(); + emit clonesChanged(); } void ProjectLoader::initTimer() @@ -356,26 +395,31 @@ void ProjectLoader::deleteClone(SpriteModel *model) void ProjectLoader::addMonitor(Monitor *monitor) { - auto section = monitor->blockSection(); + auto extension = monitor->extension(); - if (!section) + if (!extension) return; MonitorModel *model = nullptr; switch (monitor->mode()) { case Monitor::Mode::List: - model = new ListMonitorModel(section.get()); + model = new ListMonitorModel(extension); break; default: - model = new ValueMonitorModel(section.get()); + model = new ValueMonitorModel(extension); break; } if (!model) return; + if (monitor->needsAutoPosition()) { + if (std::find(m_unpositionedMonitors.begin(), m_unpositionedMonitors.end(), monitor) == m_unpositionedMonitors.end()) + m_unpositionedMonitors.push_back(monitor); + } + model->moveToThread(qApp->thread()); model->setParent(this); monitor->setInterface(model); diff --git a/src/projectloader.h b/src/projectloader.h index 8c949c9..21f95e6 100644 --- a/src/projectloader.h +++ b/src/projectloader.h @@ -24,11 +24,13 @@ class ProjectLoader : public QObject QML_ELEMENT Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged) Q_PROPERTY(bool loadStatus READ loadStatus NOTIFY loadStatusChanged) + Q_PROPERTY(bool running READ running NOTIFY runningChanged) Q_PROPERTY(libscratchcpp::IEngine *engine READ engine NOTIFY engineChanged) Q_PROPERTY(StageModel *stage READ stage NOTIFY stageChanged) Q_PROPERTY(QQmlListProperty sprites READ sprites NOTIFY spritesChanged) Q_PROPERTY(QQmlListProperty clones READ clones NOTIFY clonesChanged) Q_PROPERTY(QQmlListProperty monitors READ monitors NOTIFY monitorsChanged) + Q_PROPERTY(QStringList unsupportedBlocks READ unsupportedBlocks NOTIFY unsupportedBlocksChanged) Q_PROPERTY(double fps READ fps WRITE setFps NOTIFY fpsChanged) Q_PROPERTY(bool turboMode READ turboMode WRITE setTurboMode NOTIFY turboModeChanged) Q_PROPERTY(unsigned int stageWidth READ stageWidth WRITE setStageWidth NOTIFY stageWidthChanged) @@ -48,6 +50,8 @@ class ProjectLoader : public QObject bool loadStatus() const; + bool running() const; + libscratchcpp::IEngine *engine() const; void setEngine(libscratchcpp::IEngine *engine); @@ -62,6 +66,8 @@ class ProjectLoader : public QObject QQmlListProperty monitors(); const QList &monitorList() const; + const QStringList &unsupportedBlocks() const; + Q_INVOKABLE void start(); Q_INVOKABLE void stop(); @@ -96,11 +102,13 @@ class ProjectLoader : public QObject void fileNameChanged(); void loadStatusChanged(); void loadingFinished(); + void runningChanged(); void engineChanged(); void stageChanged(); void spritesChanged(); void clonesChanged(); void monitorsChanged(); + void unsupportedBlocksChanged(); void fpsChanged(); void turboModeChanged(); void stageWidthChanged(); @@ -135,6 +143,7 @@ class ProjectLoader : public QObject QString m_fileName; QFuture m_loadThread; libscratchcpp::Project m_project; + bool m_running = false; libscratchcpp::IEngine *m_engine = nullptr; QMutex m_engineMutex; bool m_loadStatus = false; @@ -142,6 +151,8 @@ class ProjectLoader : public QObject QList m_sprites; QList m_clones; QList m_monitors; + std::vector m_unpositionedMonitors; + QStringList m_unsupportedBlocks; double m_fps = 30; bool m_turboMode = false; unsigned int m_stageWidth = 480; diff --git a/src/renderedtarget.cpp b/src/renderedtarget.cpp index 1f20b54..348aabf 100644 --- a/src/renderedtarget.cpp +++ b/src/renderedtarget.cpp @@ -371,31 +371,16 @@ void RenderedTarget::setHeight(qreal height) Rect RenderedTarget::getBounds() const { // https://github.com/scratchfoundation/scratch-render/blob/c3ede9c3d54769730c7b023021511e2aba167b1f/src/Rectangle.js#L33-L55 - if (!m_costume || !m_skin || !m_texture.isValid() || !m_cpuTexture.isValid()) - return Rect(m_x, m_y, m_x, m_y); - - const double textureScale = m_skin->getTextureScale(m_cpuTexture); - const double bitmapRes = m_costume->bitmapResolution(); - const double width = m_cpuTexture.width() * m_size / textureScale; - const double height = m_cpuTexture.height() * m_size / textureScale; - const double originX = m_costume->rotationCenterX() * m_size / bitmapRes - width / 2; - const double originY = -m_costume->rotationCenterY() * m_size / bitmapRes + height / 2; - const double rot = -rotation() * pi / 180; - const double sinRot = std::sin(rot); - const double cosRot = std::cos(rot); double left = std::numeric_limits::infinity(); double top = -std::numeric_limits::infinity(); double right = -std::numeric_limits::infinity(); double bottom = std::numeric_limits::infinity(); - const std::vector &points = hullPoints(); + const std::vector &points = transformedHullPoints(); for (const QPointF &point : points) { - double x = point.x() * m_size / textureScale / bitmapRes - width / 2; - double y = height / 2 - point.y() * m_size / textureScale / bitmapRes; - const QPointF transformed = transformPoint(x, y, originX, originY, sinRot, cosRot); - x = transformed.x() * (m_mirrorHorizontally ? -1 : 1); - y = transformed.y(); + const double x = point.x(); + const double y = point.y(); if (x < left) left = x; @@ -518,9 +503,6 @@ void RenderedTarget::mouseMoveEvent(QMouseEvent *event) m_dragDeltaX = m_engine->mouseX() - sprite->x(); m_dragDeltaY = m_engine->mouseY() - sprite->y(); m_mouseArea->setDraggedSprite(this); - - // Move the sprite to the front layer - m_engine->moveSpriteToFront(sprite); } } @@ -636,11 +618,7 @@ QRgb RenderedTarget::colorAtScratchPoint(double x, double y) const if ((x < 0 || x >= width) || (y < 0 || y >= height)) return qRgba(0, 0, 0, 0); - GLubyte *data = textureManager()->getTextureData(m_cpuTexture); - const int index = (y * width + x) * 4; // RGBA channels - Q_ASSERT(index >= 0 && index < width * height * 4); - // TODO: Apply graphic effects (#117) - return qRgba(data[index], data[index + 1], data[index + 2], data[index + 3]); + return textureManager()->getPointColor(m_cpuTexture, x, y, m_graphicEffects); } bool RenderedTarget::touchingClones(const std::vector &clones) const @@ -677,40 +655,12 @@ bool RenderedTarget::touchingClones(const std::vector & bool RenderedTarget::touchingColor(const Value &color) const { - // https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841 - QRgb rgb = convertColor(color); - - std::vector targets; - getVisibleTargets(targets); - - QRectF myRect = touchingBounds(); - std::vector candidates; - QRectF bounds = candidatesBounds(myRect, targets, candidates); - - if (colorMatches(rgb, qRgb(255, 255, 255))) { - // The color we're checking for is the background color which spans the entire stage - bounds = myRect; - - if (bounds.isEmpty()) - return false; - } else if (candidates.empty()) { - // If not checking for the background color, we can return early if there are no candidate drawables - return false; - } - - // Loop through the points of the union - for (int y = bounds.top(); y <= bounds.bottom(); y++) { - for (int x = bounds.left(); x <= bounds.right(); x++) { - if (this->containsScratchPoint(x, y)) { - QRgb pixelColor = sampleColor3b(x, y, candidates); - - if (colorMatches(rgb, pixelColor)) - return true; - } - } - } + return touchingColor(color, false, Value()); +} - return false; +bool RenderedTarget::touchingColor(const Value &color, const Value &mask) const +{ + return touchingColor(color, true, mask); } void RenderedTarget::calculatePos() @@ -734,6 +684,8 @@ void RenderedTarget::calculatePos() else setTransformOrigin(QQuickItem::Center); } + + m_transformedHullDirty = true; } void RenderedTarget::calculateRotation() @@ -765,6 +717,8 @@ void RenderedTarget::calculateRotation() if (m_mirrorHorizontally != oldMirrorHorizontally) emit mirrorHorizontallyChanged(); } + + m_transformedHullDirty = true; } void RenderedTarget::calculateSize() @@ -780,6 +734,8 @@ void RenderedTarget::calculateSize() if (wasValid && m_cpuTexture.handle() != oldTexture) m_convexHullDirty = true; + + m_transformedHullDirty = true; } } @@ -812,9 +768,45 @@ void RenderedTarget::updateHullPoints() // TODO: Apply graphic effects (#117) } +const std::vector &RenderedTarget::transformedHullPoints() const +{ + // https://github.com/scratchfoundation/scratch-render/blob/9fe90e8f4c2da35d4684359f84b69c264d884133/src/Drawable.js#L594-L616 + if (!m_transformedHullDirty) + return m_transformedHullPoints; + + m_transformedHullPoints.clear(); + + if (!m_costume || !m_skin || !m_texture.isValid() || !m_cpuTexture.isValid()) + return m_transformedHullPoints; + + const double textureScale = m_skin->getTextureScale(m_cpuTexture); + const double bitmapRes = m_costume->bitmapResolution(); + const double width = m_cpuTexture.width() * m_size / textureScale; + const double height = m_cpuTexture.height() * m_size / textureScale; + const double originX = m_costume->rotationCenterX() * m_size / bitmapRes - width / 2; + const double originY = -m_costume->rotationCenterY() * m_size / bitmapRes + height / 2; + const double rot = -rotation() * pi / 180; + const double sinRot = std::sin(rot); + const double cosRot = std::cos(rot); + + const std::vector &points = hullPoints(); + m_transformedHullPoints.reserve(points.size()); + + for (const QPoint &point : points) { + double x = point.x() * m_size / textureScale / bitmapRes - width / 2; + double y = height / 2 - point.y() * m_size / textureScale / bitmapRes; + const QPointF transformed = transformPoint(x, y, originX, originY, sinRot, cosRot); + x = transformed.x() * (m_mirrorHorizontally ? -1 : 1); + y = transformed.y(); + m_transformedHullPoints.push_back(QPointF(x, y)); + } + + return m_transformedHullPoints; +} + bool RenderedTarget::containsLocalPoint(const QPointF &point) const { - return textureManager()->textureContainsPoint(m_cpuTexture, point); + return textureManager()->textureContainsPoint(m_cpuTexture, point, m_graphicEffects); } QPointF RenderedTarget::transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const @@ -870,36 +862,68 @@ CpuTextureManager *RenderedTarget::textureManager() const return m_textureManager.get(); } -void RenderedTarget::getVisibleTargets(std::vector &dst) const +bool RenderedTarget::touchingColor(const libscratchcpp::Value &color, bool hasMask, const libscratchcpp::Value &mask) const { - dst.clear(); - + // https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841 if (!m_engine) - return; + return false; - const auto &targets = m_engine->targets(); + QRgb rgb = convertColor(color); + QRgb mask3b; + double ghostValue = 0; - for (auto target : targets) { - Q_ASSERT(target); + if (hasMask) { + // Ignore ghost effect when checking mask + auto it = m_graphicEffects.find(ShaderManager::Effect::Ghost); + + if (it != m_graphicEffects.cend()) { + ghostValue = it->second; + m_graphicEffects.erase(ShaderManager::Effect::Ghost); + } + + mask3b = convertColor(mask); + } + + std::vector targets; + m_engine->getVisibleTargets(targets); - if (target->isStage()) - dst.push_back(target.get()); - else { - Sprite *sprite = static_cast(target.get()); + QRectF myRect = touchingBounds(); + std::vector candidates; + QRectF bounds = candidatesBounds(myRect, targets, candidates); + + if (colorMatches(rgb, qRgb(255, 255, 255))) { + // The color we're checking for is the background color which spans the entire stage + bounds = myRect; + + if (bounds.isEmpty()) + return false; + } else if (candidates.empty()) { + // If not checking for the background color, we can return early if there are no candidate drawables + return false; + } - if (sprite->visible()) - dst.push_back(target.get()); + // Loop through the points of the union + for (int y = bounds.top(); y <= bounds.bottom(); y++) { + for (int x = bounds.left(); x <= bounds.right(); x++) { + if (hasMask ? maskMatches(colorAtScratchPoint(x, y), mask3b) : this->containsScratchPoint(x, y)) { + QRgb pixelColor = sampleColor3b(x, y, candidates); - const auto &clones = sprite->clones(); + if (colorMatches(rgb, pixelColor)) { + // Restore ghost effect value + if (hasMask && ghostValue != 0) + m_graphicEffects[ShaderManager::Effect::Ghost] = ghostValue; - for (auto clone : clones) { - if (clone->visible()) - dst.push_back(clone.get()); + return true; + } } } } - std::sort(dst.begin(), dst.end(), [](Target *t1, Target *t2) { return t1->layerOrder() > t2->layerOrder(); }); + // Restore ghost effect value + if (hasMask && ghostValue != 0) + m_graphicEffects[ShaderManager::Effect::Ghost] = ghostValue; + + return false; } QRectF RenderedTarget::touchingBounds() const @@ -1057,7 +1081,13 @@ QRgb RenderedTarget::convertColor(const libscratchcpp::Value &color) bool RenderedTarget::colorMatches(QRgb a, QRgb b) { // https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L77-L81 - return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000); + return qAlpha(a) > 0 && (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000); +} + +bool RenderedTarget::maskMatches(QRgb a, QRgb b) +{ + // https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L59-L65 + return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11111000) == (qBlue(b) & 0b11111000); } QRgb RenderedTarget::sampleColor3b(double x, double y, const std::vector &targets) const diff --git a/src/renderedtarget.h b/src/renderedtarget.h index e752e1c..bcbfae3 100644 --- a/src/renderedtarget.h +++ b/src/renderedtarget.h @@ -102,6 +102,7 @@ class RenderedTarget : public IRenderedTarget bool touchingClones(const std::vector &) const override; bool touchingColor(const libscratchcpp::Value &color) const override; + bool touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const override; signals: void engineChanged(); @@ -124,13 +125,14 @@ class RenderedTarget : public IRenderedTarget void handleSceneMouseMove(qreal x, qreal y); bool convexHullPointsNeeded() const; void updateHullPoints(); + const std::vector &transformedHullPoints() const; bool containsLocalPoint(const QPointF &point) const; QPointF transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const; QPointF transformPoint(double scratchX, double scratchY, double originX, double originY, double sinRot, double cosRot) const; QPointF mapFromStageWithOriginPoint(const QPointF &scenePoint) const; QPointF mapFromScratchToLocal(const QPointF &point) const; CpuTextureManager *textureManager() const; - void getVisibleTargets(std::vector &dst) const; + bool touchingColor(const libscratchcpp::Value &color, bool hasMask, const libscratchcpp::Value &mask) const; QRectF touchingBounds() const; QRectF candidatesBounds(const QRectF &targetRect, const std::vector &candidates, std::vector &dst) const; QRectF candidatesBounds(const QRectF &targetRect, const std::vector &candidates, std::vector &dst) const; @@ -139,6 +141,7 @@ class RenderedTarget : public IRenderedTarget static void clampRect(libscratchcpp::Rect &rect, double left, double right, double bottom, double top); static QRgb convertColor(const libscratchcpp::Value &color); static bool colorMatches(QRgb a, QRgb b); + static bool maskMatches(QRgb a, QRgb b); QRgb sampleColor3b(double x, double y, const std::vector &targets) const; libscratchcpp::IEngine *m_engine = nullptr; @@ -156,7 +159,7 @@ class RenderedTarget : public IRenderedTarget Texture m_cpuTexture; // without stage scale mutable std::shared_ptr m_textureManager; // NOTE: Use textureManager()! std::unique_ptr m_glF; - std::unordered_map m_graphicEffects; + mutable std::unordered_map m_graphicEffects; double m_size = 1; double m_x = 0; double m_y = 0; @@ -170,7 +173,9 @@ class RenderedTarget : public IRenderedTarget qreal m_maximumHeight = std::numeric_limits::infinity(); bool m_convexHullDirty = true; std::vector m_hullPoints; - bool m_clicked = false; // left mouse button only! + bool m_transformedHullDirty = true; + mutable std::vector m_transformedHullPoints; // NOTE: Use transformedHullPoints(); + bool m_clicked = false; // left mouse button only! double m_dragX = 0; double m_dragY = 0; double m_dragDeltaX = 0; diff --git a/src/shadermanager.cpp b/src/shadermanager.cpp index a87f9b7..928e32f 100644 --- a/src/shadermanager.cpp +++ b/src/shadermanager.cpp @@ -101,12 +101,10 @@ QOpenGLShaderProgram *ShaderManager::getShaderProgram(const std::unordered_mapsecond; } -void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map &effectValues) +void ShaderManager::getUniformValuesForEffects(const std::unordered_map &effectValues, std::unordered_map &dst) { - // Set the texture unit - program->setUniformValue(TEXTURE_UNIT_UNIFORM, textureUnit); + dst.clear(); - // Set the uniform values for the enabled effects and reset the other effects for (const auto &[effect, name] : EFFECT_TO_NAME) { const auto it = effectValues.find(effect); double value; @@ -117,10 +115,23 @@ void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, value = it->second; auto converter = EFFECT_CONVERTER.at(effect); - program->setUniformValue(EFFECT_UNIFORM_NAME.at(effect), converter(value)); + dst[effect] = converter(value); } } +void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map &effectValues) +{ + // Set the texture unit + program->setUniformValue(TEXTURE_UNIT_UNIFORM, textureUnit); + + // Set uniform values + std::unordered_map values; + getUniformValuesForEffects(effectValues, values); + + for (const auto &[effect, value] : values) + program->setUniformValue(EFFECT_UNIFORM_NAME.at(effect), value); +} + void ShaderManager::registerEffects() { // Register graphic effects in libscratchcpp diff --git a/src/shadermanager.h b/src/shadermanager.h index dde8245..edcb614 100644 --- a/src/shadermanager.h +++ b/src/shadermanager.h @@ -26,6 +26,7 @@ class ShaderManager : public QObject static ShaderManager *instance(); QOpenGLShaderProgram *getShaderProgram(const std::unordered_map &effectValues); + static void getUniformValuesForEffects(const std::unordered_map &effectValues, std::unordered_map &dst); void setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map &effectValues); private: diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index 70e440e..9aca0db 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "spritemodel.h" #include "renderedtarget.h" @@ -19,7 +20,35 @@ SpriteModel::SpriteModel(QObject *parent) : void SpriteModel::init(libscratchcpp::Sprite *sprite) { + if (!sprite) + return; + m_sprite = sprite; + + m_sprite->bubble()->typeChanged().connect([this](libscratchcpp::TextBubble::Type type) { + if (type == libscratchcpp::TextBubble::Type::Say) { + if (m_bubbleType == TextBubbleShape::Type::Say) + return; + + m_bubbleType = TextBubbleShape::Type::Say; + } else { + if (m_bubbleType == TextBubbleShape::Type::Think) + return; + + m_bubbleType = TextBubbleShape::Type::Think; + } + + emit bubbleTypeChanged(); + }); + + m_sprite->bubble()->textChanged().connect([this](const std::string &text) { + QString newText = QString::fromStdString(text); + + if (m_bubbleText != newText) { + m_bubbleText = newText; + emit bubbleTextChanged(); + } + }); } void SpriteModel::deinitClone() @@ -113,33 +142,6 @@ void SpriteModel::onGraphicsEffectsCleared() m_renderedTarget->clearGraphicEffects(); } -void SpriteModel::onBubbleTypeChanged(libscratchcpp::Target::BubbleType type) -{ - if (type == libscratchcpp::Target::BubbleType::Say) { - if (m_bubbleType == TextBubbleShape::Type::Say) - return; - - m_bubbleType = TextBubbleShape::Type::Say; - } else { - if (m_bubbleType == TextBubbleShape::Type::Think) - return; - - m_bubbleType = TextBubbleShape::Type::Think; - } - - emit bubbleTypeChanged(); -} - -void SpriteModel::onBubbleTextChanged(const std::string &text) -{ - QString newText = QString::fromStdString(text); - - if (m_bubbleText != newText) { - m_bubbleText = newText; - emit bubbleTextChanged(); - } -} - int SpriteModel::costumeWidth() const { return m_renderedTarget->costumeWidth(); @@ -175,6 +177,11 @@ bool SpriteModel::touchingColor(const libscratchcpp::Value &color) const return m_renderedTarget->touchingColor(color); } +bool SpriteModel::touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const +{ + return m_renderedTarget->touchingColor(color, mask); +} + libscratchcpp::Sprite *SpriteModel::sprite() const { return m_sprite; @@ -254,4 +261,9 @@ const QString &SpriteModel::bubbleText() const return m_bubbleText; } +int SpriteModel::bubbleLayer() const +{ + return m_sprite ? m_sprite->bubble()->layerOrder() : 0; +} + } // namespace scratchcpprender diff --git a/src/spritemodel.h b/src/spritemodel.h index 52e2076..e54c47c 100644 --- a/src/spritemodel.h +++ b/src/spritemodel.h @@ -28,6 +28,7 @@ class SpriteModel Q_PROPERTY(IPenLayer *penLayer READ penLayer WRITE setPenLayer NOTIFY penLayerChanged) Q_PROPERTY(TextBubbleShape::Type bubbleType READ bubbleType NOTIFY bubbleTypeChanged) Q_PROPERTY(QString bubbleText READ bubbleText NOTIFY bubbleTextChanged) + Q_PROPERTY(int bubbleLayer READ bubbleLayer NOTIFY bubbleLayerChanged) public: SpriteModel(QObject *parent = nullptr); @@ -51,9 +52,6 @@ class SpriteModel void onGraphicsEffectChanged(libscratchcpp::IGraphicsEffect *effect, double value) override; void onGraphicsEffectsCleared() override; - void onBubbleTypeChanged(libscratchcpp::Target::BubbleType type) override; - void onBubbleTextChanged(const std::string &text) override; - int costumeWidth() const override; int costumeHeight() const override; @@ -63,6 +61,7 @@ class SpriteModel bool touchingClones(const std::vector &clones) const override; bool touchingPoint(double x, double y) const override; bool touchingColor(const libscratchcpp::Value &color) const override; + bool touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const override; libscratchcpp::Sprite *sprite() const; @@ -84,6 +83,8 @@ class SpriteModel const QString &bubbleText() const; + int bubbleLayer() const; + signals: void renderedTargetChanged(); void penLayerChanged(); @@ -91,6 +92,7 @@ class SpriteModel void bubbleTextChanged(); void cloned(SpriteModel *cloneModel); void cloneDeleted(SpriteModel *clone); + void bubbleLayerChanged(); private: libscratchcpp::Sprite *m_sprite = nullptr; diff --git a/src/stagemodel.cpp b/src/stagemodel.cpp index 3290424..31f8f58 100644 --- a/src/stagemodel.cpp +++ b/src/stagemodel.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include +#include #include "stagemodel.h" #include "renderedtarget.h" @@ -15,7 +16,35 @@ StageModel::StageModel(QObject *parent) : void StageModel::init(libscratchcpp::Stage *stage) { + if (!stage) + return; + m_stage = stage; + + m_stage->bubble()->typeChanged().connect([this](libscratchcpp::TextBubble::Type type) { + if (type == libscratchcpp::TextBubble::Type::Say) { + if (m_bubbleType == TextBubbleShape::Type::Say) + return; + + m_bubbleType = TextBubbleShape::Type::Say; + } else { + if (m_bubbleType == TextBubbleShape::Type::Think) + return; + + m_bubbleType = TextBubbleShape::Type::Think; + } + + emit bubbleTypeChanged(); + }); + + m_stage->bubble()->textChanged().connect([this](const std::string &text) { + QString newText = QString::fromStdString(text); + + if (m_bubbleText != newText) { + m_bubbleText = newText; + emit bubbleTextChanged(); + } + }); } void StageModel::onCostumeChanged(libscratchcpp::Costume *costume) @@ -50,33 +79,6 @@ void StageModel::onGraphicsEffectsCleared() m_renderedTarget->clearGraphicEffects(); } -void StageModel::onBubbleTypeChanged(libscratchcpp::Target::BubbleType type) -{ - if (type == libscratchcpp::Target::BubbleType::Say) { - if (m_bubbleType == TextBubbleShape::Type::Say) - return; - - m_bubbleType = TextBubbleShape::Type::Say; - } else { - if (m_bubbleType == TextBubbleShape::Type::Think) - return; - - m_bubbleType = TextBubbleShape::Type::Think; - } - - emit bubbleTypeChanged(); -} - -void StageModel::onBubbleTextChanged(const std::string &text) -{ - QString newText = QString::fromStdString(text); - - if (m_bubbleText != newText) { - m_bubbleText = newText; - emit bubbleTextChanged(); - } -} - int StageModel::costumeWidth() const { return m_renderedTarget->costumeWidth(); @@ -112,6 +114,11 @@ bool StageModel::touchingColor(const libscratchcpp::Value &color) const return m_renderedTarget->touchingColor(color); } +bool StageModel::touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const +{ + return m_renderedTarget->touchingColor(color, mask); +} + void StageModel::loadCostume() { if (m_renderedTarget && m_stage) @@ -147,3 +154,8 @@ const QString &StageModel::bubbleText() const { return m_bubbleText; } + +int StageModel::bubbleLayer() const +{ + return m_stage ? m_stage->bubble()->layerOrder() : 0; +} diff --git a/src/stagemodel.h b/src/stagemodel.h index b5423e8..269e11c 100644 --- a/src/stagemodel.h +++ b/src/stagemodel.h @@ -22,6 +22,7 @@ class StageModel Q_PROPERTY(IRenderedTarget *renderedTarget READ renderedTarget WRITE setRenderedTarget NOTIFY renderedTargetChanged) Q_PROPERTY(TextBubbleShape::Type bubbleType READ bubbleType NOTIFY bubbleTypeChanged) Q_PROPERTY(QString bubbleText READ bubbleText NOTIFY bubbleTextChanged) + Q_PROPERTY(int bubbleLayer READ bubbleLayer NOTIFY bubbleLayerChanged) public: explicit StageModel(QObject *parent = nullptr); @@ -37,9 +38,6 @@ class StageModel void onGraphicsEffectChanged(libscratchcpp::IGraphicsEffect *effect, double value) override; void onGraphicsEffectsCleared() override; - void onBubbleTypeChanged(libscratchcpp::Target::BubbleType type) override; - void onBubbleTextChanged(const std::string &text) override; - int costumeWidth() const override; int costumeHeight() const override; @@ -49,6 +47,7 @@ class StageModel bool touchingClones(const std::vector &clones) const override; bool touchingPoint(double x, double y) const override; bool touchingColor(const libscratchcpp::Value &color) const override; + bool touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const override; Q_INVOKABLE void loadCostume(); @@ -61,10 +60,13 @@ class StageModel const QString &bubbleText() const; + int bubbleLayer() const; + signals: void renderedTargetChanged(); void bubbleTypeChanged(); void bubbleTextChanged(); + void bubbleLayerChanged(); private: libscratchcpp::Stage *m_stage = nullptr; diff --git a/src/valuemonitormodel.cpp b/src/valuemonitormodel.cpp index 1e55a5d..78d8c90 100644 --- a/src/valuemonitormodel.cpp +++ b/src/valuemonitormodel.cpp @@ -16,8 +16,8 @@ ValueMonitorModel::ValueMonitorModel(QObject *parent) : { } -ValueMonitorModel::ValueMonitorModel(IBlockSection *section, QObject *parent) : - MonitorModel(section, parent) +ValueMonitorModel::ValueMonitorModel(IExtension *extension, QObject *parent) : + MonitorModel(extension, parent) { } diff --git a/src/valuemonitormodel.h b/src/valuemonitormodel.h index 8f8b3ff..c256c74 100644 --- a/src/valuemonitormodel.h +++ b/src/valuemonitormodel.h @@ -28,7 +28,7 @@ class ValueMonitorModel : public MonitorModel Q_ENUM(Mode) ValueMonitorModel(QObject *parent = nullptr); - ValueMonitorModel(libscratchcpp::IBlockSection *section, QObject *parent = nullptr); + ValueMonitorModel(libscratchcpp::IExtension *extension, QObject *parent = nullptr); void onValueChanged(const libscratchcpp::VirtualMachine *vm) override; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dcd6bef..0159df6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -41,3 +41,4 @@ add_subdirectory(graphicseffect) add_subdirectory(shadermanager) add_subdirectory(textbubbleshape) add_subdirectory(textbubblepainter) +add_subdirectory(effecttransform) diff --git a/test/blocks/CMakeLists.txt b/test/blocks/CMakeLists.txt index 6d6151d..b3b8888 100644 --- a/test/blocks/CMakeLists.txt +++ b/test/blocks/CMakeLists.txt @@ -15,21 +15,3 @@ target_link_libraries( add_test(pen_blocks_test) gtest_discover_tests(pen_blocks_test) - -# penextension_test -add_executable( - penextension_test - penextension_test.cpp -) - -target_link_libraries( - penextension_test - GTest::gtest_main - GTest::gmock_main - scratchcpp-render - scratchcpprender_mocks - ${QT_LIBS} -) - -add_test(penextension_test) -gtest_discover_tests(penextension_test) diff --git a/test/blocks/pen_blocks_test.cpp b/test/blocks/pen_blocks_test.cpp index 52d9712..8428c82 100644 --- a/test/blocks/pen_blocks_test.cpp +++ b/test/blocks/pen_blocks_test.cpp @@ -20,7 +20,7 @@ using ::testing::Return; class PenBlocksTest : public testing::Test { public: - void SetUp() override { m_section = std::make_unique(); } + void SetUp() override { m_extension = std::make_unique(); } void addValueInput(std::shared_ptr block, const std::string &name, PenBlocks::Inputs id, const Value &value) const { @@ -77,46 +77,41 @@ class PenBlocksTest : public testing::Test return block; } - std::unique_ptr m_section; + std::unique_ptr m_extension; EngineMock m_engineMock; }; TEST_F(PenBlocksTest, Name) { - ASSERT_EQ(m_section->name(), "Pen"); -} - -TEST_F(PenBlocksTest, CategoryVisible) -{ - ASSERT_TRUE(m_section->categoryVisible()); + ASSERT_EQ(m_extension->name(), "pen"); } TEST_F(PenBlocksTest, RegisterBlocks) { // Blocks - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_clear", &PenBlocks::compileClear)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_stamp", &PenBlocks::compileStamp)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_penDown", &PenBlocks::compilePenDown)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_penUp", &PenBlocks::compilePenUp)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_setPenColorToColor", &PenBlocks::compileSetPenColorToColor)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_changePenColorParamBy", &PenBlocks::compileChangePenColorParamBy)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_setPenColorParamTo", &PenBlocks::compileSetPenColorParamTo)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_changePenSizeBy", &PenBlocks::compileChangePenSizeBy)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_setPenSizeTo", &PenBlocks::compileSetPenSizeTo)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_changePenShadeBy", &PenBlocks::compileChangePenShadeBy)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_setPenShadeToNumber", &PenBlocks::compileSetPenShadeToNumber)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_changePenHueBy", &PenBlocks::compileChangePenHueBy)); - EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "pen_setPenHueToNumber", &PenBlocks::compileSetPenHueToNumber)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_clear", &PenBlocks::compileClear)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_stamp", &PenBlocks::compileStamp)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_penDown", &PenBlocks::compilePenDown)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_penUp", &PenBlocks::compilePenUp)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_setPenColorToColor", &PenBlocks::compileSetPenColorToColor)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_changePenColorParamBy", &PenBlocks::compileChangePenColorParamBy)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_setPenColorParamTo", &PenBlocks::compileSetPenColorParamTo)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_changePenSizeBy", &PenBlocks::compileChangePenSizeBy)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_setPenSizeTo", &PenBlocks::compileSetPenSizeTo)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_changePenShadeBy", &PenBlocks::compileChangePenShadeBy)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_setPenShadeToNumber", &PenBlocks::compileSetPenShadeToNumber)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_changePenHueBy", &PenBlocks::compileChangePenHueBy)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_extension.get(), "pen_setPenHueToNumber", &PenBlocks::compileSetPenHueToNumber)); // Inputs - EXPECT_CALL(m_engineMock, addInput(m_section.get(), "COLOR", PenBlocks::COLOR)); - EXPECT_CALL(m_engineMock, addInput(m_section.get(), "COLOR_PARAM", PenBlocks::COLOR_PARAM)); - EXPECT_CALL(m_engineMock, addInput(m_section.get(), "VALUE", PenBlocks::VALUE)); - EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SIZE", PenBlocks::SIZE)); - EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SHADE", PenBlocks::SHADE)); - EXPECT_CALL(m_engineMock, addInput(m_section.get(), "HUE", PenBlocks::HUE)); - - m_section->registerBlocks(&m_engineMock); + EXPECT_CALL(m_engineMock, addInput(m_extension.get(), "COLOR", PenBlocks::COLOR)); + EXPECT_CALL(m_engineMock, addInput(m_extension.get(), "COLOR_PARAM", PenBlocks::COLOR_PARAM)); + EXPECT_CALL(m_engineMock, addInput(m_extension.get(), "VALUE", PenBlocks::VALUE)); + EXPECT_CALL(m_engineMock, addInput(m_extension.get(), "SIZE", PenBlocks::SIZE)); + EXPECT_CALL(m_engineMock, addInput(m_extension.get(), "SHADE", PenBlocks::SHADE)); + EXPECT_CALL(m_engineMock, addInput(m_extension.get(), "HUE", PenBlocks::HUE)); + + m_extension->registerBlocks(&m_engineMock); } TEST_F(PenBlocksTest, Clear) diff --git a/test/blocks/penextension_test.cpp b/test/blocks/penextension_test.cpp deleted file mode 100644 index 009a497..0000000 --- a/test/blocks/penextension_test.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include - -#include "../common.h" - -using namespace scratchcpprender; -using namespace libscratchcpp; - -using ::testing::WithArgs; -using ::testing::Invoke; -using ::testing::_; - -TEST(PenExtensionTest, Name) -{ - PenExtension ext; - ASSERT_EQ(ext.name(), "pen"); -} - -TEST(PenExtensionTest, Description) -{ - PenExtension ext; - ASSERT_EQ(ext.description(), "Pen extension"); -} - -TEST(PenExtensionTest, IncludeByDefault) -{ - PenExtension ext; - ASSERT_FALSE(ext.includeByDefault()); -} - -TEST(PenExtensionTest, RegisterSections) -{ - PenExtension ext; - EngineMock engine; - - EXPECT_CALL(engine, registerSection(_)).WillOnce(WithArgs<0>(Invoke([](std::shared_ptr section) { ASSERT_TRUE(dynamic_cast(section.get())); }))); - ext.registerSections(&engine); -} diff --git a/test/effecttransform/CMakeLists.txt b/test/effecttransform/CMakeLists.txt new file mode 100644 index 0000000..89e9206 --- /dev/null +++ b/test/effecttransform/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable( + effecttransform_test + effecttransform_test.cpp +) + +target_link_libraries( + effecttransform_test + GTest::gtest_main + scratchcpp-render + qnanopainter +) + +add_test(effecttransform_test) +gtest_discover_tests(effecttransform_test) diff --git a/test/effecttransform/effecttransform_test.cpp b/test/effecttransform/effecttransform_test.cpp new file mode 100644 index 0000000..d5f92fa --- /dev/null +++ b/test/effecttransform/effecttransform_test.cpp @@ -0,0 +1,123 @@ +#include +#include + +#include "../common.h" + +using namespace scratchcpprender; + +class EffectTransformTest : public testing::Test +{ + public: + void SetUp() override { } + + std::unordered_map m_effects; +}; + +TEST_F(EffectTransformTest, NoEffect) +{ + QRgb color = qRgba(0, 0, 0, 0); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + color = qRgba(0, 255, 255, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + color = qRgba(255, 255, 255, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + QVector2D dst; + EffectTransform::transformPoint(m_effects, QVector2D(0.5, -0.3), dst); + ASSERT_EQ(dst, QVector2D(0.5, -0.3)); +} + +TEST_F(EffectTransformTest, ColorEffect) +{ + // 100 + m_effects[ShaderManager::Effect::Color] = 100; + QRgb color = qRgba(0, 0, 0, 0); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(0, 255, 255)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 100, 100, 128)); + + // 175 + m_effects[ShaderManager::Effect::Color] = 175; + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(255, 0, 191)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(100, 128, 107, 128)); +} + +TEST_F(EffectTransformTest, BrightnessEffect) +{ + // -100 + m_effects[ShaderManager::Effect::Brightness] = -100; + QRgb color = qRgba(0, 0, 0, 0); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(0, 0, 0)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(0, 0, 0, 128)); + + // -50 + m_effects[ShaderManager::Effect::Brightness] = -50; + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(127, 0, 0)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(36, 64, 64, 128)); + + // 50 + m_effects[ShaderManager::Effect::Brightness] = 50; + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(255, 127, 127)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 128, 128, 128)); + + // 100 + m_effects[ShaderManager::Effect::Brightness] = 100; + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(255, 255, 255)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 128, 128, 128)); +} + +TEST_F(EffectTransformTest, GhostEffect) +{ + // 25 + m_effects[ShaderManager::Effect::Ghost] = 25; + QRgb color = qRgba(0, 0, 0, 0); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color); + + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(191, 0, 0, 191)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(75, 191, 150, 96)); + + // 50 + m_effects[ShaderManager::Effect::Ghost] = 50; + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 0, 0, 128)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(50, 128, 100, 64)); + + // 100 + m_effects[ShaderManager::Effect::Ghost] = 100; + color = qRgba(255, 0, 0, 255); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(0, 0, 0, 0)); + + color = qRgba(100, 255, 200, 128); + ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(0, 0, 0, 0)); +} diff --git a/test/graphicseffect/graphicseffect_test.cpp b/test/graphicseffect/graphicseffect_test.cpp index 7ea7338..bdd7238 100644 --- a/test/graphicseffect/graphicseffect_test.cpp +++ b/test/graphicseffect/graphicseffect_test.cpp @@ -27,3 +27,32 @@ TEST(GraphicsEffectTest, Constructor) ASSERT_EQ(iface->name(), "brightness"); } } + +TEST(GraphicsEffectTest, Clamp) +{ + { + GraphicsEffect effect(ShaderManager::Effect::Color, "color"); + ASSERT_EQ(effect.clamp(-500), -500); + ASSERT_EQ(effect.clamp(0), 0); + ASSERT_EQ(effect.clamp(500), 500); + } + + { + GraphicsEffect effect(ShaderManager::Effect::Brightness, "brightness"); + ASSERT_EQ(effect.clamp(-125), -100); + ASSERT_EQ(effect.clamp(-100), -100); + ASSERT_EQ(effect.clamp(0), 0); + ASSERT_EQ(effect.clamp(100), 100); + ASSERT_EQ(effect.clamp(125), 100); + } + + { + GraphicsEffect effect(ShaderManager::Effect::Ghost, "ghost"); + ASSERT_EQ(effect.clamp(-50), 0); + ASSERT_EQ(effect.clamp(0), 0); + ASSERT_EQ(effect.clamp(100), 100); + ASSERT_EQ(effect.clamp(125), 100); + } + + // TODO: Test remaining effects +} diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index 2d9aaa7..5a9714e 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -18,12 +18,12 @@ class EngineMock : public IEngine MOCK_METHOD(void, start, (), (override)); MOCK_METHOD(void, stop, (), (override)); - MOCK_METHOD(VirtualMachine *, startScript, (std::shared_ptr, Target *), (override)); - MOCK_METHOD(void, broadcast, (int), (override)); - MOCK_METHOD(void, broadcastByPtr, (Broadcast *), (override)); - MOCK_METHOD(void, startBackdropScripts, (Broadcast *), (override)); - MOCK_METHOD(void, stopScript, (VirtualMachine *), (override)); - MOCK_METHOD(void, stopTarget, (Target *, VirtualMachine *), (override)); + MOCK_METHOD(Thread *, startScript, (std::shared_ptr, Target *), (override)); + MOCK_METHOD(void, broadcast, (int, Thread *, bool), (override)); + MOCK_METHOD(void, broadcastByPtr, (Broadcast *, Thread *, bool), (override)); + MOCK_METHOD(void, startBackdropScripts, (Broadcast *, Thread *, bool), (override)); + MOCK_METHOD(void, stopScript, (Thread *), (override)); + MOCK_METHOD(void, stopTarget, (Target *, Thread *), (override)); MOCK_METHOD(void, initClone, (std::shared_ptr), (override)); MOCK_METHOD(void, deinitClone, (std::shared_ptr), (override)); @@ -38,7 +38,7 @@ class EngineMock : public IEngine MOCK_METHOD(void, stopEventLoop, (), (override)); MOCK_METHOD(sigslot::signal<> &, aboutToRender, (), (override)); - MOCK_METHOD(sigslot::signal &, threadAboutToStop, (), (override)); + MOCK_METHOD(sigslot::signal &, threadAboutToStop, (), (override)); MOCK_METHOD(sigslot::signal<> &, stopped, (), (override)); MOCK_METHOD(bool, isRunning, (), (const, override)); @@ -82,25 +82,21 @@ class EngineMock : public IEngine MOCK_METHOD(bool, spriteFencingEnabled, (), (const, override)); MOCK_METHOD(void, setSpriteFencingEnabled, (bool), (override)); - MOCK_METHOD(bool, broadcastRunning, (unsigned int), (override)); - MOCK_METHOD(bool, broadcastByPtrRunning, (Broadcast *), (override)); - MOCK_METHOD(void, requestRedraw, (), (override)); MOCK_METHOD(ITimer *, timer, (), (const, override)); - MOCK_METHOD(void, registerSection, (std::shared_ptr), (override)); MOCK_METHOD(unsigned int, functionIndex, (BlockFunc), (override)); MOCK_METHOD(const std::vector &, blockFunctions, (), (const, override)); - MOCK_METHOD(void, addCompileFunction, (IBlockSection *, const std::string &, BlockComp), (override)); - MOCK_METHOD(void, addHatPredicateCompileFunction, (IBlockSection *, const std::string &, HatPredicateCompileFunc), (override)); - MOCK_METHOD(void, addMonitorNameFunction, (IBlockSection *, const std::string &, MonitorNameFunc), (override)); - MOCK_METHOD(void, addMonitorChangeFunction, (IBlockSection *, const std::string &, MonitorChangeFunc), (override)); - MOCK_METHOD(void, addHatBlock, (IBlockSection *, const std::string &), (override)); - MOCK_METHOD(void, addInput, (IBlockSection *, const std::string &, int), (override)); - MOCK_METHOD(void, addField, (IBlockSection *, const std::string &, int), (override)); - MOCK_METHOD(void, addFieldValue, (IBlockSection *, const std::string &, int), (override)); + MOCK_METHOD(void, addCompileFunction, (IExtension *, const std::string &, BlockComp), (override)); + MOCK_METHOD(void, addHatPredicateCompileFunction, (IExtension *, const std::string &, HatPredicateCompileFunc), (override)); + MOCK_METHOD(void, addMonitorNameFunction, (IExtension *, const std::string &, MonitorNameFunc), (override)); + MOCK_METHOD(void, addMonitorChangeFunction, (IExtension *, const std::string &, MonitorChangeFunc), (override)); + MOCK_METHOD(void, addHatBlock, (IExtension *, const std::string &), (override)); + MOCK_METHOD(void, addInput, (IExtension *, const std::string &, int), (override)); + MOCK_METHOD(void, addField, (IExtension *, const std::string &, int), (override)); + MOCK_METHOD(void, addFieldValue, (IExtension *, const std::string &, int), (override)); MOCK_METHOD(const std::vector> &, broadcasts, (), (const, override)); MOCK_METHOD(void, setBroadcasts, (const std::vector> &), (override)); @@ -110,28 +106,31 @@ class EngineMock : public IEngine MOCK_METHOD(void, addWhenTouchingObjectScript, (std::shared_ptr), (override)); MOCK_METHOD(void, addGreenFlagScript, (std::shared_ptr), (override)); - MOCK_METHOD(void, addBroadcastScript, (std::shared_ptr, int, Broadcast *), (override)); - MOCK_METHOD(void, addBackdropChangeScript, (std::shared_ptr, int), (override)); + MOCK_METHOD(void, addBroadcastScript, (std::shared_ptr, Field *, Broadcast *), (override)); + MOCK_METHOD(void, addBackdropChangeScript, (std::shared_ptr, Field *), (override)); MOCK_METHOD(void, addCloneInitScript, (std::shared_ptr), (override)); - MOCK_METHOD(void, addKeyPressScript, (std::shared_ptr, int), (override)); + MOCK_METHOD(void, addKeyPressScript, (std::shared_ptr, Field *), (override)); MOCK_METHOD(void, addTargetClickScript, (std::shared_ptr), (override)); MOCK_METHOD(void, addWhenGreaterThanScript, (std::shared_ptr), (override)); MOCK_METHOD(const std::vector> &, targets, (), (const, override)); MOCK_METHOD(void, setTargets, (const std::vector> &), (override)); MOCK_METHOD(Target *, targetAt, (int), (const, override)); + MOCK_METHOD(void, getVisibleTargets, (std::vector &), (const, override)); MOCK_METHOD(int, findTarget, (const std::string &), (const, override)); - MOCK_METHOD(void, moveSpriteToFront, (Sprite * sprite), (override)); - MOCK_METHOD(void, moveSpriteToBack, (Sprite * sprite), (override)); - MOCK_METHOD(void, moveSpriteForwardLayers, (Sprite * sprite, int layers), (override)); - MOCK_METHOD(void, moveSpriteBackwardLayers, (Sprite * sprite, int layers), (override)); - MOCK_METHOD(void, moveSpriteBehindOther, (Sprite * sprite, Sprite *other), (override)); + MOCK_METHOD(void, moveDrawableToFront, (Drawable *), (override)); + MOCK_METHOD(void, moveDrawableToBack, (Drawable *), (override)); + MOCK_METHOD(void, moveDrawableForwardLayers, (Drawable *, int), (override)); + MOCK_METHOD(void, moveDrawableBackwardLayers, (Drawable *, int), (override)); + MOCK_METHOD(void, moveDrawableBehindOther, (Drawable *, Drawable *), (override)); MOCK_METHOD(Stage *, stage, (), (const, override)); MOCK_METHOD(const std::vector> &, monitors, (), (const, override)); MOCK_METHOD(void, setMonitors, (const std::vector> &), (override)); + MOCK_METHOD(Monitor *, createVariableMonitor, (std::shared_ptr, const std::string &, const std::string &, int, BlockComp), (override)); + MOCK_METHOD(Monitor *, createListMonitor, (std::shared_ptr, const std::string &, const std::string &, int, BlockComp), (override)); MOCK_METHOD(sigslot::signal &, monitorAdded, (), (override)); MOCK_METHOD((sigslot::signal &), monitorRemoved, (), (override)); diff --git a/test/mocks/blocksectionmock.h b/test/mocks/extensionmock.h similarity index 58% rename from test/mocks/blocksectionmock.h rename to test/mocks/extensionmock.h index 1963f11..9c5e9a2 100644 --- a/test/mocks/blocksectionmock.h +++ b/test/mocks/extensionmock.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include using namespace libscratchcpp; @@ -8,12 +8,14 @@ using namespace libscratchcpp; namespace scratchcpprender { -class BlockSectionMock : public IBlockSection +class ExtensionMock : public IExtension { public: MOCK_METHOD(std::string, name, (), (const, override)); - MOCK_METHOD(bool, categoryVisible, (), (const, override)); + MOCK_METHOD(std::string, description, (), (const, override)); + MOCK_METHOD(void, registerBlocks, (IEngine * engine), (override)); + MOCK_METHOD(void, onInit, (IEngine * engine), (override)); }; } // namespace scratchcpprender diff --git a/test/mocks/renderedtargetmock.h b/test/mocks/renderedtargetmock.h index a120bb6..bdb610a 100644 --- a/test/mocks/renderedtargetmock.h +++ b/test/mocks/renderedtargetmock.h @@ -78,6 +78,7 @@ class RenderedTargetMock : public IRenderedTarget MOCK_METHOD(bool, touchingClones, (const std::vector &), (const, override)); MOCK_METHOD(bool, touchingColor, (const libscratchcpp::Value &), (const, override)); + MOCK_METHOD(bool, touchingColor, (const libscratchcpp::Value &, const libscratchcpp::Value &), (const, override)); MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override)); MOCK_METHOD(void, hoverEnterEvent, (QHoverEvent *), (override)); diff --git a/test/monitor_models/listmonitorlistmodel_test.cpp b/test/monitor_models/listmonitorlistmodel_test.cpp index 91584b3..42c1539 100644 --- a/test/monitor_models/listmonitorlistmodel_test.cpp +++ b/test/monitor_models/listmonitorlistmodel_test.cpp @@ -26,8 +26,8 @@ TEST(ListMonitorListModelTest, LoadData) QSignalSpy removeSpy(&model, &ListMonitorListModel::rowsRemoved); List list1("", ""); - list1.push_back(1); - list1.push_back(2); + list1.append(1); + list1.append(2); model.setList(&list1); ASSERT_TRUE(dataChangedSpy.empty()); ASSERT_EQ(aboutToResetSpy.count(), 1); @@ -87,8 +87,8 @@ TEST(ListMonitorListModelTest, AddRows) QSignalSpy removeSpy(&model, &ListMonitorListModel::rowsRemoved); List list1("", ""); - list1.push_back(1); - list1.push_back(2); + list1.append(1); + list1.append(2); model.setList(&list1); ASSERT_TRUE(dataChangedSpy.empty()); ASSERT_EQ(aboutToResetSpy.count(), 1); @@ -98,9 +98,9 @@ TEST(ListMonitorListModelTest, AddRows) ASSERT_TRUE(aboutToRemoveSpy.empty()); ASSERT_TRUE(removeSpy.empty()); - list1.push_back(9); - list1.push_back(8); - list1.push_back(7); + list1.append(9); + list1.append(8); + list1.append(7); model.setList(&list1); ASSERT_EQ(dataChangedSpy.count(), 2); ASSERT_EQ(aboutToResetSpy.count(), 1); @@ -147,9 +147,9 @@ TEST(ListMonitorListModelTest, RemoveRows) QSignalSpy removeSpy(&model, &ListMonitorListModel::rowsRemoved); List list1("", ""); - list1.push_back(1); - list1.push_back(2); - list1.push_back(3); + list1.append(1); + list1.append(2); + list1.append(3); model.setList(&list1); ASSERT_TRUE(dataChangedSpy.empty()); ASSERT_EQ(aboutToResetSpy.count(), 1); @@ -190,9 +190,9 @@ TEST(ListMonitorListModelTest, RowCount) { ListMonitorListModel model; List list("", ""); - list.push_back(1); - list.push_back(2); - list.push_back(3); + list.append(1); + list.append(2); + list.append(3); model.setList(&list); ASSERT_EQ(model.rowCount(QModelIndex()), list.size()); } @@ -201,9 +201,9 @@ TEST(ListMonitorListModelTest, Data) { ListMonitorListModel model; List list("", ""); - list.push_back(1); - list.push_back(2); - list.push_back(3); + list.append(1); + list.append(2); + list.append(3); model.setList(&list); ASSERT_EQ(model.data(model.index(0), 0).toString(), "1"); ASSERT_EQ(model.data(model.index(1), 0).toString(), "2"); diff --git a/test/monitor_models/listmonitormodel_test.cpp b/test/monitor_models/listmonitormodel_test.cpp index 86029f6..1b00f00 100644 --- a/test/monitor_models/listmonitormodel_test.cpp +++ b/test/monitor_models/listmonitormodel_test.cpp @@ -31,19 +31,19 @@ TEST(ListMonitorModelTest, OnValueChanged) VirtualMachine vm; List list1("", ""); - list1.push_back(1); - list1.push_back(2); + list1.append(1); + list1.append(2); List list2("", ""); - list2.push_back(1); - list2.push_back(2); - list2.push_back(3); - list2.push_back(4); + list2.append(1); + list2.append(2); + list2.append(3); + list2.append(4); List list3("", ""); - list3.push_back(1); - list3.push_back(2); - list3.push_back(3); + list3.append(1); + list3.append(2); + list3.append(3); List *lists[] = { &list1, &list2, &list3 }; vm.setLists(lists); diff --git a/test/monitor_models/monitormodel_test.cpp b/test/monitor_models/monitormodel_test.cpp index 4b47e90..7d2a57c 100644 --- a/test/monitor_models/monitormodel_test.cpp +++ b/test/monitor_models/monitormodel_test.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "../common.h" @@ -71,6 +71,9 @@ TEST(MonitorModelTest, Visible) Monitor monitor("", ""); monitor.setVisible(true); model.init(&monitor); + ASSERT_FALSE(model.visible()); + + monitor.autoPosition({}); ASSERT_TRUE(model.visible()); monitor.setVisible(false); @@ -98,47 +101,47 @@ TEST(MonitorModelTest, Color) ASSERT_EQ(model.color(), Qt::green); } - BlockSectionMock section; + ExtensionMock extension; { // Invalid - EXPECT_CALL(section, name()).WillOnce(Return("")); - MonitorModel model(§ion); + EXPECT_CALL(extension, name()).WillOnce(Return("")); + MonitorModel model(&extension); ASSERT_EQ(model.color(), Qt::green); } { // Motion - EXPECT_CALL(section, name()).WillOnce(Return("Motion")); - MonitorModel model(§ion); + EXPECT_CALL(extension, name()).WillOnce(Return("Motion")); + MonitorModel model(&extension); ASSERT_EQ(model.color(), QColor::fromString("#4C97FF")); } { // Looks - EXPECT_CALL(section, name()).WillOnce(Return("Looks")); - MonitorModel model(§ion); + EXPECT_CALL(extension, name()).WillOnce(Return("Looks")); + MonitorModel model(&extension); ASSERT_EQ(model.color(), QColor::fromString("#9966FF")); } { // Sound - EXPECT_CALL(section, name()).WillOnce(Return("Sound")); - MonitorModel model(§ion); + EXPECT_CALL(extension, name()).WillOnce(Return("Sound")); + MonitorModel model(&extension); ASSERT_EQ(model.color(), QColor::fromString("#CF63CF")); } { // Variables - EXPECT_CALL(section, name()).WillOnce(Return("Variables")); - MonitorModel model(§ion); + EXPECT_CALL(extension, name()).WillOnce(Return("Variables")); + MonitorModel model(&extension); ASSERT_EQ(model.color(), QColor::fromString("#FF8C1A")); } { // Lists - EXPECT_CALL(section, name()).WillOnce(Return("Lists")); - MonitorModel model(§ion); + EXPECT_CALL(extension, name()).WillOnce(Return("Lists")); + MonitorModel model(&extension); ASSERT_EQ(model.color(), QColor::fromString("#FF661A")); } } @@ -153,6 +156,13 @@ TEST(MonitorModelTest, X) monitor.setX(-2); ASSERT_EQ(model.x(), -2); + + QSignalSpy xSpy(&model, &MonitorModel::xChanged); + QSignalSpy visibleSpy(&model, &MonitorModel::visibleChanged); + + model.onXChanged(-2); + ASSERT_EQ(xSpy.count(), 1); + ASSERT_EQ(visibleSpy.count(), 1); } TEST(MonitorModelTest, Y) @@ -165,6 +175,14 @@ TEST(MonitorModelTest, Y) monitor.setY(-8); ASSERT_EQ(model.y(), -8); + + QSignalSpy ySpy(&model, &MonitorModel::yChanged); + QSignalSpy visibleSpy(&model, &MonitorModel::visibleChanged); + + model.onYChanged(-8); + ASSERT_EQ(model.y(), -8); + ASSERT_EQ(ySpy.count(), 1); + ASSERT_EQ(visibleSpy.count(), 1); } TEST(MonitorModelTest, Width) diff --git a/test/mouseeventhandler/mouseeventhandler_test.cpp b/test/mouseeventhandler/mouseeventhandler_test.cpp index c034158..49a2ae3 100644 --- a/test/mouseeventhandler/mouseeventhandler_test.cpp +++ b/test/mouseeventhandler/mouseeventhandler_test.cpp @@ -281,20 +281,24 @@ TEST(MouseEventHandlerTest, MouseMoveEvent) TEST(MouseEventHandlerTest, MousePressReleaseEvent) { MouseEventHandler handler; - RenderedTargetMock stage, renderedTarget1, renderedTarget2, renderedTarget3; - SpriteModel model1, model2, model3; + RenderedTargetMock stage, renderedTarget1, renderedTarget2, renderedTarget3, renderedTarget4; + SpriteModel model1, model2, model3, model4; model1.setRenderedTarget(&renderedTarget1); model2.setRenderedTarget(&renderedTarget2); model3.setRenderedTarget(&renderedTarget3); - Sprite sprite1, sprite2, sprite3; + model4.setRenderedTarget(&renderedTarget4); + Sprite sprite1, sprite2, sprite3, sprite4; sprite1.setLayerOrder(2); sprite2.setLayerOrder(1); sprite3.setLayerOrder(3); + sprite4.setLayerOrder(4); + sprite4.setVisible(false); ProjectLoader loader; auto sprites = loader.sprites(); sprites.append(&sprites, &model1); sprites.append(&sprites, &model2); sprites.append(&sprites, &model3); + sprites.append(&sprites, &model4); handler.setStage(&stage); handler.setProjectLoader(&loader); QPointingDevice dev; @@ -307,6 +311,7 @@ TEST(MouseEventHandlerTest, MousePressReleaseEvent) EXPECT_CALL(renderedTarget1, scratchTarget()).WillRepeatedly(Return(&sprite1)); EXPECT_CALL(renderedTarget2, scratchTarget()).WillRepeatedly(Return(&sprite2)); EXPECT_CALL(renderedTarget3, scratchTarget()).WillRepeatedly(Return(&sprite3)); + EXPECT_CALL(renderedTarget4, scratchTarget()).WillRepeatedly(Return(&sprite4)); emit loader.spritesChanged(); EXPECT_CALL(renderedTarget1, mapFromScene(scenePos)).WillRepeatedly(Return(localPos)); @@ -387,6 +392,7 @@ TEST(MouseEventHandlerTest, MousePressReleaseEvent) EXPECT_CALL(renderedTarget3, contains(localPos)).WillOnce(Return(false)); EXPECT_CALL(renderedTarget1, contains(localPos)).WillOnce(Return(false)); EXPECT_CALL(renderedTarget2, contains(localPos)).WillOnce(Return(false)); + EXPECT_CALL(renderedTarget4, contains).Times(0); EXPECT_CALL(stage, mousePressEvent(_)).WillOnce(WithArgs<0>(Invoke(checkPressEvent))); ASSERT_TRUE(handler.eventFilter(nullptr, &pressEvent)); ASSERT_EQ(pressedSpy.count(), 1); diff --git a/test/projectloader/projectloader_test.cpp b/test/projectloader/projectloader_test.cpp index ea0350e..5684efa 100644 --- a/test/projectloader/projectloader_test.cpp +++ b/test/projectloader/projectloader_test.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -32,6 +32,7 @@ class ProjectLoaderTest : public testing::Test QSignalSpy clonesSpy(loader, &ProjectLoader::clonesChanged); QSignalSpy monitorsSpy(loader, &ProjectLoader::monitorsChanged); QSignalSpy monitorAddedSpy(loader, &ProjectLoader::monitorAdded); + QSignalSpy unsupportedBlocksSpy(loader, &ProjectLoader::unsupportedBlocksChanged); loader->setFileName(fileName); @@ -56,10 +57,11 @@ class ProjectLoaderTest : public testing::Test ASSERT_EQ(loadingFinishedSpy.count(), 1); ASSERT_EQ(engineSpy.count(), 2); ASSERT_EQ(stageSpy.count(), 1); - ASSERT_EQ(spritesSpy.count(), 2); - ASSERT_EQ(clonesSpy.count(), 1); + ASSERT_EQ(spritesSpy.count(), 3); + ASSERT_EQ(clonesSpy.count(), 3); ASSERT_EQ(monitorsSpy.count(), loader->monitorList().size() + 1); ASSERT_EQ(monitorAddedSpy.count(), loader->monitorList().size()); + ASSERT_EQ(unsupportedBlocksSpy.count(), 1); } }; @@ -75,7 +77,7 @@ TEST_F(ProjectLoaderTest, Constructors) ASSERT_EQ(loader2.parent(), &loader1); // Pen extension should be registered - ASSERT_TRUE(dynamic_cast(ScratchConfiguration::getExtension("pen"))); + ASSERT_TRUE(dynamic_cast(ScratchConfiguration::getExtension("pen"))); } TEST_F(ProjectLoaderTest, Load) @@ -96,7 +98,7 @@ TEST_F(ProjectLoaderTest, Load) ASSERT_EQ(sprites[1]->sprite(), engine->targetAt(2)); const auto &monitors = loader.monitorList(); - ASSERT_EQ(monitors.size(), 11); + ASSERT_EQ(monitors.size(), 10); ListMonitorModel *listMonitorModel = dynamic_cast(monitors[0]); ASSERT_EQ(listMonitorModel->monitor(), engine->monitors().at(0).get()); @@ -107,6 +109,25 @@ TEST_F(ProjectLoaderTest, Load) ASSERT_EQ(valueMonitorModel->color(), QColor::fromString("#FF8C1A")); } +TEST_F(ProjectLoaderTest, UnsupportedBlocks) +{ + ProjectLoader loader; + ASSERT_TRUE(loader.fileName().isEmpty()); + ASSERT_FALSE(loader.loadStatus()); + ASSERT_TRUE(loader.stage()); + + loader.setFileName("unsupported_blocks.sb3"); + loader.start(); // wait until it loads + + auto engine = loader.engine(); + const auto &blocks = loader.unsupportedBlocks(); + const auto &refBlocks = engine->unsupportedBlocks(); + ASSERT_EQ(blocks.size(), refBlocks.size()); + + for (const QString &opcode : blocks) + ASSERT_NE(refBlocks.find(opcode.toStdString()), refBlocks.cend()); +} + TEST_F(ProjectLoaderTest, Clones) { ProjectLoader loader; @@ -116,13 +137,13 @@ TEST_F(ProjectLoaderTest, Clones) load(&loader, "clones.sb3"); ASSERT_TRUE(cloneCreatedSpy.empty()); ASSERT_TRUE(cloneDeletedSpy.empty()); - ASSERT_EQ(clonesChangedSpy.count(), 1); + ASSERT_EQ(clonesChangedSpy.count(), 3); auto engine = loader.engine(); engine->run(); ASSERT_EQ(cloneCreatedSpy.count(), 3); ASSERT_EQ(cloneDeletedSpy.count(), 0); - ASSERT_EQ(clonesChangedSpy.count(), 4); + ASSERT_EQ(clonesChangedSpy.count(), 6); const auto &sprites = loader.spriteList(); const auto &clones = loader.cloneList(); @@ -141,7 +162,7 @@ TEST_F(ProjectLoaderTest, Clones) clones[1]->sprite()->deleteClone(); ASSERT_EQ(cloneCreatedSpy.count(), 3); ASSERT_EQ(cloneDeletedSpy.count(), 1); - ASSERT_EQ(clonesChangedSpy.count(), 5); + ASSERT_EQ(clonesChangedSpy.count(), 7); ASSERT_EQ(clones.size(), 2); } @@ -163,12 +184,35 @@ TEST_F(ProjectLoaderTest, StartStop) TEST_F(ProjectLoaderTest, TimerEvent) { ProjectLoader loader; + ASSERT_FALSE(loader.running()); EngineMock engine; loader.setEngine(&engine); QTimerEvent event(0); + QSignalSpy runningSpy(&loader, &ProjectLoader::runningChanged); + EXPECT_CALL(engine, step()); + EXPECT_CALL(engine, isRunning()).WillOnce(Return(false)); + QCoreApplication::sendEvent(&loader, &event); + ASSERT_FALSE(loader.running()); + ASSERT_TRUE(runningSpy.empty()); + + EXPECT_CALL(engine, step()); + EXPECT_CALL(engine, isRunning()).WillOnce(Return(true)); + QCoreApplication::sendEvent(&loader, &event); + ASSERT_TRUE(loader.running()); + ASSERT_EQ(runningSpy.size(), 1); + + EXPECT_CALL(engine, step()); + EXPECT_CALL(engine, isRunning()).WillOnce(Return(true)); + QCoreApplication::sendEvent(&loader, &event); + ASSERT_TRUE(loader.running()); + ASSERT_EQ(runningSpy.size(), 1); + EXPECT_CALL(engine, step()); + EXPECT_CALL(engine, isRunning()).WillOnce(Return(false)); QCoreApplication::sendEvent(&loader, &event); + ASSERT_FALSE(loader.running()); + ASSERT_EQ(runningSpy.size(), 2); } TEST_F(ProjectLoaderTest, QuestionAsked) diff --git a/test/renderedtarget/renderedtarget_test.cpp b/test/renderedtarget/renderedtarget_test.cpp index 850600e..8a9e6a5 100644 --- a/test/renderedtarget/renderedtarget_test.cpp +++ b/test/renderedtarget/renderedtarget_test.cpp @@ -23,6 +23,8 @@ using namespace libscratchcpp; using ::testing::Return; using ::testing::ReturnRef; +using ::testing::Invoke; +using ::testing::_; class RenderedTargetTest : public testing::Test { @@ -457,6 +459,15 @@ TEST_F(RenderedTargetTest, CpuRendering) ASSERT_EQ(target.colorAtScratchPoint(-225, 162), 4286611456); // [3, 3] ASSERT_EQ(target.colorAtScratchPoint(-224.7, 161.5), 4286611456); // [3.3, 3.5] + target.setGraphicEffect(ShaderManager::Effect::Color, 50); + ASSERT_EQ(target.colorAtScratchPoint(-227, 162), 4286595072); // [1, 3] + ASSERT_EQ(target.colorAtScratchPoint(-226, 162), 4294934720); // [2, 3] + ASSERT_EQ(target.colorAtScratchPoint(-225, 162), 4278222912); // [3, 3] + ASSERT_EQ(target.colorAtScratchPoint(-224.7, 161.5), 4278222912); // [3.3, 3.5] + target.setGraphicEffect(ShaderManager::Effect::Color, 0); + + // TODO: Test point transform (graphic effects that change shape) + // Cleanup context.doneCurrent(); } @@ -557,7 +568,6 @@ TEST_F(RenderedTargetTest, SpriteDragging) QCoreApplication::sendEvent(&target, &pressEvent); EXPECT_CALL(engine, mouseX()).WillOnce(Return(67.95)); EXPECT_CALL(engine, mouseY()).WillOnce(Return(2.1)); - EXPECT_CALL(engine, moveSpriteToFront(&sprite)); QCoreApplication::sendEvent(&target, &moveEvent); ASSERT_EQ(sprite.x(), 64.08); ASSERT_EQ(sprite.y(), -6.86); @@ -1079,7 +1089,7 @@ TEST_F(RenderedTargetTest, TouchingColor) EXPECT_CALL(engine, cloneLimit()).WillOnce(Return(-1)); EXPECT_CALL(engine, initClone); EXPECT_CALL(engine, requestRedraw); - EXPECT_CALL(engine, moveSpriteBehindOther); + EXPECT_CALL(engine, moveDrawableBehindOther); auto sprite2 = sprite1->clone(); StageModel stageModel; SpriteModel model, model1, model2; @@ -1093,6 +1103,31 @@ TEST_F(RenderedTargetTest, TouchingColor) sprite2->setLayerOrder(3); const std::vector> targets = { stage, sprite, sprite1 }; + EXPECT_CALL(engine, getVisibleTargets(_)).WillRepeatedly(Invoke([&targets](std::vector &dst) { + dst.clear(); + + for (auto target : targets) { + ASSERT_TRUE(target); + + if (target->isStage()) + dst.push_back(target.get()); + else { + Sprite *sprite = static_cast(target.get()); + + if (sprite->visible()) + dst.push_back(target.get()); + + const auto &clones = sprite->clones(); + + for (auto clone : clones) { + if (clone->visible()) + dst.push_back(clone.get()); + } + } + } + + std::sort(dst.begin(), dst.end(), [](Target *t1, Target *t2) { return t1->layerOrder() > t2->layerOrder(); }); + })); QQuickItem parent; parent.setWidth(480); @@ -1128,7 +1163,6 @@ TEST_F(RenderedTargetTest, TouchingColor) target.beforeRedraw(); Rect penBounds(5, 1, 6, -5); - EXPECT_CALL(engine, targets()).WillRepeatedly(ReturnRef(targets)); EXPECT_CALL(stageTarget, stageModel()).WillRepeatedly(Return(&stageModel)); EXPECT_CALL(target1, stageModel()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(target2, stageModel()).WillRepeatedly(Return(nullptr)); @@ -1222,6 +1256,33 @@ TEST_F(RenderedTargetTest, TouchingColor) EXPECT_CALL(stageTarget, colorAtScratchPoint).Times(0); ASSERT_FALSE(target.touchingColor(color3)); + // Mask (color is touching color) + EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5))); + EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5))); + EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8))); + EXPECT_CALL(target2, colorAtScratchPoint(3, -3)).WillOnce(Return(color4.toInt())); + EXPECT_CALL(target1, colorAtScratchPoint(3, -3)).WillOnce(Return(color1.toInt())); + ASSERT_TRUE(target.touchingColor(color5, color3)); + + EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(5, 1, 6, -5))); + EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(5, 1, 6, -5))); + EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8))); + EXPECT_CALL(target2, colorAtScratchPoint).Times(0); + EXPECT_CALL(target1, colorAtScratchPoint).Times(0); + EXPECT_CALL(penLayer, colorAtScratchPoint).Times(0); + EXPECT_CALL(stageTarget, colorAtScratchPoint).Times(0); + ASSERT_FALSE(target.touchingColor(color3, color3)); + + // Ghost effect shouldn't affect mask check + target.setGraphicEffect(ShaderManager::Effect::Ghost, 100); + EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5))); + EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5))); + EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8))); + EXPECT_CALL(target2, colorAtScratchPoint(3, -3)).WillOnce(Return(color4.toInt())); + EXPECT_CALL(target1, colorAtScratchPoint(3, -3)).WillOnce(Return(color1.toInt())); + ASSERT_TRUE(target.touchingColor(color5, color3)); + ASSERT_EQ(target.graphicEffects().at(ShaderManager::Effect::Ghost), 100); + // Out of bounds: top left target.updateX(-300); target.updateY(200); diff --git a/test/shadermanager/shadermanager_test.cpp b/test/shadermanager/shadermanager_test.cpp index ad6a68d..6bbe0a2 100644 --- a/test/shadermanager/shadermanager_test.cpp +++ b/test/shadermanager/shadermanager_test.cpp @@ -179,6 +179,8 @@ TEST_F(ShaderManagerTest, ColorEffectValue) static const QString uniformName = "u_" + effectName; static const ShaderManager::Effect effect = ShaderManager::Effect::Color; + std::unordered_map values; + QOpenGLFunctions glF(&m_context); glF.initializeOpenGLFunctions(); ShaderManager manager; @@ -190,28 +192,34 @@ TEST_F(ShaderManagerTest, ColorEffectValue) std::unordered_map effects = { { effect, 64.9 } }; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); GLfloat value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(value, 0.3245f); + ASSERT_EQ(values.at(effect), value); // Below the minimum effects[effect] = -395.7; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(std::round(value * 100.0f) / 100.0f, 0.02f); + ASSERT_EQ(values.at(effect), value); // Above the maximum effects[effect] = 579.05; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(std::round(value * 100.0f) / 100.0f, 0.9f); + ASSERT_EQ(values.at(effect), value); program.release(); } @@ -222,6 +230,8 @@ TEST_F(ShaderManagerTest, BrightnessEffectValue) static const QString uniformName = "u_" + effectName; static const ShaderManager::Effect effect = ShaderManager::Effect::Brightness; + std::unordered_map values; + QOpenGLFunctions glF(&m_context); glF.initializeOpenGLFunctions(); ShaderManager manager; @@ -233,28 +243,34 @@ TEST_F(ShaderManagerTest, BrightnessEffectValue) std::unordered_map effects = { { effect, 4.6 } }; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); GLfloat value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(value, 0.046f); + ASSERT_EQ(values.at(effect), value); // Below the minimum effects[effect] = -102.9; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(value, -1.0f); + ASSERT_EQ(values.at(effect), value); // Above the maximum effects[effect] = 353.2; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(value, 1.0f); + ASSERT_EQ(values.at(effect), value); program.release(); } @@ -265,6 +281,8 @@ TEST_F(ShaderManagerTest, GhostEffectValue) static const QString uniformName = "u_" + effectName; static const ShaderManager::Effect effect = ShaderManager::Effect::Ghost; + std::unordered_map values; + QOpenGLFunctions glF(&m_context); glF.initializeOpenGLFunctions(); ShaderManager manager; @@ -276,28 +294,34 @@ TEST_F(ShaderManagerTest, GhostEffectValue) std::unordered_map effects = { { effect, 58.5 } }; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); GLfloat value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(std::round(value * 1000.0f) / 1000.0f, 0.415f); + ASSERT_EQ(values.at(effect), value); // Below the minimum effects[effect] = -20.8; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(value, 1.0f); + ASSERT_EQ(values.at(effect), value); // Above the maximum effects[effect] = 248.2; program.bind(); manager.setUniforms(&program, 0, effects); + manager.getUniformValuesForEffects(effects, values); value = 0.0f; glF.glGetUniformfv(program.programId(), program.uniformLocation(uniformName), &value); ASSERT_EQ(value, 0.0f); + ASSERT_EQ(values.at(effect), value); program.release(); } diff --git a/test/target_models/spritemodel_test.cpp b/test/target_models/spritemodel_test.cpp index 5b406e0..3b1a776 100644 --- a/test/target_models/spritemodel_test.cpp +++ b/test/target_models/spritemodel_test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -248,22 +249,24 @@ TEST(SpriteModelTest, OnGraphicsEffectsCleared) TEST(SpriteModelTest, OnBubbleTypeChanged) { SpriteModel model; + Sprite sprite; + model.init(&sprite); QSignalSpy spy(&model, &SpriteModel::bubbleTypeChanged); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Say); - model.onBubbleTypeChanged(Target::BubbleType::Think); + sprite.bubble()->setType(TextBubble::Type::Think); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Think); ASSERT_EQ(spy.count(), 1); - model.onBubbleTypeChanged(Target::BubbleType::Think); + sprite.bubble()->setType(TextBubble::Type::Think); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Think); ASSERT_EQ(spy.count(), 1); - model.onBubbleTypeChanged(Target::BubbleType::Say); + sprite.bubble()->setType(TextBubble::Type::Say); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Say); ASSERT_EQ(spy.count(), 2); - model.onBubbleTypeChanged(Target::BubbleType::Say); + sprite.bubble()->setType(TextBubble::Type::Say); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Say); ASSERT_EQ(spy.count(), 2); } @@ -271,18 +274,20 @@ TEST(SpriteModelTest, OnBubbleTypeChanged) TEST(SpriteModelTest, OnBubbleTextChanged) { SpriteModel model; + Sprite sprite; + model.init(&sprite); QSignalSpy spy(&model, &SpriteModel::bubbleTextChanged); ASSERT_TRUE(model.bubbleText().isEmpty()); - model.onBubbleTextChanged("Hello!"); + sprite.bubble()->setText("Hello!"); ASSERT_EQ(model.bubbleText(), "Hello!"); ASSERT_EQ(spy.count(), 1); - model.onBubbleTextChanged("Hello!"); + sprite.bubble()->setText("Hello!"); ASSERT_EQ(model.bubbleText(), "Hello!"); ASSERT_EQ(spy.count(), 1); - model.onBubbleTextChanged("test"); + sprite.bubble()->setText("test"); ASSERT_EQ(model.bubbleText(), "test"); ASSERT_EQ(spy.count(), 2); } @@ -388,12 +393,18 @@ TEST(SpriteModelTest, TouchingColor) RenderedTargetMock renderedTarget; model.setRenderedTarget(&renderedTarget); - Value color = 123; - EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(false)); - ASSERT_FALSE(model.touchingColor(color)); + Value color1 = 123, color2 = 456; + EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(false)); + ASSERT_FALSE(model.touchingColor(color1)); - EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(true)); - ASSERT_TRUE(model.touchingColor(color)); + EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(true)); + ASSERT_TRUE(model.touchingColor(color1)); + + EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(false)); + ASSERT_FALSE(model.touchingColor(color1, color2)); + + EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(true)); + ASSERT_TRUE(model.touchingColor(color1, color2)); } TEST(SpriteModelTest, RenderedTarget) @@ -455,3 +466,15 @@ TEST(SpriteModelTest, PenDown) ASSERT_FALSE(model.penDown()); ASSERT_FALSE(model.penState().penDown); } + +TEST(SpriteModelTest, BubbleLayer) +{ + SpriteModel model; + Sprite sprite; + model.init(&sprite); + QSignalSpy spy(&model, &SpriteModel::bubbleLayerChanged); + + sprite.bubble()->setLayerOrder(5); + ASSERT_EQ(model.bubbleLayer(), 5); + // TODO: Use spy here +} diff --git a/test/target_models/stagemodel_test.cpp b/test/target_models/stagemodel_test.cpp index 672916d..629f758 100644 --- a/test/target_models/stagemodel_test.cpp +++ b/test/target_models/stagemodel_test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -70,44 +71,48 @@ TEST(StageModelTest, OnGraphicsEffectsCleared) model.onGraphicsEffectsCleared(); } -TEST(StageModelTest, OnBubbleTypeChanged) +TEST(StageModelTest, BubbleTypeChange) { StageModel model; + Stage stage; + model.init(&stage); QSignalSpy spy(&model, &StageModel::bubbleTypeChanged); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Say); - model.onBubbleTypeChanged(Target::BubbleType::Think); + stage.bubble()->setType(TextBubble::Type::Think); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Think); ASSERT_EQ(spy.count(), 1); - model.onBubbleTypeChanged(Target::BubbleType::Think); + stage.bubble()->setType(TextBubble::Type::Think); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Think); ASSERT_EQ(spy.count(), 1); - model.onBubbleTypeChanged(Target::BubbleType::Say); + stage.bubble()->setType(TextBubble::Type::Say); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Say); ASSERT_EQ(spy.count(), 2); - model.onBubbleTypeChanged(Target::BubbleType::Say); + stage.bubble()->setType(TextBubble::Type::Say); ASSERT_EQ(model.bubbleType(), TextBubbleShape::Type::Say); ASSERT_EQ(spy.count(), 2); } -TEST(StageModelTest, OnBubbleTextChanged) +TEST(StageModelTest, BubbleTextChange) { StageModel model; + Stage stage; + model.init(&stage); QSignalSpy spy(&model, &StageModel::bubbleTextChanged); ASSERT_TRUE(model.bubbleText().isEmpty()); - model.onBubbleTextChanged("Hello!"); + stage.bubble()->setText("Hello!"); ASSERT_EQ(model.bubbleText(), "Hello!"); ASSERT_EQ(spy.count(), 1); - model.onBubbleTextChanged("Hello!"); + stage.bubble()->setText("Hello!"); ASSERT_EQ(model.bubbleText(), "Hello!"); ASSERT_EQ(spy.count(), 1); - model.onBubbleTextChanged("test"); + stage.bubble()->setText("test"); ASSERT_EQ(model.bubbleText(), "test"); ASSERT_EQ(spy.count(), 2); } @@ -181,12 +186,18 @@ TEST(StageModelTest, TouchingColor) RenderedTargetMock renderedTarget; model.setRenderedTarget(&renderedTarget); - Value color = 123; - EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(false)); - ASSERT_FALSE(model.touchingColor(color)); + Value color1 = 123, color2 = 456; + EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(false)); + ASSERT_FALSE(model.touchingColor(color1)); - EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(true)); - ASSERT_TRUE(model.touchingColor(color)); + EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(true)); + ASSERT_TRUE(model.touchingColor(color1)); + + EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(false)); + ASSERT_FALSE(model.touchingColor(color1, color2)); + + EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(true)); + ASSERT_TRUE(model.touchingColor(color1, color2)); } TEST(StageModelTest, RenderedTarget) @@ -214,3 +225,15 @@ TEST(StageModelTest, RenderedTarget) stage.setCostumeIndex(2); model.loadCostume(); } + +TEST(StageModelTest, BubbleLayer) +{ + StageModel model; + Stage stage; + model.init(&stage); + QSignalSpy spy(&model, &StageModel::bubbleLayerChanged); + + stage.bubble()->setLayerOrder(5); + ASSERT_EQ(model.bubbleLayer(), 5); + // TODO: Use spy here +} diff --git a/test/texture/cputexturemanager_test.cpp b/test/texture/cputexturemanager_test.cpp index 2d0ec05..e2634fa 100644 --- a/test/texture/cputexturemanager_test.cpp +++ b/test/texture/cputexturemanager_test.cpp @@ -129,6 +129,49 @@ TEST_F(CpuTextureManagerTest, TextureDataAndHullPoints) context.doneCurrent(); } +TEST_F(CpuTextureManagerTest, GetPointColor) +{ + // Create OpenGL context + QOpenGLContext context; + QOffscreenSurface surface; + createContextAndSurface(&context, &surface); + + // Paint + QNanoPainter painter; + ImagePainter imgPainter(&painter, "image.png"); + + // Read texture data + Texture texture(imgPainter.fbo()->texture(), imgPainter.fbo()->size()); + + // Test + CpuTextureManager manager; + ASSERT_EQ(manager.getPointColor(texture, 0, 0, {}), qRgba(0, 0, 0, 0)); + ASSERT_EQ(manager.getPointColor(texture, 1, 0, {}), qRgba(0, 0, 0, 0)); + ASSERT_EQ(manager.getPointColor(texture, 2, 0, {}), qRgba(0, 0, 0, 0)); + ASSERT_EQ(manager.getPointColor(texture, 3, 0, {}), qRgba(0, 0, 0, 0)); + + ASSERT_FALSE(manager.textureContainsPoint(texture, { 0, 1 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 1 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 1.4, 1.25 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 2, 1 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 1 }, {})); + + ASSERT_EQ(manager.getPointColor(texture, 0, 1, {}), qRgba(0, 0, 0, 0)); + ASSERT_EQ(manager.getPointColor(texture, 1, 1, {}), qRgb(0, 0, 255)); + ASSERT_EQ(manager.getPointColor(texture, 2, 1, {}), qRgb(255, 0, 255)); + ASSERT_EQ(manager.getPointColor(texture, 3, 1, {}), qRgb(255, 128, 128)); + + std::unordered_map effects = { { ShaderManager::Effect::Color, 50 } }; + ASSERT_EQ(manager.getPointColor(texture, 1, 1, effects), qRgb(255, 0, 128)); + ASSERT_EQ(manager.getPointColor(texture, 2, 1, effects), qRgb(255, 128, 0)); + ASSERT_EQ(manager.getPointColor(texture, 3, 1, effects), qRgb(192, 255, 128)); + + // TODO: Test point transform (graphic effects that change shape) + + // Cleanup + context.doneCurrent(); +} + TEST_F(CpuTextureManagerTest, TextureContainsPoint) { // Create OpenGL context @@ -145,26 +188,28 @@ TEST_F(CpuTextureManagerTest, TextureContainsPoint) // Test CpuTextureManager manager; - ASSERT_FALSE(manager.textureContainsPoint(texture, { 0, 0 })); - ASSERT_FALSE(manager.textureContainsPoint(texture, { 1, 0 })); - ASSERT_FALSE(manager.textureContainsPoint(texture, { 2, 0 })); - ASSERT_FALSE(manager.textureContainsPoint(texture, { 3, 0 })); - - ASSERT_FALSE(manager.textureContainsPoint(texture, { 0, 1 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 1 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 1.4, 1.25 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 2, 1 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 1 })); - - ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 2 })); - ASSERT_FALSE(manager.textureContainsPoint(texture, { 2, 2 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 2 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 3.5, 2.1 })); - - ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 3 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 2, 3 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 3 })); - ASSERT_TRUE(manager.textureContainsPoint(texture, { 3.3, 3.5 })); + ASSERT_FALSE(manager.textureContainsPoint(texture, { 0, 0 }, {})); + ASSERT_FALSE(manager.textureContainsPoint(texture, { 1, 0 }, {})); + ASSERT_FALSE(manager.textureContainsPoint(texture, { 2, 0 }, {})); + ASSERT_FALSE(manager.textureContainsPoint(texture, { 3, 0 }, {})); + + ASSERT_FALSE(manager.textureContainsPoint(texture, { 0, 1 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 1 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 1.4, 1.25 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 2, 1 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 1 }, {})); + + ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 2 }, {})); + ASSERT_FALSE(manager.textureContainsPoint(texture, { 2, 2 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 2 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 3.5, 2.1 }, {})); + + ASSERT_TRUE(manager.textureContainsPoint(texture, { 1, 3 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 2, 3 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 3, 3 }, {})); + ASSERT_TRUE(manager.textureContainsPoint(texture, { 3.3, 3.5 }, {})); + + // TODO: Test point transform (graphic effects that change shape) // Cleanup context.doneCurrent(); diff --git a/test/unsupported_blocks.sb3 b/test/unsupported_blocks.sb3 new file mode 100644 index 0000000..7098a65 Binary files /dev/null and b/test/unsupported_blocks.sb3 differ