From 4cbea03d97cf8f765d0a1bba522a0deb7f10beef Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 11 Feb 2024 16:29:33 +0100 Subject: [PATCH 1/4] Update libscratchcpp to v0.8.0 --- libscratchcpp | 2 +- test/mocks/enginemock.h | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/libscratchcpp b/libscratchcpp index d14fe3b..4f51860 160000 --- a/libscratchcpp +++ b/libscratchcpp @@ -1 +1 @@ -Subproject commit d14fe3b07835637504dc8855b606c6094ddd3879 +Subproject commit 4f51860662928084cddd053ad1fed8bce274f80f diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index 6d5f988..bae97e2 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -49,6 +49,9 @@ class EngineMock : public IEngine MOCK_METHOD(void, setKeyState, (const KeyEvent &, bool), (override)); MOCK_METHOD(void, setAnyKeyPressed, (bool), (override)); + MOCK_METHOD(void, mouseWheelUp, (), (override)); + MOCK_METHOD(void, mouseWheelDown, (), (override)); + MOCK_METHOD(double, mouseX, (), (const, override)); MOCK_METHOD(void, setMouseX, (double x), (override)); @@ -103,6 +106,7 @@ class EngineMock : public IEngine MOCK_METHOD(void, addBackdropChangeScript, (std::shared_ptr, int), (override)); MOCK_METHOD(void, addCloneInitScript, (std::shared_ptr), (override)); MOCK_METHOD(void, addKeyPressScript, (std::shared_ptr, int), (override)); + MOCK_METHOD(void, addTargetClickScript, (std::shared_ptr), (override)); MOCK_METHOD(const std::vector> &, targets, (), (const, override)); MOCK_METHOD(void, setTargets, (const std::vector> &), (override)); @@ -122,10 +126,19 @@ class EngineMock : public IEngine MOCK_METHOD(void, setAddMonitorHandler, (const std::function &), (override)); MOCK_METHOD(void, setRemoveMonitorHandler, (const std::function &), (override)); + MOCK_METHOD(const std::function &, questionAsked, (), (const, override)); + MOCK_METHOD(void, setQuestionAsked, (const std::function &), (override)); + + MOCK_METHOD(const std::function &, questionAnswered, (), (const, override)); + MOCK_METHOD(void, setQuestionAnswered, (const std::function &), (override)); + MOCK_METHOD(std::vector &, extensions, (), (const, override)); MOCK_METHOD(void, setExtensions, (const std::vector &), (override)); MOCK_METHOD(const ScriptMap &, scripts, (), (const, override)); + + MOCK_METHOD(const std::string &, userAgent, (), (const, override)); + MOCK_METHOD(void, setUserAgent, (const std::string &), (override)); }; } // namespace scratchcpprender From 39d12ef07f7505ac49bcb9b235b433a9402e06ed Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 11 Feb 2024 16:49:16 +0100 Subject: [PATCH 2/4] Add question API to ProjectLoader --- src/projectloader.cpp | 12 +++++++ src/projectloader.h | 3 ++ test/projectloader/projectloader_test.cpp | 38 +++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/projectloader.cpp b/src/projectloader.cpp index 85fafba..9def881 100644 --- a/src/projectloader.cpp +++ b/src/projectloader.cpp @@ -166,6 +166,16 @@ void ProjectLoader::stop() } } +void ProjectLoader::answerQuestion(const QString &answer) +{ + if (m_engine) { + auto f = m_engine->questionAnswered(); + + if (f) + f(answer.toStdString()); + } +} + void ProjectLoader::timerEvent(QTimerEvent *event) { if (m_loadThread.isRunning()) @@ -220,6 +230,8 @@ void ProjectLoader::load() auto removeMonitorHandler = std::bind(&ProjectLoader::removeMonitor, this, std::placeholders::_1, std::placeholders::_2); m_engine->setRemoveMonitorHandler(std::function(removeMonitorHandler)); + m_engine->setQuestionAsked([this](const std::string &question) { emit questionAsked(QString::fromStdString(question)); }); + // Load targets const auto &targets = m_engine->targets(); diff --git a/src/projectloader.h b/src/projectloader.h index 06ac070..06731ae 100644 --- a/src/projectloader.h +++ b/src/projectloader.h @@ -64,6 +64,8 @@ class ProjectLoader : public QObject Q_INVOKABLE void start(); Q_INVOKABLE void stop(); + Q_INVOKABLE void answerQuestion(const QString &answer); + double fps() const; void setFps(double newFps); @@ -107,6 +109,7 @@ class ProjectLoader : public QObject void cloneDeleted(SpriteModel *model); void monitorAdded(MonitorModel *model); void monitorRemoved(MonitorModel *model); + void questionAsked(QString question); protected: void timerEvent(QTimerEvent *event) override; diff --git a/test/projectloader/projectloader_test.cpp b/test/projectloader/projectloader_test.cpp index 47420f6..23ae4a0 100644 --- a/test/projectloader/projectloader_test.cpp +++ b/test/projectloader/projectloader_test.cpp @@ -14,6 +14,7 @@ using namespace scratchcpprender; using namespace libscratchcpp; using ::testing::Return; +using ::testing::ReturnRef; class ProjectLoaderTest : public testing::Test { @@ -59,6 +60,11 @@ class ProjectLoaderTest : public testing::Test } }; +struct AnswerQuestionMock +{ + MOCK_METHOD(void, answer, (const std::string &), ()); +}; + TEST_F(ProjectLoaderTest, Constructors) { ProjectLoader loader1; @@ -162,6 +168,38 @@ TEST_F(ProjectLoaderTest, TimerEvent) QCoreApplication::sendEvent(&loader, &event); } +TEST_F(ProjectLoaderTest, QuestionAsked) +{ + ProjectLoader loader; + QSignalSpy spy(&loader, &ProjectLoader::questionAsked); + + load(&loader, "load_test.sb3"); + + auto engine = loader.engine(); + auto f = engine->questionAsked(); + ASSERT_TRUE(f); + ASSERT_TRUE(spy.isEmpty()); + f("test"); + ASSERT_EQ(spy.count(), 1); + + auto args = spy.takeFirst(); + ASSERT_EQ(args.size(), 1); + ASSERT_EQ(args.first().toString(), "test"); +} + +TEST_F(ProjectLoaderTest, AnswerQuestion) +{ + ProjectLoader loader; + EngineMock engine; + loader.setEngine(&engine); + + AnswerQuestionMock mock; + std::function f = std::bind(&AnswerQuestionMock::answer, &mock, std::placeholders::_1); + EXPECT_CALL(engine, questionAnswered()).WillOnce(ReturnRef(f)); + EXPECT_CALL(mock, answer("hello")); + loader.answerQuestion("hello"); +} + TEST_F(ProjectLoaderTest, Fps) { ProjectLoader loader; From e3ffc6dd529ea76e4303d0bbd7c68785a559e0d4 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 11 Feb 2024 16:50:10 +0100 Subject: [PATCH 3/4] Add Question component --- src/CMakeLists.txt | 2 + src/icons/enter.svg | 81 ++++++++++++++++++++++++++++++ src/internal/Question.qml | 100 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 src/icons/enter.svg create mode 100644 src/internal/Question.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 288117b..c133a57 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,8 +13,10 @@ qt_add_qml_module(scratchcpp-render internal/MonitorSlider.qml internal/ListMonitor.qml internal/TextBubble.qml + internal/Question.qml shaders/sprite.vert shaders/sprite.frag + icons/enter.svg SOURCES global.h projectloader.cpp diff --git a/src/icons/enter.svg b/src/icons/enter.svg new file mode 100644 index 0000000..2238638 --- /dev/null +++ b/src/icons/enter.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + General/Check + Created with Sketch. + + General/Check + Created with Sketch. + + General/Check + Created with Sketch. + + + + + + diff --git a/src/internal/Question.qml b/src/internal/Question.qml new file mode 100644 index 0000000..5bc47b0 --- /dev/null +++ b/src/internal/Question.qml @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + property string question: "" + readonly property alias answer: textField.text + signal closed() + + QtObject { + id: priv + readonly property int margin: 18 + readonly property int spacing: 6 + } + + color: "white" + border.color: Qt.rgba(0.85, 0.85, 0.85, 1) + border.width: 2 + radius: 9 + implicitHeight: labelLoader.height + textField.height + 2 * priv.margin + priv.spacing + onActiveFocusChanged: { + if(activeFocus) + textField.forceActiveFocus(); + } + + function clear() { + textField.clear(); + question = ""; + } + + Loader { + id: labelLoader + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: priv.margin + anchors.topMargin: priv.margin + anchors.rightMargin: priv.margin + active: root.question !== "" + + sourceComponent: Text { + text: root.question + color: "#575E75" + font.family: "Helvetica" + font.pointSize: 9 + font.bold: true + } + } + + TextField { + id: textField + anchors.left: parent.left + anchors.right: parent.right + anchors.top: labelLoader.bottom + anchors.leftMargin: priv.margin - leftInset + anchors.topMargin: priv.spacing - topInset + anchors.rightMargin: priv.margin - rightInset + anchors.bottomMargin: priv.margin - bottomInset + color: "#575E75" + font.family: "Helvetica" + font.pointSize: 8 + leftInset: 5 + topInset: 0 + rightInset: 0 + bottomInset: 0 + padding: 0 + Keys.onReturnPressed: closed() + Keys.onEnterPressed: closed() + + background: Rectangle { + color: root.color + border.color: root.border.color + radius: height / 2 + implicitHeight: 30 + } + + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: 4 + anchors.topMargin: 3.5 + color: "#4D97FF" + width: 25 + height: 25 + radius: width / 2 + + Image { + anchors.fill: parent + source: "qrc:/qt/qml/ScratchCPP/Render/icons/enter.svg" + + MouseArea { + anchors.fill: parent + onClicked: closed() + } + } + } + } +} From 36d233a80e86ff2242fff0a03c82c83ec22f6d64 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 11 Feb 2024 17:10:19 +0100 Subject: [PATCH 4/4] ProjectPlayer: Add question text box --- src/ProjectPlayer.qml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 1c27795..702d02e 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -81,6 +81,12 @@ ProjectScene { if(i !== monitors.model.count) monitors.model.remove(i); } + + onQuestionAsked: (question)=> { + questionLoader.active = true; + questionLoader.item.clear(); + questionLoader.item.question = question; + } } function start() { @@ -268,4 +274,22 @@ ProjectScene { } } } + + Loader { + id: questionLoader + anchors.left: contentRect.left + anchors.right: contentRect.right + anchors.bottom: contentRect.bottom + anchors.margins: 9 + active: false + + sourceComponent: Question { + onClosed: { + loader.answerQuestion(answer); + questionLoader.active = false; + } + + Component.onCompleted: forceActiveFocus() + } + } }