diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 067a232..c228c0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,14 @@ env: jobs: build: + strategy: + matrix: + include: + - qt_version: '6.6' + qt_arch: 'gcc_64' + - qt_version: '6.7' + qt_arch: 'linux_gcc_64' + runs-on: ubuntu-latest steps: @@ -24,9 +32,10 @@ jobs: sudo apt-get install -y nlohmann-json3-dev libutfcpp-dev libgd-dev xvfb libxcb-cursor0 shell: bash - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '6.6.*' + version: '${{ matrix.qt_version }}' + arch: '${{ matrix.qt_arch }}' - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} diff --git a/.github/workflows/utests.yml b/.github/workflows/utests.yml index b4db17f..bc84c7b 100644 --- a/.github/workflows/utests.yml +++ b/.github/workflows/utests.yml @@ -11,6 +11,14 @@ env: jobs: build: + strategy: + matrix: + include: + - qt_version: '6.6' + qt_arch: 'gcc_64' + - qt_version: '6.7' + qt_arch: 'linux_gcc_64' + runs-on: ubuntu-latest steps: @@ -24,9 +32,10 @@ jobs: sudo apt-get install -y nlohmann-json3-dev libutfcpp-dev libgd-dev xvfb libxcb-cursor0 shell: bash - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: - version: '6.6.*' + version: '${{ matrix.qt_version }}' + arch: '${{ matrix.qt_arch }}' - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DSCRATCHCPPRENDER_BUILD_UNIT_TESTS=ON diff --git a/CMakeLists.txt b/CMakeLists.txt index f61624d..5d4e5aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(scratchcpp-render VERSION 0.5.0 LANGUAGES CXX) +project(scratchcpp-render VERSION 0.6.0 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) diff --git a/README.md b/README.md index 7855bf7..670e6b9 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ int main(int argc, char **argv) { - [x] Sprite dragging - [x] Touching sprite block - [ ] Touching color blocks (color is touching color is not implemented yet) -- [ ] Pen blocks (all blocks except the stamp block are implemented) +- [x] Pen blocks - [x] Monitors - [ ] Graphics effects (color, brightness and ghost are implemented) - [x] Speech and thought bubbles diff --git a/libscratchcpp b/libscratchcpp index a16fa2b..363bec1 160000 --- a/libscratchcpp +++ b/libscratchcpp @@ -1 +1 @@ -Subproject commit a16fa2b933ffb5acf0ea9714d8db36d5fd8b2a08 +Subproject commit 363bec1b8658a25cb558021f09907bdf3b69f80f diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 35cd450..2355484 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -14,7 +14,9 @@ ProjectScene { property alias fps: loader.fps property alias turboMode: loader.turboMode property alias cloneLimit: loader.cloneLimit - property alias spriteFencing: loader.spriteFencing + property alias spriteFencing: loader.spriteFencing + property alias mute: loader.mute + property alias hqPen: projectPenLayer.hqPen property bool showLoadingProgress: true readonly property bool loading: priv.loading readonly property int downloadedAssets: loader.downloadedAssets @@ -27,6 +29,8 @@ ProjectScene { 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) { loader.fileName = fileName; @@ -35,6 +39,7 @@ ProjectScene { QtObject { id: priv property bool loading: false + property bool loaded: false } ProjectLoader { @@ -91,6 +96,8 @@ ProjectScene { questionLoader.item.clear(); questionLoader.item.question = question; } + + onQuestionAborted: questionLoader.active = false } function start() { @@ -107,7 +114,7 @@ ProjectScene { anchors.top: parent.top width: stageWidth * stageScale height: stageHeight * stageScale - color: priv.loading ? "transparent" : "white" + color: priv.loading || !priv.loaded ? "transparent" : "white" clip: true RenderedTarget { @@ -138,9 +145,9 @@ ProjectScene { engine: loader.engine anchors.top: parent.top anchors.left: parent.left - width: stageWidth - height: stageHeight - scale: stageScale + width: hqPen ? parent.width : stageWidth + height: hqPen ? parent.height : stageHeight + scale: hqPen ? 1 : stageScale transformOrigin: Item.TopLeft visible: !priv.loading } diff --git a/src/bitmapskin.cpp b/src/bitmapskin.cpp index d8b1c98..e499f8a 100644 --- a/src/bitmapskin.cpp +++ b/src/bitmapskin.cpp @@ -28,7 +28,7 @@ BitmapSkin::BitmapSkin(libscratchcpp::Costume *costume) : m_image.load(&buffer, format); // Paint the image into a texture - m_texture = createAndPaintTexture(m_image.width(), m_image.height(), false); + m_texture = createAndPaintTexture(m_image.width(), m_image.height()); m_textureSize.setWidth(m_image.width()); m_textureSize.setHeight(m_image.height()); diff --git a/src/blocks/penblocks.cpp b/src/blocks/penblocks.cpp index 88c38c4..5c3e1cd 100644 --- a/src/blocks/penblocks.cpp +++ b/src/blocks/penblocks.cpp @@ -2,12 +2,14 @@ #include #include +#include #include #include "penblocks.h" #include "penlayer.h" #include "penstate.h" #include "spritemodel.h" +#include "stagemodel.h" using namespace scratchcpprender; using namespace libscratchcpp; @@ -31,6 +33,7 @@ void PenBlocks::registerBlocks(IEngine *engine) { // Blocks engine->addCompileFunction(this, "pen_clear", &compileClear); + engine->addCompileFunction(this, "pen_stamp", &compileStamp); engine->addCompileFunction(this, "pen_penDown", &compilePenDown); engine->addCompileFunction(this, "pen_penUp", &compilePenUp); engine->addCompileFunction(this, "pen_setPenColorToColor", &compileSetPenColorToColor); @@ -57,6 +60,11 @@ void PenBlocks::compileClear(Compiler *compiler) compiler->addFunctionCall(&clear); } +void PenBlocks::compileStamp(Compiler *compiler) +{ + compiler->addFunctionCall(&stamp); +} + void PenBlocks::compilePenDown(Compiler *compiler) { compiler->addFunctionCall(&penDown); @@ -77,8 +85,7 @@ void PenBlocks::compileChangePenColorParamBy(libscratchcpp::Compiler *compiler) { Input *input = compiler->input(COLOR_PARAM); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); BlockFunc f = nullptr; @@ -106,8 +113,7 @@ void PenBlocks::compileSetPenColorParamTo(Compiler *compiler) { Input *input = compiler->input(COLOR_PARAM); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); BlockFunc f = nullptr; @@ -179,6 +185,29 @@ unsigned int PenBlocks::clear(VirtualMachine *vm) return 0; } +unsigned int PenBlocks::stamp(libscratchcpp::VirtualMachine *vm) +{ + IPenLayer *penLayer = PenLayer::getProjectPenLayer(vm->engine()); + + if (penLayer) { + Target *target = vm->target(); + IRenderedTarget *renderedTarget = nullptr; + + if (target->isStage()) { + IStageHandler *iface = static_cast(target)->getInterface(); + renderedTarget = static_cast(iface)->renderedTarget(); + } else { + ISpriteHandler *iface = static_cast(target)->getInterface(); + renderedTarget = static_cast(iface)->renderedTarget(); + } + + penLayer->stamp(renderedTarget); + vm->engine()->requestRedraw(); + } + + return 0; +} + unsigned int PenBlocks::penDown(VirtualMachine *vm) { SpriteModel *model = getSpriteModel(vm); diff --git a/src/blocks/penblocks.h b/src/blocks/penblocks.h index 5eb057f..53f6596 100644 --- a/src/blocks/penblocks.h +++ b/src/blocks/penblocks.h @@ -29,6 +29,7 @@ class PenBlocks : public libscratchcpp::IBlockSection void registerBlocks(libscratchcpp::IEngine *engine) override; static void compileClear(libscratchcpp::Compiler *compiler); + static void compileStamp(libscratchcpp::Compiler *compiler); static void compilePenDown(libscratchcpp::Compiler *compiler); static void compilePenUp(libscratchcpp::Compiler *compiler); static void compileSetPenColorToColor(libscratchcpp::Compiler *compiler); @@ -42,6 +43,7 @@ class PenBlocks : public libscratchcpp::IBlockSection static void compileSetPenHueToNumber(libscratchcpp::Compiler *compiler); static unsigned int clear(libscratchcpp::VirtualMachine *vm); + static unsigned int stamp(libscratchcpp::VirtualMachine *vm); static unsigned int penDown(libscratchcpp::VirtualMachine *vm); static unsigned int penUp(libscratchcpp::VirtualMachine *vm); static unsigned int setPenColorToColor(libscratchcpp::VirtualMachine *vm); diff --git a/src/cputexturemanager.cpp b/src/cputexturemanager.cpp index 00b2671..c97b37d 100644 --- a/src/cputexturemanager.cpp +++ b/src/cputexturemanager.cpp @@ -51,6 +51,12 @@ const std::vector &CpuTextureManager::getTextureConvexHullPoints(const T return it->second; } +bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint) +{ + // https://github.com/scratchfoundation/scratch-render/blob/7b823985bc6fe92f572cc3276a8915e550f7c5e6/src/Silhouette.js#L219-L226 + return getPointAlpha(texture, localPoint.x(), localPoint.y()) > 0; +} + void CpuTextureManager::removeTexture(const Texture &texture) { if (!texture.isValid()) @@ -131,3 +137,12 @@ 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 561e9d5..df19588 100644 --- a/src/cputexturemanager.h +++ b/src/cputexturemanager.h @@ -20,10 +20,13 @@ class CpuTextureManager GLubyte *getTextureData(const Texture &texture); const std::vector &getTextureConvexHullPoints(const Texture &texture); + bool textureContainsPoint(const Texture &texture, const QPointF &localPoint); + 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/ipenlayer.h b/src/ipenlayer.h index d21d26e..1ca3fe6 100644 --- a/src/ipenlayer.h +++ b/src/ipenlayer.h @@ -16,6 +16,7 @@ namespace scratchcpprender { struct PenAttributes; +class IRenderedTarget; class IPenLayer : public QNanoQuickItem { @@ -36,6 +37,7 @@ class IPenLayer : public QNanoQuickItem virtual void clear() = 0; virtual void drawPoint(const PenAttributes &penAttributes, double x, double y) = 0; virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0; + virtual void stamp(IRenderedTarget *target) = 0; virtual QOpenGLFramebufferObject *framebufferObject() const = 0; virtual QRgb colorAtScratchPoint(double x, double y) const = 0; diff --git a/src/irenderedtarget.h b/src/irenderedtarget.h index 59b8106..7925cd0 100644 --- a/src/irenderedtarget.h +++ b/src/irenderedtarget.h @@ -78,6 +78,9 @@ class IRenderedTarget : public QNanoQuickItem virtual bool mirrorHorizontally() const = 0; virtual Texture texture() const = 0; + virtual const Texture &cpuTexture() const = 0; + virtual int costumeWidth() const = 0; + virtual int costumeHeight() const = 0; virtual const std::unordered_map &graphicEffects() const = 0; virtual void setGraphicEffect(ShaderManager::Effect effect, double value) = 0; diff --git a/src/listmonitormodel.cpp b/src/listmonitormodel.cpp index 67f920a..2a42315 100644 --- a/src/listmonitormodel.cpp +++ b/src/listmonitormodel.cpp @@ -1,6 +1,5 @@ // SPDX-License-Identifier: LGPL-3.0-or-later -#include #include #include "listmonitormodel.h" @@ -9,31 +8,14 @@ using namespace scratchcpprender; ListMonitorModel::ListMonitorModel(QObject *parent) : - MonitorModel(parent) + ListMonitorModel(nullptr, parent) { - m_listModel = new ListMonitorListModel(this); } ListMonitorModel::ListMonitorModel(libscratchcpp::IBlockSection *section, QObject *parent) : - ListMonitorModel(parent) + MonitorModel(section, parent) { - if (!section) - return; - - // TODO: Get the color from the block section - std::string name = section->name(); - if (name == "Motion") - m_color = QColor::fromString("#4C97FF"); - else if (name == "Looks") - m_color = QColor::fromString("#9966FF"); - else if (name == "Sound") - m_color = QColor::fromString("#CF63CF"); - else if (name == "Sensing") - m_color = QColor::fromString("#5CB1D6"); - else if (name == "Variables") - m_color = QColor::fromString("#FF8C1A"); - else if (name == "Lists") - m_color = QColor::fromString("#FF661A"); + m_listModel = new ListMonitorListModel(this); } void ListMonitorModel::onValueChanged(const libscratchcpp::VirtualMachine *vm) @@ -50,11 +32,6 @@ MonitorModel::Type ListMonitorModel::type() const return Type::List; } -const QColor &ListMonitorModel::color() const -{ - return m_color; -} - ListMonitorListModel *ListMonitorModel::listModel() const { return m_listModel; diff --git a/src/listmonitormodel.h b/src/listmonitormodel.h index 2afe1d1..45a6e8b 100644 --- a/src/listmonitormodel.h +++ b/src/listmonitormodel.h @@ -3,19 +3,11 @@ #pragma once #include -#include #include "monitormodel.h" Q_MOC_INCLUDE("listmonitorlistmodel.h") -namespace libscratchcpp -{ - -class IBlockSection; - -} - namespace scratchcpprender { @@ -25,7 +17,6 @@ class ListMonitorModel : public MonitorModel { Q_OBJECT QML_ELEMENT - Q_PROPERTY(QColor color READ color NOTIFY colorChanged) Q_PROPERTY(ListMonitorListModel *listModel READ listModel NOTIFY listModelChanged) public: @@ -36,8 +27,6 @@ class ListMonitorModel : public MonitorModel Type type() const override; - const QColor &color() const; - ListMonitorListModel *listModel() const; signals: @@ -45,7 +34,6 @@ class ListMonitorModel : public MonitorModel void listModelChanged(); private: - QColor m_color = Qt::green; ListMonitorListModel *m_listModel = nullptr; }; diff --git a/src/monitormodel.cpp b/src/monitormodel.cpp index 5ff9d8c..9554276 100644 --- a/src/monitormodel.cpp +++ b/src/monitormodel.cpp @@ -2,14 +2,37 @@ #include #include +#include #include "monitormodel.h" using namespace scratchcpprender; MonitorModel::MonitorModel(QObject *parent) : + MonitorModel(nullptr, parent) +{ +} + +MonitorModel::MonitorModel(libscratchcpp::IBlockSection *section, QObject *parent) : QObject(parent) { + if (!section) + return; + + // TODO: Get the color from the block section + std::string name = section->name(); + if (name == "Motion") + m_color = QColor::fromString("#4C97FF"); + else if (name == "Looks") + m_color = QColor::fromString("#9966FF"); + else if (name == "Sound") + m_color = QColor::fromString("#CF63CF"); + else if (name == "Sensing") + m_color = QColor::fromString("#5CB1D6"); + else if (name == "Variables") + m_color = QColor::fromString("#FF8C1A"); + else if (name == "Lists") + m_color = QColor::fromString("#FF661A"); } QString MonitorModel::name() const @@ -31,6 +54,11 @@ bool MonitorModel::visible() const return false; } +const QColor &MonitorModel::color() const +{ + return m_color; +} + void MonitorModel::init(libscratchcpp::Monitor *monitor) { m_monitor = monitor; diff --git a/src/monitormodel.h b/src/monitormodel.h index 29c7ba1..329a284 100644 --- a/src/monitormodel.h +++ b/src/monitormodel.h @@ -4,8 +4,16 @@ #include #include +#include #include +namespace libscratchcpp +{ + +class IBlockSection; + +} + namespace scratchcpprender { @@ -18,6 +26,7 @@ class MonitorModel Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(Type type READ type NOTIFY typeChanged) + Q_PROPERTY(QColor color READ color NOTIFY colorChanged) Q_PROPERTY(int x READ x NOTIFY xChanged) Q_PROPERTY(int y READ y NOTIFY yChanged) Q_PROPERTY(unsigned int width READ width WRITE setWidth NOTIFY widthChanged) @@ -34,6 +43,7 @@ class MonitorModel Q_ENUM(Type) MonitorModel(QObject *parent = nullptr); + MonitorModel(libscratchcpp::IBlockSection *section, QObject *parent = nullptr); void init(libscratchcpp::Monitor *monitor) override final; @@ -48,6 +58,8 @@ class MonitorModel virtual Type type() const { return Type::Invalid; } + const QColor &color() const; + int x() const; int y() const; @@ -61,6 +73,7 @@ class MonitorModel signals: void visibleChanged(); void typeChanged(); + void colorChanged(); void xChanged(); void yChanged(); void widthChanged(); @@ -69,6 +82,7 @@ class MonitorModel private: libscratchcpp::Monitor *m_monitor = nullptr; + QColor m_color = Qt::green; }; } // namespace scratchcpprender diff --git a/src/penattributes.h b/src/penattributes.h index 900991f..b2573d6 100644 --- a/src/penattributes.h +++ b/src/penattributes.h @@ -2,14 +2,14 @@ #pragma once -#include +#include namespace scratchcpprender { struct PenAttributes { - QColor color = QColor(0, 0, 255); + QNanoColor color = QNanoColor(0, 0, 255); double diameter = 1; }; diff --git a/src/penlayer.cpp b/src/penlayer.cpp index e2b7642..031d504 100644 --- a/src/penlayer.cpp +++ b/src/penlayer.cpp @@ -1,8 +1,13 @@ // SPDX-License-Identifier: LGPL-3.0-or-later +#include + #include "penlayer.h" #include "penlayerpainter.h" #include "penattributes.h" +#include "irenderedtarget.h" +#include "spritemodel.h" +#include "stagemodel.h" using namespace scratchcpprender; @@ -11,8 +16,6 @@ std::unordered_map PenLayer::m_projectPen PenLayer::PenLayer(QNanoQuickItem *parent) : IPenLayer(parent) { - m_fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - m_fboFormat.setSamples(m_antialiasingEnabled ? 4 : 0); setSmooth(false); } @@ -20,6 +23,12 @@ PenLayer::~PenLayer() { if (m_engine) m_projectPenLayers.erase(m_engine); + + if (m_blitter.isCreated()) { + // Delete vertex array and buffer + m_glF->glDeleteVertexArrays(1, &m_vao); + m_glF->glDeleteBuffers(1, &m_vbo); + } } bool PenLayer::antialiasingEnabled() const @@ -30,7 +39,6 @@ bool PenLayer::antialiasingEnabled() const void PenLayer::setAntialiasingEnabled(bool enabled) { m_antialiasingEnabled = enabled; - m_fboFormat.setSamples(enabled ? 4 : 0); } libscratchcpp::IEngine *PenLayer::engine() const @@ -50,26 +58,70 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) if (m_engine && QOpenGLContext::currentContext()) { m_projectPenLayers[m_engine] = this; - m_fbo = std::make_unique(m_engine->stageWidth(), m_engine->stageHeight(), m_fboFormat); - Q_ASSERT(m_fbo->isValid()); + createFbo(); + + if (!m_painter) + m_painter = std::make_unique(); + + if (!m_glF) { + m_glF = std::make_unique(); + m_glF->initializeOpenGLFunctions(); + } + + if (!m_blitter.isCreated()) { + m_blitter.create(); + + // Set up VBO and VAO + float vertices[] = { + -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + }; + + m_glF->glGenVertexArrays(1, &m_vao); + m_glF->glGenBuffers(1, &m_vbo); + + m_glF->glBindVertexArray(m_vao); + + m_glF->glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + m_glF->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Position attribute + m_glF->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); + m_glF->glEnableVertexAttribArray(0); + + // Texture coordinate attribute + m_glF->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float))); + m_glF->glEnableVertexAttribArray(1); + + m_glF->glBindVertexArray(0); + m_glF->glBindBuffer(GL_ARRAY_BUFFER, 0); + } - m_paintDevice = std::make_unique(m_fbo->size()); clear(); } emit engineChanged(); } +bool PenLayer::hqPen() const +{ + return m_hqPen; +} + +void PenLayer::setHqPen(bool newHqPen) +{ + if (m_hqPen == newHqPen) + return; + + m_hqPen = newHqPen; + createFbo(); + emit hqPenChanged(); +} + void scratchcpprender::PenLayer::clear() { if (!m_fbo) return; - if (!m_glF) { - m_glF = std::make_unique(); - m_glF->initializeOpenGLFunctions(); - } - m_fbo->bind(); m_glF->glDisable(GL_SCISSOR_TEST); m_glF->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -89,39 +141,53 @@ void scratchcpprender::PenLayer::drawPoint(const PenAttributes &penAttributes, d void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) { - if (!m_fbo || !m_paintDevice || !m_engine) + if (!m_fbo || !m_painter || !m_engine) return; // Begin painting m_fbo->bind(); - QPainter painter(m_paintDevice.get()); - painter.beginNativePainting(); - painter.setRenderHint(QPainter::Antialiasing, m_antialiasingEnabled); - painter.setRenderHint(QPainter::SmoothPixmapTransform, false); + + m_painter->beginFrame(m_fbo->width(), m_fbo->height()); + + // Apply scale (HQ pen) + x0 *= m_scale; + y0 *= m_scale; + x1 *= m_scale; + y1 *= m_scale; // Translate to Scratch coordinate system - double stageWidthHalf = m_engine->stageWidth() / 2; - double stageHeightHalf = m_engine->stageHeight() / 2; + double stageWidthHalf = width() / 2; + double stageHeightHalf = height() / 2; x0 += stageWidthHalf; y0 = stageHeightHalf - y0; x1 += stageWidthHalf; y1 = stageHeightHalf - y1; // Set pen attributes - QPen pen(penAttributes.color); - pen.setWidthF(penAttributes.diameter); - pen.setCapStyle(Qt::RoundCap); - painter.setPen(pen); + const double diameter = penAttributes.diameter * m_scale; + m_painter->setLineWidth(diameter); + m_painter->setStrokeStyle(penAttributes.color); + m_painter->setFillStyle(penAttributes.color); + m_painter->setLineJoin(QNanoPainter::JOIN_ROUND); + m_painter->setLineCap(QNanoPainter::CAP_ROUND); + m_painter->setAntialias(m_antialiasingEnabled ? 1.0f : 0.0f); + m_painter->beginPath(); + + // Width 1 and 3 lines need to be offset by 0.5 + const double offset = (std::fmod(std::max(4 - diameter, 0.0), 2)) / 2; // If the start and end coordinates are the same, draw a point, otherwise draw a line - if (x0 == x1 && y0 == y1) - painter.drawPoint(x0, y0); - else - painter.drawLine(x0, y0, x1, y1); + if (x0 == x1 && y0 == y1) { + m_painter->circle(x0 + offset, y0 + offset, diameter / 2); + m_painter->fill(); + } else { + m_painter->moveTo(x0 + offset, y0 + offset); + m_painter->lineTo(x1 + offset, y1 + offset); + m_painter->stroke(); + } // End painting - painter.endNativePainting(); - painter.end(); + m_painter->endFrame(); m_fbo->release(); m_textureDirty = true; @@ -129,6 +195,171 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do update(); } +/* + * A brief description of how stamping is implemented: + * 1. Get rotation, size and coordinates and translate them. + * 2. Draw the texture onto a temporary texture using shaders. + * 3. Blit the resulting texture to a FBO with a square texture (required for rotation). + * 4. Blit the resulting texture to the pen layer using QOpenGLTextureBlitter with transform. + * + * If you think this is too complicated, contributions are welcome! + */ +void PenLayer::stamp(IRenderedTarget *target) +{ + if (!target || !m_fbo || !m_texture.isValid() || !m_blitter.isCreated()) + return; + + double x = 0; + double y = 0; + double angle = 0; + double scale = 1; + bool mirror = false; + std::shared_ptr costume; + + SpriteModel *spriteModel = target->spriteModel(); + + if (spriteModel) { + libscratchcpp::Sprite *sprite = spriteModel->sprite(); + x = sprite->x(); + y = sprite->y(); + + switch (sprite->rotationStyle()) { + case libscratchcpp::Sprite::RotationStyle::AllAround: + angle = 90 - sprite->direction(); + break; + + case libscratchcpp::Sprite::RotationStyle::LeftRight: + mirror = (sprite->direction() < 0); + break; + + default: + break; + } + + scale = sprite->size() / 100; + costume = sprite->currentCostume(); + } else + costume = target->stageModel()->stage()->currentCostume(); + + // Apply scale (HQ pen) + scale *= m_scale; + + const double bitmapRes = costume->bitmapResolution(); + const double centerX = costume->rotationCenterX() / bitmapRes; + const double centerY = costume->rotationCenterY() / bitmapRes; + + const Texture &texture = target->cpuTexture(); + + if (!texture.isValid()) + return; + + const double textureScale = texture.width() / static_cast(target->costumeWidth()); + + // Apply scale (HQ pen) + x *= m_scale; + y *= m_scale; + + // Translate the coordinates + x = std::floor(x + m_texture.width() / 2.0); + y = std::floor(-y + m_texture.height() / 2.0); + + m_glF->glDisable(GL_SCISSOR_TEST); + + // For some reason nothing is rendered without this + // TODO: Find out why this is happening + m_painter->beginFrame(m_fbo->width(), m_fbo->height()); + m_painter->stroke(); + m_painter->endFrame(); + + // Create a temporary FBO for graphic effects + QOpenGLFramebufferObject tmpFbo(texture.size()); + m_painter->beginFrame(tmpFbo.width(), tmpFbo.height()); + + // Create a FBO for the current texture + unsigned int fbo; + m_glF->glGenFramebuffers(1, &fbo); + m_glF->glBindFramebuffer(GL_FRAMEBUFFER, fbo); + m_glF->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle(), 0); + + if (m_glF->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + qWarning() << "error: framebuffer incomplete (stamp " + target->scratchTarget()->name() + ")"; + m_glF->glDeleteFramebuffers(1, &fbo); + return; + } + + // Get the shader program for the current set of effects + ShaderManager *shaderManager = ShaderManager::instance(); + + const auto &effects = target->graphicEffects(); + QOpenGLShaderProgram *shaderProgram = shaderManager->getShaderProgram(effects); + Q_ASSERT(shaderProgram); + Q_ASSERT(shaderProgram->isLinked()); + + m_glF->glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + + // Render to the target framebuffer + m_glF->glBindFramebuffer(GL_FRAMEBUFFER, tmpFbo.handle()); + shaderProgram->bind(); + m_glF->glBindVertexArray(m_vao); + m_glF->glActiveTexture(GL_TEXTURE0); + m_glF->glBindTexture(GL_TEXTURE_2D, texture.handle()); + shaderManager->setUniforms(shaderProgram, 0, effects); // set texture and effect uniforms + m_glF->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + m_painter->endFrame(); + + // Resize to square (for rotation) + const double dim = std::max(tmpFbo.width(), tmpFbo.height()); + QOpenGLFramebufferObject resizeFbo(dim, dim); + resizeFbo.bind(); + m_painter->beginFrame(dim, dim); + + const QRect resizeRect(QPoint(0, 0), tmpFbo.size()); + const QMatrix4x4 matrix = QOpenGLTextureBlitter::targetTransform(resizeRect, QRect(QPoint(0, 0), resizeFbo.size())); + m_glF->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + m_glF->glClear(GL_COLOR_BUFFER_BIT); + m_blitter.bind(); + m_blitter.blit(tmpFbo.texture(), matrix, QOpenGLTextureBlitter::OriginBottomLeft); + m_blitter.release(); + + m_painter->endFrame(); + resizeFbo.release(); + + // Cleanup + shaderProgram->release(); + m_glF->glBindVertexArray(0); + m_glF->glBindBuffer(GL_ARRAY_BUFFER, 0); + m_glF->glBindFramebuffer(GL_FRAMEBUFFER, 0); + m_glF->glDeleteFramebuffers(1, &fbo); + + // Transform + const double width = resizeFbo.width() / textureScale; + const double height = resizeFbo.height() / textureScale; + QRectF targetRect(QPoint(x, y), QSizeF(width, height)); + QTransform transform = QOpenGLTextureBlitter::targetTransform(targetRect, QRect(QPoint(centerX, centerY), m_fbo->size())).toTransform(); + const double dx = 2 * (centerX - width / 2.0) / width; + const double dy = -2 * (centerY - height / 2.0) / height; + transform.translate(dx, dy); + transform.rotate(angle); + transform.scale(scale * (mirror ? -1 : 1), scale); + transform.translate(-dx, -dy); + + // Blit + m_fbo->bind(); + m_painter->beginFrame(m_fbo->width(), m_fbo->height()); + m_blitter.bind(); + m_blitter.blit(resizeFbo.texture(), transform, QOpenGLTextureBlitter::OriginBottomLeft); + m_blitter.release(); + m_painter->endFrame(); + m_fbo->release(); + + m_glF->glEnable(GL_SCISSOR_TEST); + + m_textureDirty = true; + m_boundsDirty = true; + update(); +} + QOpenGLFramebufferObject *PenLayer::framebufferObject() const { return m_fbo.get(); @@ -145,8 +376,11 @@ QRgb PenLayer::colorAtScratchPoint(double x, double y) const const double width = m_texture.width(); const double height = m_texture.height(); + // Apply scale (HQ pen) + x *= m_scale; + y *= m_scale; + // Translate the coordinates - // TODO: Apply scale x = std::floor(x + width / 2.0); y = std::floor(-y + height / 2.0); @@ -186,7 +420,6 @@ const libscratchcpp::Rect &PenLayer::getBounds() const } for (const QPointF &point : points) { - // TODO: Apply scale double x = point.x() - width / 2; double y = -point.y() + height / 2; @@ -203,10 +436,10 @@ const libscratchcpp::Rect &PenLayer::getBounds() const bottom = y; } - m_bounds.setLeft(left); - m_bounds.setTop(top); - m_bounds.setRight(right + 1); - m_bounds.setBottom(bottom - 1); + m_bounds.setLeft(left / m_scale); + m_bounds.setTop(top / m_scale); + m_bounds.setRight(right / m_scale + 1); + m_bounds.setBottom(bottom / m_scale - 1); } return m_bounds; @@ -232,6 +465,33 @@ QNanoQuickItemPainter *PenLayer::createItemPainter() const return new PenLayerPainter; } +void PenLayer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (m_hqPen && newGeometry != oldGeometry) + createFbo(); + + QNanoQuickItem::geometryChange(newGeometry, oldGeometry); +} + +void PenLayer::createFbo() +{ + if (!QOpenGLContext::currentContext() || !m_engine) + return; + + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + + QOpenGLFramebufferObject *newFbo = new QOpenGLFramebufferObject(width(), height(), fboFormat); + Q_ASSERT(newFbo->isValid()); + + if (m_fbo) + QOpenGLFramebufferObject::blitFramebuffer(newFbo, m_fbo.get()); + + m_fbo.reset(newFbo); + m_texture = Texture(m_fbo->texture(), m_fbo->size()); + m_scale = width() / m_engine->stageWidth(); +} + void PenLayer::updateTexture() { if (!m_fbo) @@ -239,13 +499,4 @@ void PenLayer::updateTexture() m_textureDirty = false; m_textureManager.removeTexture(m_texture); - - if (!m_resolvedFbo || m_resolvedFbo->size() != m_fbo->size()) { - QOpenGLFramebufferObjectFormat format; - format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - m_resolvedFbo = std::make_unique(m_fbo->size(), format); - } - - QOpenGLFramebufferObject::blitFramebuffer(m_resolvedFbo.get(), m_fbo.get()); - m_texture = Texture(m_resolvedFbo->texture(), m_resolvedFbo->size()); } diff --git a/src/penlayer.h b/src/penlayer.h index 55d7e27..2f93a9c 100644 --- a/src/penlayer.h +++ b/src/penlayer.h @@ -3,9 +3,8 @@ #pragma once #include -#include -#include -#include +#include +#include #include #include "ipenlayer.h" @@ -20,6 +19,7 @@ class PenLayer : public IPenLayer Q_OBJECT QML_ELEMENT Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged) + Q_PROPERTY(bool hqPen READ hqPen WRITE setHqPen NOTIFY hqPenChanged) public: PenLayer(QNanoQuickItem *parent = nullptr); @@ -31,9 +31,13 @@ class PenLayer : public IPenLayer libscratchcpp::IEngine *engine() const override; void setEngine(libscratchcpp::IEngine *newEngine) override; + bool hqPen() const; + void setHqPen(bool newHqPen); + void clear() override; void drawPoint(const PenAttributes &penAttributes, double x, double y) override; void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override; + void stamp(IRenderedTarget *target) override; QOpenGLFramebufferObject *framebufferObject() const override; QRgb colorAtScratchPoint(double x, double y) const override; @@ -45,26 +49,32 @@ class PenLayer : public IPenLayer signals: void engineChanged(); + void hqPenChanged(); protected: QNanoQuickItemPainter *createItemPainter() const override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; private: + void createFbo(); void updateTexture(); static std::unordered_map m_projectPenLayers; bool m_antialiasingEnabled = true; libscratchcpp::IEngine *m_engine = nullptr; + bool m_hqPen = false; std::unique_ptr m_fbo; - std::unique_ptr m_resolvedFbo; - std::unique_ptr m_paintDevice; - QOpenGLFramebufferObjectFormat m_fboFormat; - std::unique_ptr m_glF; + double m_scale = 1; + std::unique_ptr m_painter; + std::unique_ptr m_glF; Texture m_texture; bool m_textureDirty = true; mutable CpuTextureManager m_textureManager; mutable bool m_boundsDirty = true; mutable libscratchcpp::Rect m_bounds; + QOpenGLTextureBlitter m_blitter; + GLuint m_vbo = 0; + GLuint m_vao = 0; }; } // namespace scratchcpprender diff --git a/src/penlayerpainter.cpp b/src/penlayerpainter.cpp index 4c6e5f6..212deec 100644 --- a/src/penlayerpainter.cpp +++ b/src/penlayerpainter.cpp @@ -24,9 +24,6 @@ void PenLayerPainter::paint(QNanoPainter *painter) // Custom FBO - only used for testing QOpenGLFramebufferObject *targetFbo = m_targetFbo ? m_targetFbo : framebufferObject(); - QOpenGLFramebufferObjectFormat format; - format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - // Blit the FBO to the item FBO QOpenGLFramebufferObject::blitFramebuffer(targetFbo, m_fbo); } diff --git a/src/penstate.h b/src/penstate.h index aa134f2..f1e6400 100644 --- a/src/penstate.h +++ b/src/penstate.h @@ -31,7 +31,7 @@ struct PenState const int v = brightness * 255 / 100; const int a = 255 - transparency * 255 / 100; - penAttributes.color = QColor::fromHsv(h, s, v, a); + penAttributes.color = QNanoColor::fromQColor(QColor::fromHsv(h, s, v, a)); } }; diff --git a/src/projectloader.cpp b/src/projectloader.cpp index c7a4984..3090487 100644 --- a/src/projectloader.cpp +++ b/src/projectloader.cpp @@ -104,7 +104,6 @@ void ProjectLoader::setFileName(const QString &newFileName) m_engine = nullptr; emit engineChanged(); - m_project.setScratchVersion(ScratchVersion::Scratch3); m_project.setFileName(m_fileName.toStdString()); m_loadStatus = false; @@ -250,12 +249,14 @@ void ProjectLoader::load() m_engine->setStageHeight(m_stageHeight); m_engine->setCloneLimit(m_cloneLimit); m_engine->setSpriteFencingEnabled(m_spriteFencing); + m_engine->setGlobalVolume(m_mute ? 0 : 100); m_engine->aboutToRender().connect(&ProjectLoader::redraw, this); m_engine->monitorAdded().connect(&ProjectLoader::addMonitor, this); m_engine->monitorRemoved().connect(&ProjectLoader::removeMonitor, this); m_engine->questionAsked().connect([this](const std::string &question) { emit questionAsked(QString::fromStdString(question)); }); + m_engine->questionAborted().connect([this]() { emit questionAborted(); }); // Load targets const auto &targets = m_engine->targets(); @@ -522,6 +523,26 @@ void ProjectLoader::setSpriteFencing(bool newSpriteFencing) emit spriteFencingChanged(); } +bool ProjectLoader::mute() const +{ + return m_mute; +} + +void ProjectLoader::setMute(bool newMute) +{ + if (m_mute == newMute) + return; + + m_mute = newMute; + m_engineMutex.lock(); + + if (m_engine) + m_engine->setGlobalVolume(newMute ? 0 : 100); + + m_engineMutex.unlock(); + emit muteChanged(); +} + unsigned int ProjectLoader::downloadedAssets() const { return m_downloadedAssets; diff --git a/src/projectloader.h b/src/projectloader.h index 198497c..8c949c9 100644 --- a/src/projectloader.h +++ b/src/projectloader.h @@ -35,6 +35,7 @@ class ProjectLoader : public QObject Q_PROPERTY(unsigned int stageHeight READ stageHeight WRITE setStageHeight NOTIFY stageHeightChanged) Q_PROPERTY(int cloneLimit READ cloneLimit WRITE setCloneLimit NOTIFY cloneLimitChanged) Q_PROPERTY(bool spriteFencing READ spriteFencing WRITE setSpriteFencing NOTIFY spriteFencingChanged) + Q_PROPERTY(bool mute READ mute WRITE setMute NOTIFY muteChanged) Q_PROPERTY(unsigned int downloadedAssets READ downloadedAssets NOTIFY downloadedAssetsChanged) Q_PROPERTY(unsigned int assetCount READ assetCount NOTIFY assetCountChanged) @@ -84,6 +85,9 @@ class ProjectLoader : public QObject bool spriteFencing() const; void setSpriteFencing(bool newSpriteFencing); + bool mute() const; + void setMute(bool newMute); + unsigned int downloadedAssets() const; unsigned int assetCount() const; @@ -103,6 +107,7 @@ class ProjectLoader : public QObject void stageHeightChanged(); void cloneLimitChanged(); void spriteFencingChanged(); + void muteChanged(); void downloadedAssetsChanged(); void assetCountChanged(); void cloneCreated(SpriteModel *model); @@ -110,6 +115,7 @@ class ProjectLoader : public QObject void monitorAdded(MonitorModel *model); void monitorRemoved(MonitorModel *model); void questionAsked(QString question); + void questionAborted(); protected: void timerEvent(QTimerEvent *event) override; @@ -142,6 +148,7 @@ class ProjectLoader : public QObject unsigned int m_stageHeight = 360; int m_cloneLimit = 300; bool m_spriteFencing = true; + bool m_mute = false; std::atomic m_downloadedAssets = 0; std::atomic m_assetCount = 0; std::atomic m_stopLoading = false; diff --git a/src/renderedtarget.cpp b/src/renderedtarget.cpp index 38c7147..1f20b54 100644 --- a/src/renderedtarget.cpp +++ b/src/renderedtarget.cpp @@ -529,6 +529,27 @@ Texture RenderedTarget::texture() const return m_texture; } +const Texture &RenderedTarget::cpuTexture() const +{ + return m_cpuTexture; +} + +int RenderedTarget::costumeWidth() const +{ + if (!m_skin || !m_costume) + return 0; + + return m_skin->getTexture(1).width() / m_costume->bitmapResolution(); +} + +int RenderedTarget::costumeHeight() const +{ + if (!m_skin || !m_costume) + return 0; + + return m_skin->getTexture(1).height() / m_costume->bitmapResolution(); +} + const std::unordered_map &RenderedTarget::graphicEffects() const { return m_graphicEffects; @@ -591,7 +612,7 @@ bool RenderedTarget::contains(const QPointF &point) const bool RenderedTarget::containsScratchPoint(double x, double y) const { - if (!m_engine) + if (!m_engine || !m_skin || !m_costume) return false; return containsLocalPoint(mapFromScratchToLocal(QPointF(x, y))); @@ -793,20 +814,7 @@ void RenderedTarget::updateHullPoints() bool RenderedTarget::containsLocalPoint(const QPointF &point) const { - if (!boundingRect().contains(point)) - return false; - - const std::vector &points = hullPoints(); - QPoint intPoint = point.toPoint(); - auto it = std::lower_bound(points.begin(), points.end(), intPoint, [](const QPointF &lhs, const QPointF &rhs) { return (lhs.y() < rhs.y()) || (lhs.y() == rhs.y() && lhs.x() < rhs.x()); }); - - if (it == points.end()) { - // The point is beyond the last point in the convex hull - return false; - } - - // Check if the point is equal to the one found - return *it == intPoint; + return textureManager()->textureContainsPoint(m_cpuTexture, point); } QPointF RenderedTarget::transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const diff --git a/src/renderedtarget.h b/src/renderedtarget.h index 7d5d1c8..e752e1c 100644 --- a/src/renderedtarget.h +++ b/src/renderedtarget.h @@ -86,6 +86,9 @@ class RenderedTarget : public IRenderedTarget bool mirrorHorizontally() const override; Texture texture() const override; + const Texture &cpuTexture() const override; + int costumeWidth() const override; + int costumeHeight() const override; const std::unordered_map &graphicEffects() const override; void setGraphicEffect(ShaderManager::Effect effect, double value) override; diff --git a/src/skin.cpp b/src/skin.cpp index dd6c150..5d25b48 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -9,83 +9,55 @@ using namespace scratchcpprender; Skin::Skin() { + QOpenGLContext *context = QOpenGLContext::currentContext(); + Q_ASSERT(context); + + if (context) { + QObject::connect(context, &QOpenGLContext::aboutToBeDestroyed, &m_signalHandler, [this]() { + // Destroy textures + m_textures.clear(); + }); + } } -Texture Skin::createAndPaintTexture(int width, int height, bool multisampled) +Texture Skin::createAndPaintTexture(int width, int height) { QOpenGLContext *context = QOpenGLContext::currentContext(); if (!context || !context->isValid() || (width <= 0 || height <= 0)) return Texture(); - QOpenGLFunctions glF(context); + QOpenGLExtraFunctions glF(context); glF.initializeOpenGLFunctions(); - // Create offscreen surface - QOffscreenSurface surface; - surface.setFormat(context->format()); - surface.create(); - Q_ASSERT(surface.isValid()); - - // Save old surface - QSurface *oldSurface = context->surface(); - - // Make context active on the surface - context->makeCurrent(&surface); - - const QRectF drawRect(0, 0, width, height); - const QSize drawRectSize = drawRect.size().toSize(); - - // Create multisampled FBO (if the multisampled parameter is set) - QOpenGLFramebufferObjectFormat format; - format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - - if (multisampled) - format.setSamples(16); + // Render to QImage + QImage image(width, height, QImage::Format_RGBA8888); - QOpenGLFramebufferObject fbo(drawRectSize, format); - fbo.bind(); + // Clear the image to be fully transparent + image.fill(Qt::transparent); - // Create paint device - QOpenGLPaintDevice device(drawRectSize); - QPainter painter(&device); - painter.beginNativePainting(); - painter.setRenderHint(QPainter::Antialiasing, false); - glF.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glF.glClear(GL_COLOR_BUFFER_BIT); - - // Call the skin-specific paint method - paint(&painter); - - // Done with the painting - painter.endNativePainting(); + QPainter painter(&image); + paint(&painter); // Custom paint function painter.end(); - fbo.release(); - - GLuint textureHandle; - - if (multisampled) { - // Create non-multisampled FBO (we can't take the texture from the multisampled FBO) - format.setSamples(0); - - QOpenGLFramebufferObject targetFbo(drawRectSize, format); - targetFbo.bind(); - - // Blit the multisampled FBO to target FBO - QOpenGLFramebufferObject::blitFramebuffer(&targetFbo, &fbo); - - // Take the texture (will call targetFbo.release()) - textureHandle = targetFbo.takeTexture(); - } else { - // Take the texture - textureHandle = fbo.takeTexture(); + image.mirror(); + + // Premultiply alpha + for (int y = 0; y < image.height(); ++y) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (int x = 0; x < image.width(); ++x) { + QColor color = QColor::fromRgba(line[x]); + color.setRedF(color.redF() * color.alphaF()); + color.setGreenF(color.greenF() * color.alphaF()); + color.setBlueF(color.blueF() * color.alphaF()); + line[x] = color.rgba(); + } } - // Restore old surface - context->doneCurrent(); - - if (oldSurface) - context->makeCurrent(oldSurface); + // Create final texture from the image + auto texture = std::make_shared(image); + m_textures.push_back(texture); + texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + texture->setMagnificationFilter(QOpenGLTexture::Linear); - return Texture(textureHandle, drawRectSize); + return Texture(texture->textureId(), width, height); } diff --git a/src/skin.h b/src/skin.h index 1bfc147..855ef65 100644 --- a/src/skin.h +++ b/src/skin.h @@ -22,8 +22,12 @@ class Skin virtual double getTextureScale(const Texture &texture) const = 0; protected: - Texture createAndPaintTexture(int width, int height, bool multisampled); + Texture createAndPaintTexture(int width, int height); virtual void paint(QPainter *painter) = 0; + + private: + std::vector> m_textures; + QObject m_signalHandler; // for disconnecting signals after destroyed }; } // namespace scratchcpprender diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index 0fe5b98..70e440e 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -140,6 +140,16 @@ void SpriteModel::onBubbleTextChanged(const std::string &text) } } +int SpriteModel::costumeWidth() const +{ + return m_renderedTarget->costumeWidth(); +} + +int SpriteModel::costumeHeight() const +{ + return m_renderedTarget->costumeHeight(); +} + libscratchcpp::Rect SpriteModel::boundingRect() const { return m_renderedTarget->getBounds(); diff --git a/src/spritemodel.h b/src/spritemodel.h index ee4042c..52e2076 100644 --- a/src/spritemodel.h +++ b/src/spritemodel.h @@ -54,6 +54,9 @@ class SpriteModel void onBubbleTypeChanged(libscratchcpp::Target::BubbleType type) override; void onBubbleTextChanged(const std::string &text) override; + int costumeWidth() const override; + int costumeHeight() const override; + libscratchcpp::Rect boundingRect() const override; libscratchcpp::Rect fastBoundingRect() const override; diff --git a/src/stagemodel.cpp b/src/stagemodel.cpp index 50f834a..3290424 100644 --- a/src/stagemodel.cpp +++ b/src/stagemodel.cpp @@ -77,6 +77,16 @@ void StageModel::onBubbleTextChanged(const std::string &text) } } +int StageModel::costumeWidth() const +{ + return m_renderedTarget->costumeWidth(); +} + +int StageModel::costumeHeight() const +{ + return m_renderedTarget->costumeHeight(); +} + libscratchcpp::Rect StageModel::boundingRect() const { return libscratchcpp::Rect(); diff --git a/src/stagemodel.h b/src/stagemodel.h index 8f586c7..b5423e8 100644 --- a/src/stagemodel.h +++ b/src/stagemodel.h @@ -40,6 +40,9 @@ class StageModel void onBubbleTypeChanged(libscratchcpp::Target::BubbleType type) override; void onBubbleTextChanged(const std::string &text) override; + int costumeWidth() const override; + int costumeHeight() const override; + libscratchcpp::Rect boundingRect() const override; libscratchcpp::Rect fastBoundingRect() const override; diff --git a/src/svgskin.cpp b/src/svgskin.cpp index 1119bf1..d2c35a6 100644 --- a/src/svgskin.cpp +++ b/src/svgskin.cpp @@ -9,9 +9,8 @@ using namespace scratchcpprender; static const int MAX_TEXTURE_DIMENSION = 2048; static const int INDEX_OFFSET = 8; -SVGSkin::SVGSkin(libscratchcpp::Costume *costume, bool antialiasing) : - Skin(), - m_antialiasing(antialiasing) +SVGSkin::SVGSkin(libscratchcpp::Costume *costume) : + Skin() { if (!costume) return; @@ -86,7 +85,7 @@ Texture SVGSkin::createScaledTexture(int index) return Texture(); } - const Texture texture = createAndPaintTexture(viewBox.width() * scale, viewBox.height() * scale, m_antialiasing); + const Texture texture = createAndPaintTexture(viewBox.width() * scale, viewBox.height() * scale); if (texture.isValid()) { m_textures[index] = texture.handle(); diff --git a/src/svgskin.h b/src/svgskin.h index 5352827..f722e63 100644 --- a/src/svgskin.h +++ b/src/svgskin.h @@ -20,7 +20,7 @@ namespace scratchcpprender class SVGSkin : public Skin { public: - SVGSkin(libscratchcpp::Costume *costume, bool antialiasing = true); + SVGSkin(libscratchcpp::Costume *costume); ~SVGSkin(); Texture getTexture(double scale) const override; @@ -37,7 +37,6 @@ class SVGSkin : public Skin std::unordered_map m_textureObjects; QSvgRenderer m_svgRen; int m_maxIndex = 0; - bool m_antialiasing = false; }; } // namespace scratchcpprender diff --git a/src/valuemonitormodel.cpp b/src/valuemonitormodel.cpp index d716730..1e55a5d 100644 --- a/src/valuemonitormodel.cpp +++ b/src/valuemonitormodel.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include -#include #include #include "valuemonitormodel.h" @@ -13,30 +12,13 @@ static const std::unordered_map MODE_MAP = { { Monitor::Mode::Default, ValueMonitorModel::Mode::Default }, { Monitor::Mode::Large, ValueMonitorModel::Mode::Large }, { Monitor::Mode::Slider, ValueMonitorModel::Mode::Slider } }; ValueMonitorModel::ValueMonitorModel(QObject *parent) : - MonitorModel(parent) + ValueMonitorModel(nullptr, parent) { } ValueMonitorModel::ValueMonitorModel(IBlockSection *section, QObject *parent) : - MonitorModel(parent) + MonitorModel(section, parent) { - if (!section) - return; - - // TODO: Get the color from the block section - std::string name = section->name(); - if (name == "Motion") - m_color = QColor::fromString("#4C97FF"); - else if (name == "Looks") - m_color = QColor::fromString("#9966FF"); - else if (name == "Sound") - m_color = QColor::fromString("#CF63CF"); - else if (name == "Sensing") - m_color = QColor::fromString("#5CB1D6"); - else if (name == "Variables") - m_color = QColor::fromString("#FF8C1A"); - else if (name == "Lists") - m_color = QColor::fromString("#FF661A"); } void ValueMonitorModel::onValueChanged(const VirtualMachine *vm) @@ -70,11 +52,6 @@ void ValueMonitorModel::setValue(const QString &newValue) } } -const QColor &ValueMonitorModel::color() const -{ - return m_color; -} - ValueMonitorModel::Mode ValueMonitorModel::mode() const { if (monitor()) diff --git a/src/valuemonitormodel.h b/src/valuemonitormodel.h index aa7f2cc..8f8b3ff 100644 --- a/src/valuemonitormodel.h +++ b/src/valuemonitormodel.h @@ -2,17 +2,8 @@ #pragma once -#include - #include "monitormodel.h" -namespace libscratchcpp -{ - -class IBlockSection; - -} - namespace scratchcpprender { @@ -21,7 +12,6 @@ class ValueMonitorModel : public MonitorModel Q_OBJECT QML_ELEMENT Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged) - Q_PROPERTY(QColor color READ color NOTIFY colorChanged) Q_PROPERTY(Mode mode READ mode NOTIFY modeChanged) Q_PROPERTY(double sliderMin READ sliderMin NOTIFY sliderMinChanged) Q_PROPERTY(double sliderMax READ sliderMax NOTIFY sliderMaxChanged) @@ -47,7 +37,6 @@ class ValueMonitorModel : public MonitorModel const QString &value() const; void setValue(const QString &newValue); - const QColor &color() const; Mode mode() const; double sliderMin() const; double sliderMax() const; @@ -63,7 +52,6 @@ class ValueMonitorModel : public MonitorModel private: QString m_value; - QColor m_color = Qt::green; }; } // namespace scratchcpprender diff --git a/test/blocks/pen_blocks_test.cpp b/test/blocks/pen_blocks_test.cpp index 57b423e..52d9712 100644 --- a/test/blocks/pen_blocks_test.cpp +++ b/test/blocks/pen_blocks_test.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include #include #include +#include #include "../common.h" @@ -52,6 +54,7 @@ class PenBlocksTest : public testing::Test else { auto input = addNullInput(block, name, id); auto menu = std::make_shared(block->id() + "_menu", block->opcode() + "_menu"); + menu->setShadow(true); input->setValueBlock(menu); addDropdownField(menu, name, -1, selectedValue, -1); } @@ -92,6 +95,7 @@ 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)); @@ -133,6 +137,66 @@ TEST_F(PenBlocksTest, Clear) ASSERT_TRUE(compiler.lists().empty()); } +TEST_F(PenBlocksTest, Stamp) +{ + Compiler compiler(&m_engineMock); + + auto block = std::make_shared("a", "pen_stamp"); + + EXPECT_CALL(m_engineMock, functionIndex(&PenBlocks::stamp)).WillOnce(Return(2)); + compiler.init(); + compiler.setBlock(block); + PenBlocks::compileStamp(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_EXEC, 2, vm::OP_HALT })); + ASSERT_TRUE(compiler.constValues().empty()); + ASSERT_TRUE(compiler.variables().empty()); + ASSERT_TRUE(compiler.lists().empty()); +} + +TEST_F(PenBlocksTest, StampImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &PenBlocks::stamp }; + + PenLayerMock penLayer; + PenLayer::addPenLayer(&m_engineMock, &penLayer); + RenderedTargetMock renderedTarget; + + // Test sprite + libscratchcpp::Sprite sprite; + SpriteModel spriteModel; + sprite.setInterface(&spriteModel); + spriteModel.setRenderedTarget(&renderedTarget); + + VirtualMachine vm1(&sprite, &m_engineMock, nullptr); + vm1.setBytecode(bytecode); + vm1.setFunctions(functions); + + EXPECT_CALL(penLayer, stamp(&renderedTarget)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm1.run(); + + ASSERT_EQ(vm1.registerCount(), 0); + + // Test stage + libscratchcpp::Stage stage; + StageModel stageModel; + stage.setInterface(&stageModel); + stageModel.setRenderedTarget(&renderedTarget); + + VirtualMachine vm2(&stage, &m_engineMock, nullptr); + vm2.setBytecode(bytecode); + vm2.setFunctions(functions); + + EXPECT_CALL(penLayer, stamp(&renderedTarget)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); +} + TEST_F(PenBlocksTest, ClearImpl) { static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }; @@ -289,43 +353,43 @@ TEST_F(PenBlocksTest, SetPenColorToColorImpl) vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(210, 42, 204)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(210, 42, 204))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(228, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(228, 255, 255))); vm.reset(); vm.setBytecode(bytecode3); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(359, 0, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(359, 0, 0))); vm.reset(); vm.setBytecode(bytecode4); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(359, 0, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(359, 0, 0))); vm.reset(); vm.setBytecode(bytecode5); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(359, 0, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(359, 0, 0))); vm.reset(); vm.setBytecode(bytecode6); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(162, 74, 72, 73)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(162, 74, 72, 73))); vm.reset(); vm.setBytecode(bytecode7); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(239, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(239, 255, 255))); } TEST_F(PenBlocksTest, ChangePenColorParamBy) @@ -435,18 +499,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) // color vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(71, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(71, 255, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(263, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(263, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 255, 255, 150))); // saturation model.penState().saturation = 32.4; @@ -454,18 +518,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode3); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 218, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 218, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode4); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 255, 150))); // brightness model.penState().brightness = 12.5; @@ -473,18 +537,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode5); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 167, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 167, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 255, 150))); vm.reset(); vm.setBytecode(bytecode6); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 150))); // transparency model.penState().transparency = 6.28; @@ -492,25 +556,25 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode7); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 103)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 103))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 0))); vm.reset(); vm.setBytecode(bytecode8); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 255))); // invalid parameter vm.reset(); vm.setBytecode(bytecode9); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 255))); // color (optimized) model.penState() = PenState(); @@ -519,18 +583,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode10); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(71, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(71, 255, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(263, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(263, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode11); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 255, 255, 150))); // saturation (optimized) model.penState().saturation = 32.4; @@ -538,18 +602,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode12); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 218, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 218, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode13); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 255, 150))); // brightness (optimized) model.penState().brightness = 12.5; @@ -557,18 +621,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode14); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 167, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 167, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 255, 150))); vm.reset(); vm.setBytecode(bytecode15); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 150))); // transparency (optimized) model.penState().transparency = 6.28; @@ -576,18 +640,18 @@ TEST_F(PenBlocksTest, ChangePenColorParamByImpl) vm.setBytecode(bytecode16); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 103)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 103))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 0))); vm.reset(); vm.setBytecode(bytecode17); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(188, 0, 0, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(188, 0, 0, 255))); } TEST_F(PenBlocksTest, SetPenColorParamTo) @@ -705,19 +769,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) // color vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(191, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(191, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(234, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(234, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode3); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 150))); // saturation model.penState().saturation = 32.4; @@ -725,19 +789,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode4); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 135, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 135, 255, 150))); vm.reset(); vm.setBytecode(bytecode5); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 0, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 0, 255, 150))); vm.reset(); vm.setBytecode(bytecode6); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 150))); // brightness model.penState().brightness = 12.5; @@ -745,19 +809,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode7); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 135, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 135, 150))); vm.reset(); vm.setBytecode(bytecode8); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 0, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 0, 150))); vm.reset(); vm.setBytecode(bytecode9); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 150))); // transparency model.penState().transparency = 12.5; @@ -765,26 +829,26 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode10); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 119)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 119))); vm.reset(); vm.setBytecode(bytecode11); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 255))); vm.reset(); vm.setBytecode(bytecode12); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 0))); // invalid parameter vm.reset(); vm.setBytecode(bytecode13); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 0))); // color (optimized) model.penState() = PenState(); @@ -794,19 +858,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode14); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(191, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(191, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode15); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(234, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(234, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode16); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 150))); // saturation (optimized) model.penState().saturation = 32.4; @@ -814,19 +878,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode17); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 135, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 135, 255, 150))); vm.reset(); vm.setBytecode(bytecode18); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 0, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 0, 255, 150))); vm.reset(); vm.setBytecode(bytecode19); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 150))); // brightness (optimized) model.penState().brightness = 12.5; @@ -834,19 +898,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode20); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 135, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 135, 150))); vm.reset(); vm.setBytecode(bytecode21); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 0, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 0, 150))); vm.reset(); vm.setBytecode(bytecode22); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 150))); // transparency (optimized) model.penState().transparency = 12.5; @@ -854,19 +918,19 @@ TEST_F(PenBlocksTest, SetPenColorParamToImpl) vm.setBytecode(bytecode23); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 119)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 119))); vm.reset(); vm.setBytecode(bytecode24); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 255))); vm.reset(); vm.setBytecode(bytecode25); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(313, 255, 255, 0)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(313, 255, 255, 0))); } TEST_F(PenBlocksTest, ChangePenSizeBy) @@ -1057,28 +1121,28 @@ TEST_F(PenBlocksTest, ChangePenShadeByImpl) vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 255, 110, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 255, 110, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 119, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 119, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 247, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 247, 255, 150))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 162, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 162, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 255, 55, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 255, 55, 150))); } TEST_F(PenBlocksTest, SetPenShadeToNumber) @@ -1132,19 +1196,19 @@ TEST_F(PenBlocksTest, SetPenShadeToNumberImpl) vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 148, 253, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 148, 253, 150))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 102, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 102, 255, 150))); vm.reset(); vm.setBytecode(bytecode3); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(240, 89, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(240, 89, 255, 150))); } TEST_F(PenBlocksTest, ChangePenHueBy) @@ -1197,28 +1261,28 @@ TEST_F(PenBlocksTest, ChangePenHueByImpl) vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(106, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(106, 255, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(332, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(332, 255, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(199, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(199, 255, 255, 150))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(353, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(353, 255, 255, 150))); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(148, 255, 255, 150)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(148, 255, 255, 150))); } TEST_F(PenBlocksTest, SetPenHueToNumber) @@ -1272,17 +1336,17 @@ TEST_F(PenBlocksTest, SetPenHueToNumberImpl) vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(226, 255, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(226, 255, 255, 255))); vm.reset(); vm.setBytecode(bytecode2); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(154, 255, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(154, 255, 255, 255))); vm.reset(); vm.setBytecode(bytecode3); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(model.penAttributes().color, QColor::fromHsv(160, 255, 255, 255)); + ASSERT_EQ(model.penAttributes().color, QNanoColor::fromQColor(QColor::fromHsv(160, 255, 255, 255))); } diff --git a/test/common.h b/test/common.h index c639833..847aa84 100644 --- a/test/common.h +++ b/test/common.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -18,6 +19,23 @@ std::string readFileStr(const std::string &fileName) return buffer.str(); } +double fuzzyCompareImages(const QImage &a, const QImage &b) +{ + if (a.size() != b.size()) + return 1; + + int x, y, c = 0; + + for (y = 0; y < a.height(); y++) { + for (x = 0; x < a.width(); x++) { + if (a.pixel(x, y) != b.pixel(x, y)) + c++; + } + } + + return c / static_cast((a.width() * a.height())); +} + int main(int argc, char **argv) { QGuiApplication a(argc, argv); diff --git a/test/lines.png b/test/lines.png index 3cb9ded..245885e 100644 Binary files a/test/lines.png and b/test/lines.png differ diff --git a/test/lines_hq.png b/test/lines_hq.png new file mode 100644 index 0000000..c78d1d3 Binary files /dev/null and b/test/lines_hq.png differ diff --git a/test/lines_old.png b/test/lines_old.png deleted file mode 100644 index 3cb9ded..0000000 Binary files a/test/lines_old.png and /dev/null differ diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index 89db942..2d9aaa7 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -19,14 +19,17 @@ 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, (unsigned int), (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(void, initClone, (std::shared_ptr), (override)); MOCK_METHOD(void, deinitClone, (std::shared_ptr), (override)); + MOCK_METHOD(void, stopSounds, (), (override)); + MOCK_METHOD(double, globalVolume, (), (const, override)); + MOCK_METHOD(void, setGlobalVolume, (double), (override)); MOCK_METHOD(void, updateMonitors, (), (override)); MOCK_METHOD(void, step, (), (override)); @@ -36,6 +39,7 @@ class EngineMock : public IEngine MOCK_METHOD(sigslot::signal<> &, aboutToRender, (), (override)); MOCK_METHOD(sigslot::signal &, threadAboutToStop, (), (override)); + MOCK_METHOD(sigslot::signal<> &, stopped, (), (override)); MOCK_METHOD(bool, isRunning, (), (const, override)); @@ -101,9 +105,10 @@ class EngineMock : public IEngine MOCK_METHOD(const std::vector> &, broadcasts, (), (const, override)); MOCK_METHOD(void, setBroadcasts, (const std::vector> &), (override)); MOCK_METHOD(std::shared_ptr, broadcastAt, (int), (const, override)); - MOCK_METHOD(int, findBroadcast, (const std::string &), (const, override)); + MOCK_METHOD(std::vector, findBroadcasts, (const std::string &), (const, override)); MOCK_METHOD(int, findBroadcastById, (const std::string &), (const, override)); + 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)); @@ -131,6 +136,7 @@ class EngineMock : public IEngine MOCK_METHOD((sigslot::signal &), monitorRemoved, (), (override)); MOCK_METHOD(sigslot::signal &, questionAsked, (), (override)); + MOCK_METHOD(sigslot::signal<> &, questionAborted, (), (override)); MOCK_METHOD(sigslot::signal &, questionAnswered, (), (override)); MOCK_METHOD(std::vector &, extensions, (), (const, override)); @@ -140,6 +146,8 @@ class EngineMock : public IEngine MOCK_METHOD(const std::string &, userAgent, (), (const, override)); MOCK_METHOD(void, setUserAgent, (const std::string &), (override)); + + MOCK_METHOD(const std::unordered_set &, unsupportedBlocks, (), (const, override)); }; } // namespace scratchcpprender diff --git a/test/mocks/penlayermock.h b/test/mocks/penlayermock.h index 2f8a0d5..5c76938 100644 --- a/test/mocks/penlayermock.h +++ b/test/mocks/penlayermock.h @@ -21,6 +21,7 @@ class PenLayerMock : public IPenLayer MOCK_METHOD(void, clear, (), (override)); MOCK_METHOD(void, drawPoint, (const PenAttributes &, double, double), (override)); MOCK_METHOD(void, drawLine, (const PenAttributes &, double, double, double, double), (override)); + MOCK_METHOD(void, stamp, (IRenderedTarget *), (override)); MOCK_METHOD(QOpenGLFramebufferObject *, framebufferObject, (), (const, override)); MOCK_METHOD(QRgb, colorAtScratchPoint, (double, double), (const, override)); diff --git a/test/mocks/renderedtargetmock.h b/test/mocks/renderedtargetmock.h index 6bfc154..a120bb6 100644 --- a/test/mocks/renderedtargetmock.h +++ b/test/mocks/renderedtargetmock.h @@ -62,6 +62,9 @@ class RenderedTargetMock : public IRenderedTarget MOCK_METHOD(bool, mirrorHorizontally, (), (const, override)); MOCK_METHOD(Texture, texture, (), (const, override)); + MOCK_METHOD(const Texture &, cpuTexture, (), (const, override)); + MOCK_METHOD(int, costumeWidth, (), (const, override)); + MOCK_METHOD(int, costumeHeight, (), (const, override)); MOCK_METHOD((const std::unordered_map &), graphicEffects, (), (const, override)); MOCK_METHOD(void, setGraphicEffect, (ShaderManager::Effect effect, double value), (override)); diff --git a/test/monitor_models/CMakeLists.txt b/test/monitor_models/CMakeLists.txt index ebd6d03..b79245e 100644 --- a/test/monitor_models/CMakeLists.txt +++ b/test/monitor_models/CMakeLists.txt @@ -7,7 +7,9 @@ add_executable( target_link_libraries( monitormodel_test GTest::gtest_main + GTest::gmock_main scratchcpp-render + scratchcpprender_mocks ${QT_LIBS} Qt6::Test ) @@ -24,9 +26,7 @@ add_executable( target_link_libraries( valuemonitormodel_test GTest::gtest_main - GTest::gmock_main scratchcpp-render - scratchcpprender_mocks ${QT_LIBS} Qt6::Test ) @@ -60,9 +60,7 @@ add_executable( target_link_libraries( listmonitormodel_test GTest::gtest_main - GTest::gmock_main scratchcpp-render - scratchcpprender_mocks ${QT_LIBS} Qt6::Test ) diff --git a/test/monitor_models/listmonitormodel_test.cpp b/test/monitor_models/listmonitormodel_test.cpp index 84bc740..86029f6 100644 --- a/test/monitor_models/listmonitormodel_test.cpp +++ b/test/monitor_models/listmonitormodel_test.cpp @@ -3,15 +3,12 @@ #include #include #include -#include #include "../common.h" using namespace scratchcpprender; using namespace libscratchcpp; -using ::testing::Return; - TEST(ListMonitorModelTest, Constructors) { { @@ -71,60 +68,3 @@ TEST(ListMonitorModelTest, Type) ListMonitorModel model; ASSERT_EQ(model.type(), MonitorModel::Type::List); } - -TEST(ListMonitorModelTest, Color) -{ - { - ListMonitorModel model; - ASSERT_EQ(model.color(), Qt::green); - } - - { - ListMonitorModel model(nullptr, nullptr); - ASSERT_EQ(model.color(), Qt::green); - } - - BlockSectionMock section; - - { - // Invalid - EXPECT_CALL(section, name()).WillOnce(Return("")); - ListMonitorModel model(§ion); - ASSERT_EQ(model.color(), Qt::green); - } - - { - // Motion - EXPECT_CALL(section, name()).WillOnce(Return("Motion")); - ListMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#4C97FF")); - } - - { - // Looks - EXPECT_CALL(section, name()).WillOnce(Return("Looks")); - ListMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#9966FF")); - } - - { - // Sound - EXPECT_CALL(section, name()).WillOnce(Return("Sound")); - ListMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#CF63CF")); - } - - { - // Variables - EXPECT_CALL(section, name()).WillOnce(Return("Variables")); - ListMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#FF8C1A")); - } - - { - // Lists - EXPECT_CALL(section, name()).WillOnce(Return("Lists")); - ListMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#FF661A")); - } -} diff --git a/test/monitor_models/monitormodel_test.cpp b/test/monitor_models/monitormodel_test.cpp index f02373a..4b47e90 100644 --- a/test/monitor_models/monitormodel_test.cpp +++ b/test/monitor_models/monitormodel_test.cpp @@ -2,17 +2,28 @@ #include #include #include +#include #include "../common.h" using namespace scratchcpprender; using namespace libscratchcpp; +using ::testing::Return; + TEST(MonitorModelTest, Constructors) { - MonitorModel model1; - MonitorModel model2(&model1); - ASSERT_EQ(model2.parent(), &model1); + { + MonitorModel model1; + MonitorModel model2(&model1); + ASSERT_EQ(model2.parent(), &model1); + } + + { + MonitorModel model1; + MonitorModel model2(nullptr, &model1); + ASSERT_EQ(model2.parent(), &model1); + } } TEST(MonitorModelTest, Init) @@ -75,6 +86,63 @@ TEST(MonitorModelTest, Type) ASSERT_EQ(model.type(), MonitorModel::Type::Invalid); } +TEST(MonitorModelTest, Color) +{ + { + MonitorModel model; + ASSERT_EQ(model.color(), Qt::green); + } + + { + MonitorModel model(nullptr, nullptr); + ASSERT_EQ(model.color(), Qt::green); + } + + BlockSectionMock section; + + { + // Invalid + EXPECT_CALL(section, name()).WillOnce(Return("")); + MonitorModel model(§ion); + ASSERT_EQ(model.color(), Qt::green); + } + + { + // Motion + EXPECT_CALL(section, name()).WillOnce(Return("Motion")); + MonitorModel model(§ion); + ASSERT_EQ(model.color(), QColor::fromString("#4C97FF")); + } + + { + // Looks + EXPECT_CALL(section, name()).WillOnce(Return("Looks")); + MonitorModel model(§ion); + ASSERT_EQ(model.color(), QColor::fromString("#9966FF")); + } + + { + // Sound + EXPECT_CALL(section, name()).WillOnce(Return("Sound")); + MonitorModel model(§ion); + ASSERT_EQ(model.color(), QColor::fromString("#CF63CF")); + } + + { + // Variables + EXPECT_CALL(section, name()).WillOnce(Return("Variables")); + MonitorModel model(§ion); + ASSERT_EQ(model.color(), QColor::fromString("#FF8C1A")); + } + + { + // Lists + EXPECT_CALL(section, name()).WillOnce(Return("Lists")); + MonitorModel model(§ion); + ASSERT_EQ(model.color(), QColor::fromString("#FF661A")); + } +} + TEST(MonitorModelTest, X) { MonitorModel model; diff --git a/test/monitor_models/valuemonitormodel_test.cpp b/test/monitor_models/valuemonitormodel_test.cpp index a1000e7..d432b3c 100644 --- a/test/monitor_models/valuemonitormodel_test.cpp +++ b/test/monitor_models/valuemonitormodel_test.cpp @@ -4,15 +4,12 @@ #include #include #include -#include #include "../common.h" using namespace scratchcpprender; using namespace libscratchcpp; -using ::testing::Return; - TEST(ValueMonitorModelTest, Constructors) { { @@ -81,63 +78,6 @@ TEST(ValueMonitorModelTest, Type) ASSERT_EQ(model.type(), MonitorModel::Type::Value); } -TEST(ValueMonitorModelTest, Color) -{ - { - ValueMonitorModel model; - ASSERT_EQ(model.color(), Qt::green); - } - - { - ValueMonitorModel model(nullptr, nullptr); - ASSERT_EQ(model.color(), Qt::green); - } - - BlockSectionMock section; - - { - // Invalid - EXPECT_CALL(section, name()).WillOnce(Return("")); - ValueMonitorModel model(§ion); - ASSERT_EQ(model.color(), Qt::green); - } - - { - // Motion - EXPECT_CALL(section, name()).WillOnce(Return("Motion")); - ValueMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#4C97FF")); - } - - { - // Looks - EXPECT_CALL(section, name()).WillOnce(Return("Looks")); - ValueMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#9966FF")); - } - - { - // Sound - EXPECT_CALL(section, name()).WillOnce(Return("Sound")); - ValueMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#CF63CF")); - } - - { - // Variables - EXPECT_CALL(section, name()).WillOnce(Return("Variables")); - ValueMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#FF8C1A")); - } - - { - // Lists - EXPECT_CALL(section, name()).WillOnce(Return("Lists")); - ValueMonitorModel model(§ion); - ASSERT_EQ(model.color(), QColor::fromString("#FF661A")); - } -} - TEST(ValueMonitorModelTest, Mode) { ValueMonitorModel model; diff --git a/test/penlayer/CMakeLists.txt b/test/penlayer/CMakeLists.txt index 5ad79f2..525f090 100644 --- a/test/penlayer/CMakeLists.txt +++ b/test/penlayer/CMakeLists.txt @@ -10,6 +10,7 @@ target_link_libraries( scratchcpp-render scratchcpprender_mocks ${QT_LIBS} + Qt6::Test ) add_test(penlayer_test) diff --git a/test/penlayer/penlayer_test.cpp b/test/penlayer/penlayer_test.cpp index a1073e0..9470f5b 100644 --- a/test/penlayer/penlayer_test.cpp +++ b/test/penlayer/penlayer_test.cpp @@ -2,8 +2,12 @@ #include #include #include +#include #include #include +#include +#include +#include #include #include @@ -50,21 +54,42 @@ TEST_F(PenLayerTest, Constructors) TEST_F(PenLayerTest, Engine) { PenLayer penLayer; + QSignalSpy spy(&penLayer, &PenLayer::engineChanged); ASSERT_EQ(penLayer.engine(), nullptr); EngineMock engine1, engine2; + penLayer.setWidth(480); + penLayer.setHeight(360); EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine1); ASSERT_EQ(penLayer.engine(), &engine1); + ASSERT_EQ(spy.count(), 1); + penLayer.setWidth(500); + penLayer.setHeight(400); EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); - EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); penLayer.setEngine(&engine2); ASSERT_EQ(penLayer.engine(), &engine2); + ASSERT_EQ(spy.count(), 2); penLayer.setEngine(nullptr); ASSERT_EQ(penLayer.engine(), nullptr); + ASSERT_EQ(spy.count(), 3); +} + +TEST_F(PenLayerTest, HqPen) +{ + PenLayer penLayer; + QSignalSpy spy(&penLayer, &PenLayer::hqPenChanged); + ASSERT_FALSE(penLayer.hqPen()); + + penLayer.setHqPen(true); + ASSERT_TRUE(penLayer.hqPen()); + ASSERT_EQ(spy.count(), 1); + + penLayer.setHqPen(false); + ASSERT_FALSE(penLayer.hqPen()); + ASSERT_EQ(spy.count(), 2); } TEST_F(PenLayerTest, FramebufferObject) @@ -72,22 +97,24 @@ TEST_F(PenLayerTest, FramebufferObject) PenLayer penLayer; ASSERT_TRUE(penLayer.antialiasingEnabled()); - EngineMock engine1, engine2; + EngineMock engine1, engine2, engine3; + penLayer.setWidth(480); + penLayer.setHeight(360); EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine1); QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); ASSERT_EQ(fbo->width(), 480); ASSERT_EQ(fbo->height(), 360); ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); - ASSERT_EQ(fbo->format().samples(), 4); + ASSERT_EQ(fbo->format().samples(), 0); penLayer.setAntialiasingEnabled(false); ASSERT_FALSE(penLayer.antialiasingEnabled()); + penLayer.setWidth(500); + penLayer.setHeight(400); EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); - EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); penLayer.setEngine(&engine2); fbo = penLayer.framebufferObject(); @@ -95,11 +122,36 @@ TEST_F(PenLayerTest, FramebufferObject) ASSERT_EQ(fbo->height(), 400); ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); ASSERT_EQ(fbo->format().samples(), 0); + + penLayer.setWidth(960); + penLayer.setHeight(720); + EXPECT_CALL(engine3, stageWidth()).WillOnce(Return(480)); + penLayer.setEngine(&engine3); + + fbo = penLayer.framebufferObject(); + ASSERT_EQ(fbo->width(), 960); + ASSERT_EQ(fbo->height(), 720); + ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); + ASSERT_EQ(fbo->format().samples(), 0); + + EXPECT_CALL(engine3, stageWidth()).Times(3).WillRepeatedly(Return(100)); + penLayer.setHqPen(true); + penLayer.setWidth(500); + penLayer.setHeight(400); + penLayer.setEngine(&engine3); + + fbo = penLayer.framebufferObject(); + ASSERT_EQ(fbo->width(), 500); + ASSERT_EQ(fbo->height(), 400); + ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); + ASSERT_EQ(fbo->format().samples(), 0); } TEST_F(PenLayerTest, GetProjectPenLayer) { PenLayer penLayer; + penLayer.setWidth(480); + penLayer.setHeight(360); ASSERT_EQ(PenLayer::getProjectPenLayer(nullptr), nullptr); EngineMock engine1, engine2; @@ -107,13 +159,11 @@ TEST_F(PenLayerTest, GetProjectPenLayer) ASSERT_EQ(PenLayer::getProjectPenLayer(&engine2), nullptr); EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(1)); - EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(1)); penLayer.setEngine(&engine1); ASSERT_EQ(PenLayer::getProjectPenLayer(&engine1), &penLayer); ASSERT_EQ(PenLayer::getProjectPenLayer(&engine2), nullptr); EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(1)); - EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(1)); penLayer.setEngine(&engine2); ASSERT_EQ(PenLayer::getProjectPenLayer(&engine1), nullptr); ASSERT_EQ(PenLayer::getProjectPenLayer(&engine2), &penLayer); @@ -131,8 +181,9 @@ TEST_F(PenLayerTest, Clear) { PenLayer penLayer; EngineMock engine; + penLayer.setWidth(480); + penLayer.setHeight(360); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); QOpenGLExtraFunctions glF(&m_context); @@ -172,104 +223,220 @@ TEST_F(PenLayerTest, DrawPoint) { PenLayer penLayer; penLayer.setAntialiasingEnabled(false); + penLayer.setWidth(480); + penLayer.setHeight(360); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); - PenAttributes attr; - attr.color = QColor(255, 0, 0); - attr.diameter = 3; - - EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); - EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); - penLayer.drawPoint(attr, 63, 164); - penLayer.drawPoint(attr, -56, 93); - penLayer.drawPoint(attr, 130, 77); - - attr.color = QColor(0, 128, 0, 128); - attr.diameter = 10; - - EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); - EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); - penLayer.drawPoint(attr, 152, -158); - penLayer.drawPoint(attr, -228, 145); - penLayer.drawPoint(attr, -100, 139); - - attr.color = QColor(255, 50, 200, 185); - attr.diameter = 25.6; + auto draw = [&penLayer]() { + PenAttributes attr; + attr.color = QNanoColor(255, 0, 0); + attr.diameter = 3; + + penLayer.drawPoint(attr, 63, 164); + penLayer.drawPoint(attr, -56, 93); + penLayer.drawPoint(attr, 130, 77); + + attr.color = QNanoColor(0, 128, 0, 128); + attr.diameter = 10; + + penLayer.drawPoint(attr, 152, -158); + penLayer.drawPoint(attr, -228, 145); + penLayer.drawPoint(attr, -100, 139); + + attr.color = QNanoColor(255, 50, 200, 185); + attr.diameter = 25.6; + + penLayer.drawPoint(attr, -11, 179); + penLayer.drawPoint(attr, 90, -48); + penLayer.drawPoint(attr, -54, 21); + }; + + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("points.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } + // Test HQ pen - resize existing texture EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); - EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); - penLayer.drawPoint(attr, -11, 179); - penLayer.drawPoint(attr, 90, -48); - penLayer.drawPoint(attr, -54, 21); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QImage ref("points_resized.png"); + ASSERT_LE(fuzzyCompareImages(image, ref), 0.01); + } - QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); - QImage image = fbo->toImage(); - QBuffer buffer; - image.save(&buffer, "png"); - QFile ref("points.png"); - ref.open(QFile::ReadOnly); - buffer.open(QFile::ReadOnly); - ASSERT_EQ(ref.readAll(), buffer.readAll()); + // Test HQ pen - draw + penLayer.clear(); + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("points_hq.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } } TEST_F(PenLayerTest, DrawLine) { PenLayer penLayer; penLayer.setAntialiasingEnabled(false); + penLayer.setWidth(480); + penLayer.setHeight(360); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); - PenAttributes attr; - attr.color = QColor(255, 0, 0); - attr.diameter = 3; + auto draw = [&penLayer]() { + PenAttributes attr; + attr.color = QNanoColor(255, 0, 0); + attr.diameter = 3; - EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); - EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); - penLayer.drawLine(attr, 63, 164, -56, 93); - penLayer.drawLine(attr, 130, 77, 125, -22); + penLayer.drawLine(attr, 63, 164, -56, 93); + penLayer.drawLine(attr, 130, 77, 125, -22); - attr.color = QColor(0, 128, 0, 128); - attr.diameter = 10; + attr.color = QNanoColor(0, 128, 0, 128); + attr.diameter = 10; - EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); - EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); - penLayer.drawLine(attr, 152, -158, -228, 145); - penLayer.drawLine(attr, -100, 139, 20, 72); + penLayer.drawLine(attr, 152, -158, -228, 145); + penLayer.drawLine(attr, -100, 139, 20, 72); - attr.color = QColor(255, 50, 200, 185); - attr.diameter = 25.6; + attr.color = QNanoColor(255, 50, 200, 185); + attr.diameter = 25.6; - EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); - EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); - penLayer.drawLine(attr, -11, 179, 90, -48); - penLayer.drawLine(attr, -54, 21, 88, -6); + penLayer.drawLine(attr, -11, 179, 90, -48); + penLayer.drawLine(attr, -54, 21, 88, -6); + }; - QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); - QImage image = fbo->toImage().scaled(240, 180); - QBuffer buffer; - image.save(&buffer, "png"); - QFile ref("lines.png"); - ref.open(QFile::ReadOnly); - buffer.open(QFile::ReadOnly); - ASSERT_EQ(ref.readAll(), buffer.readAll()); + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage().scaled(240, 180); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("lines.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } + + // Test HQ pen + penLayer.clear(); + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("lines_hq.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } +} + +TEST_F(PenLayerTest, Stamp) +{ + PenLayer penLayer; + penLayer.setWidth(480); + penLayer.setHeight(360); + EngineMock engine; + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + penLayer.setEngine(&engine); + + ProjectLoader loader; + loader.setFileName("stamp_env.sb3"); + loader.start(); // wait until it loads + + std::vector> targets; + StageModel *stage = loader.stage(); + targets.push_back(std::make_unique()); + targets.back()->setStageModel(stage); + targets.back()->setEngine(loader.engine()); + targets.back()->loadCostumes(); + targets.back()->updateCostume(stage->stage()->currentCostume().get()); + targets.back()->setGraphicEffect(ShaderManager::Effect::Color, 25); + stage->setRenderedTarget(targets.back().get()); + const auto &sprites = loader.spriteList(); + + int i = 0; + + for (SpriteModel *sprite : sprites) { + targets.push_back(std::make_unique()); + targets.back()->setSpriteModel(sprite); + targets.back()->setEngine(loader.engine()); + targets.back()->loadCostumes(); + targets.back()->updateCostume(sprite->sprite()->currentCostume().get()); + targets.back()->setGraphicEffect(ShaderManager::Effect::Color, i * 25); + targets.back()->setGraphicEffect(ShaderManager::Effect::Ghost, i * 5); + sprite->setRenderedTarget(targets.back().get()); + i++; + } + + for (const auto &target : targets) + penLayer.stamp(target.get()); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage().scaled(240, 180); + QImage ref("stamp.png"); + ASSERT_LE(fuzzyCompareImages(image, ref), 0.1668); + } + + // Test HQ pen + penLayer.clear(); + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + + for (const auto &target : targets) + penLayer.stamp(target.get()); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QImage ref("stamp_hq.png"); + ASSERT_LE(fuzzyCompareImages(image, ref), 0.32); + } } TEST_F(PenLayerTest, TextureData) { PenLayer penLayer; + penLayer.setWidth(6); + penLayer.setHeight(4); penLayer.setAntialiasingEnabled(false); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillRepeatedly(Return(6)); - EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(4)); penLayer.setEngine(&engine); PenAttributes attr; - attr.color = QColor(255, 0, 0); + attr.color = QNanoColor(255, 0, 0); attr.diameter = 1; penLayer.drawLine(attr, -3, 2, 3, -2); ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0)); @@ -282,7 +449,7 @@ TEST_F(PenLayerTest, TextureData) ASSERT_EQ(bounds.right(), 3); ASSERT_EQ(bounds.bottom(), -2); - attr.color = QColor(0, 128, 0, 128); + attr.color = QNanoColor(0, 128, 0, 128); attr.diameter = 2; penLayer.drawLine(attr, -3, -2, 3, 2); ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0)); @@ -306,28 +473,98 @@ TEST_F(PenLayerTest, TextureData) ASSERT_EQ(bounds.right(), 0); ASSERT_EQ(bounds.bottom(), 0); - attr.color = QColor(0, 255, 0, 255); + attr.color = QNanoColor(0, 255, 0, 255); attr.diameter = 1; penLayer.drawLine(attr, 0, -1, 1, 1); - ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgb(0, 255, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgba(0, 0, 0, 0)); ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0)); ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), qRgba(0, 0, 0, 0)); bounds = penLayer.getBounds(); ASSERT_EQ(bounds.left(), 0); - ASSERT_EQ(bounds.top(), 1); - ASSERT_EQ(bounds.right(), 1); - ASSERT_EQ(bounds.bottom(), -1); + ASSERT_EQ(bounds.top(), 0); + ASSERT_EQ(bounds.right(), 2); + ASSERT_EQ(bounds.bottom(), -2); attr.diameter = 2; penLayer.drawPoint(attr, -2, 0); - ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgb(0, 255, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgba(0, 0, 0, 0)); ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0)); ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), qRgb(0, 255, 0)); bounds = penLayer.getBounds(); ASSERT_EQ(bounds.left(), -3); ASSERT_EQ(bounds.top(), 1); - ASSERT_EQ(bounds.right(), 1); - ASSERT_EQ(bounds.bottom(), -1); + ASSERT_EQ(bounds.right(), 2); + ASSERT_EQ(bounds.bottom(), -2); + + // Test HQ pen + penLayer.clear(); + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + + attr = PenAttributes(); + attr.color = QNanoColor(255, 0, 0); + attr.diameter = 1; + penLayer.drawLine(attr, -3, 2, 3, -2); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), qRgba(0, 0, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), qRgb(255, 0, 0)); + + bounds = penLayer.getBounds(); + ASSERT_EQ(std::round(bounds.left() * 100) / 100, -3.33); + ASSERT_EQ(bounds.top(), 2); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 3.67); + ASSERT_EQ(bounds.bottom(), -3); + + attr.color = QNanoColor(0, 128, 0, 128); + attr.diameter = 2; + penLayer.drawLine(attr, -3, -2, 3, 2); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), qRgb(255, 0, 0)); + + bounds = penLayer.getBounds(); + ASSERT_EQ(std::round(bounds.left() * 100) / 100, -3.33); + ASSERT_EQ(std::round(bounds.top() * 100) / 100, 2.67); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 4.33); + ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -3.67); + + penLayer.clear(); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), 0); + + bounds = penLayer.getBounds(); + ASSERT_EQ(bounds.left(), 0); + ASSERT_EQ(bounds.top(), 0); + ASSERT_EQ(bounds.right(), 0); + ASSERT_EQ(bounds.bottom(), 0); + + attr.color = QNanoColor(0, 255, 0, 255); + attr.diameter = 1; + penLayer.drawLine(attr, 0, -1, 1, 1); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgba(0, 0, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), qRgba(0, 0, 0, 0)); + + bounds = penLayer.getBounds(); + ASSERT_EQ(bounds.left(), 0); + ASSERT_EQ(std::round(bounds.top() * 100) / 100, 1.33); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 1.67); + ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -1.67); + + attr.diameter = 2; + penLayer.drawPoint(attr, -2, 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgba(0, 0, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), 0); + + bounds = penLayer.getBounds(); + ASSERT_EQ(std::round(bounds.left() * 100) / 100, -2.67); + ASSERT_EQ(std::round(bounds.top() * 100) / 100, 1.33); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 1.67); + ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -1.67); } diff --git a/test/penstate/penstate_test.cpp b/test/penstate/penstate_test.cpp index 9a7be7d..bcea105 100644 --- a/test/penstate/penstate_test.cpp +++ b/test/penstate/penstate_test.cpp @@ -28,5 +28,5 @@ TEST(PenStateTest, UpdateColor) state.transparency = 36.09; state.shade = 85; state.updateColor(); - ASSERT_EQ(state.penAttributes.color, QColor::fromHsv(283, 114, 31, 162)); + ASSERT_EQ(state.penAttributes.color, QNanoColor::fromQColor(QColor::fromHsv(283, 114, 31, 162))); } diff --git a/test/points.png b/test/points.png index 03a886a..ae5668e 100644 Binary files a/test/points.png and b/test/points.png differ diff --git a/test/points_hq.png b/test/points_hq.png new file mode 100644 index 0000000..4a6076c Binary files /dev/null and b/test/points_hq.png differ diff --git a/test/points_resized.png b/test/points_resized.png new file mode 100644 index 0000000..4770b99 Binary files /dev/null and b/test/points_resized.png differ diff --git a/test/projectloader/projectloader_test.cpp b/test/projectloader/projectloader_test.cpp index 29ba171..ea0350e 100644 --- a/test/projectloader/projectloader_test.cpp +++ b/test/projectloader/projectloader_test.cpp @@ -188,6 +188,19 @@ TEST_F(ProjectLoaderTest, QuestionAsked) ASSERT_EQ(args.first().toString(), "test"); } +TEST_F(ProjectLoaderTest, QuestionAborted) +{ + ProjectLoader loader; + QSignalSpy spy(&loader, &ProjectLoader::questionAborted); + + load(&loader, "load_test.sb3"); + + auto engine = loader.engine(); + ASSERT_TRUE(spy.isEmpty()); + engine->questionAborted()(); + ASSERT_EQ(spy.count(), 1); +} + TEST_F(ProjectLoaderTest, AnswerQuestion) { ProjectLoader loader; @@ -311,3 +324,23 @@ TEST_F(ProjectLoaderTest, SpriteFencing) ASSERT_EQ(spy.count(), 1); ASSERT_TRUE(loader.spriteFencing()); } + +TEST_F(ProjectLoaderTest, Mute) +{ + ProjectLoader loader; + EngineMock engine; + loader.setEngine(&engine); + ASSERT_FALSE(loader.mute()); + + EXPECT_CALL(engine, setGlobalVolume(0)); + QSignalSpy spy(&loader, SIGNAL(muteChanged())); + loader.setMute(true); + ASSERT_EQ(spy.count(), 1); + ASSERT_TRUE(loader.mute()); + + EXPECT_CALL(engine, setGlobalVolume(100)); + spy.clear(); + loader.setMute(false); + ASSERT_EQ(spy.count(), 1); + ASSERT_FALSE(loader.mute()); +} diff --git a/test/projectscene/CMakeLists.txt b/test/projectscene/CMakeLists.txt index d9bdf67..2f040f7 100644 --- a/test/projectscene/CMakeLists.txt +++ b/test/projectscene/CMakeLists.txt @@ -10,6 +10,7 @@ target_link_libraries( scratchcpp-render scratchcpprender_mocks ${QT_LIBS} + Qt6::Test ) add_test(projectscene_test) diff --git a/test/projectscene/projectscene_test.cpp b/test/projectscene/projectscene_test.cpp index 5920b39..74443f3 100644 --- a/test/projectscene/projectscene_test.cpp +++ b/test/projectscene/projectscene_test.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -11,20 +12,24 @@ using ::testing::Return; TEST(ProjectSceneTest, Engine) { ProjectScene scene; + QSignalSpy spy(&scene, &ProjectScene::engineChanged); ASSERT_EQ(scene.engine(), nullptr); EngineMock engine; scene.setEngine(&engine); ASSERT_EQ(scene.engine(), &engine); + ASSERT_EQ(spy.count(), 1); } TEST(ProjectSceneTest, StageScale) { ProjectScene scene; + QSignalSpy spy(&scene, &ProjectScene::stageScaleChanged); ASSERT_EQ(scene.stageScale(), 1); scene.setStageScale(5.79); ASSERT_EQ(scene.stageScale(), 5.79); + ASSERT_EQ(spy.count(), 1); } TEST(ProjectSceneTest, HandleMouseMove) diff --git a/test/renderedtarget/renderedtarget_test.cpp b/test/renderedtarget/renderedtarget_test.cpp index 77018c8..850600e 100644 --- a/test/renderedtarget/renderedtarget_test.cpp +++ b/test/renderedtarget/renderedtarget_test.cpp @@ -87,6 +87,8 @@ TEST_F(RenderedTargetTest, UpdateMethods) target.beforeRedraw(); ASSERT_EQ(target.width(), 4); ASSERT_EQ(target.height(), 6); + ASSERT_EQ(target.costumeWidth(), 1); + ASSERT_EQ(target.costumeHeight(), 2); ASSERT_EQ(target.x(), 263); ASSERT_EQ(target.y(), 108); ASSERT_EQ(target.z(), 0); @@ -104,6 +106,11 @@ TEST_F(RenderedTargetTest, UpdateMethods) ASSERT_EQ(texture.width(), 4); ASSERT_EQ(texture.height(), 6); + texture = target.cpuTexture(); + ASSERT_TRUE(texture.isValid()); + ASSERT_EQ(texture.width(), 4); + ASSERT_EQ(texture.height(), 6); + // Sprite Sprite sprite; sprite.setVisible(true); @@ -125,6 +132,8 @@ TEST_F(RenderedTargetTest, UpdateMethods) ASSERT_EQ(target.width(), 4); ASSERT_EQ(target.height(), 6); + ASSERT_EQ(target.costumeWidth(), 1); + ASSERT_EQ(target.costumeHeight(), 2); ASSERT_EQ(target.x(), 263); ASSERT_EQ(target.y(), 108); ASSERT_EQ(target.z(), 3); @@ -141,6 +150,11 @@ TEST_F(RenderedTargetTest, UpdateMethods) ASSERT_EQ(texture.width(), 4); ASSERT_EQ(texture.height(), 6); + texture = target.cpuTexture(); + ASSERT_TRUE(texture.isValid()); + ASSERT_EQ(texture.width(), 4); + ASSERT_EQ(texture.height(), 6); + // Visibility target.updateVisibility(false); ASSERT_FALSE(target.isVisible()); @@ -171,6 +185,8 @@ TEST_F(RenderedTargetTest, UpdateMethods) target.beforeRedraw(); ASSERT_EQ(target.width(), 4); ASSERT_EQ(target.height(), 6); + ASSERT_EQ(target.costumeWidth(), 1); + ASSERT_EQ(target.costumeHeight(), 2); ASSERT_EQ(target.x(), 276); ASSERT_EQ(target.y(), 184); ASSERT_EQ(target.transformOriginPoint().x(), -23); @@ -241,6 +257,8 @@ TEST_F(RenderedTargetTest, UpdateMethods) target.beforeRedraw(); ASSERT_EQ(target.width(), 4); ASSERT_EQ(target.height(), 6); + ASSERT_EQ(target.costumeWidth(), 1); + ASSERT_EQ(target.costumeHeight(), 2); ASSERT_EQ(target.x(), 379.5); ASSERT_EQ(target.y(), 384); ASSERT_EQ(target.transformOriginPoint().x(), 0); @@ -263,6 +281,8 @@ TEST_F(RenderedTargetTest, UpdateMethods) ASSERT_EQ(target.width(), 26); ASSERT_EQ(target.height(), 26); + ASSERT_EQ(target.costumeWidth(), 13); + ASSERT_EQ(target.costumeHeight(), 13); ASSERT_EQ(target.x(), 329.5); ASSERT_EQ(target.y(), 400); ASSERT_EQ(target.z(), 3); @@ -277,6 +297,13 @@ TEST_F(RenderedTargetTest, UpdateMethods) ASSERT_TRUE(texture.isValid()); ASSERT_EQ(texture.width(), 26); ASSERT_EQ(texture.height(), 26); + ASSERT_EQ(target.costumeWidth(), 13); + ASSERT_EQ(target.costumeHeight(), 13); + + texture = target.cpuTexture(); + ASSERT_TRUE(texture.isValid()); + ASSERT_EQ(texture.width(), 13); + ASSERT_EQ(texture.height(), 13); // Stage scale (SVG) - should update width and height EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); @@ -284,6 +311,18 @@ TEST_F(RenderedTargetTest, UpdateMethods) target.setStageScale(3.5); ASSERT_EQ(target.width(), 52); ASSERT_EQ(target.height(), 52); + ASSERT_EQ(target.costumeWidth(), 13); + ASSERT_EQ(target.costumeHeight(), 13); + + texture = target.texture(); + ASSERT_TRUE(texture.isValid()); + ASSERT_EQ(texture.width(), 52); + ASSERT_EQ(texture.height(), 52); + + texture = target.cpuTexture(); + ASSERT_TRUE(texture.isValid()); + ASSERT_EQ(texture.width(), 13); + ASSERT_EQ(texture.height(), 13); context.doneCurrent(); } @@ -355,12 +394,12 @@ TEST_F(RenderedTargetTest, CpuRendering) ASSERT_TRUE(target.contains({ 1, 2 })); ASSERT_FALSE(target.contains({ 2, 2 })); ASSERT_TRUE(target.contains({ 3, 2 })); - ASSERT_FALSE(target.contains({ 3.5, 2.1 })); + ASSERT_TRUE(target.contains({ 3.5, 2.1 })); ASSERT_TRUE(target.contains({ 1, 3 })); ASSERT_TRUE(target.contains({ 2, 3 })); ASSERT_TRUE(target.contains({ 3, 3 })); - ASSERT_FALSE(target.contains({ 3.3, 3.5 })); + ASSERT_TRUE(target.contains({ 3.3, 3.5 })); // Test contains() with horizontal mirroring target.updateRotationStyle(Sprite::RotationStyle::LeftRight); @@ -369,9 +408,9 @@ TEST_F(RenderedTargetTest, CpuRendering) ASSERT_TRUE(target.contains({ -1, 1 })); ASSERT_FALSE(target.contains({ -2, 2 })); ASSERT_TRUE(target.contains({ -3, 2 })); - ASSERT_FALSE(target.contains({ -3.5, 2.1 })); + ASSERT_TRUE(target.contains({ -3.5, 2.1 })); ASSERT_TRUE(target.contains({ -2, 3 })); - ASSERT_FALSE(target.contains({ -3.3, 3.5 })); + ASSERT_TRUE(target.contains({ -3.3, 3.5 })); // Test containsScratchPoint() target.updateDirection(0); @@ -386,15 +425,15 @@ TEST_F(RenderedTargetTest, CpuRendering) ASSERT_TRUE(target.containsScratchPoint(-226, 164)); // [2, 1] ASSERT_TRUE(target.containsScratchPoint(-225, 164)); // [3, 1] - ASSERT_TRUE(target.containsScratchPoint(-227, 163)); // [1, 2] - ASSERT_FALSE(target.containsScratchPoint(-226, 163)); // [2, 2] - ASSERT_TRUE(target.containsScratchPoint(-225, 163)); // [3, 2] - ASSERT_FALSE(target.containsScratchPoint(-224.5, 162.9)); // [3.5, 2.1] + ASSERT_TRUE(target.containsScratchPoint(-227, 163)); // [1, 2] + ASSERT_FALSE(target.containsScratchPoint(-226, 163)); // [2, 2] + ASSERT_TRUE(target.containsScratchPoint(-225, 163)); // [3, 2] + ASSERT_TRUE(target.containsScratchPoint(-224.5, 162.9)); // [3.5, 2.1] - ASSERT_TRUE(target.containsScratchPoint(-227, 162)); // [1, 3] - ASSERT_TRUE(target.containsScratchPoint(-226, 162)); // [2, 3] - ASSERT_TRUE(target.containsScratchPoint(-225, 162)); // [3, 3] - ASSERT_FALSE(target.containsScratchPoint(-224.7, 161.5)); // [3.3, 3.5] + ASSERT_TRUE(target.containsScratchPoint(-227, 162)); // [1, 3] + ASSERT_TRUE(target.containsScratchPoint(-226, 162)); // [2, 3] + ASSERT_TRUE(target.containsScratchPoint(-225, 162)); // [3, 3] + ASSERT_TRUE(target.containsScratchPoint(-224.7, 161.5)); // [3.3, 3.5] // Test colorAtScratchPoint() ASSERT_EQ(target.colorAtScratchPoint(-228, 165), 0); // [0, 0] @@ -584,31 +623,37 @@ TEST_F(RenderedTargetTest, SpriteDragging) TEST_F(RenderedTargetTest, Engine) { RenderedTarget target; + QSignalSpy spy(&target, &RenderedTarget::engineChanged); ASSERT_EQ(target.engine(), nullptr); EngineMock engine; target.setEngine(&engine); ASSERT_EQ(target.engine(), &engine); + ASSERT_EQ(spy.count(), 1); } TEST_F(RenderedTargetTest, StageModel) { RenderedTarget target; + QSignalSpy spy(&target, &RenderedTarget::stageModelChanged); ASSERT_EQ(target.stageModel(), nullptr); StageModel model; target.setStageModel(&model); ASSERT_EQ(target.stageModel(), &model); + ASSERT_EQ(spy.count(), 1); } TEST_F(RenderedTargetTest, SpriteModel) { RenderedTarget target; + QSignalSpy spy(&target, &RenderedTarget::spriteModelChanged); ASSERT_EQ(target.spriteModel(), nullptr); SpriteModel model; target.setSpriteModel(&model); ASSERT_EQ(target.spriteModel(), &model); + ASSERT_EQ(spy.count(), 1); } TEST_F(RenderedTargetTest, ScratchTarget) @@ -633,20 +678,24 @@ TEST_F(RenderedTargetTest, ScratchTarget) TEST_F(RenderedTargetTest, MouseArea) { RenderedTarget target; + QSignalSpy spy(&target, &RenderedTarget::mouseAreaChanged); ASSERT_EQ(target.mouseArea(), nullptr); SceneMouseArea mouseArea; target.setMouseArea(&mouseArea); ASSERT_EQ(target.mouseArea(), &mouseArea); + ASSERT_EQ(spy.count(), 1); } TEST_F(RenderedTargetTest, StageScale) { RenderedTarget target; + QSignalSpy spy(&target, &RenderedTarget::stageScaleChanged); ASSERT_EQ(target.stageScale(), 1); target.setStageScale(6.4); ASSERT_EQ(target.stageScale(), 6.4); + ASSERT_EQ(spy.count(), 1); } TEST_F(RenderedTargetTest, GraphicEffects) diff --git a/test/scenemousearea/scenemousearea_test.cpp b/test/scenemousearea/scenemousearea_test.cpp index 554b3ba..630bd65 100644 --- a/test/scenemousearea/scenemousearea_test.cpp +++ b/test/scenemousearea/scenemousearea_test.cpp @@ -7,6 +7,8 @@ using namespace scratchcpprender; +using ::testing::NiceMock; + TEST(SceneMouseAreaTest, Constructors) { SceneMouseArea area1; @@ -15,6 +17,56 @@ TEST(SceneMouseAreaTest, Constructors) ASSERT_EQ(area2.parentItem(), &area1); } +TEST(SceneMouseAreaTest, Events) +{ + QPointingDevice dev; + SceneMouseArea mouseArea; + ProjectLoader loader; + mouseArea.setProjectLoader(&loader); + NiceMock stage; + mouseArea.setStage(&stage); + + // mouseMoved + { + QHoverEvent event(QEvent::HoverMove, {}, {}, {}, Qt::NoModifier, &dev); + QSignalSpy spy(&mouseArea, &SceneMouseArea::mouseMoved); + QCoreApplication::sendEvent(&mouseArea, &event); + ASSERT_EQ(spy.count(), 1); + } + + // mousePressed + { + QMouseEvent event(QEvent::MouseButtonPress, {}, {}, {}, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, &dev); + QSignalSpy spy(&mouseArea, &SceneMouseArea::mousePressed); + QCoreApplication::sendEvent(&mouseArea, &event); + ASSERT_EQ(spy.count(), 1); + } + + // mouseReleased + { + QMouseEvent event(QEvent::MouseButtonRelease, {}, {}, {}, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, &dev); + QSignalSpy spy(&mouseArea, &SceneMouseArea::mouseReleased); + QCoreApplication::sendEvent(&mouseArea, &event); + ASSERT_EQ(spy.count(), 1); + } + + // mouseWheelUp + { + QWheelEvent event(QPointF(), QPointF(), QPoint(2, 3), QPoint(10, 15), Qt::LeftButton, Qt::NoModifier, Qt::NoScrollPhase, false); + QSignalSpy spy(&mouseArea, &SceneMouseArea::mouseWheelUp); + QCoreApplication::sendEvent(&mouseArea, &event); + ASSERT_EQ(spy.count(), 1); + } + + // mouseWheelDown + { + QWheelEvent event(QPointF(), QPointF(), QPoint(2, 3), QPoint(10, -15), Qt::LeftButton, Qt::NoModifier, Qt::NoScrollPhase, false); + QSignalSpy spy(&mouseArea, &SceneMouseArea::mouseWheelDown); + QCoreApplication::sendEvent(&mouseArea, &event); + ASSERT_EQ(spy.count(), 1); + } +} + TEST(SceneMouseAreaTest, Stage) { SceneMouseArea mouseArea; diff --git a/test/skins/bitmapskin_test.cpp b/test/skins/bitmapskin_test.cpp index ab6eb17..48c28d1 100644 --- a/test/skins/bitmapskin_test.cpp +++ b/test/skins/bitmapskin_test.cpp @@ -33,6 +33,8 @@ class BitmapSkinTest : public testing::Test void TearDown() override { ASSERT_EQ(m_context.surface(), &m_surface); + m_jpegSkin.reset(); + m_pngSkin.reset(); m_context.doneCurrent(); } diff --git a/test/skins/svgskin_test.cpp b/test/skins/svgskin_test.cpp index d028d38..7d68832 100644 --- a/test/skins/svgskin_test.cpp +++ b/test/skins/svgskin_test.cpp @@ -22,12 +22,13 @@ class SVGSkinTest : public testing::Test Costume costume("", "", ""); std::string costumeData = readFileStr("image.svg"); costume.setData(costumeData.size(), costumeData.data()); - m_skin = std::make_unique(&costume, false); + m_skin = std::make_unique(&costume); } void TearDown() override { ASSERT_EQ(m_context.surface(), &m_surface); + m_skin.reset(); m_context.doneCurrent(); } @@ -59,10 +60,6 @@ TEST_F(SVGSkinTest, Textures) ASSERT_EQ(m_skin->getTextureScale(texture), scale); } - // Skip images 12, 13 and 15 because they're different on xvfb - if (i == 12 || i == 13 || i >= 15) - continue; - QBuffer buffer; texture.toImage().save(&buffer, "png"); QFile ref("svg_texture_results/" + QString::number(std::min(i, 15)) + ".png"); diff --git a/test/stamp.png b/test/stamp.png new file mode 100644 index 0000000..7206580 Binary files /dev/null and b/test/stamp.png differ diff --git a/test/stamp_env.sb3 b/test/stamp_env.sb3 new file mode 100644 index 0000000..9a0f435 Binary files /dev/null and b/test/stamp_env.sb3 differ diff --git a/test/stamp_hq.png b/test/stamp_hq.png new file mode 100644 index 0000000..b259b79 Binary files /dev/null and b/test/stamp_hq.png differ diff --git a/test/svg_texture_results/10.png b/test/svg_texture_results/10.png index 01436ab..209737c 100644 Binary files a/test/svg_texture_results/10.png and b/test/svg_texture_results/10.png differ diff --git a/test/svg_texture_results/11.png b/test/svg_texture_results/11.png index 73db02b..5d19737 100644 Binary files a/test/svg_texture_results/11.png and b/test/svg_texture_results/11.png differ diff --git a/test/svg_texture_results/12.png b/test/svg_texture_results/12.png new file mode 100644 index 0000000..3e19546 Binary files /dev/null and b/test/svg_texture_results/12.png differ diff --git a/test/svg_texture_results/13.png b/test/svg_texture_results/13.png new file mode 100644 index 0000000..0a947a3 Binary files /dev/null and b/test/svg_texture_results/13.png differ diff --git a/test/svg_texture_results/14.png b/test/svg_texture_results/14.png index 40bb626..9c2b844 100644 Binary files a/test/svg_texture_results/14.png and b/test/svg_texture_results/14.png differ diff --git a/test/svg_texture_results/15.png b/test/svg_texture_results/15.png new file mode 100644 index 0000000..6b51219 Binary files /dev/null and b/test/svg_texture_results/15.png differ diff --git a/test/svg_texture_results/5.png b/test/svg_texture_results/5.png index b0c553b..26536be 100644 Binary files a/test/svg_texture_results/5.png and b/test/svg_texture_results/5.png differ diff --git a/test/svg_texture_results/6.png b/test/svg_texture_results/6.png index dddac55..c5856f6 100644 Binary files a/test/svg_texture_results/6.png and b/test/svg_texture_results/6.png differ diff --git a/test/svg_texture_results/7.png b/test/svg_texture_results/7.png index 5c80b87..164330a 100644 Binary files a/test/svg_texture_results/7.png and b/test/svg_texture_results/7.png differ diff --git a/test/svg_texture_results/8.png b/test/svg_texture_results/8.png index bb6c391..bba0608 100644 Binary files a/test/svg_texture_results/8.png and b/test/svg_texture_results/8.png differ diff --git a/test/svg_texture_results/9.png b/test/svg_texture_results/9.png index acc5697..dd8ed6d 100644 Binary files a/test/svg_texture_results/9.png and b/test/svg_texture_results/9.png differ diff --git a/test/target_models/spritemodel_test.cpp b/test/target_models/spritemodel_test.cpp index 8e81e29..5b406e0 100644 --- a/test/target_models/spritemodel_test.cpp +++ b/test/target_models/spritemodel_test.cpp @@ -83,7 +83,7 @@ TEST(SpriteModelTest, OnCloned) QSignalSpy spy2(cloneModel, &SpriteModel::cloned); PenLayerMock penLayer; cloneModel->setPenLayer(&penLayer); - cloneModel->penAttributes().color = QColor(255, 0, 0); + cloneModel->penAttributes().color = QNanoColor(255, 0, 0); cloneModel->penAttributes().diameter = 20.3; EXPECT_CALL(penLayer, drawPoint); cloneModel->setPenDown(true); @@ -98,7 +98,7 @@ TEST(SpriteModelTest, OnCloned) ASSERT_EQ(cloneModel->sprite(), &clone3); ASSERT_EQ(cloneModel->cloneRoot(), &model); ASSERT_EQ(cloneModel->penLayer(), &penLayer); - ASSERT_EQ(cloneModel->penAttributes().color, QColor(255, 0, 0)); + ASSERT_EQ(cloneModel->penAttributes().color, QNanoColor(255, 0, 0)); ASSERT_EQ(cloneModel->penAttributes().diameter, 20.3); ASSERT_TRUE(cloneModel->penDown()); } @@ -287,6 +287,28 @@ TEST(SpriteModelTest, OnBubbleTextChanged) ASSERT_EQ(spy.count(), 2); } +TEST(SpriteModelTest, CostumeWidth) +{ + SpriteModel model; + + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, costumeWidth()).WillOnce(Return(15)); + ASSERT_EQ(model.costumeWidth(), 15); +} + +TEST(SpriteModelTest, CostumeHeight) +{ + SpriteModel model; + + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, costumeHeight()).WillOnce(Return(10)); + ASSERT_EQ(model.costumeHeight(), 10); +} + TEST(SpriteModelTest, BoundingRect) { SpriteModel model; diff --git a/test/target_models/stagemodel_test.cpp b/test/target_models/stagemodel_test.cpp index 62a1974..672916d 100644 --- a/test/target_models/stagemodel_test.cpp +++ b/test/target_models/stagemodel_test.cpp @@ -112,6 +112,28 @@ TEST(StageModelTest, OnBubbleTextChanged) ASSERT_EQ(spy.count(), 2); } +TEST(StageModelTest, CostumeWidth) +{ + StageModel model; + + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, costumeWidth()).WillOnce(Return(15)); + ASSERT_EQ(model.costumeWidth(), 15); +} + +TEST(StageModelTest, CostumeHeight) +{ + StageModel model; + + RenderedTargetMock renderedTarget; + model.setRenderedTarget(&renderedTarget); + + EXPECT_CALL(renderedTarget, costumeHeight()).WillOnce(Return(10)); + ASSERT_EQ(model.costumeHeight(), 10); +} + TEST(SpriteModelTest, TouchingClones) { StageModel model; diff --git a/test/textbubbleshape/CMakeLists.txt b/test/textbubbleshape/CMakeLists.txt index 7edf397..8aed41c 100644 --- a/test/textbubbleshape/CMakeLists.txt +++ b/test/textbubbleshape/CMakeLists.txt @@ -8,6 +8,7 @@ target_link_libraries( GTest::gtest_main scratchcpp-render ${QT_LIBS} + Qt6::Test ) add_test(textbubbleshape_test) diff --git a/test/textbubbleshape/textbubbleshape_test.cpp b/test/textbubbleshape/textbubbleshape_test.cpp index bc6d9c2..3bb5c92 100644 --- a/test/textbubbleshape/textbubbleshape_test.cpp +++ b/test/textbubbleshape/textbubbleshape_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "../common.h" @@ -41,56 +42,70 @@ TEST_F(TextBubbleShapeTest, Constructors) TEST_F(TextBubbleShapeTest, Type) { TextBubbleShape bubble; + QSignalSpy spy(&bubble, &TextBubbleShape::typeChanged); ASSERT_EQ(bubble.type(), TextBubbleShape::Type::Say); bubble.setType(TextBubbleShape::Type::Think); ASSERT_EQ(bubble.type(), TextBubbleShape::Type::Think); + ASSERT_EQ(spy.count(), 1); bubble.setType(TextBubbleShape::Type::Think); ASSERT_EQ(bubble.type(), TextBubbleShape::Type::Think); + ASSERT_EQ(spy.count(), 1); bubble.setType(TextBubbleShape::Type::Say); ASSERT_EQ(bubble.type(), TextBubbleShape::Type::Say); + ASSERT_EQ(spy.count(), 2); } TEST_F(TextBubbleShapeTest, OnSpriteRight) { TextBubbleShape bubble; + QSignalSpy spy(&bubble, &TextBubbleShape::onSpriteRightChanged); ASSERT_TRUE(bubble.onSpriteRight()); bubble.setOnSpriteRight(false); ASSERT_FALSE(bubble.onSpriteRight()); + ASSERT_EQ(spy.count(), 1); bubble.setOnSpriteRight(false); ASSERT_FALSE(bubble.onSpriteRight()); + ASSERT_EQ(spy.count(), 1); bubble.setOnSpriteRight(true); ASSERT_TRUE(bubble.onSpriteRight()); + ASSERT_EQ(spy.count(), 2); } TEST_F(TextBubbleShapeTest, StageScale) { TextBubbleShape bubble; + QSignalSpy spy(&bubble, &TextBubbleShape::stageScaleChanged); ASSERT_EQ(bubble.stageScale(), 1); bubble.setStageScale(6.48); ASSERT_EQ(bubble.stageScale(), 6.48); + ASSERT_EQ(spy.count(), 1); } TEST_F(TextBubbleShapeTest, NativeWidth) { TextBubbleShape bubble; + QSignalSpy spy(&bubble, &TextBubbleShape::nativeWidthChanged); ASSERT_EQ(bubble.nativeWidth(), 0); bubble.setNativeWidth(48.1); ASSERT_EQ(bubble.nativeWidth(), 48.1); + ASSERT_EQ(spy.count(), 1); } TEST_F(TextBubbleShapeTest, NativeHeight) { TextBubbleShape bubble; + QSignalSpy spy(&bubble, &TextBubbleShape::nativeHeightChanged); ASSERT_EQ(bubble.nativeHeight(), 0); bubble.setNativeHeight(87.5); ASSERT_EQ(bubble.nativeHeight(), 87.5); + ASSERT_EQ(spy.count(), 1); } diff --git a/test/texture/cputexturemanager_test.cpp b/test/texture/cputexturemanager_test.cpp index ca6ca7b..2d0ec05 100644 --- a/test/texture/cputexturemanager_test.cpp +++ b/test/texture/cputexturemanager_test.cpp @@ -119,7 +119,6 @@ TEST_F(CpuTextureManagerTest, TextureDataAndHullPoints) const auto &hullPoints1 = manager.getTextureConvexHullPoints(texture); ASSERT_EQ(hullPoints1, refHullPoints1); - imgPainter1.fbo()->toImage().save("/home/adazem009/test.png"); manager.removeTexture(texture); data = manager.getTextureData(texture); ASSERT_EQ(memcmp(data, refData2, 96), 0); @@ -129,3 +128,44 @@ TEST_F(CpuTextureManagerTest, TextureDataAndHullPoints) // Cleanup context.doneCurrent(); } + +TEST_F(CpuTextureManagerTest, TextureContainsPoint) +{ + // 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_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 })); + + // Cleanup + context.doneCurrent(); +} diff --git a/thirdparty/libqnanopainter/CMakeLists.txt b/thirdparty/libqnanopainter/CMakeLists.txt index c92ff7f..0930441 100644 --- a/thirdparty/libqnanopainter/CMakeLists.txt +++ b/thirdparty/libqnanopainter/CMakeLists.txt @@ -1,8 +1,6 @@ cmake_minimum_required(VERSION 3.0.0) project(qnanopainter VERSION 0.1.0) -set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON" FORCE) - option(QNANO_DEBUG_COLLECT "Enable this to get the drawing debug information" OFF) option(QNANO_DEBUG_RENDER "Enable this to render the drawing debug information" OFF) option(QNANO_QT_GL_INCLUDE "Enable this to let Qt include OpenGL headers" ON) @@ -12,6 +10,7 @@ option(QNANO_ENABLE_TOUCH_SIGNALS "This will enable signalling touch events option(QNANO_ENABLE_PAINT_SIGNALS "This will enable signalling paint events Can be useful when using view/widget classes directly" OFF) option(QNANO_USE_RENDERNODE "Enable this to use QRenderNode (available since Qt 5.8.0) instead of QQuickFramebufferObject" OFF) +option(QNANO_ENABLE_PAINTER_SHARING "Enable this to share QNanoPainter instance between QNanoQuickItemPainters" ON) option(QNANO_USE_RHI "Enable this to use QRhi (available since Qt 6.7) instead of QQuickFramebufferObject" OFF) option(QNANO_BUILD_GLES_BACKENDS "When building for embedded devices you can define manually which backends are supported" OFF) @@ -168,7 +167,9 @@ endif() target_link_libraries(qnanopainter ${QNANOPAINTER_QT_LIBS} ) # add to INCLUDE PATH -target_include_directories(qnanopainter PUBLIC .) +target_include_directories(qnanopainter + PUBLIC . + PUBLIC nanovg) # cmake variable ==> c++ #define if(QNANO_QT_GL_INCLUDE) diff --git a/thirdparty/libqnanopainter/include.pri b/thirdparty/libqnanopainter/include.pri index 99bc9ed..c2a49ed 100644 --- a/thirdparty/libqnanopainter/include.pri +++ b/thirdparty/libqnanopainter/include.pri @@ -33,6 +33,11 @@ DEFINES += QNANO_ENABLE_GLES3 # Enable this to use QRenderNode (available since Qt 5.8.0) instead of QQuickFramebufferObject #DEFINES += QNANO_USE_RENDERNODE +# This will enable sharing the QNanoPainter instance between QNanoQuickItemPainters +# Sharing the painter saves memory, but can have issues e.g. when using QNanoPainter +# from multiple windows (see https://github.com/QUItCoding/qnanopainter/issues/80) +DEFINES += QNANO_ENABLE_PAINTER_SHARING + # Suppress fontstash warnings about fopen & strncpy usage DEFINES += _CRT_SECURE_NO_WARNINGS diff --git a/thirdparty/libqnanopainter/qnanoimage.cpp b/thirdparty/libqnanopainter/qnanoimage.cpp index e15ca56..91faf30 100644 --- a/thirdparty/libqnanopainter/qnanoimage.cpp +++ b/thirdparty/libqnanopainter/qnanoimage.cpp @@ -99,15 +99,6 @@ QNanoImage::QNanoImage(const QImage &image, const QString &filename, ImageFlags updateUniqueKey(); } -QNanoImage::QNanoImage(QIODevice *device, const QString &uniqueKey, ImageFlags flags) - : m_device(device) - , m_uniqueKey(uniqueKey) - , m_flags(flags) -{ - Q_ASSERT(device); - updateUniqueKey(); -} - /*! \fn void QNanoImage::setFilename(const QString &filename) @@ -220,21 +211,6 @@ QNanoImage QNanoImage::fromCache(QNanoPainter *painter, const QString &filename, return image; } -QNanoImage QNanoImage::fromCache(QNanoPainter *painter, QIODevice *device, const QString &uniqueKey, ImageFlags flags) -{ - Q_ASSERT(painter); - Q_ASSERT(device); - QNanoImage image; - image.m_imageData.reset(new QNanoDataElement()); - image.m_device = device; - image.m_uniqueKey = uniqueKey; - image.m_flags = flags; - image.updateUniqueKey(); - image.m_parentPainter = painter; - image.getID(painter->nvgCtx()); - return image; -} - // ***** Private ***** /*! @@ -280,17 +256,10 @@ int QNanoImage::getID(NVGcontext* nvg) } } else { // Image is not yet in cache, so load and add it - QIODevice *device; - QFile file; - if (m_device) - device = m_device; - else { - file.setFileName(m_filename); - device = &file; - } - if (device->open(QIODevice::ReadOnly)) { + QFile file(m_filename); + if (file.open(QFile::ReadOnly)) { m_imageData.reset(new QNanoDataElement()); - QByteArray array = device->readAll(); + QByteArray array = file.readAll(); int length = array.size(); unsigned char* data = reinterpret_cast(&array.data()[0]); m_imageData->id = nvgCreateImageMem(nvg, m_flags, data, length); @@ -310,7 +279,7 @@ void QNanoImage::updateUniqueKey() { if (m_textureId > 0) m_uniqueKey = QString("%1_").arg(QString::number(m_textureId)); - else if(!m_device) + else m_uniqueKey = m_filename; m_uniqueKey.append(QString::number(m_flags)); diff --git a/thirdparty/libqnanopainter/qnanoimage.h b/thirdparty/libqnanopainter/qnanoimage.h index a2ad384..850cfd0 100644 --- a/thirdparty/libqnanopainter/qnanoimage.h +++ b/thirdparty/libqnanopainter/qnanoimage.h @@ -55,9 +55,6 @@ class QNanoImage // Constructs an image from QImage with the filename and flags QNanoImage(const QImage &image, const QString &filename, ImageFlags flags = {}); - // Constructs an image from the data in the QIODevice, unique key and flags - QNanoImage(QIODevice *device, const QString &uniqueKey, ImageFlags flags = {}); - // Set the filename of the image void setFilename(const QString &filename); @@ -72,7 +69,6 @@ class QNanoImage static QNanoImage fromFrameBuffer(const QOpenGLFramebufferObject *fbo, ImageFlags flags = QNanoImage::FLIPY); static QNanoImage fromCache(QNanoPainter *painter, const QString &filename, ImageFlags flags = {}); - static QNanoImage fromCache(QNanoPainter *painter, QIODevice *device, const QString &uniqueKey, ImageFlags flags = {}); private: friend class QNanoPainter; @@ -89,7 +85,6 @@ class QNanoImage QNanoPainter *m_parentPainter = nullptr; QSharedPointer m_imageData; std::unique_ptr m_image; - QIODevice *m_device = nullptr; QString m_filename; GLuint m_textureId = 0; QNanoImage::ImageFlags m_flags = {}; diff --git a/thirdparty/libqnanopainter/qnanopainter.h b/thirdparty/libqnanopainter/qnanopainter.h index 8415d13..e4fbd8c 100644 --- a/thirdparty/libqnanopainter/qnanopainter.h +++ b/thirdparty/libqnanopainter/qnanopainter.h @@ -246,6 +246,10 @@ class QNanoPainter int textBreakLines(const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows); */ + inline NVGcontext* nvgCtx() const { + return m_nvgContext; + } + private: friend class QNanoDebug; @@ -261,9 +265,6 @@ class QNanoPainter static QNanoPainter *getInstance(); - inline NVGcontext* nvgCtx() const { - return m_nvgContext; - } #ifdef QNANO_USE_RHI inline void setNvgCtx(NVGcontext* ctx) { m_nvgContext = ctx; diff --git a/thirdparty/libqnanopainter/qnanoquickitempainter.cpp b/thirdparty/libqnanopainter/qnanoquickitempainter.cpp index 170e344..8d214a8 100644 --- a/thirdparty/libqnanopainter/qnanoquickitempainter.cpp +++ b/thirdparty/libqnanopainter/qnanoquickitempainter.cpp @@ -46,7 +46,11 @@ */ QNanoQuickItemPainter::QNanoQuickItemPainter() +#ifdef QNANO_ENABLE_PAINTER_SHARING : m_painter(QNanoPainter::getInstance()) +#else + : m_painter(new QNanoPainter()) +#endif { #ifndef QNANO_USE_RHI // Initialize QOpenGLFunctions for the context @@ -60,6 +64,10 @@ QNanoQuickItemPainter::QNanoQuickItemPainter() QNanoQuickItemPainter::~QNanoQuickItemPainter() { +#ifndef QNANO_ENABLE_PAINTER_SHARING + delete m_painter; + m_painter = nullptr; +#endif } /*! @@ -385,7 +393,9 @@ void QNanoQuickItemPainter::render(const RenderState *) void QNanoQuickItemPainter::render() #endif { +#ifdef QNANO_ENABLE_PAINTER_SHARING m_painter->reset(); // reset context data as painter is shared. +#endif m_painter->setPixelAlign(static_cast(m_pixelAlign)); m_painter->setPixelAlignText(static_cast(m_pixelAlignText));