From 783eb38c7afb719d118bc6cbb07873870eec0b3e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 2 Mar 2025 12:51:03 +0100 Subject: [PATCH 01/43] Reset bubble text and graphic effects when project is stopped --- src/blocks/looksblocks.cpp | 15 ++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 39 ++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 8da59e7a9..dd757492f 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -1,5 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include #include "looksblocks.h" using namespace libscratchcpp; @@ -22,3 +25,15 @@ Rgb LooksBlocks::color() const void LooksBlocks::registerBlocks(IEngine *engine) { } + +void LooksBlocks::onInit(IEngine *engine) +{ + engine->stopped().connect([engine]() { + const auto &targets = engine->targets(); + + for (auto target : targets) { + target->bubble()->setText(""); + target->clearGraphicsEffects(); + } + }); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index b75297549..0a46bc495 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -15,6 +15,7 @@ class LooksBlocks : public IExtension Rgb color() const override; void registerBlocks(IEngine *engine) override; + void onInit(IEngine *engine) override; }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 9074d9d41..52633de00 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1,15 +1,52 @@ +#include +#include +#include +#include +#include #include +#include #include "../common.h" #include "blocks/looksblocks.h" using namespace libscratchcpp; +using ::testing::Return; + class LooksBlocksTest : public testing::Test { public: - void SetUp() override { m_extension = std::make_unique(); } + void SetUp() override + { + m_extension = std::make_unique(); + m_engine = m_project.engine().get(); + m_extension->registerBlocks(m_engine); + m_extension->onInit(m_engine); + } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; }; + +TEST_F(LooksBlocksTest, StopProject) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + GraphicsEffectMock effect; + m_engine->setTargets({ stage, sprite }); + + stage->bubble()->setText("abc"); + sprite->bubble()->setText("def"); + EXPECT_CALL(effect, clamp(10)).WillOnce(Return(10)); + sprite->setGraphicsEffectValue(&effect, 10); + EXPECT_CALL(effect, clamp(2.5)).WillOnce(Return(2.5)); + stage->setGraphicsEffectValue(&effect, 2.5); + + m_engine->stop(); + ASSERT_TRUE(stage->bubble()->text().empty()); + ASSERT_TRUE(sprite->bubble()->text().empty()); + ASSERT_EQ(stage->graphicsEffectValue(&effect), 0); + ASSERT_EQ(sprite->graphicsEffectValue(&effect), 0); +} From 5656468ad8c969491e86453247e73feca2ea4a99 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:33:09 +0100 Subject: [PATCH 02/43] Sprite: Add missing test for empty bubble text --- test/scratch_classes/sprite_test.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp index 5749896a4..23bb9020b 100644 --- a/test/scratch_classes/sprite_test.cpp +++ b/test/scratch_classes/sprite_test.cpp @@ -904,4 +904,9 @@ TEST(SpriteTest, BubbleTextRedraw) EXPECT_CALL(engine, requestRedraw).Times(0); sprite.bubble()->setText(""); + + EXPECT_CALL(engine, requestRedraw()); + sprite.setVisible(true); + EXPECT_CALL(engine, requestRedraw).Times(0); + sprite.bubble()->setText(""); } From 846f65d69486a9013cc8add364fec6b1f80891cc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:39:08 +0100 Subject: [PATCH 03/43] TextBubble: Add owner property --- include/scratchcpp/textbubble.h | 4 ++++ src/scratch/textbubble.cpp | 12 ++++++++++++ src/scratch/textbubble_p.h | 1 + test/scratch_classes/textbubble_test.cpp | 9 +++++++++ 4 files changed, 26 insertions(+) diff --git a/include/scratchcpp/textbubble.h b/include/scratchcpp/textbubble.h index baec89454..890b2b55b 100644 --- a/include/scratchcpp/textbubble.h +++ b/include/scratchcpp/textbubble.h @@ -8,6 +8,7 @@ namespace libscratchcpp { +class Thread; class TextBubblePrivate; /*! \brief The TextBubble class represents a text bubble created using say or think block. */ @@ -31,8 +32,11 @@ class LIBSCRATCHCPP_EXPORT TextBubble : public Drawable const std::string &text() const; virtual void setText(const std::string &text); + virtual void setText(const std::string &text, Thread *owner); sigslot::signal &textChanged() const; + Thread *owner() const; + private: spimpl::unique_impl_ptr impl; }; diff --git a/src/scratch/textbubble.cpp b/src/scratch/textbubble.cpp index c27aece6d..0b78c5673 100644 --- a/src/scratch/textbubble.cpp +++ b/src/scratch/textbubble.cpp @@ -53,10 +53,16 @@ const std::string &TextBubble::text() const * \note If the text is an empty string, the TextBubble is supposed to be hidden. */ void TextBubble::setText(const std::string &text) +{ + setText(text, nullptr); +} + +void TextBubble::setText(const std::string &text, Thread *owner) { // https://github.com/scratchfoundation/scratch-vm/blob/7313ce5199f8a3da7850085d0f7f6a3ca2c89bf6/src/blocks/scratch3_looks.js#L251-L257 const Value v(text); std::string converted = text; + impl->owner = owner; // Non-integers should be rounded to 2 decimal places (no more, no less), unless they're small enough that rounding would display them as 0.00. if (v.isValidNumber()) { @@ -85,3 +91,9 @@ sigslot::signal &TextBubble::textChanged() const { return impl->textChanged; } + +/*! Returns the thread which currently owns the text bubble. */ +Thread *TextBubble::owner() const +{ + return impl->owner; +} diff --git a/src/scratch/textbubble_p.h b/src/scratch/textbubble_p.h index 118b4ebb8..4718b58c7 100644 --- a/src/scratch/textbubble_p.h +++ b/src/scratch/textbubble_p.h @@ -11,6 +11,7 @@ struct TextBubblePrivate { TextBubble::Type type = TextBubble::Type::Say; std::string text; + Thread *owner = nullptr; mutable sigslot::signal typeChanged; mutable sigslot::signal textChanged; }; diff --git a/test/scratch_classes/textbubble_test.cpp b/test/scratch_classes/textbubble_test.cpp index 77f30bc15..e07455a6d 100644 --- a/test/scratch_classes/textbubble_test.cpp +++ b/test/scratch_classes/textbubble_test.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "../common.h" @@ -33,15 +34,23 @@ TEST(TextBubbleTest, BubbleText) { TextBubble bubble; ASSERT_TRUE(bubble.text().empty()); + ASSERT_EQ(bubble.owner(), nullptr); bubble.setText("hello"); ASSERT_EQ(bubble.text(), "hello"); + ASSERT_EQ(bubble.owner(), nullptr); + + Thread thread(nullptr, nullptr, nullptr); + bubble.setText("test", &thread); + ASSERT_EQ(bubble.text(), "test"); + ASSERT_EQ(bubble.owner(), &thread); EngineMock engine; bubble.setEngine(&engine); EXPECT_CALL(engine, moveDrawableToFront(&bubble)); bubble.setText("world"); ASSERT_EQ(bubble.text(), "world"); + ASSERT_EQ(bubble.owner(), nullptr); bubble.setEngine(nullptr); // longstr.length = 384, should be limited to 330 in bubble text From 95d1a8692bd357e98b05af6b0e393c5d9256f3ba Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:42:50 +0100 Subject: [PATCH 04/43] Implement looks_sayforsecs block --- src/blocks/looksblocks.cpp | 86 +++++++++++++++++++++- src/blocks/looksblocks.h | 9 +++ test/blocks/looks_blocks_test.cpp | 114 +++++++++++++++++++++++++++++- 3 files changed, 206 insertions(+), 3 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index dd757492f..0b7886cf0 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -1,8 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "looksblocks.h" using namespace libscratchcpp; @@ -24,10 +32,24 @@ Rgb LooksBlocks::color() const void LooksBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "looks_sayforsecs", &compileSayForSecs); } void LooksBlocks::onInit(IEngine *engine) { + engine->threadAboutToStop().connect([](Thread *thread) { + /* + * TODO: Scratch uses promises for text bubble timeout + * which clear the text bubbles even after the thread + * destroyed. Since we don't use promises, we just + * clear the text bubble when the thread is destroyed. + */ + Target *target = thread->target(); + + if (target->bubble()->owner() == thread) + target->bubble()->setText(""); + }); + engine->stopped().connect([engine]() { const auto &targets = engine->targets(); @@ -37,3 +59,63 @@ void LooksBlocks::onInit(IEngine *engine) } }); } + +void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string function) +{ + auto message = compiler->addInput("MESSAGE"); + auto duration = compiler->addInput("SECS"); + auto saveThread = compiler->addConstValue(true); + compiler->addFunctionCallWithCtx(function, Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { message, saveThread }); + compiler->addFunctionCallWithCtx("looks_start_stack_timer", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration }); + compiler->createYield(); + + compiler->beginLoopCondition(); + auto elapsed = compiler->addFunctionCallWithCtx("looks_update_bubble", Compiler::StaticType::Bool); + compiler->beginRepeatUntilLoop(elapsed); + compiler->endLoop(); +} + +CompilerValue *LooksBlocks::compileSayForSecs(Compiler *compiler) +{ + compileSayOrThinkForSecs(compiler, "looks_say"); + return nullptr; +} + +extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) +{ + ctx->stackTimer()->start(duration); +} + +void looks_show_bubble(Thread *thread, TextBubble::Type type, const StringPtr *message, bool saveThread) +{ + Target *target = thread->target(); + // TODO: Use UTF-16 for text bubbles + std::string u8message = utf8::utf16to8(std::u16string(message->data)); + target->bubble()->setType(type); + + if (saveThread) + target->bubble()->setText(u8message, thread); + else + target->bubble()->setText(u8message); +} + +extern "C" bool looks_update_bubble(ExecutionContext *ctx) +{ + if (ctx->stackTimer()->elapsed()) { + Thread *thread = ctx->thread(); + Target *target = thread->target(); + + // Clear bubble if it hasn't been changed + if (target->bubble()->owner() == thread) + target->bubble()->setText(""); + + return true; + } + + return false; +} + +extern "C" void looks_say(ExecutionContext *ctx, const StringPtr *message, bool saveThread) +{ + looks_show_bubble(ctx->thread(), TextBubble::Type::Say, message, saveThread); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 0a46bc495..d3626292c 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -3,10 +3,15 @@ #pragma once #include +#include +#include namespace libscratchcpp { +class Target; +class Thread; + class LooksBlocks : public IExtension { public: @@ -16,6 +21,10 @@ class LooksBlocks : public IExtension void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; + + private: + static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function); + static CompilerValue *compileSayForSecs(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 52633de00..527cce9c2 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -2,14 +2,20 @@ #include #include #include -#include +#include +#include +#include +#include +#include #include #include +#include #include "../common.h" #include "blocks/looksblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; using ::testing::Return; @@ -50,3 +56,109 @@ TEST_F(LooksBlocksTest, StopProject) ASSERT_EQ(stage->graphicsEffectValue(&effect), 0); ASSERT_EQ(sprite->graphicsEffectValue(&effect), 0); } + +TEST_F(LooksBlocksTest, SayForSecs) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_sayforsecs"); + builder.addValueInput("MESSAGE", "Hello world"); + builder.addValueInput("SECS", 2.5); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_TRUE(sprite->bubble()->text().empty()); + + // Change text while waiting + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite->bubble()->setText("test"); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "test"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite->bubble()->setText("Hello world"); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + + // Kill waiting thread + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + m_engine->threadAboutToStop()(&thread); + code->kill(ctx.get()); + ASSERT_EQ(sprite->bubble()->owner(), nullptr); + ASSERT_TRUE(sprite->bubble()->text().empty()); + + // Kill waiting thread after changing text + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite->bubble()->setText("test"); + + m_engine->threadAboutToStop()(&thread); + code->kill(ctx.get()); + ASSERT_EQ(sprite->bubble()->owner(), nullptr); + ASSERT_EQ(sprite->bubble()->text(), "test"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); +} From b544b744d5606046393d3d6a62624a42132d9713 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:54:02 +0100 Subject: [PATCH 05/43] Implement looks_say block --- src/blocks/looksblocks.cpp | 9 +++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 16 ++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 0b7886cf0..ae545250b 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -33,6 +33,7 @@ Rgb LooksBlocks::color() const void LooksBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "looks_sayforsecs", &compileSayForSecs); + engine->addCompileFunction(this, "looks_say", &compileSay); } void LooksBlocks::onInit(IEngine *engine) @@ -81,6 +82,14 @@ CompilerValue *LooksBlocks::compileSayForSecs(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileSay(Compiler *compiler) +{ + auto message = compiler->addInput("MESSAGE"); + auto saveThread = compiler->addConstValue(false); + compiler->addFunctionCallWithCtx("looks_say", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { message, saveThread }); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index d3626292c..241f63088 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -25,6 +25,7 @@ class LooksBlocks : public IExtension private: static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function); static CompilerValue *compileSayForSecs(Compiler *compiler); + static CompilerValue *compileSay(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 527cce9c2..39dc8ede6 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -162,3 +162,19 @@ TEST_F(LooksBlocksTest, SayForSecs) ASSERT_EQ(sprite->bubble()->text(), "test"); ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); } + +TEST_F(LooksBlocksTest, Say) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_say"); + builder.addValueInput("MESSAGE", "Hello world"); + + builder.build(); + builder.run(); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + ASSERT_EQ(sprite->bubble()->owner(), nullptr); +} From 10b50acf75d59633534284eee689ea753f609976 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:01:53 +0100 Subject: [PATCH 06/43] Engine: Fix use after free when using removed clone threads All threads are copied to m_threadsToStop, including clone threads that are removed later which can lead to use after free when the threadAboutToStop signal is emitted for these threads. --- src/engine/internal/engine.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 265e7e587..d3f859ddf 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -321,17 +321,13 @@ void Engine::stop() // https://github.com/scratchfoundation/scratch-vm/blob/f1aa92fad79af17d9dd1c41eeeadca099339a9f1/src/engine/runtime.js#L2057-L2081 if (m_activeThread) { stopThread(m_activeThread.get()); - // NOTE: The project should continue running even after "stop all" is called and the remaining threads should be stepped once. - // The remaining threads can even start new threads which will ignore the "stop all" call and will "restart" the project. - // This is probably a bug in the Scratch VM, but let's keep it here to keep it compatible. - m_threadsToStop = m_threads; // Remove threads owned by clones because clones are going to be deleted (#547) m_threads.erase( std::remove_if( m_threads.begin(), m_threads.end(), - [](std::shared_ptr thread) { + [this](std::shared_ptr thread) { assert(thread); Target *target = thread->target(); assert(target); @@ -339,13 +335,20 @@ void Engine::stop() if (!target->isStage()) { Sprite *sprite = static_cast(target); - if (sprite->isClone()) + if (sprite->isClone()) { + m_threadAboutToStop(thread.get()); return true; + } } return false; }), m_threads.end()); + + // NOTE: The project should continue running even after "stop all" is called and the remaining threads should be stepped once. + // The remaining threads can even start new threads which will ignore the "stop all" call and will "restart" the project. + // This is probably a bug in the Scratch VM, but let's keep it here to keep it compatible. + m_threadsToStop = m_threads; } else { // If there isn't any active thread, it means the project was stopped from the outside // In this case all threads should be removed and the project should be considered stopped From 776efe965d0dd2bd84a497a7d9c57ed78371816f Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:14:26 +0100 Subject: [PATCH 07/43] Implement looks_thinkforsecs block --- src/blocks/looksblocks.cpp | 12 ++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 216 ++++++++++++++++-------------- 3 files changed, 126 insertions(+), 103 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index ae545250b..bd8bc7c8a 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -34,6 +34,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "looks_sayforsecs", &compileSayForSecs); engine->addCompileFunction(this, "looks_say", &compileSay); + engine->addCompileFunction(this, "looks_thinkforsecs", &compileThinkForSecs); } void LooksBlocks::onInit(IEngine *engine) @@ -90,6 +91,12 @@ CompilerValue *LooksBlocks::compileSay(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileThinkForSecs(Compiler *compiler) +{ + compileSayOrThinkForSecs(compiler, "looks_think"); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -128,3 +135,8 @@ extern "C" void looks_say(ExecutionContext *ctx, const StringPtr *message, bool { looks_show_bubble(ctx->thread(), TextBubble::Type::Say, message, saveThread); } + +extern "C" void looks_think(ExecutionContext *ctx, const StringPtr *message, bool saveThread) +{ + looks_show_bubble(ctx->thread(), TextBubble::Type::Think, message, saveThread); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 241f63088..45d778f95 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -26,6 +26,7 @@ class LooksBlocks : public IExtension static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function); static CompilerValue *compileSayForSecs(Compiler *compiler); static CompilerValue *compileSay(Compiler *compiler); + static CompilerValue *compileThinkForSecs(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 39dc8ede6..cf338f3e3 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -57,110 +57,120 @@ TEST_F(LooksBlocksTest, StopProject) ASSERT_EQ(sprite->graphicsEffectValue(&effect), 0); } -TEST_F(LooksBlocksTest, SayForSecs) +TEST_F(LooksBlocksTest, SayAndThinkForSecs) { - auto sprite = std::make_shared(); - sprite->setEngine(&m_engineMock); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - - builder.addBlock("looks_sayforsecs"); - builder.addValueInput("MESSAGE", "Hello world"); - builder.addValueInput("SECS", 2.5); - auto block = builder.currentBlock(); - - Compiler compiler(&m_engineMock, sprite.get()); - auto code = compiler.compile(block); - Script script(sprite.get(), block, &m_engineMock); - script.setCode(code); - Thread thread(sprite.get(), &m_engineMock, &script); - auto ctx = code->createExecutionContext(&thread); - StackTimerMock timer; - ctx->setStackTimer(&timer); - - EXPECT_CALL(timer, start(2.5)); - EXPECT_CALL(m_engineMock, requestRedraw()); - code->run(ctx.get()); - ASSERT_FALSE(code->isFinished(ctx.get())); - ASSERT_EQ(sprite->bubble()->text(), "Hello world"); - ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); - - EXPECT_CALL(timer, elapsed()).WillOnce(Return(false)); - EXPECT_CALL(m_engineMock, requestRedraw).Times(0); - code->run(ctx.get()); - ASSERT_FALSE(code->isFinished(ctx.get())); - ASSERT_EQ(sprite->bubble()->text(), "Hello world"); - ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); - - EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); - EXPECT_CALL(m_engineMock, requestRedraw).Times(0); - code->run(ctx.get()); - ASSERT_TRUE(code->isFinished(ctx.get())); - ASSERT_TRUE(sprite->bubble()->text().empty()); - - // Change text while waiting - code->reset(ctx.get()); - - EXPECT_CALL(timer, start(2.5)); - EXPECT_CALL(m_engineMock, requestRedraw()); - code->run(ctx.get()); - ASSERT_FALSE(code->isFinished(ctx.get())); - - EXPECT_CALL(m_engineMock, requestRedraw()); - sprite->bubble()->setText("test"); - - EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); - EXPECT_CALL(m_engineMock, requestRedraw).Times(0); - code->run(ctx.get()); - ASSERT_TRUE(code->isFinished(ctx.get())); - ASSERT_EQ(sprite->bubble()->text(), "test"); - ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); - - code->reset(ctx.get()); - - EXPECT_CALL(timer, start(2.5)); - EXPECT_CALL(m_engineMock, requestRedraw()); - code->run(ctx.get()); - ASSERT_FALSE(code->isFinished(ctx.get())); - - EXPECT_CALL(m_engineMock, requestRedraw()); - sprite->bubble()->setText("Hello world"); - - EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); - EXPECT_CALL(m_engineMock, requestRedraw).Times(0); - code->run(ctx.get()); - ASSERT_TRUE(code->isFinished(ctx.get())); - ASSERT_EQ(sprite->bubble()->text(), "Hello world"); - ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); - - // Kill waiting thread - code->reset(ctx.get()); - - EXPECT_CALL(timer, start(2.5)); - EXPECT_CALL(m_engineMock, requestRedraw()); - code->run(ctx.get()); - ASSERT_FALSE(code->isFinished(ctx.get())); - - m_engine->threadAboutToStop()(&thread); - code->kill(ctx.get()); - ASSERT_EQ(sprite->bubble()->owner(), nullptr); - ASSERT_TRUE(sprite->bubble()->text().empty()); - - // Kill waiting thread after changing text - code->reset(ctx.get()); - - EXPECT_CALL(timer, start(2.5)); - EXPECT_CALL(m_engineMock, requestRedraw()); - code->run(ctx.get()); - ASSERT_FALSE(code->isFinished(ctx.get())); - - EXPECT_CALL(m_engineMock, requestRedraw()); - sprite->bubble()->setText("test"); - - m_engine->threadAboutToStop()(&thread); - code->kill(ctx.get()); - ASSERT_EQ(sprite->bubble()->owner(), nullptr); - ASSERT_EQ(sprite->bubble()->text(), "test"); - ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); + std::vector types = { TextBubble::Type::Say, TextBubble::Type::Think }; + std::vector opcodes = { "looks_sayforsecs", "looks_thinkforsecs" }; + + for (int i = 0; i < types.size(); i++) { + TextBubble::Type type = types[i]; + const std::string &opcode = opcodes[i]; + + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + m_engine->clear(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock(opcode); + builder.addValueInput("MESSAGE", "Hello world"); + builder.addValueInput("SECS", 2.5); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), type); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), type); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_TRUE(sprite->bubble()->text().empty()); + + // Change text while waiting + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite->bubble()->setText("test"); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "test"); + ASSERT_EQ(sprite->bubble()->type(), type); + + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite->bubble()->setText("Hello world"); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), type); + + // Kill waiting thread + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + m_engine->threadAboutToStop()(&thread); + code->kill(ctx.get()); + ASSERT_EQ(sprite->bubble()->owner(), nullptr); + ASSERT_TRUE(sprite->bubble()->text().empty()); + + // Kill waiting thread after changing text + code->reset(ctx.get()); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite->bubble()->setText("test"); + + m_engine->threadAboutToStop()(&thread); + code->kill(ctx.get()); + ASSERT_EQ(sprite->bubble()->owner(), nullptr); + ASSERT_EQ(sprite->bubble()->text(), "test"); + ASSERT_EQ(sprite->bubble()->type(), type); + } } TEST_F(LooksBlocksTest, Say) From b9a72c5feed3e73c78e6e3dc91fd5fe9c0fc11c6 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:24:40 +0100 Subject: [PATCH 08/43] Implement looks_think block --- src/blocks/looksblocks.cpp | 9 +++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 32 ++++++++++++++++++++----------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index bd8bc7c8a..d901e6235 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -35,6 +35,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_sayforsecs", &compileSayForSecs); engine->addCompileFunction(this, "looks_say", &compileSay); engine->addCompileFunction(this, "looks_thinkforsecs", &compileThinkForSecs); + engine->addCompileFunction(this, "looks_think", &compileThink); } void LooksBlocks::onInit(IEngine *engine) @@ -97,6 +98,14 @@ CompilerValue *LooksBlocks::compileThinkForSecs(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileThink(Compiler *compiler) +{ + auto message = compiler->addInput("MESSAGE"); + auto saveThread = compiler->addConstValue(false); + compiler->addFunctionCallWithCtx("looks_think", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { message, saveThread }); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 45d778f95..ab14959ed 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -27,6 +27,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSayForSecs(Compiler *compiler); static CompilerValue *compileSay(Compiler *compiler); static CompilerValue *compileThinkForSecs(Compiler *compiler); + static CompilerValue *compileThink(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index cf338f3e3..bd8a11434 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -173,18 +173,28 @@ TEST_F(LooksBlocksTest, SayAndThinkForSecs) } } -TEST_F(LooksBlocksTest, Say) +TEST_F(LooksBlocksTest, SayAndThink) { - auto sprite = std::make_shared(); - sprite->setEngine(&m_engineMock); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + std::vector types = { TextBubble::Type::Say, TextBubble::Type::Think }; + std::vector opcodes = { "looks_say", "looks_think" }; + + for (int i = 0; i < types.size(); i++) { + TextBubble::Type type = types[i]; + const std::string &opcode = opcodes[i]; + + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + m_engine->clear(); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.addBlock("looks_say"); - builder.addValueInput("MESSAGE", "Hello world"); + builder.addBlock(opcode); + builder.addValueInput("MESSAGE", "Hello world"); - builder.build(); - builder.run(); - ASSERT_EQ(sprite->bubble()->text(), "Hello world"); - ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say); - ASSERT_EQ(sprite->bubble()->owner(), nullptr); + builder.build(); + builder.run(); + ASSERT_EQ(sprite->bubble()->text(), "Hello world"); + ASSERT_EQ(sprite->bubble()->type(), type); + ASSERT_EQ(sprite->bubble()->owner(), nullptr); + } } From 36d4f2921810d74fc61de91d8b5c9518c3c103a4 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 10:21:34 +0100 Subject: [PATCH 09/43] Implement looks_show block --- src/blocks/looksblocks.cpp | 14 ++++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index d901e6235..26cda661d 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -36,6 +36,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_say", &compileSay); engine->addCompileFunction(this, "looks_thinkforsecs", &compileThinkForSecs); engine->addCompileFunction(this, "looks_think", &compileThink); + engine->addCompileFunction(this, "looks_show", &compileShow); } void LooksBlocks::onInit(IEngine *engine) @@ -106,6 +107,14 @@ CompilerValue *LooksBlocks::compileThink(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileShow(Compiler *compiler) +{ + if (!compiler->target()->isStage()) + compiler->addTargetFunctionCall("looks_show"); + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -149,3 +158,8 @@ extern "C" void looks_think(ExecutionContext *ctx, const StringPtr *message, boo { looks_show_bubble(ctx->thread(), TextBubble::Type::Think, message, saveThread); } + +extern "C" void looks_show(Sprite *sprite) +{ + sprite->setVisible(true); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index ab14959ed..9604c2892 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -28,6 +28,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSay(Compiler *compiler); static CompilerValue *compileThinkForSecs(Compiler *compiler); static CompilerValue *compileThink(Compiler *compiler); + static CompilerValue *compileShow(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index bd8a11434..b206734ac 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -198,3 +198,34 @@ TEST_F(LooksBlocksTest, SayAndThink) ASSERT_EQ(sprite->bubble()->owner(), nullptr); } } + +TEST_F(LooksBlocksTest, Show) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_show"); + builder.build(); + + sprite->setVisible(false); + + builder.run(); + ASSERT_TRUE(sprite->visible()); + + builder.run(); + ASSERT_TRUE(sprite->visible()); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_show"); + + builder.build(); + builder.run(); + } +} From d6f10af789a6da610619d4ee9a30e610a278570e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 10:23:31 +0100 Subject: [PATCH 10/43] Implement looks_hide block --- src/blocks/looksblocks.cpp | 14 ++++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 26cda661d..f35fd944c 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -37,6 +37,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_thinkforsecs", &compileThinkForSecs); engine->addCompileFunction(this, "looks_think", &compileThink); engine->addCompileFunction(this, "looks_show", &compileShow); + engine->addCompileFunction(this, "looks_hide", &compileHide); } void LooksBlocks::onInit(IEngine *engine) @@ -115,6 +116,14 @@ CompilerValue *LooksBlocks::compileShow(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileHide(Compiler *compiler) +{ + if (!compiler->target()->isStage()) + compiler->addTargetFunctionCall("looks_hide"); + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -163,3 +172,8 @@ extern "C" void looks_show(Sprite *sprite) { sprite->setVisible(true); } + +extern "C" void looks_hide(Sprite *sprite) +{ + sprite->setVisible(false); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 9604c2892..469d6b22a 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -29,6 +29,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileThinkForSecs(Compiler *compiler); static CompilerValue *compileThink(Compiler *compiler); static CompilerValue *compileShow(Compiler *compiler); + static CompilerValue *compileHide(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index b206734ac..29748031f 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -229,3 +229,34 @@ TEST_F(LooksBlocksTest, Show) builder.run(); } } + +TEST_F(LooksBlocksTest, Hide) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_hide"); + builder.build(); + + sprite->setVisible(true); + + builder.run(); + ASSERT_FALSE(sprite->visible()); + + builder.run(); + ASSERT_FALSE(sprite->visible()); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_hide"); + + builder.build(); + builder.run(); + } +} From 207adcd8e3ced34fa1bd11b32938c5eb4edcfce3 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:06:16 +0100 Subject: [PATCH 11/43] Implement looks_changeeffectby block --- src/blocks/looksblocks.cpp | 61 +++++++++++++ src/blocks/looksblocks.h | 13 +++ test/blocks/looks_blocks_test.cpp | 144 ++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index f35fd944c..a8bf46eaa 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -9,12 +9,21 @@ #include #include #include +#include +#include +#include #include #include "looksblocks.h" using namespace libscratchcpp; +LooksBlocks::~LooksBlocks() +{ + if (m_engine) + m_instances.erase(m_engine); +} + std::string LooksBlocks::name() const { return "Looks"; @@ -38,6 +47,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_think", &compileThink); engine->addCompileFunction(this, "looks_show", &compileShow); engine->addCompileFunction(this, "looks_hide", &compileHide); + engine->addCompileFunction(this, "looks_changeeffectby", &compileChangeEffectBy); } void LooksBlocks::onInit(IEngine *engine) @@ -63,6 +73,9 @@ void LooksBlocks::onInit(IEngine *engine) target->clearGraphicsEffects(); } }); + + m_engine = engine; + m_instances[engine] = this; } void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string function) @@ -80,6 +93,30 @@ void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string compiler->endLoop(); } +long LooksBlocks::getEffectIndex(IEngine *engine, const std::string &name) +{ + assert(engine); + assert(m_instances.find(engine) != m_instances.cend()); + + LooksBlocks *instance = m_instances[engine]; + auto it = instance->m_effectMap.find(name); + + if (it == instance->m_effectMap.cend()) { + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect(name); + + if (effect) { + instance->m_effects.push_back(effect); + instance->m_effectMap[name] = instance->m_effects.size() - 1; + return instance->m_effects.size() - 1; + } else { + std::cout << "warning: graphic effect '" << name << "' is not registered" << std::endl; + return -1; + } + } + + return it->second; +} + CompilerValue *LooksBlocks::compileSayForSecs(Compiler *compiler) { compileSayOrThinkForSecs(compiler, "looks_say"); @@ -124,6 +161,24 @@ CompilerValue *LooksBlocks::compileHide(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileChangeEffectBy(Compiler *compiler) +{ + Field *field = compiler->field("EFFECT"); + + if (!field) + return nullptr; + + auto index = getEffectIndex(compiler->engine(), field->value().toString()); + + if (index != -1) { + auto indexValue = compiler->addConstValue(index); + auto change = compiler->addInput("CHANGE"); + compiler->addTargetFunctionCall("looks_changeeffectby", Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { indexValue, change }); + } + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -177,3 +232,9 @@ extern "C" void looks_hide(Sprite *sprite) { sprite->setVisible(false); } + +extern "C" void looks_changeeffectby(Target *target, double index, double change) +{ + IGraphicsEffect *effect = LooksBlocks::getEffect(target->engine(), index); + target->setGraphicsEffectValue(effect, target->graphicsEffectValue(effect) + change); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 469d6b22a..6196bcc5c 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -11,10 +11,13 @@ namespace libscratchcpp class Target; class Thread; +class IGraphicsEffect; class LooksBlocks : public IExtension { public: + ~LooksBlocks(); + std::string name() const override; std::string description() const override; Rgb color() const override; @@ -22,14 +25,24 @@ class LooksBlocks : public IExtension void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; + static inline IGraphicsEffect *getEffect(IEngine *engine, long index) { return m_instances[engine]->m_effects[index]; } + private: static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function); + static long getEffectIndex(IEngine *engine, const std::string &name); + static CompilerValue *compileSayForSecs(Compiler *compiler); static CompilerValue *compileSay(Compiler *compiler); static CompilerValue *compileThinkForSecs(Compiler *compiler); static CompilerValue *compileThink(Compiler *compiler); static CompilerValue *compileShow(Compiler *compiler); static CompilerValue *compileHide(Compiler *compiler); + static CompilerValue *compileChangeEffectBy(Compiler *compiler); + + IEngine *m_engine = nullptr; + std::unordered_map m_effectMap; + std::vector m_effects; + static inline std::unordered_map m_instances; }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 29748031f..18eb13442 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,8 @@ using namespace libscratchcpp; using namespace libscratchcpp::test; using ::testing::Return; +using ::testing::ReturnArg; +using ::testing::_; class LooksBlocksTest : public testing::Test { @@ -28,6 +31,56 @@ class LooksBlocksTest : public testing::Test m_engine = m_project.engine().get(); m_extension->registerBlocks(m_engine); m_extension->onInit(m_engine); + + // Create and register fake graphic effects + auto colorEffect = std::make_shared(); + auto fisheyeEffect = std::make_shared(); + auto whirlEffect = std::make_shared(); + auto pixelateEffect = std::make_shared(); + auto mosaicEffect = std::make_shared(); + auto brightnessEffect = std::make_shared(); + auto ghostEffect = std::make_shared(); + + EXPECT_CALL(*colorEffect, name()).WillOnce(Return("COLOR")); + ScratchConfiguration::registerGraphicsEffect(colorEffect); + + EXPECT_CALL(*fisheyeEffect, name()).WillOnce(Return("FISHEYE")); + ScratchConfiguration::registerGraphicsEffect(fisheyeEffect); + + EXPECT_CALL(*whirlEffect, name()).WillOnce(Return("WHIRL")); + ScratchConfiguration::registerGraphicsEffect(whirlEffect); + + EXPECT_CALL(*pixelateEffect, name()).WillOnce(Return("PIXELATE")); + ScratchConfiguration::registerGraphicsEffect(pixelateEffect); + + EXPECT_CALL(*mosaicEffect, name()).WillOnce(Return("MOSAIC")); + ScratchConfiguration::registerGraphicsEffect(mosaicEffect); + + EXPECT_CALL(*brightnessEffect, name()).WillOnce(Return("BRIGHTNESS")); + ScratchConfiguration::registerGraphicsEffect(brightnessEffect); + + EXPECT_CALL(*ghostEffect, name()).WillOnce(Return("GHOST")); + ScratchConfiguration::registerGraphicsEffect(ghostEffect); + + EXPECT_CALL(*colorEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + EXPECT_CALL(*fisheyeEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + EXPECT_CALL(*whirlEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + EXPECT_CALL(*pixelateEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + EXPECT_CALL(*mosaicEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + EXPECT_CALL(*brightnessEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + EXPECT_CALL(*ghostEffect, clamp(_)).WillRepeatedly(ReturnArg<0>()); + } + + void TearDown() override + { + // Remove fake graphic effects + ScratchConfiguration::removeGraphicsEffect("COLOR"); + ScratchConfiguration::removeGraphicsEffect("FISHEYE"); + ScratchConfiguration::removeGraphicsEffect("WHIRL"); + ScratchConfiguration::removeGraphicsEffect("PIXELATE"); + ScratchConfiguration::removeGraphicsEffect("MOSAIC"); + ScratchConfiguration::removeGraphicsEffect("BRIGHTNESS"); + ScratchConfiguration::removeGraphicsEffect("GHOST"); } std::unique_ptr m_extension; @@ -260,3 +313,94 @@ TEST_F(LooksBlocksTest, Hide) builder.run(); } } + +TEST_F(LooksBlocksTest, ChangeEffectBy) +{ + // Color + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("COLOR"); + ASSERT_TRUE(effect); + + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "COLOR"); + builder.addValueInput("CHANGE", 45.2); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); + + stage->setGraphicsEffectValue(effect, 86.84); + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 132.04); + } + + // Brightness + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("BRIGHTNESS"); + ASSERT_TRUE(effect); + + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "BRIGHTNESS"); + builder.addValueInput("CHANGE", 12.05); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); + + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 12.05); + } + + // Ghost + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("GHOST"); + ASSERT_TRUE(effect); + + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "GHOST"); + builder.addValueInput("CHANGE", -8.06); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); + + stage->setGraphicsEffectValue(effect, 13.12); + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 5.06); + } + + // Invalid + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "INVALID"); + builder.addValueInput("CHANGE", 8.3); + + builder.build(); + builder.run(); + } +} From 576f7a0e018480f56f36588f979cb3dfc547a0cc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:20:26 +0100 Subject: [PATCH 12/43] Implement looks_seteffectto block --- src/blocks/looksblocks.cpp | 24 ++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 91 +++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index a8bf46eaa..36c45b8c2 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -48,6 +48,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_show", &compileShow); engine->addCompileFunction(this, "looks_hide", &compileHide); engine->addCompileFunction(this, "looks_changeeffectby", &compileChangeEffectBy); + engine->addCompileFunction(this, "looks_seteffectto", &compileSetEffectTo); } void LooksBlocks::onInit(IEngine *engine) @@ -179,6 +180,24 @@ CompilerValue *LooksBlocks::compileChangeEffectBy(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileSetEffectTo(Compiler *compiler) +{ + Field *field = compiler->field("EFFECT"); + + if (!field) + return nullptr; + + auto index = getEffectIndex(compiler->engine(), field->value().toString()); + + if (index != -1) { + auto indexValue = compiler->addConstValue(index); + auto value = compiler->addInput("VALUE"); + compiler->addTargetFunctionCall("looks_seteffectto", Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { indexValue, value }); + } + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -238,3 +257,8 @@ extern "C" void looks_changeeffectby(Target *target, double index, double change IGraphicsEffect *effect = LooksBlocks::getEffect(target->engine(), index); target->setGraphicsEffectValue(effect, target->graphicsEffectValue(effect) + change); } + +extern "C" void looks_seteffectto(Target *target, double index, double value) +{ + target->setGraphicsEffectValue(LooksBlocks::getEffect(target->engine(), index), value); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 6196bcc5c..7f1f5d5f3 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -38,6 +38,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileShow(Compiler *compiler); static CompilerValue *compileHide(Compiler *compiler); static CompilerValue *compileChangeEffectBy(Compiler *compiler); + static CompilerValue *compileSetEffectTo(Compiler *compiler); IEngine *m_engine = nullptr; std::unordered_map m_effectMap; diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 18eb13442..8673396e7 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -404,3 +404,94 @@ TEST_F(LooksBlocksTest, ChangeEffectBy) builder.run(); } } + +TEST_F(LooksBlocksTest, SetEffectTo) +{ + // Fisheye + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("FISHEYE"); + ASSERT_TRUE(effect); + + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "FISHEYE"); + builder.addValueInput("VALUE", 45.2); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); + + stage->setGraphicsEffectValue(effect, 86.84); + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 45.2); + } + + // Pixelate + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("PIXELATE"); + ASSERT_TRUE(effect); + + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "PIXELATE"); + builder.addValueInput("VALUE", 12.05); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); + + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 12.05); + } + + // Mosaic + { + auto sprite = std::make_shared(); + sprite->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("MOSAIC"); + ASSERT_TRUE(effect); + + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "MOSAIC"); + builder.addValueInput("VALUE", -8.06); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, m_engine); + script.setCode(code); + Thread thread(sprite.get(), m_engine, &script); + + sprite->setGraphicsEffectValue(effect, 13.12); + thread.run(); + ASSERT_EQ(std::round(sprite->graphicsEffectValue(effect) * 100) / 100, -8.06); + } + + // Invalid + { + auto stage = std::make_shared(); + stage->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "INVALID"); + builder.addValueInput("VALUE", 8.3); + + builder.build(); + builder.run(); + } +} From 0267704d4254b61adc5e380d7f8f78a7b6afd18b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:29:29 +0100 Subject: [PATCH 13/43] Implement looks_cleargraphiceffects --- src/blocks/looksblocks.cpp | 12 ++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 36c45b8c2..3f0a45038 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -49,6 +49,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_hide", &compileHide); engine->addCompileFunction(this, "looks_changeeffectby", &compileChangeEffectBy); engine->addCompileFunction(this, "looks_seteffectto", &compileSetEffectTo); + engine->addCompileFunction(this, "looks_cleargraphiceffects", &compileClearGraphicEffects); } void LooksBlocks::onInit(IEngine *engine) @@ -198,6 +199,12 @@ CompilerValue *LooksBlocks::compileSetEffectTo(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileClearGraphicEffects(Compiler *compiler) +{ + compiler->addTargetFunctionCall("looks_cleargraphiceffects"); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -262,3 +269,8 @@ extern "C" void looks_seteffectto(Target *target, double index, double value) { target->setGraphicsEffectValue(LooksBlocks::getEffect(target->engine(), index), value); } + +extern "C" void looks_cleargraphiceffects(Target *target) +{ + target->clearGraphicsEffects(); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 7f1f5d5f3..31e87d73b 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -39,6 +39,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileHide(Compiler *compiler); static CompilerValue *compileChangeEffectBy(Compiler *compiler); static CompilerValue *compileSetEffectTo(Compiler *compiler); + static CompilerValue *compileClearGraphicEffects(Compiler *compiler); IEngine *m_engine = nullptr; std::unordered_map m_effectMap; diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 8673396e7..7f628237b 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -495,3 +495,34 @@ TEST_F(LooksBlocksTest, SetEffectTo) builder.run(); } } + +TEST_F(LooksBlocksTest, ClearGraphicEffects) +{ + auto sprite = std::make_shared(); + sprite->setEngine(m_engine); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + IGraphicsEffect *effect1 = ScratchConfiguration::getGraphicsEffect("WHIRL"); + IGraphicsEffect *effect2 = ScratchConfiguration::getGraphicsEffect("GHOST"); + IGraphicsEffect *effect3 = ScratchConfiguration::getGraphicsEffect("MOSAIC"); + ASSERT_TRUE(effect1); + ASSERT_TRUE(effect2); + ASSERT_TRUE(effect3); + + builder.addBlock("looks_cleargraphiceffects"); + auto block = builder.currentBlock(); + + Compiler compiler(m_engine, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, m_engine); + script.setCode(code); + Thread thread(sprite.get(), m_engine, &script); + + sprite->setGraphicsEffectValue(effect1, 86.84); + sprite->setGraphicsEffectValue(effect2, -5.18); + sprite->setGraphicsEffectValue(effect3, 12.98); + thread.run(); + ASSERT_EQ(sprite->graphicsEffectValue(effect1), 0); + ASSERT_EQ(sprite->graphicsEffectValue(effect2), 0); + ASSERT_EQ(sprite->graphicsEffectValue(effect3), 0); +} From 0faa70f8932bfb8c103bb51cb376fefdba760b97 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:49:37 +0100 Subject: [PATCH 14/43] Implement looks_changesizeby block --- src/blocks/looksblocks.cpp | 16 ++++++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 3f0a45038..7c8f33996 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -50,6 +50,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_changeeffectby", &compileChangeEffectBy); engine->addCompileFunction(this, "looks_seteffectto", &compileSetEffectTo); engine->addCompileFunction(this, "looks_cleargraphiceffects", &compileClearGraphicEffects); + engine->addCompileFunction(this, "looks_changesizeby", &compileChangeSizeBy); } void LooksBlocks::onInit(IEngine *engine) @@ -205,6 +206,16 @@ CompilerValue *LooksBlocks::compileClearGraphicEffects(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileChangeSizeBy(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + auto change = compiler->addInput("CHANGE"); + compiler->addTargetFunctionCall("looks_changesizeby", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { change }); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -274,3 +285,8 @@ extern "C" void looks_cleargraphiceffects(Target *target) { target->clearGraphicsEffects(); } + +extern "C" void looks_changesizeby(Sprite *sprite, double change) +{ + sprite->setSize(sprite->size() + change); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 31e87d73b..e4d65b10f 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -40,6 +40,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileChangeEffectBy(Compiler *compiler); static CompilerValue *compileSetEffectTo(Compiler *compiler); static CompilerValue *compileClearGraphicEffects(Compiler *compiler); + static CompilerValue *compileChangeSizeBy(Compiler *compiler); IEngine *m_engine = nullptr; std::unordered_map m_effectMap; diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 7f628237b..3bde0121a 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -526,3 +526,31 @@ TEST_F(LooksBlocksTest, ClearGraphicEffects) ASSERT_EQ(sprite->graphicsEffectValue(effect2), 0); ASSERT_EQ(sprite->graphicsEffectValue(effect3), 0); } + +TEST_F(LooksBlocksTest, ChangeSizeBy) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_changesizeby"); + builder.addValueInput("CHANGE", 2.5); + builder.build(); + + sprite->setEngine(nullptr); + sprite->setSize(45.62); + builder.run(); + ASSERT_EQ(sprite->size(), 48.12); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_changesizeby"); + builder.addValueInput("CHANGE", 2.5); + + builder.build(); + builder.run(); + } +} From 091b14a8d8fd90a1c6c0d19bd89a804e336229c9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:53:06 +0100 Subject: [PATCH 15/43] Implement looks_setsizeto block --- src/blocks/looksblocks.cpp | 16 ++++++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 7c8f33996..fe3cb3ba8 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -51,6 +51,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_seteffectto", &compileSetEffectTo); engine->addCompileFunction(this, "looks_cleargraphiceffects", &compileClearGraphicEffects); engine->addCompileFunction(this, "looks_changesizeby", &compileChangeSizeBy); + engine->addCompileFunction(this, "looks_setsizeto", &compileSetSizeTo); } void LooksBlocks::onInit(IEngine *engine) @@ -216,6 +217,16 @@ CompilerValue *LooksBlocks::compileChangeSizeBy(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileSetSizeTo(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + auto size = compiler->addInput("SIZE"); + compiler->addTargetFunctionCall("looks_setsizeto", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { size }); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -290,3 +301,8 @@ extern "C" void looks_changesizeby(Sprite *sprite, double change) { sprite->setSize(sprite->size() + change); } + +extern "C" void looks_setsizeto(Sprite *sprite, double size) +{ + sprite->setSize(size); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index e4d65b10f..a6c568737 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -41,6 +41,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSetEffectTo(Compiler *compiler); static CompilerValue *compileClearGraphicEffects(Compiler *compiler); static CompilerValue *compileChangeSizeBy(Compiler *compiler); + static CompilerValue *compileSetSizeTo(Compiler *compiler); IEngine *m_engine = nullptr; std::unordered_map m_effectMap; diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 3bde0121a..a84035df0 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -554,3 +554,31 @@ TEST_F(LooksBlocksTest, ChangeSizeBy) builder.run(); } } + +TEST_F(LooksBlocksTest, SetSizeTo) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_setsizeto"); + builder.addValueInput("SIZE", 2.5); + builder.build(); + + sprite->setEngine(nullptr); + sprite->setSize(45.62); + builder.run(); + ASSERT_EQ(sprite->size(), 2.5); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_setsizeto"); + builder.addValueInput("SIZE", 2.5); + + builder.build(); + builder.run(); + } +} From 58f55346f4fa9e4a1e9ced8330ca25d124631d4e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:57:49 +0100 Subject: [PATCH 16/43] Implement looks_size block --- src/blocks/looksblocks.cpp | 14 ++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 36 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index fe3cb3ba8..6726c20be 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -52,6 +52,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_cleargraphiceffects", &compileClearGraphicEffects); engine->addCompileFunction(this, "looks_changesizeby", &compileChangeSizeBy); engine->addCompileFunction(this, "looks_setsizeto", &compileSetSizeTo); + engine->addCompileFunction(this, "looks_size", &compileSize); } void LooksBlocks::onInit(IEngine *engine) @@ -227,6 +228,14 @@ CompilerValue *LooksBlocks::compileSetSizeTo(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileSize(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return compiler->addConstValue(100); + else + return compiler->addTargetFunctionCall("looks_size", Compiler::StaticType::Number); +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -306,3 +315,8 @@ extern "C" void looks_setsizeto(Sprite *sprite, double size) { sprite->setSize(size); } + +extern "C" double looks_size(Sprite *sprite) +{ + return sprite->size(); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index a6c568737..4bad0913b 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -42,6 +42,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileClearGraphicEffects(Compiler *compiler); static CompilerValue *compileChangeSizeBy(Compiler *compiler); static CompilerValue *compileSetSizeTo(Compiler *compiler); + static CompilerValue *compileSize(Compiler *compiler); IEngine *m_engine = nullptr; std::unordered_map m_effectMap; diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index a84035df0..d26ad397b 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -582,3 +583,38 @@ TEST_F(LooksBlocksTest, SetSizeTo) builder.run(); } } + +TEST_F(LooksBlocksTest, Size) +{ + { + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_size"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setEngine(nullptr); + sprite->setSize(45.62); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 45.62); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_size"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 100); + } +} From 0ca7317c35d32d0526ea4d78b949f6438640310c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:12:26 +0100 Subject: [PATCH 17/43] Use pointer type for graphic effects in looks blocks --- src/blocks/looksblocks.cpp | 63 +++++++++----------------------------- src/blocks/looksblocks.h | 11 +------ 2 files changed, 15 insertions(+), 59 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 6726c20be..40a744247 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -18,12 +18,6 @@ using namespace libscratchcpp; -LooksBlocks::~LooksBlocks() -{ - if (m_engine) - m_instances.erase(m_engine); -} - std::string LooksBlocks::name() const { return "Looks"; @@ -78,9 +72,6 @@ void LooksBlocks::onInit(IEngine *engine) target->clearGraphicsEffects(); } }); - - m_engine = engine; - m_instances[engine] = this; } void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string function) @@ -98,28 +89,15 @@ void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string compiler->endLoop(); } -long LooksBlocks::getEffectIndex(IEngine *engine, const std::string &name) +void LooksBlocks::compileSetOrChangeEffect(Compiler *compiler, const std::string &function, const std::string &effectName, CompilerValue *arg) { - assert(engine); - assert(m_instances.find(engine) != m_instances.cend()); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect(effectName); - LooksBlocks *instance = m_instances[engine]; - auto it = instance->m_effectMap.find(name); - - if (it == instance->m_effectMap.cend()) { - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect(name); - - if (effect) { - instance->m_effects.push_back(effect); - instance->m_effectMap[name] = instance->m_effects.size() - 1; - return instance->m_effects.size() - 1; - } else { - std::cout << "warning: graphic effect '" << name << "' is not registered" << std::endl; - return -1; - } - } - - return it->second; + if (effect) { + CompilerValue *effectPtr = compiler->addConstValue(effect); + compiler->addTargetFunctionCall(function, Compiler::StaticType::Void, { Compiler::StaticType::Pointer, Compiler::StaticType::Number }, { effectPtr, arg }); + } else + std::cout << "warning: graphic effect '" << effectName << "' is not registered" << std::endl; } CompilerValue *LooksBlocks::compileSayForSecs(Compiler *compiler) @@ -173,14 +151,8 @@ CompilerValue *LooksBlocks::compileChangeEffectBy(Compiler *compiler) if (!field) return nullptr; - auto index = getEffectIndex(compiler->engine(), field->value().toString()); - - if (index != -1) { - auto indexValue = compiler->addConstValue(index); - auto change = compiler->addInput("CHANGE"); - compiler->addTargetFunctionCall("looks_changeeffectby", Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { indexValue, change }); - } - + CompilerValue *change = compiler->addInput("CHANGE"); + compileSetOrChangeEffect(compiler, "looks_changeeffectby", field->value().toString(), change); return nullptr; } @@ -191,14 +163,8 @@ CompilerValue *LooksBlocks::compileSetEffectTo(Compiler *compiler) if (!field) return nullptr; - auto index = getEffectIndex(compiler->engine(), field->value().toString()); - - if (index != -1) { - auto indexValue = compiler->addConstValue(index); - auto value = compiler->addInput("VALUE"); - compiler->addTargetFunctionCall("looks_seteffectto", Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { indexValue, value }); - } - + CompilerValue *change = compiler->addInput("VALUE"); + compileSetOrChangeEffect(compiler, "looks_seteffectto", field->value().toString(), change); return nullptr; } @@ -290,15 +256,14 @@ extern "C" void looks_hide(Sprite *sprite) sprite->setVisible(false); } -extern "C" void looks_changeeffectby(Target *target, double index, double change) +extern "C" void looks_changeeffectby(Target *target, IGraphicsEffect *effect, double change) { - IGraphicsEffect *effect = LooksBlocks::getEffect(target->engine(), index); target->setGraphicsEffectValue(effect, target->graphicsEffectValue(effect) + change); } -extern "C" void looks_seteffectto(Target *target, double index, double value) +extern "C" void looks_seteffectto(Target *target, IGraphicsEffect *effect, double value) { - target->setGraphicsEffectValue(LooksBlocks::getEffect(target->engine(), index), value); + target->setGraphicsEffectValue(effect, value); } extern "C" void looks_cleargraphiceffects(Target *target) diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 4bad0913b..fcb444afa 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -16,8 +16,6 @@ class IGraphicsEffect; class LooksBlocks : public IExtension { public: - ~LooksBlocks(); - std::string name() const override; std::string description() const override; Rgb color() const override; @@ -25,11 +23,9 @@ class LooksBlocks : public IExtension void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; - static inline IGraphicsEffect *getEffect(IEngine *engine, long index) { return m_instances[engine]->m_effects[index]; } - private: static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function); - static long getEffectIndex(IEngine *engine, const std::string &name); + static void compileSetOrChangeEffect(Compiler *compiler, const std::string &function, const std::string &effectName, CompilerValue *arg); static CompilerValue *compileSayForSecs(Compiler *compiler); static CompilerValue *compileSay(Compiler *compiler); @@ -43,11 +39,6 @@ class LooksBlocks : public IExtension static CompilerValue *compileChangeSizeBy(Compiler *compiler); static CompilerValue *compileSetSizeTo(Compiler *compiler); static CompilerValue *compileSize(Compiler *compiler); - - IEngine *m_engine = nullptr; - std::unordered_map m_effectMap; - std::vector m_effects; - static inline std::unordered_map m_instances; }; } // namespace libscratchcpp From 4fdef81c77cfa5a38ec37ba505ac00318d3bb2a1 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:29:07 +0100 Subject: [PATCH 18/43] Use reference for function name in say/think for secs --- src/blocks/looksblocks.cpp | 2 +- src/blocks/looksblocks.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 40a744247..d6f0d20ad 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -74,7 +74,7 @@ void LooksBlocks::onInit(IEngine *engine) }); } -void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string function) +void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string &function) { auto message = compiler->addInput("MESSAGE"); auto duration = compiler->addInput("SECS"); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index fcb444afa..ccb570cd8 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -24,7 +24,7 @@ class LooksBlocks : public IExtension void onInit(IEngine *engine) override; private: - static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function); + static void compileSayOrThinkForSecs(Compiler *compiler, const std::string &function); static void compileSetOrChangeEffect(Compiler *compiler, const std::string &function, const std::string &effectName, CompilerValue *arg); static CompilerValue *compileSayForSecs(Compiler *compiler); From 0e631b1166149dbce0838d40a7606458ba702119 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:51:29 +0100 Subject: [PATCH 19/43] Implement looks_switchcostumeto block --- src/blocks/looksblocks.cpp | 65 +++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 293 ++++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index d6f0d20ad..9a5720202 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_changesizeby", &compileChangeSizeBy); engine->addCompileFunction(this, "looks_setsizeto", &compileSetSizeTo); engine->addCompileFunction(this, "looks_size", &compileSize); + engine->addCompileFunction(this, "looks_switchcostumeto", &compileSwitchCostumeTo); } void LooksBlocks::onInit(IEngine *engine) @@ -202,6 +204,13 @@ CompilerValue *LooksBlocks::compileSize(Compiler *compiler) return compiler->addTargetFunctionCall("looks_size", Compiler::StaticType::Number); } +CompilerValue *LooksBlocks::compileSwitchCostumeTo(Compiler *compiler) +{ + auto costume = compiler->addInput("COSTUME"); + compiler->addTargetFunctionCall("looks_switchcostumeto", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { costume }); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -285,3 +294,59 @@ extern "C" double looks_size(Sprite *sprite) { return sprite->size(); } + +extern "C" void looks_set_costume_by_index(Target *target, long index) +{ + const size_t costumeCount = target->costumes().size(); + + if (index < 0) + index = (costumeCount + index % (-costumeCount)) % costumeCount; + else if (index >= costumeCount) + index = index % costumeCount; + + target->setCostumeIndex(index); +} + +extern "C" void looks_nextcostume(Target *target) +{ + looks_set_costume_by_index(target, target->costumeIndex() + 1); +} + +extern "C" void looks_previouscostume(Target *target) +{ + looks_set_costume_by_index(target, target->costumeIndex() - 1); +} + +extern "C" void looks_switchcostumeto(Target *target, const ValueData *costume) +{ + // https://github.com/scratchfoundation/scratch-vm/blob/8dbcc1fc8f8d8c4f1e40629fe8a388149d6dfd1c/src/blocks/scratch3_looks.js#L389-L413 + if (!value_isString(costume)) { + // Numbers should be treated as costume indices, always + if (value_isNaN(costume) || value_isInfinity(costume) || value_isNegativeInfinity(costume)) + target->setCostumeIndex(0); + else + looks_set_costume_by_index(target, value_toLong(costume) - 1); + } else { + // Strings should be treated as costume names, where possible + // TODO: Use UTF-16 in Target + // StringPtr *nameStr = value_toStringPtr(costume); + std::string nameStr; + value_toString(costume, &nameStr); + const int costumeIndex = target->findCostume(nameStr); + + auto it = std::find_if(nameStr.begin(), nameStr.end(), [](char c) { return !std::isspace(c); }); + bool isWhiteSpace = (it == nameStr.end()); + + if (costumeIndex != -1) + looks_set_costume_by_index(target, costumeIndex); + else if (nameStr == "next costume") + looks_nextcostume(target); + else if (nameStr == "previous costume") { + looks_previouscostume(target); + // Try to cast the string to a number (and treat it as a costume index) + // Pure whitespace should not be treated as a number + // Note: isNaN will cast the string to a number before checking if it's NaN + } else if (value_isValidNumber(costume) && !isWhiteSpace) + looks_set_costume_by_index(target, value_toLong(costume) - 1); + } +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index ccb570cd8..292506824 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -39,6 +39,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileChangeSizeBy(Compiler *compiler); static CompilerValue *compileSetSizeTo(Compiler *compiler); static CompilerValue *compileSize(Compiler *compiler); + static CompilerValue *compileSwitchCostumeTo(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index d26ad397b..b1568c39d 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -618,3 +619,295 @@ TEST_F(LooksBlocksTest, Size) ASSERT_EQ(Value(list->data()[0]).toDouble(), 100); } } + +TEST_F(LooksBlocksTest, SwitchCostumeTo) +{ + // Valid + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "costume2"); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + } + + m_engine->clear(); + + // Invalid + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "invalid"); + builder.build(); + + sprite->setCostumeIndex(1); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + + sprite->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); + } + + m_engine->clear(); + + // "next costume" + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "next costume"); + builder.build(); + + sprite->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + } + + m_engine->clear(); + + // "next costume" (a costume with this name exists) + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto nextCostume = std::make_shared("next costume", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(nextCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "next costume"); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + } + + m_engine->clear(); + + // "previous costume" + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "previous costume"); + builder.build(); + + sprite->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + } + + m_engine->clear(); + + // "previous costume" (a costume with this name exists) + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto previousCostume = std::make_shared("previous costume", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(previousCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "previous costume"); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + } + + m_engine->clear(); + + // Index (number) + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", 3); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); + } + + m_engine->clear(); + + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", 5); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + } + + m_engine->clear(); + + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", -1); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + } + + m_engine->clear(); + + // Index (string) + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", "3"); + builder.build(); + + sprite->setCostumeIndex(1); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + } + + m_engine->clear(); + + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", "+7.0"); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", ""); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", " "); + builder.build(); + + sprite->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + } + + m_engine->clear(); + + // Stage + { + auto stage = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + stage->addCostume(costume1); + stage->addCostume(costume2); + stage->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "costume1"); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + } +} From a3b1002c1ff72d7eb42a15f2f91ca11ab2842148 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:00:42 +0100 Subject: [PATCH 20/43] Implement looks_nextcostume block --- src/blocks/looksblocks.cpp | 7 ++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 55 +++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 9a5720202..06a5bc158 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -49,6 +49,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_setsizeto", &compileSetSizeTo); engine->addCompileFunction(this, "looks_size", &compileSize); engine->addCompileFunction(this, "looks_switchcostumeto", &compileSwitchCostumeTo); + engine->addCompileFunction(this, "looks_nextcostume", &compileNextCostume); } void LooksBlocks::onInit(IEngine *engine) @@ -211,6 +212,12 @@ CompilerValue *LooksBlocks::compileSwitchCostumeTo(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileNextCostume(Compiler *compiler) +{ + compiler->addTargetFunctionCall("looks_nextcostume"); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 292506824..c46ef5c27 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -40,6 +40,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSetSizeTo(Compiler *compiler); static CompilerValue *compileSize(Compiler *compiler); static CompilerValue *compileSwitchCostumeTo(Compiler *compiler); + static CompilerValue *compileNextCostume(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index b1568c39d..8c632d45a 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -911,3 +911,58 @@ TEST_F(LooksBlocksTest, SwitchCostumeTo) ASSERT_EQ(stage->costumeIndex(), 0); } } + +TEST_F(LooksBlocksTest, NextCostume) +{ + { + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_nextcostume"); + builder.build(); + + sprite->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + } + + m_engine->clear(); + + { + auto stage = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + stage->addCostume(costume1); + stage->addCostume(costume2); + stage->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_nextcostume"); + builder.build(); + + stage->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + } +} From 502e44fa49d40ac8658f11963cedb661a599b738 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:31:38 +0100 Subject: [PATCH 21/43] Rewrite looks_switchcostumeto test --- test/blocks/looks_blocks_test.cpp | 465 +++++++++++++++--------------- 1 file changed, 231 insertions(+), 234 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 8c632d45a..660689641 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -620,296 +620,293 @@ TEST_F(LooksBlocksTest, Size) } } -TEST_F(LooksBlocksTest, SwitchCostumeTo) +TEST_F(LooksBlocksTest, SwitchCostumeTo_CostumeName) { - // Valid - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(costume2); - sprite->addCostume(testCostume); - - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "costume2"); - builder.build(); - - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); - } - - m_engine->clear(); - - // Invalid - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(costume2); - sprite->addCostume(testCostume); - - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "invalid"); - builder.build(); - - sprite->setCostumeIndex(1); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); - - sprite->setCostumeIndex(2); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 2); - } - - m_engine->clear(); + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); - // "next costume" - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(costume2); - sprite->addCostume(testCostume); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "costume2"); + builder.build(); - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "next costume"); - builder.build(); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); +} - sprite->setCostumeIndex(0); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); +TEST_F(LooksBlocksTest, SwitchCostumeTo_InvalidCostumeName) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 2); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 0); - } + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "invalid"); + builder.build(); - m_engine->clear(); + sprite->setCostumeIndex(1); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); - // "next costume" (a costume with this name exists) - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto nextCostume = std::make_shared("next costume", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(nextCostume); - sprite->addCostume(testCostume); + sprite->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); +} - ScriptBuilder builder(m_extension.get(), m_engine, sprite); +TEST_F(LooksBlocksTest, SwitchCostumeTo_NextCostume) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "next costume"); - builder.build(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "next costume"); + builder.build(); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); - } + sprite->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); - m_engine->clear(); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); - // "previous costume" - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(costume2); - sprite->addCostume(testCostume); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); +} - ScriptBuilder builder(m_extension.get(), m_engine, sprite); +TEST_F(LooksBlocksTest, SwitchCostumeTo_NextCostumeExists) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto nextCostume = std::make_shared("next costume", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(nextCostume); + sprite->addCostume(testCostume); - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "previous costume"); - builder.build(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - sprite->setCostumeIndex(0); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 2); + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "next costume"); + builder.build(); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 0); - } + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); +} - m_engine->clear(); +TEST_F(LooksBlocksTest, SwitchCostumeTo_PreviousCostume) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); - // "previous costume" (a costume with this name exists) - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto previousCostume = std::make_shared("previous costume", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(previousCostume); - sprite->addCostume(testCostume); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "previous costume"); + builder.build(); - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "previous costume"); - builder.build(); + sprite->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); - } + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); +} - m_engine->clear(); +TEST_F(LooksBlocksTest, SwitchCostumeTo_PreviousCostumeExists) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto previousCostume = std::make_shared("previous costume", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(previousCostume); + sprite->addCostume(testCostume); - // Index (number) - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto numberCostume = std::make_shared("3", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(numberCostume); - sprite->addCostume(testCostume); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "previous costume"); + builder.build(); - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", 3); - builder.build(); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 2); - } + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); +} - m_engine->clear(); +TEST_F(LooksBlocksTest, SwitchCostumeTo_NumberIndex) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto numberCostume = std::make_shared("3", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(numberCostume); - sprite->addCostume(testCostume); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", 3); + builder.build(); - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", 5); - builder.build(); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); +} - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); - } +TEST_F(LooksBlocksTest, SwitchCostumeTo_PositiveOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); - m_engine->clear(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto numberCostume = std::make_shared("3", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(numberCostume); - sprite->addCostume(testCostume); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", 5); + builder.build(); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); +} - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", -1); - builder.build(); +TEST_F(LooksBlocksTest, SwitchCostumeTo_NegativeOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); - } + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - m_engine->clear(); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", -1); + builder.build(); - // Index (string) - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(testCostume); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); +} - ScriptBuilder builder(m_extension.get(), m_engine, sprite); +TEST_F(LooksBlocksTest, SwitchCostumeTo_StringIndex) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(testCostume); - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", "3"); - builder.build(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - sprite->setCostumeIndex(1); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 0); - } + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", "3"); + builder.build(); - m_engine->clear(); + sprite->setCostumeIndex(1); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); +} - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto numberCostume = std::make_shared("3", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(numberCostume); - sprite->addCostume(testCostume); +TEST_F(LooksBlocksTest, SwitchCostumeTo_OutOfRangeStringIndex) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", "+7.0"); - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", ""); - builder.addBlock("looks_switchcostumeto"); - builder.addValueInput("COSTUME", " "); - builder.build(); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", "+7.0"); + builder.build(); - sprite->setCostumeIndex(2); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 0); - } + sprite->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); +} - m_engine->clear(); +TEST_F(LooksBlocksTest, SwitchCostumeTo_WhitespaceString) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); - // Stage - { - auto stage = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - stage->addCostume(costume1); - stage->addCostume(costume2); - stage->addCostume(testCostume); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", ""); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", " "); + builder.build(); - builder.addBlock("looks_switchcostumeto"); - builder.addDropdownInput("COSTUME", "costume1"); - builder.build(); + sprite->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); +} - stage->setCostumeIndex(2); - builder.run(); - ASSERT_EQ(stage->costumeIndex(), 0); - } +TEST_F(LooksBlocksTest, SwitchCostumeTo_Stage) +{ + auto stage = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + stage->addCostume(costume1); + stage->addCostume(costume2); + stage->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "costume1"); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 0); } TEST_F(LooksBlocksTest, NextCostume) From 5b3174a333c00f00483f01aa2a1da3b6b94596c8 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:33:55 +0100 Subject: [PATCH 22/43] Rewrite looks_nextcostume test --- test/blocks/looks_blocks_test.cpp | 79 +++++++++++++++---------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 660689641..232ae3009 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -909,57 +909,54 @@ TEST_F(LooksBlocksTest, SwitchCostumeTo_Stage) ASSERT_EQ(stage->costumeIndex(), 0); } -TEST_F(LooksBlocksTest, NextCostume) +TEST_F(LooksBlocksTest, NextCostume_Sprite) { - { - auto sprite = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - sprite->addCostume(costume1); - sprite->addCostume(costume2); - sprite->addCostume(testCostume); - - ScriptBuilder builder(m_extension.get(), m_engine, sprite); + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); - builder.addBlock("looks_nextcostume"); - builder.build(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - sprite->setCostumeIndex(0); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 1); + builder.addBlock("looks_nextcostume"); + builder.build(); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 2); + sprite->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); - builder.run(); - ASSERT_EQ(sprite->costumeIndex(), 0); - } + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 2); - m_engine->clear(); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); +} - { - auto stage = std::make_shared(); - auto costume1 = std::make_shared("costume1", "a", "png"); - auto costume2 = std::make_shared("costume2", "b", "png"); - auto testCostume = std::make_shared("test", "c", "svg"); - stage->addCostume(costume1); - stage->addCostume(costume2); - stage->addCostume(testCostume); +TEST_F(LooksBlocksTest, NextCostume_Stage) +{ + auto stage = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + stage->addCostume(costume1); + stage->addCostume(costume2); + stage->addCostume(testCostume); - ScriptBuilder builder(m_extension.get(), m_engine, stage); + ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_nextcostume"); - builder.build(); + builder.addBlock("looks_nextcostume"); + builder.build(); - stage->setCostumeIndex(0); - builder.run(); - ASSERT_EQ(stage->costumeIndex(), 1); + stage->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 1); - builder.run(); - ASSERT_EQ(stage->costumeIndex(), 2); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 2); - builder.run(); - ASSERT_EQ(stage->costumeIndex(), 0); - } + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 0); } From d49c5295f9de393c27e69b5af9768a542d903e29 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:35:11 +0100 Subject: [PATCH 23/43] Rewrite looks_size test --- test/blocks/looks_blocks_test.cpp | 51 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 232ae3009..df006e022 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -585,39 +585,36 @@ TEST_F(LooksBlocksTest, SetSizeTo) } } -TEST_F(LooksBlocksTest, Size) +TEST_F(LooksBlocksTest, Size_Sprite) { - { - auto sprite = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.addBlock("looks_size"); - builder.captureBlockReturnValue(); - builder.build(); - - sprite->setEngine(nullptr); - sprite->setSize(45.62); - builder.run(); + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_size"); + builder.captureBlockReturnValue(); + builder.build(); - List *list = builder.capturedValues(); - ASSERT_EQ(list->size(), 1); - ASSERT_EQ(Value(list->data()[0]).toDouble(), 45.62); - } + sprite->setEngine(nullptr); + sprite->setSize(45.62); + builder.run(); - m_engine->clear(); + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 45.62); +} - { - auto stage = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_size"); - builder.captureBlockReturnValue(); +TEST_F(LooksBlocksTest, Size_Stage) +{ + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_size"); + builder.captureBlockReturnValue(); - builder.build(); - builder.run(); + builder.build(); + builder.run(); - List *list = builder.capturedValues(); - ASSERT_EQ(list->size(), 1); - ASSERT_EQ(Value(list->data()[0]).toDouble(), 100); - } + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 1); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 100); } TEST_F(LooksBlocksTest, SwitchCostumeTo_CostumeName) From fd2504aa2d1f7aac696b657cb097c9c989bf30dd Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:36:17 +0100 Subject: [PATCH 24/43] Rewrite looks_setsizeto test --- test/blocks/looks_blocks_test.cpp | 41 ++++++++++++++----------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index df006e022..3997a7d9e 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -557,32 +557,29 @@ TEST_F(LooksBlocksTest, ChangeSizeBy) } } -TEST_F(LooksBlocksTest, SetSizeTo) +TEST_F(LooksBlocksTest, SetSizeTo_Sprite) { - { - auto sprite = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.addBlock("looks_setsizeto"); - builder.addValueInput("SIZE", 2.5); - builder.build(); - - sprite->setEngine(nullptr); - sprite->setSize(45.62); - builder.run(); - ASSERT_EQ(sprite->size(), 2.5); - } + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_setsizeto"); + builder.addValueInput("SIZE", 2.5); + builder.build(); - m_engine->clear(); + sprite->setEngine(nullptr); + sprite->setSize(45.62); + builder.run(); + ASSERT_EQ(sprite->size(), 2.5); +} - { - auto stage = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_setsizeto"); - builder.addValueInput("SIZE", 2.5); +TEST_F(LooksBlocksTest, SetSizeTo_Stage) +{ + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_setsizeto"); + builder.addValueInput("SIZE", 2.5); - builder.build(); - builder.run(); - } + builder.build(); + builder.run(); } TEST_F(LooksBlocksTest, Size_Sprite) From 6e505f9e12423f9d0a5980f95b6173e9352bf80b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:37:32 +0100 Subject: [PATCH 25/43] Rewrite looks_changesizeby test --- test/blocks/looks_blocks_test.cpp | 41 ++++++++++++++----------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 3997a7d9e..bc797e991 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -529,32 +529,29 @@ TEST_F(LooksBlocksTest, ClearGraphicEffects) ASSERT_EQ(sprite->graphicsEffectValue(effect3), 0); } -TEST_F(LooksBlocksTest, ChangeSizeBy) +TEST_F(LooksBlocksTest, ChangeSizeBy_Sprite) { - { - auto sprite = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - builder.addBlock("looks_changesizeby"); - builder.addValueInput("CHANGE", 2.5); - builder.build(); - - sprite->setEngine(nullptr); - sprite->setSize(45.62); - builder.run(); - ASSERT_EQ(sprite->size(), 48.12); - } + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + builder.addBlock("looks_changesizeby"); + builder.addValueInput("CHANGE", 2.5); + builder.build(); - m_engine->clear(); + sprite->setEngine(nullptr); + sprite->setSize(45.62); + builder.run(); + ASSERT_EQ(sprite->size(), 48.12); +} - { - auto stage = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_changesizeby"); - builder.addValueInput("CHANGE", 2.5); +TEST_F(LooksBlocksTest, ChangeSizeBy_Stage) +{ + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_changesizeby"); + builder.addValueInput("CHANGE", 2.5); - builder.build(); - builder.run(); - } + builder.build(); + builder.run(); } TEST_F(LooksBlocksTest, SetSizeTo_Sprite) From 293136375c3d9cda09a2e24688714d51dec88e2a Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:40:58 +0100 Subject: [PATCH 26/43] Rewrite looks_seteffectto test --- test/blocks/looks_blocks_test.cpp | 139 +++++++++++++++--------------- 1 file changed, 68 insertions(+), 71 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index bc797e991..12f20b213 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -407,95 +407,92 @@ TEST_F(LooksBlocksTest, ChangeEffectBy) } } -TEST_F(LooksBlocksTest, SetEffectTo) +TEST_F(LooksBlocksTest, SetEffectTo_Fisheye) { - // Fisheye - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("FISHEYE"); - ASSERT_TRUE(effect); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("FISHEYE"); + ASSERT_TRUE(effect); - builder.addBlock("looks_seteffectto"); - builder.addDropdownField("EFFECT", "FISHEYE"); - builder.addValueInput("VALUE", 45.2); - auto block = builder.currentBlock(); + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "FISHEYE"); + builder.addValueInput("VALUE", 45.2); + auto block = builder.currentBlock(); - Compiler compiler(m_engine, stage.get()); - auto code = compiler.compile(block); - Script script(stage.get(), block, m_engine); - script.setCode(code); - Thread thread(stage.get(), m_engine, &script); + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); - stage->setGraphicsEffectValue(effect, 86.84); - thread.run(); - ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 45.2); - } + stage->setGraphicsEffectValue(effect, 86.84); + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 45.2); +} - // Pixelate - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); +TEST_F(LooksBlocksTest, SetEffectTo_Pixelate) +{ + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("PIXELATE"); - ASSERT_TRUE(effect); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("PIXELATE"); + ASSERT_TRUE(effect); - builder.addBlock("looks_seteffectto"); - builder.addDropdownField("EFFECT", "PIXELATE"); - builder.addValueInput("VALUE", 12.05); - auto block = builder.currentBlock(); + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "PIXELATE"); + builder.addValueInput("VALUE", 12.05); + auto block = builder.currentBlock(); - Compiler compiler(m_engine, stage.get()); - auto code = compiler.compile(block); - Script script(stage.get(), block, m_engine); - script.setCode(code); - Thread thread(stage.get(), m_engine, &script); + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); - thread.run(); - ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 12.05); - } + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 12.05); +} - // Mosaic - { - auto sprite = std::make_shared(); - sprite->setEngine(m_engine); +TEST_F(LooksBlocksTest, SetEffectTo_Mosaic) +{ + auto sprite = std::make_shared(); + sprite->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("MOSAIC"); - ASSERT_TRUE(effect); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("MOSAIC"); + ASSERT_TRUE(effect); - builder.addBlock("looks_seteffectto"); - builder.addDropdownField("EFFECT", "MOSAIC"); - builder.addValueInput("VALUE", -8.06); - auto block = builder.currentBlock(); + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "MOSAIC"); + builder.addValueInput("VALUE", -8.06); + auto block = builder.currentBlock(); - Compiler compiler(m_engine, sprite.get()); - auto code = compiler.compile(block); - Script script(sprite.get(), block, m_engine); - script.setCode(code); - Thread thread(sprite.get(), m_engine, &script); + Compiler compiler(m_engine, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, m_engine); + script.setCode(code); + Thread thread(sprite.get(), m_engine, &script); - sprite->setGraphicsEffectValue(effect, 13.12); - thread.run(); - ASSERT_EQ(std::round(sprite->graphicsEffectValue(effect) * 100) / 100, -8.06); - } + sprite->setGraphicsEffectValue(effect, 13.12); + thread.run(); + ASSERT_EQ(std::round(sprite->graphicsEffectValue(effect) * 100) / 100, -8.06); +} - // Invalid - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); +TEST_F(LooksBlocksTest, SetEffectTo_Invalid) +{ + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_seteffectto"); - builder.addDropdownField("EFFECT", "INVALID"); - builder.addValueInput("VALUE", 8.3); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_seteffectto"); + builder.addDropdownField("EFFECT", "INVALID"); + builder.addValueInput("VALUE", 8.3); - builder.build(); - builder.run(); - } + builder.build(); + builder.run(); } TEST_F(LooksBlocksTest, ClearGraphicEffects) From bb4a212f1f5328bf4aaf20cb59485bd6e2f30404 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:41:37 +0100 Subject: [PATCH 27/43] Rewrite looks_changeeffectby test --- test/blocks/looks_blocks_test.cpp | 139 +++++++++++++++--------------- 1 file changed, 68 insertions(+), 71 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 12f20b213..60043dc31 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -316,95 +316,92 @@ TEST_F(LooksBlocksTest, Hide) } } -TEST_F(LooksBlocksTest, ChangeEffectBy) +TEST_F(LooksBlocksTest, ChangeEffectBy_Color) { - // Color - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("COLOR"); - ASSERT_TRUE(effect); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("COLOR"); + ASSERT_TRUE(effect); - builder.addBlock("looks_changeeffectby"); - builder.addDropdownField("EFFECT", "COLOR"); - builder.addValueInput("CHANGE", 45.2); - auto block = builder.currentBlock(); + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "COLOR"); + builder.addValueInput("CHANGE", 45.2); + auto block = builder.currentBlock(); - Compiler compiler(m_engine, stage.get()); - auto code = compiler.compile(block); - Script script(stage.get(), block, m_engine); - script.setCode(code); - Thread thread(stage.get(), m_engine, &script); + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); - stage->setGraphicsEffectValue(effect, 86.84); - thread.run(); - ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 132.04); - } + stage->setGraphicsEffectValue(effect, 86.84); + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 132.04); +} - // Brightness - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); +TEST_F(LooksBlocksTest, ChangeEffectBy_Brightness) +{ + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("BRIGHTNESS"); - ASSERT_TRUE(effect); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("BRIGHTNESS"); + ASSERT_TRUE(effect); - builder.addBlock("looks_changeeffectby"); - builder.addDropdownField("EFFECT", "BRIGHTNESS"); - builder.addValueInput("CHANGE", 12.05); - auto block = builder.currentBlock(); + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "BRIGHTNESS"); + builder.addValueInput("CHANGE", 12.05); + auto block = builder.currentBlock(); - Compiler compiler(m_engine, stage.get()); - auto code = compiler.compile(block); - Script script(stage.get(), block, m_engine); - script.setCode(code); - Thread thread(stage.get(), m_engine, &script); + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); - thread.run(); - ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 12.05); - } + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 12.05); +} - // Ghost - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); +TEST_F(LooksBlocksTest, ChangeEffectBy_Ghost) +{ + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("GHOST"); - ASSERT_TRUE(effect); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + IGraphicsEffect *effect = ScratchConfiguration::getGraphicsEffect("GHOST"); + ASSERT_TRUE(effect); - builder.addBlock("looks_changeeffectby"); - builder.addDropdownField("EFFECT", "GHOST"); - builder.addValueInput("CHANGE", -8.06); - auto block = builder.currentBlock(); + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "GHOST"); + builder.addValueInput("CHANGE", -8.06); + auto block = builder.currentBlock(); - Compiler compiler(m_engine, stage.get()); - auto code = compiler.compile(block); - Script script(stage.get(), block, m_engine); - script.setCode(code); - Thread thread(stage.get(), m_engine, &script); + Compiler compiler(m_engine, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, m_engine); + script.setCode(code); + Thread thread(stage.get(), m_engine, &script); - stage->setGraphicsEffectValue(effect, 13.12); - thread.run(); - ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 5.06); - } + stage->setGraphicsEffectValue(effect, 13.12); + thread.run(); + ASSERT_EQ(std::round(stage->graphicsEffectValue(effect) * 100) / 100, 5.06); +} - // Invalid - { - auto stage = std::make_shared(); - stage->setEngine(m_engine); +TEST_F(LooksBlocksTest, ChangeEffectBy_Invalid) +{ + auto stage = std::make_shared(); + stage->setEngine(m_engine); - ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_changeeffectby"); - builder.addDropdownField("EFFECT", "INVALID"); - builder.addValueInput("CHANGE", 8.3); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + builder.addBlock("looks_changeeffectby"); + builder.addDropdownField("EFFECT", "INVALID"); + builder.addValueInput("CHANGE", 8.3); - builder.build(); - builder.run(); - } + builder.build(); + builder.run(); } TEST_F(LooksBlocksTest, SetEffectTo_Fisheye) From 0f76450499a97ec35953ab2da597aadd0007ba2e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:43:09 +0100 Subject: [PATCH 28/43] Rewrite looks_hide test --- test/blocks/looks_blocks_test.cpp | 39 ++++++++++++++----------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 60043dc31..18b589bb3 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -285,35 +285,32 @@ TEST_F(LooksBlocksTest, Show) } } -TEST_F(LooksBlocksTest, Hide) +TEST_F(LooksBlocksTest, Hide_Sprite) { - { - auto sprite = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - - builder.addBlock("looks_hide"); - builder.build(); + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - sprite->setVisible(true); + builder.addBlock("looks_hide"); + builder.build(); - builder.run(); - ASSERT_FALSE(sprite->visible()); + sprite->setVisible(true); - builder.run(); - ASSERT_FALSE(sprite->visible()); - } + builder.run(); + ASSERT_FALSE(sprite->visible()); - m_engine->clear(); + builder.run(); + ASSERT_FALSE(sprite->visible()); +} - { - auto stage = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, stage); +TEST_F(LooksBlocksTest, Hide_Stage) +{ + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_hide"); + builder.addBlock("looks_hide"); - builder.build(); - builder.run(); - } + builder.build(); + builder.run(); } TEST_F(LooksBlocksTest, ChangeEffectBy_Color) From 63b726df6e81dcb67cea3c48f7801e2f6a77f7f5 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:43:27 +0100 Subject: [PATCH 29/43] Rewrite looks_show test --- test/blocks/looks_blocks_test.cpp | 39 ++++++++++++++----------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 18b589bb3..9731fd718 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -254,35 +254,32 @@ TEST_F(LooksBlocksTest, SayAndThink) } } -TEST_F(LooksBlocksTest, Show) +TEST_F(LooksBlocksTest, Show_Sprite) { - { - auto sprite = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, sprite); - - builder.addBlock("looks_show"); - builder.build(); + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); - sprite->setVisible(false); + builder.addBlock("looks_show"); + builder.build(); - builder.run(); - ASSERT_TRUE(sprite->visible()); + sprite->setVisible(false); - builder.run(); - ASSERT_TRUE(sprite->visible()); - } + builder.run(); + ASSERT_TRUE(sprite->visible()); - m_engine->clear(); + builder.run(); + ASSERT_TRUE(sprite->visible()); +} - { - auto stage = std::make_shared(); - ScriptBuilder builder(m_extension.get(), m_engine, stage); +TEST_F(LooksBlocksTest, Show_Stage) +{ + auto stage = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, stage); - builder.addBlock("looks_show"); + builder.addBlock("looks_show"); - builder.build(); - builder.run(); - } + builder.build(); + builder.run(); } TEST_F(LooksBlocksTest, Hide_Sprite) From 5e2d68ec3a714cc7394e4703aae5ca5a216639c4 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 13:37:35 +0100 Subject: [PATCH 30/43] Add existing string index costume test for switch costume block --- test/blocks/looks_blocks_test.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 9731fd718..2c6841f20 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -823,6 +823,26 @@ TEST_F(LooksBlocksTest, SwitchCostumeTo_StringIndex) ASSERT_EQ(sprite->costumeIndex(), 0); } +TEST_F(LooksBlocksTest, SwitchCostumeTo_StringIndexExists) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", "3"); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 1); +} + TEST_F(LooksBlocksTest, SwitchCostumeTo_OutOfRangeStringIndex) { auto sprite = std::make_shared(); From 8f554c0aed7a16e8a22e081bf0e28b842d2e7816 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:09:34 +0100 Subject: [PATCH 31/43] Add invalid index test for switch costume block --- test/blocks/looks_blocks_test.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 2c6841f20..f55b504c3 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -804,6 +804,34 @@ TEST_F(LooksBlocksTest, SwitchCostumeTo_NegativeOutOfRangeNumberIndex) ASSERT_EQ(sprite->costumeIndex(), 1); } +TEST_F(LooksBlocksTest, SwitchCostumeTo_InvalidNumberIndex) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto numberCostume = std::make_shared("3", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(numberCostume); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", std::numeric_limits::quiet_NaN()); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", std::numeric_limits::infinity()); + builder.addBlock("looks_switchcostumeto"); + builder.addValueInput("COSTUME", -std::numeric_limits::infinity()); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + + sprite->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); +} + TEST_F(LooksBlocksTest, SwitchCostumeTo_StringIndex) { auto sprite = std::make_shared(); From f7aebefa22d2db38507f9d26cd2000e0f3fd3532 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:54:01 +0100 Subject: [PATCH 32/43] Add no costumes test for switch costume block --- test/blocks/looks_blocks_test.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index f55b504c3..6d53b3048 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -599,6 +599,17 @@ TEST_F(LooksBlocksTest, Size_Stage) ASSERT_EQ(Value(list->data()[0]).toDouble(), 100); } +TEST_F(LooksBlocksTest, SwitchCostumeTo_NoCostumes) +{ + auto sprite = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchcostumeto"); + builder.addDropdownInput("COSTUME", "costume2"); + builder.build(); + builder.run(); +} + TEST_F(LooksBlocksTest, SwitchCostumeTo_CostumeName) { auto sprite = std::make_shared(); From 5438bafa716c9765af163149a46a186bfe2e5097 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:09:04 +0100 Subject: [PATCH 33/43] Remove confusing comment from looks_switchcostumeto --- src/blocks/looksblocks.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 06a5bc158..14c66f867 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -352,7 +352,6 @@ extern "C" void looks_switchcostumeto(Target *target, const ValueData *costume) looks_previouscostume(target); // Try to cast the string to a number (and treat it as a costume index) // Pure whitespace should not be treated as a number - // Note: isNaN will cast the string to a number before checking if it's NaN } else if (value_isValidNumber(costume) && !isWhiteSpace) looks_set_costume_by_index(target, value_toLong(costume) - 1); } From 289dd14ea75f5350a29acce2e41c49e055ee2656 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:09:24 +0100 Subject: [PATCH 34/43] Implement looks_switchbackdropto block --- src/blocks/looksblocks.cpp | 69 ++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 611 ++++++++++++++++++++++++++++++ 3 files changed, 681 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 14c66f867..71f77705e 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -5,8 +5,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -50,6 +53,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_size", &compileSize); engine->addCompileFunction(this, "looks_switchcostumeto", &compileSwitchCostumeTo); engine->addCompileFunction(this, "looks_nextcostume", &compileNextCostume); + engine->addCompileFunction(this, "looks_switchbackdropto", &compileSwitchBackdropTo); } void LooksBlocks::onInit(IEngine *engine) @@ -218,6 +222,16 @@ CompilerValue *LooksBlocks::compileNextCostume(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileSwitchBackdropTo(Compiler *compiler) +{ + auto backdrop = compiler->addInput("BACKDROP"); + auto wait = compiler->addConstValue(false); + compiler->addFunctionCallWithCtx("looks_switchbackdropto", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { backdrop }); + compiler->addFunctionCallWithCtx("looks_start_backdrop_scripts", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { wait }); + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -324,6 +338,14 @@ extern "C" void looks_previouscostume(Target *target) looks_set_costume_by_index(target, target->costumeIndex() - 1); } +void looks_randomcostume(Target *target, IRandomGenerator *rng) +{ + size_t count = target->costumes().size(); + + if (count > 1) + looks_set_costume_by_index(target, rng->randintExcept(0, count - 1, target->costumeIndex())); // exclude current costume +} + extern "C" void looks_switchcostumeto(Target *target, const ValueData *costume) { // https://github.com/scratchfoundation/scratch-vm/blob/8dbcc1fc8f8d8c4f1e40629fe8a388149d6dfd1c/src/blocks/scratch3_looks.js#L389-L413 @@ -356,3 +378,50 @@ extern "C" void looks_switchcostumeto(Target *target, const ValueData *costume) looks_set_costume_by_index(target, value_toLong(costume) - 1); } } + +extern "C" void looks_start_backdrop_scripts(ExecutionContext *ctx, bool wait) +{ + IEngine *engine = ctx->engine(); + Stage *stage = engine->stage(); + Costume *backdrop = stage->currentCostume().get(); + + if (backdrop) + engine->startBackdropScripts(backdrop->broadcast(), ctx->thread(), wait); +} + +extern "C" void looks_switchbackdropto(ExecutionContext *ctx, const ValueData *backdrop) +{ + Stage *stage = ctx->engine()->stage(); + + // https://github.com/scratchfoundation/scratch-vm/blob/8dbcc1fc8f8d8c4f1e40629fe8a388149d6dfd1c/src/blocks/scratch3_looks.js#L389-L413 + if (!value_isString(backdrop)) { + // Numbers should be treated as costume indices, always + if (value_isNaN(backdrop) || value_isInfinity(backdrop) || value_isNegativeInfinity(backdrop)) + stage->setCostumeIndex(0); + else + looks_set_costume_by_index(stage, value_toLong(backdrop) - 1); + } else { + // Strings should be treated as costume names, where possible + // TODO: Use UTF-16 in Target + // StringPtr *nameStr = value_toStringPtr(backdrop); + std::string nameStr; + value_toString(backdrop, &nameStr); + const int costumeIndex = stage->findCostume(nameStr); + + auto it = std::find_if(nameStr.begin(), nameStr.end(), [](char c) { return !std::isspace(c); }); + bool isWhiteSpace = (it == nameStr.end()); + + if (costumeIndex != -1) + looks_set_costume_by_index(stage, costumeIndex); + else if (nameStr == "next backdrop") + looks_nextcostume(stage); + else if (nameStr == "previous backdrop") + looks_previouscostume(stage); + else if (nameStr == "random backdrop") { + looks_randomcostume(stage, ctx->rng()); + // Try to cast the string to a number (and treat it as a costume index) + // Pure whitespace should not be treated as a number + } else if (value_isValidNumber(backdrop) && !isWhiteSpace) + looks_set_costume_by_index(stage, value_toLong(backdrop) - 1); + } +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index c46ef5c27..4cfaca594 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -41,6 +41,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSize(Compiler *compiler); static CompilerValue *compileSwitchCostumeTo(Compiler *compiler); static CompilerValue *compileNextCostume(Compiler *compiler); + static CompilerValue *compileSwitchBackdropTo(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 6d53b3048..c7f25f1d7 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "../common.h" #include "blocks/looksblocks.h" @@ -998,3 +999,613 @@ TEST_F(LooksBlocksTest, NextCostume_Stage) builder.run(); ASSERT_EQ(stage->costumeIndex(), 0); } + +TEST_F(LooksBlocksTest, SwitchBackdropTo_NoBackdrops) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "backdrop2"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts).Times(0); + thread.run(); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_BackdropName) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "backdrop2"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_InvalidBackdropName) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "invalid"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, false)); + stage->setCostumeIndex(1); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, false)); + stage->setCostumeIndex(2); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_NextBackdrop) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "next backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, false)); + stage->setCostumeIndex(0); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_NextBackdropExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto nextBackdrop = std::make_shared("next backdrop", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(nextBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "next backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(nextBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(nextBackdrop->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_PreviousBackdrop) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "previous backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, false)); + stage->setCostumeIndex(0); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_PreviousBackdropExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto previousBackdrop = std::make_shared("previous backdrop", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(previousBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "previous backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(previousBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(previousBackdrop->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_RandomBackdrop) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "random backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + ctx->setRng(&rng); + + EXPECT_CALL(rng, randintExcept(0, 2, 1)).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + stage->setCostumeIndex(1); + code->run(ctx.get()); + ASSERT_EQ(stage->costumeIndex(), 0); + + EXPECT_CALL(rng, randintExcept(0, 2, 0)).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, false)); + code->reset(ctx.get()); + code->run(ctx.get()); + ASSERT_EQ(stage->costumeIndex(), 2); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_RandomBackdropExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto randomBackdrop = std::make_shared("random backdrop", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(randomBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "random backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(randomBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(randomBackdrop->broadcast(), &thread, false)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_NumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", 3); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_PositiveOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", 5); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(numberBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_NegativeOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", -1); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(numberBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_InvalidNumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", std::numeric_limits::quiet_NaN()); + auto block = builder.currentBlock(); + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", std::numeric_limits::infinity()); + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", -std::numeric_limits::infinity()); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + stage->setCostumeIndex(2); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_StringIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", "3"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_StringIndexExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", "3"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(numberBackdrop->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_OutOfRangeStringIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", "+7.0"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_WhitespaceString) +{ + auto sprite = std::make_shared(); + sprite->setEngine(m_engine); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + m_engine->setTargets({ stage }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", ""); + builder.addBlock("looks_switchbackdropto"); + builder.addValueInput("BACKDROP", " "); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 2); +} + +TEST_F(LooksBlocksTest, SwitchBackdropTo_Stage) +{ + auto stage = std::make_shared(); + stage->setEngine(&m_engineMock); + + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_switchbackdropto"); + builder.addDropdownInput("BACKDROP", "backdrop1"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, false)); + stage->setCostumeIndex(2); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +} From 61672175662e58a1e286e3e2b19c818209f992b2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:25:24 +0100 Subject: [PATCH 35/43] LLVMCodeBuilder: Fix raw value to unknown type conversion --- src/engine/internal/llvm/llvmcodebuilder.cpp | 3 +++ test/llvm/llvmcodebuilder_test.cpp | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index 881633534..4d879a72c 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -2320,6 +2320,9 @@ llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegister *reg, Compiler::StaticTy return nullptr; } + case Compiler::StaticType::Unknown: + return createValue(reg); + default: assert(false); return nullptr; diff --git a/test/llvm/llvmcodebuilder_test.cpp b/test/llvm/llvmcodebuilder_test.cpp index 47030a26b..5b65ed65b 100644 --- a/test/llvm/llvmcodebuilder_test.cpp +++ b/test/llvm/llvmcodebuilder_test.cpp @@ -488,12 +488,18 @@ TEST_F(LLVMCodeBuilderTest, FunctionCalls) v = m_builder->addConstValue(321.5); m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + v = m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }, { v }); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); v = m_builder->addConstValue("test"); m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + v = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { v }); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); v = m_builder->addConstValue(true); m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + v = m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v }); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); auto code = m_builder->finalize(); Script script(&m_target, nullptr, nullptr); @@ -519,7 +525,10 @@ TEST_F(LLVMCodeBuilderTest, FunctionCalls) "123\n" "1\n" "321.5\n" + "321.5\n" + "test\n" "test\n" + "true\n" "true\n"; EXPECT_CALL(m_target, isStage()).Times(7); From 18b4093d674c4ebcb50ba2e225472e7c7552154e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:02:12 +0100 Subject: [PATCH 36/43] LLVMCodeBuilder: Fix non-raw value to unknown type conversion --- src/engine/internal/llvm/llvmcodebuilder.cpp | 3 ++ test/llvm/llvmcodebuilder_test.cpp | 50 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/engine/internal/llvm/llvmcodebuilder.cpp b/src/engine/internal/llvm/llvmcodebuilder.cpp index 4d879a72c..c08bba715 100644 --- a/src/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/engine/internal/llvm/llvmcodebuilder.cpp @@ -2256,6 +2256,9 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType return nullptr; } + case Compiler::StaticType::Unknown: + return createValue(reg); + default: assert(false); return nullptr; diff --git a/test/llvm/llvmcodebuilder_test.cpp b/test/llvm/llvmcodebuilder_test.cpp index 5b65ed65b..1e88ce7a4 100644 --- a/test/llvm/llvmcodebuilder_test.cpp +++ b/test/llvm/llvmcodebuilder_test.cpp @@ -2106,6 +2106,56 @@ TEST_F(LLVMCodeBuilderTest, ReadVariable) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } +TEST_F(LLVMCodeBuilderTest, CastNonRawValueToUnknownType) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto var = std::make_shared("", "", 87); + stage.addVariable(var); + + createBuilder(&sprite, true); + + // Unknown type + CompilerValue *v = m_builder->addVariableValue(var.get()); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + + // Number + v = m_builder->addConstValue(23.5); + m_builder->createVariableWrite(var.get(), v); + v = m_builder->addVariableValue(var.get()); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + + // String + v = m_builder->addConstValue("Hello world"); + m_builder->createVariableWrite(var.get(), v); + v = m_builder->addVariableValue(var.get()); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + + // Bool + v = m_builder->addConstValue(true); + m_builder->createVariableWrite(var.get(), v); + v = m_builder->addVariableValue(var.get()); + m_builder->addFunctionCall("test_print_unknown", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { v }); + + std::string expected; + expected += var->value().toString() + '\n'; + expected += "23.5\n"; + expected += "Hello world\n"; + expected += "true\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, ClearList) { Stage stage; From 6104c4a3d708602f2ee6f7da9c58a4f69fc6289c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 24 Mar 2025 18:35:58 +0100 Subject: [PATCH 37/43] Implement looks_gotofrontback block --- src/blocks/looksblocks.cpp | 33 ++++++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 83 +++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 71f77705e..5ab28031c 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -54,6 +54,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_switchcostumeto", &compileSwitchCostumeTo); engine->addCompileFunction(this, "looks_nextcostume", &compileNextCostume); engine->addCompileFunction(this, "looks_switchbackdropto", &compileSwitchBackdropTo); + engine->addCompileFunction(this, "looks_gotofrontback", &compileGoToFrontBack); } void LooksBlocks::onInit(IEngine *engine) @@ -232,6 +233,26 @@ CompilerValue *LooksBlocks::compileSwitchBackdropTo(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileGoToFrontBack(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + Field *field = compiler->field("FRONT_BACK"); + + if (!field) + return nullptr; + + const std::string &option = field->value().toString(); + + if (option == "front") + compiler->addFunctionCallWithCtx("looks_move_to_front"); + else if (option == "back") + compiler->addFunctionCallWithCtx("looks_move_to_back"); + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -425,3 +446,15 @@ extern "C" void looks_switchbackdropto(ExecutionContext *ctx, const ValueData *b looks_set_costume_by_index(stage, value_toLong(backdrop) - 1); } } + +extern "C" void looks_move_to_front(ExecutionContext *ctx) +{ + Target *target = ctx->thread()->target(); + ctx->engine()->moveDrawableToFront(target); +} + +extern "C" void looks_move_to_back(ExecutionContext *ctx) +{ + Target *target = ctx->thread()->target(); + ctx->engine()->moveDrawableToBack(target); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 4cfaca594..93221cacf 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -42,6 +42,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSwitchCostumeTo(Compiler *compiler); static CompilerValue *compileNextCostume(Compiler *compiler); static CompilerValue *compileSwitchBackdropTo(Compiler *compiler); + static CompilerValue *compileGoToFrontBack(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index c7f25f1d7..af5b902b3 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1609,3 +1609,86 @@ TEST_F(LooksBlocksTest, SwitchBackdropTo_Stage) thread.run(); ASSERT_EQ(stage->costumeIndex(), 0); } + +TEST_F(LooksBlocksTest, GoToFrontBack_MoveSpriteToFront) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_gotofrontback"); + builder.addDropdownField("FRONT_BACK", "front"); + builder.build(); + + m_engine->moveDrawableToBack(sprite.get()); + + builder.run(); + std::initializer_list layers = { sprite->layerOrder(), sprite1->layerOrder(), sprite2->layerOrder(), sprite3->layerOrder() }; + ASSERT_EQ(sprite->layerOrder(), std::max(layers)); + + builder.run(); + ASSERT_EQ(sprite->layerOrder(), std::max(layers)); +} + +TEST_F(LooksBlocksTest, GoToFrontBack_MoveSpriteToBack) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_gotofrontback"); + builder.addDropdownField("FRONT_BACK", "back"); + builder.build(); + + m_engine->moveDrawableToFront(sprite.get()); + + builder.run(); + ASSERT_EQ(sprite->layerOrder(), 1); + + builder.run(); + ASSERT_EQ(sprite->layerOrder(), 1); +} + +TEST_F(LooksBlocksTest, GoToFrontBack_MoveStageToFront) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_gotofrontback"); + builder.addDropdownField("FRONT_BACK", "front"); + builder.build(); + + builder.run(); + ASSERT_EQ(stage->layerOrder(), 0); +} + +TEST_F(LooksBlocksTest, GoToFrontBack_MoveStageToBack) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_gotofrontback"); + builder.addDropdownField("FRONT_BACK", "back"); + builder.build(); + + builder.run(); + ASSERT_EQ(sprite->layerOrder(), 0); +} From 4e3b3cd7ff54281b570968c2f8d5d1bd514a7714 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:37:42 +0100 Subject: [PATCH 38/43] fix #637: Ignore invisible text bubbles when changing layer Resolves: #637 --- src/engine/internal/engine.cpp | 26 ++++++++++++++++------- test/engine/engine_test.cpp | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index d3f859ddf..98612f577 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -1262,16 +1262,26 @@ void Engine::moveDrawableForwardLayers(Drawable *drawable, int layers) if (it == m_sortedDrawables.end()) return; - auto target = it + layers; + auto target = it; + int layersAbs = std::abs(layers); - if (target <= m_sortedDrawables.begin()) { - moveDrawableToBack(drawable); - return; - } + for (int i = 0; i < layersAbs; i++) { + if (target <= m_sortedDrawables.begin()) { + moveDrawableToBack(drawable); + return; + } - if (target >= m_sortedDrawables.end()) { - moveDrawableToFront(drawable); - return; + if (target >= m_sortedDrawables.end()) { + moveDrawableToFront(drawable); + return; + } + + Drawable *currentDrawable; + + do { + currentDrawable = *target; + target += layers / layersAbs; + } while (currentDrawable->isTextBubble() && static_cast(currentDrawable)->text().empty()); } if (layers > 0) diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 54480ccce..c1dab56d7 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -1321,6 +1321,11 @@ TEST(EngineTest, MoveDrawableForwardLayers) std::vector sprites; createTargets(&engine, sprites); + const auto &targets = engine.targets(); + + for (auto target : targets) + target->bubble()->setText("test"); + engine.moveDrawableForwardLayers(sprites[4], 2); ASSERT_EQ(sprites[0]->layerOrder(), 1); ASSERT_EQ(sprites[1]->layerOrder(), 5); @@ -1380,6 +1385,20 @@ TEST(EngineTest, MoveDrawableForwardLayers) ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 8); ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 9); ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 11); + + sprites[1]->bubble()->setText(""); + sprites[3]->bubble()->setText(""); + engine.moveDrawableForwardLayers(sprites[2], 8); + ASSERT_EQ(sprites[0]->layerOrder(), 1); + ASSERT_EQ(sprites[1]->layerOrder(), 4); + ASSERT_EQ(sprites[2]->layerOrder(), 11); + ASSERT_EQ(sprites[3]->layerOrder(), 3); + ASSERT_EQ(sprites[4]->layerOrder(), 2); + ASSERT_EQ(sprites[0]->bubble()->layerOrder(), 9); + ASSERT_EQ(sprites[1]->bubble()->layerOrder(), 6); + ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 7); + ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 8); + ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 10); } TEST(EngineTest, MoveDrawableBackwardLayers) @@ -1388,6 +1407,11 @@ TEST(EngineTest, MoveDrawableBackwardLayers) std::vector sprites; createTargets(&engine, sprites); + const auto &targets = engine.targets(); + + for (auto target : targets) + target->bubble()->setText("test"); + engine.moveDrawableBackwardLayers(sprites[4], -2); ASSERT_EQ(sprites[0]->layerOrder(), 1); ASSERT_EQ(sprites[1]->layerOrder(), 5); @@ -1447,6 +1471,20 @@ TEST(EngineTest, MoveDrawableBackwardLayers) ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 8); ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 9); ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 11); + + sprites[1]->bubble()->setText(""); + sprites[3]->bubble()->setText(""); + engine.moveDrawableBackwardLayers(sprites[2], -8); + ASSERT_EQ(sprites[0]->layerOrder(), 1); + ASSERT_EQ(sprites[1]->layerOrder(), 4); + ASSERT_EQ(sprites[2]->layerOrder(), 11); + ASSERT_EQ(sprites[3]->layerOrder(), 3); + ASSERT_EQ(sprites[4]->layerOrder(), 2); + ASSERT_EQ(sprites[0]->bubble()->layerOrder(), 9); + ASSERT_EQ(sprites[1]->bubble()->layerOrder(), 6); + ASSERT_EQ(sprites[2]->bubble()->layerOrder(), 7); + ASSERT_EQ(sprites[3]->bubble()->layerOrder(), 8); + ASSERT_EQ(sprites[4]->bubble()->layerOrder(), 10); } TEST(EngineTest, MoveDrawableBehindOther) From 5fad56bcc04b4745db78c484e1412b6b1199ca20 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:39:50 +0100 Subject: [PATCH 39/43] Implement looks_goforwardbackwardlayers block --- src/blocks/looksblocks.cpp | 36 +++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 150 ++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 5ab28031c..f9dfb788e 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -55,6 +55,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_nextcostume", &compileNextCostume); engine->addCompileFunction(this, "looks_switchbackdropto", &compileSwitchBackdropTo); engine->addCompileFunction(this, "looks_gotofrontback", &compileGoToFrontBack); + engine->addCompileFunction(this, "looks_goforwardbackwardlayers", &compileGoForwardBackwardLayers); } void LooksBlocks::onInit(IEngine *engine) @@ -253,6 +254,29 @@ CompilerValue *LooksBlocks::compileGoToFrontBack(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileGoForwardBackwardLayers(Compiler *compiler) +{ + if (compiler->target()->isStage()) + return nullptr; + + Field *field = compiler->field("FORWARD_BACKWARD"); + + if (!field) + return nullptr; + + const std::string &option = field->value().toString(); + + if (option == "forward") { + auto layers = compiler->addInput("NUM"); + compiler->addFunctionCallWithCtx("looks_move_forward_layers", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { layers }); + } else if (option == "backward") { + auto layers = compiler->addInput("NUM"); + compiler->addFunctionCallWithCtx("looks_move_backward_layers", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { layers }); + } + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -458,3 +482,15 @@ extern "C" void looks_move_to_back(ExecutionContext *ctx) Target *target = ctx->thread()->target(); ctx->engine()->moveDrawableToBack(target); } + +extern "C" void looks_move_forward_layers(ExecutionContext *ctx, double layers) +{ + Target *target = ctx->thread()->target(); + ctx->engine()->moveDrawableForwardLayers(target, layers); +} + +extern "C" void looks_move_backward_layers(ExecutionContext *ctx, double layers) +{ + Target *target = ctx->thread()->target(); + ctx->engine()->moveDrawableBackwardLayers(target, layers); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 93221cacf..09108ea96 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -43,6 +43,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileNextCostume(Compiler *compiler); static CompilerValue *compileSwitchBackdropTo(Compiler *compiler); static CompilerValue *compileGoToFrontBack(Compiler *compiler); + static CompilerValue *compileGoForwardBackwardLayers(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index af5b902b3..d57040c3d 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1692,3 +1692,153 @@ TEST_F(LooksBlocksTest, GoToFrontBack_MoveStageToBack) builder.run(); ASSERT_EQ(sprite->layerOrder(), 0); } + +TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveSpriteForwardPositive) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_goforwardbackwardlayers"); + builder.addDropdownField("FORWARD_BACKWARD", "forward"); + builder.addValueInput("NUM", 2); + builder.build(); + + // sprite, sprite1, sprite3, sprite2 + m_engine->moveDrawableToFront(sprite1.get()); + m_engine->moveDrawableToFront(sprite3.get()); + m_engine->moveDrawableToFront(sprite2.get()); + m_engine->moveDrawableToBack(sprite.get()); + + // sprite1, sprite3, sprite, sprite2 + builder.run(); + ASSERT_GT(sprite->layerOrder(), sprite1->layerOrder()); + ASSERT_GT(sprite->layerOrder(), sprite3->layerOrder()); + ASSERT_LT(sprite->layerOrder(), sprite2->layerOrder()); +} + +TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveSpriteForwardNegative) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_goforwardbackwardlayers"); + builder.addDropdownField("FORWARD_BACKWARD", "forward"); + builder.addValueInput("NUM", -1); + builder.build(); + + // sprite1, sprite3, sprite2, sprite + m_engine->moveDrawableToFront(sprite1.get()); + m_engine->moveDrawableToFront(sprite3.get()); + m_engine->moveDrawableToFront(sprite2.get()); + m_engine->moveDrawableToFront(sprite.get()); + + // sprite1, sprite3, sprite, sprite2 + builder.run(); + ASSERT_GT(sprite->layerOrder(), sprite1->layerOrder()); + ASSERT_GT(sprite->layerOrder(), sprite3->layerOrder()); + ASSERT_LT(sprite->layerOrder(), sprite2->layerOrder()); +} + +TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveSpriteBackwardPositive) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_goforwardbackwardlayers"); + builder.addDropdownField("FORWARD_BACKWARD", "backward"); + builder.addValueInput("NUM", 1); + builder.build(); + + // sprite1, sprite3, sprite2, sprite + m_engine->moveDrawableToFront(sprite1.get()); + m_engine->moveDrawableToFront(sprite3.get()); + m_engine->moveDrawableToFront(sprite2.get()); + m_engine->moveDrawableToFront(sprite.get()); + + // sprite1, sprite3, sprite, sprite2 + builder.run(); + ASSERT_GT(sprite->layerOrder(), sprite1->layerOrder()); + ASSERT_GT(sprite->layerOrder(), sprite3->layerOrder()); + ASSERT_LT(sprite->layerOrder(), sprite2->layerOrder()); +} + +TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveSpriteBackwardNegative) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_goforwardbackwardlayers"); + builder.addDropdownField("FORWARD_BACKWARD", "backward"); + builder.addValueInput("NUM", -2); + builder.build(); + + // sprite, sprite1, sprite3, sprite2 + m_engine->moveDrawableToFront(sprite1.get()); + m_engine->moveDrawableToFront(sprite3.get()); + m_engine->moveDrawableToFront(sprite2.get()); + m_engine->moveDrawableToBack(sprite.get()); + + // sprite1, sprite3, sprite, sprite2 + builder.run(); + ASSERT_GT(sprite->layerOrder(), sprite1->layerOrder()); + ASSERT_GT(sprite->layerOrder(), sprite3->layerOrder()); + ASSERT_LT(sprite->layerOrder(), sprite2->layerOrder()); +} + +TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveStageForward) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_goforwardbackwardlayers"); + builder.addDropdownField("FORWARD_BACKWARD", "forward"); + builder.addValueInput("NUM", 3); + builder.build(); + + builder.run(); + ASSERT_EQ(stage->layerOrder(), 0); +} + +TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveStageBackward) +{ + auto stage = std::make_shared(); + auto sprite = std::make_shared(); + auto sprite1 = std::make_shared(); + auto sprite2 = std::make_shared(); + auto sprite3 = std::make_shared(); + m_engine->setTargets({ stage, sprite, sprite1, sprite2, sprite3 }); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_goforwardbackwardlayers"); + builder.addDropdownField("FORWARD_BACKWARD", "backward"); + builder.addValueInput("NUM", 3); + builder.build(); + + builder.run(); + ASSERT_EQ(stage->layerOrder(), 0); +} From afb72aeea892a094401df4e36aba6f9ffde1df97 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 27 Mar 2025 20:02:09 +0100 Subject: [PATCH 40/43] Implement looks_backdropnumbername block --- src/blocks/looksblocks.cpp | 32 ++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 122 ++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index f9dfb788e..c119e431c 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_switchbackdropto", &compileSwitchBackdropTo); engine->addCompileFunction(this, "looks_gotofrontback", &compileGoToFrontBack); engine->addCompileFunction(this, "looks_goforwardbackwardlayers", &compileGoForwardBackwardLayers); + engine->addCompileFunction(this, "looks_backdropnumbername", &compileBackdropNumberName); } void LooksBlocks::onInit(IEngine *engine) @@ -277,6 +279,23 @@ CompilerValue *LooksBlocks::compileGoForwardBackwardLayers(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileBackdropNumberName(Compiler *compiler) +{ + Field *field = compiler->field("NUMBER_NAME"); + + if (!field) + return nullptr; + + const std::string &option = field->value().toString(); + + if (option == "number") + return compiler->addFunctionCallWithCtx("looks_backdrop_number", Compiler::StaticType::Number); + else if (option == "name") + return compiler->addFunctionCallWithCtx("looks_backdrop_name", Compiler::StaticType::String); + else + return compiler->addConstValue(Value()); +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -494,3 +513,16 @@ extern "C" void looks_move_backward_layers(ExecutionContext *ctx, double layers) Target *target = ctx->thread()->target(); ctx->engine()->moveDrawableBackwardLayers(target, layers); } + +extern "C" double looks_backdrop_number(ExecutionContext *ctx) +{ + return ctx->engine()->stage()->costumeIndex() + 1; +} + +extern "C" StringPtr *looks_backdrop_name(ExecutionContext *ctx) +{ + const std::string &name = ctx->engine()->stage()->currentCostume()->name(); + StringPtr *ret = string_pool_new(); + string_assign_cstring(ret, name.c_str()); + return ret; +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 09108ea96..22db1f11e 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -44,6 +44,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileSwitchBackdropTo(Compiler *compiler); static CompilerValue *compileGoToFrontBack(Compiler *compiler); static CompilerValue *compileGoForwardBackwardLayers(Compiler *compiler); + static CompilerValue *compileBackdropNumberName(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index d57040c3d..62852694d 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1842,3 +1842,125 @@ TEST_F(LooksBlocksTest, GoForwardBackwardLayers_MoveStageBackward) builder.run(); ASSERT_EQ(stage->layerOrder(), 0); } + +TEST_F(LooksBlocksTest, BackdropNumberName_SpriteBackdropNumber) +{ + auto sprite = std::make_shared(); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + m_engine->setTargets({ stage, sprite }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_backdropnumbername"); + builder.addDropdownField("NUMBER_NAME", "number"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + + stage->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 3); + ASSERT_EQ(Value(list->data()[1]).toDouble(), 1); +} + +TEST_F(LooksBlocksTest, BackdropNumberName_SpriteBackdropName) +{ + auto sprite = std::make_shared(); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + m_engine->setTargets({ stage, sprite }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_backdropnumbername"); + builder.addDropdownField("NUMBER_NAME", "name"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + + stage->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toString(), "test"); + ASSERT_EQ(Value(list->data()[1]).toString(), "backdrop1"); +} + +TEST_F(LooksBlocksTest, BackdropNumberName_StageBackdropNumber) +{ + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_backdropnumbername"); + builder.addDropdownField("NUMBER_NAME", "number"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + + stage->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 3); + ASSERT_EQ(Value(list->data()[1]).toDouble(), 1); +} + +TEST_F(LooksBlocksTest, BackdropNumberName_StageBackdropName) +{ + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_backdropnumbername"); + builder.addDropdownField("NUMBER_NAME", "name"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + + stage->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toString(), "test"); + ASSERT_EQ(Value(list->data()[1]).toString(), "backdrop1"); +} From 37f3417e64d7e568e5248293bab56ed69e8fedc2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 27 Mar 2025 20:22:05 +0100 Subject: [PATCH 41/43] Implement looks_costumenumbername block --- src/blocks/looksblocks.cpp | 31 ++++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 116 ++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index c119e431c..adcac397f 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -58,6 +58,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_gotofrontback", &compileGoToFrontBack); engine->addCompileFunction(this, "looks_goforwardbackwardlayers", &compileGoForwardBackwardLayers); engine->addCompileFunction(this, "looks_backdropnumbername", &compileBackdropNumberName); + engine->addCompileFunction(this, "looks_costumenumbername", &compileCostumeNumberName); } void LooksBlocks::onInit(IEngine *engine) @@ -296,6 +297,23 @@ CompilerValue *LooksBlocks::compileBackdropNumberName(Compiler *compiler) return compiler->addConstValue(Value()); } +CompilerValue *LooksBlocks::compileCostumeNumberName(Compiler *compiler) +{ + Field *field = compiler->field("NUMBER_NAME"); + + if (!field) + return nullptr; + + const std::string &option = field->value().toString(); + + if (option == "number") + return compiler->addTargetFunctionCall("looks_costume_number", Compiler::StaticType::Number); + else if (option == "name") + return compiler->addTargetFunctionCall("looks_costume_name", Compiler::StaticType::String); + else + return compiler->addConstValue(Value()); +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -526,3 +544,16 @@ extern "C" StringPtr *looks_backdrop_name(ExecutionContext *ctx) string_assign_cstring(ret, name.c_str()); return ret; } + +extern "C" double looks_costume_number(Target *target) +{ + return target->costumeIndex() + 1; +} + +extern "C" StringPtr *looks_costume_name(Target *target) +{ + const std::string &name = target->currentCostume()->name(); + StringPtr *ret = string_pool_new(); + string_assign_cstring(ret, name.c_str()); + return ret; +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 22db1f11e..8697c2bf1 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -45,6 +45,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileGoToFrontBack(Compiler *compiler); static CompilerValue *compileGoForwardBackwardLayers(Compiler *compiler); static CompilerValue *compileBackdropNumberName(Compiler *compiler); + static CompilerValue *compileCostumeNumberName(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 62852694d..2c21c7109 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -1964,3 +1964,119 @@ TEST_F(LooksBlocksTest, BackdropNumberName_StageBackdropName) ASSERT_EQ(Value(list->data()[0]).toString(), "test"); ASSERT_EQ(Value(list->data()[1]).toString(), "backdrop1"); } + +TEST_F(LooksBlocksTest, CostumeNumberName_SpriteCostumeNumber) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_costumenumbername"); + builder.addDropdownField("NUMBER_NAME", "number"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setCostumeIndex(2); + builder.run(); + + sprite->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 3); + ASSERT_EQ(Value(list->data()[1]).toDouble(), 1); +} + +TEST_F(LooksBlocksTest, CostumeNumberName_SpriteCostumeName) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto costume2 = std::make_shared("costume2", "b", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(costume2); + sprite->addCostume(testCostume); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_costumenumbername"); + builder.addDropdownField("NUMBER_NAME", "name"); + builder.captureBlockReturnValue(); + builder.build(); + + sprite->setCostumeIndex(2); + builder.run(); + + sprite->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toString(), "test"); + ASSERT_EQ(Value(list->data()[1]).toString(), "costume1"); +} + +TEST_F(LooksBlocksTest, CostumeNumberName_StageCostumeNumber) +{ + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_costumenumbername"); + builder.addDropdownField("NUMBER_NAME", "number"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + + stage->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toDouble(), 3); + ASSERT_EQ(Value(list->data()[1]).toDouble(), 1); +} + +TEST_F(LooksBlocksTest, CostumeNumberName_StageCostumeName) +{ + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_costumenumbername"); + builder.addDropdownField("NUMBER_NAME", "name"); + builder.captureBlockReturnValue(); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + + stage->setCostumeIndex(0); + builder.run(); + + List *list = builder.capturedValues(); + ASSERT_EQ(list->size(), 2); + ASSERT_EQ(Value(list->data()[0]).toString(), "test"); + ASSERT_EQ(Value(list->data()[1]).toString(), "backdrop1"); +} From c91cba642b7720bc4e8fa25159b150bebc040428 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:00:47 +0100 Subject: [PATCH 42/43] Implement looks_switchbackdroptoandwait block --- src/blocks/looksblocks.cpp | 27 ++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 732 ++++++++++++++++++++++++++++++ 3 files changed, 760 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index adcac397f..b69ae51ca 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_goforwardbackwardlayers", &compileGoForwardBackwardLayers); engine->addCompileFunction(this, "looks_backdropnumbername", &compileBackdropNumberName); engine->addCompileFunction(this, "looks_costumenumbername", &compileCostumeNumberName); + engine->addCompileFunction(this, "looks_switchbackdroptoandwait", &compileSwitchBackdropToAndWait); } void LooksBlocks::onInit(IEngine *engine) @@ -314,6 +316,21 @@ CompilerValue *LooksBlocks::compileCostumeNumberName(Compiler *compiler) return compiler->addConstValue(Value()); } +CompilerValue *LooksBlocks::compileSwitchBackdropToAndWait(Compiler *compiler) +{ + auto backdrop = compiler->addInput("BACKDROP"); + auto wait = compiler->addConstValue(true); + compiler->addFunctionCallWithCtx("looks_switchbackdropto", Compiler::StaticType::Void, { Compiler::StaticType::Unknown }, { backdrop }); + compiler->addFunctionCallWithCtx("looks_start_backdrop_scripts", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { wait }); + + auto hasBackdrops = compiler->addFunctionCallWithCtx("looks_backdrop_promise", Compiler::StaticType::Bool); + compiler->beginIfStatement(hasBackdrops); + compiler->createYield(); + compiler->endIf(); + + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -557,3 +574,13 @@ extern "C" StringPtr *looks_costume_name(Target *target) string_assign_cstring(ret, name.c_str()); return ret; } + +extern "C" bool looks_backdrop_promise(ExecutionContext *ctx) +{ + if (ctx->engine()->stage()->costumes().size() > 0) { + ctx->setPromise(std::make_shared()); + return true; + } + + return false; +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 8697c2bf1..e12c2ed01 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -46,6 +46,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileGoForwardBackwardLayers(Compiler *compiler); static CompilerValue *compileBackdropNumberName(Compiler *compiler); static CompilerValue *compileCostumeNumberName(Compiler *compiler); + static CompilerValue *compileSwitchBackdropToAndWait(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 2c21c7109..cd6f303ba 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -2080,3 +2081,734 @@ TEST_F(LooksBlocksTest, CostumeNumberName_StageCostumeName) ASSERT_EQ(Value(list->data()[0]).toString(), "test"); ASSERT_EQ(Value(list->data()[1]).toString(), "backdrop1"); } + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_NoBackdrops) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "backdrop2"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts).Times(0); + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_BackdropName) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "backdrop2"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_InvalidBackdropName) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "invalid"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, true)); + stage->setCostumeIndex(1); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, true)); + stage->setCostumeIndex(2); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_NextBackdrop) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "next backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, true)); + stage->setCostumeIndex(0); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_NextBackdropExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto nextBackdrop = std::make_shared("next backdrop", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(nextBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "next backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(nextBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(nextBackdrop->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_PreviousBackdrop) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "previous backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, true)); + stage->setCostumeIndex(0); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop2->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_PreviousBackdropExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto previousBackdrop = std::make_shared("previous backdrop", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(previousBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "previous backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(previousBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(previousBackdrop->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_RandomBackdrop) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "random backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + RandomGeneratorMock rng; + ctx->setRng(&rng); + + EXPECT_CALL(rng, randintExcept(0, 2, 1)).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + stage->setCostumeIndex(1); + code->run(ctx.get()); + ASSERT_EQ(stage->costumeIndex(), 0); + + EXPECT_CALL(rng, randintExcept(0, 2, 0)).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, true)); + code->reset(ctx.get()); + code->run(ctx.get()); + ASSERT_EQ(stage->costumeIndex(), 2); + + ASSERT_TRUE(ctx->promise()); + ASSERT_FALSE(ctx->promise()->isResolved()); + ASSERT_FALSE(code->isFinished(ctx.get())); + ctx->promise()->resolve(); + + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_RandomBackdropExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto randomBackdrop = std::make_shared("random backdrop", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(randomBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "random backdrop"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(randomBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + EXPECT_CALL(m_engineMock, startBackdropScripts(randomBackdrop->broadcast(), &thread, true)); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_NumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", 3); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(testBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_PositiveOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", 5); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(numberBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_NegativeOutOfRangeNumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", -1); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(numberBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_InvalidNumberIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", std::numeric_limits::quiet_NaN()); + auto block = builder.currentBlock(); + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", std::numeric_limits::infinity()); + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", -std::numeric_limits::infinity()); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + stage->setCostumeIndex(2); + thread.reset(); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_StringIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", "3"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_StringIndexExists) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", "3"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(numberBackdrop->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 1); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_OutOfRangeStringIndex) +{ + auto sprite = std::make_shared(); + sprite->setEngine(&m_engineMock); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", "+7.0"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, sprite.get()); + auto code = compiler.compile(block); + Script script(sprite.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(sprite.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_WhitespaceString) +{ + auto sprite = std::make_shared(); + sprite->setEngine(m_engine); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto numberBackdrop = std::make_shared("3", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(numberBackdrop); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + m_engine->setTargets({ stage }); + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", ""); + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addValueInput("BACKDROP", " "); + builder.build(); + + stage->setCostumeIndex(2); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 2); +} + +TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_Stage) +{ + auto stage = std::make_shared(); + stage->setEngine(&m_engineMock); + + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + EXPECT_CALL(m_engineMock, stage()).WillRepeatedly(Return(stage.get())); + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_switchbackdroptoandwait"); + builder.addDropdownInput("BACKDROP", "backdrop1"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, stage.get()); + auto code = compiler.compile(block); + Script script(stage.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(stage.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, startBackdropScripts(backdrop1->broadcast(), &thread, true)); + stage->setCostumeIndex(2); + thread.run(); + ASSERT_EQ(stage->costumeIndex(), 0); + + ASSERT_TRUE(thread.promise()); + ASSERT_FALSE(thread.promise()->isResolved()); + ASSERT_FALSE(thread.isFinished()); + thread.promise()->resolve(); + + thread.run(); + ASSERT_TRUE(thread.isFinished()); +} From 33193cc9b1ec4e4ae62c9f38a17c3419841311eb Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:09:59 +0100 Subject: [PATCH 43/43] Implement looks_nextbackdrop block --- src/blocks/looksblocks.cpp | 12 ++++++ src/blocks/looksblocks.h | 1 + test/blocks/looks_blocks_test.cpp | 64 +++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index b69ae51ca..ae4ab08fc 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -61,6 +61,7 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "looks_backdropnumbername", &compileBackdropNumberName); engine->addCompileFunction(this, "looks_costumenumbername", &compileCostumeNumberName); engine->addCompileFunction(this, "looks_switchbackdroptoandwait", &compileSwitchBackdropToAndWait); + engine->addCompileFunction(this, "looks_nextbackdrop", &compileNextBackdrop); } void LooksBlocks::onInit(IEngine *engine) @@ -331,6 +332,12 @@ CompilerValue *LooksBlocks::compileSwitchBackdropToAndWait(Compiler *compiler) return nullptr; } +CompilerValue *LooksBlocks::compileNextBackdrop(Compiler *compiler) +{ + compiler->addFunctionCallWithCtx("looks_nextbackdrop"); + return nullptr; +} + extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration) { ctx->stackTimer()->start(duration); @@ -584,3 +591,8 @@ extern "C" bool looks_backdrop_promise(ExecutionContext *ctx) return false; } + +extern "C" void looks_nextbackdrop(ExecutionContext *ctx) +{ + looks_nextcostume(ctx->engine()->stage()); +} diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index e12c2ed01..334202346 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -47,6 +47,7 @@ class LooksBlocks : public IExtension static CompilerValue *compileBackdropNumberName(Compiler *compiler); static CompilerValue *compileCostumeNumberName(Compiler *compiler); static CompilerValue *compileSwitchBackdropToAndWait(Compiler *compiler); + static CompilerValue *compileNextBackdrop(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index cd6f303ba..049217d18 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -2812,3 +2812,67 @@ TEST_F(LooksBlocksTest, SwitchBackdropToAndWait_Stage) thread.run(); ASSERT_TRUE(thread.isFinished()); } + +TEST_F(LooksBlocksTest, NextBackdrop_Sprite) +{ + auto sprite = std::make_shared(); + auto costume1 = std::make_shared("costume1", "a", "png"); + auto testCostume = std::make_shared("test", "c", "svg"); + sprite->addCostume(costume1); + sprite->addCostume(testCostume); + + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + m_engine->setTargets({ stage, sprite }); + + ScriptBuilder builder(m_extension.get(), m_engine, sprite); + + builder.addBlock("looks_nextbackdrop"); + builder.build(); + + sprite->setCostumeIndex(0); + stage->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + ASSERT_EQ(stage->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + ASSERT_EQ(stage->costumeIndex(), 2); + + builder.run(); + ASSERT_EQ(sprite->costumeIndex(), 0); + ASSERT_EQ(stage->costumeIndex(), 0); +} + +TEST_F(LooksBlocksTest, NextBackdrop_Stage) +{ + auto stage = std::make_shared(); + auto backdrop1 = std::make_shared("backdrop1", "a", "png"); + auto backdrop2 = std::make_shared("backdrop2", "b", "png"); + auto testBackdrop = std::make_shared("test", "c", "svg"); + stage->addCostume(backdrop1); + stage->addCostume(backdrop2); + stage->addCostume(testBackdrop); + + ScriptBuilder builder(m_extension.get(), m_engine, stage); + + builder.addBlock("looks_nextbackdrop"); + builder.build(); + + stage->setCostumeIndex(0); + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 1); + + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 2); + + builder.run(); + ASSERT_EQ(stage->costumeIndex(), 0); +}