Skip to content

Implement question text box #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libscratchcpp
Submodule libscratchcpp updated 48 files
+3 −3 README.md
+27 −0 include/scratchcpp/iengine.h
+4 −0 include/scratchcpp/list.h
+2 −0 include/scratchcpp/monitor.h
+63 −51 include/scratchcpp/value.h
+4 −0 include/scratchcpp/variable.h
+6 −4 include/scratchcpp/virtualmachine.h
+12 −0 src/blocks/eventblocks.cpp
+2 −0 src/blocks/eventblocks.h
+82 −0 src/blocks/listblocks.cpp
+10 −0 src/blocks/listblocks.h
+97 −0 src/blocks/sensingblocks.cpp
+35 −0 src/blocks/sensingblocks.h
+82 −0 src/blocks/variableblocks.cpp
+10 −0 src/blocks/variableblocks.h
+73 −14 src/engine/internal/engine.cpp
+19 −1 src/engine/internal/engine.h
+20 −0 src/engine/virtualmachine.cpp
+404 −382 src/engine/virtualmachine_p.cpp
+2 −1 src/engine/virtualmachine_p.h
+1 −0 src/internal/iprojectreader.h
+12 −0 src/internal/scratch3reader.cpp
+2 −0 src/internal/scratch3reader.h
+1 −0 src/project_p.cpp
+12 −0 src/scratch/list.cpp
+2 −0 src/scratch/list_p.h
+12 −0 src/scratch/monitor.cpp
+2 −2 src/scratch/sprite.cpp
+12 −0 src/scratch/variable.cpp
+2 −0 src/scratch/variable_p.h
+42 −0 test/blocks/event_blocks_test.cpp
+212 −0 test/blocks/list_blocks_test.cpp
+182 −0 test/blocks/sensing_blocks_test.cpp
+212 −0 test/blocks/variable_blocks_test.cpp
+183 −0 test/engine/engine_test.cpp
+13 −0 test/load_project/load_project_test.cpp
+13 −0 test/mocks/enginemock.h
+ test/mouse_wheel.sb3
+2 −0 test/scratch_classes/CMakeLists.txt
+11 −0 test/scratch_classes/list_test.cpp
+20 −0 test/scratch_classes/monitor_test.cpp
+15 −0 test/scratch_classes/sprite_test.cpp
+14 −0 test/scratch_classes/testsection.cpp
+16 −0 test/scratch_classes/testsection.h
+5 −0 test/scratch_classes/value_test.cpp
+11 −0 test/scratch_classes/variable_test.cpp
+ test/target_click_scripts.sb3
+50 −0 test/virtual_machine/virtual_machine_test.cpp
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/ProjectPlayer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
}
}
}
81 changes: 81 additions & 0 deletions src/icons/enter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions src/internal/Question.qml
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
}
12 changes: 12 additions & 0 deletions src/projectloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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<void(Monitor *, IMonitorHandler *)>(removeMonitorHandler));

m_engine->setQuestionAsked([this](const std::string &question) { emit questionAsked(QString::fromStdString(question)); });

// Load targets
const auto &targets = m_engine->targets();

Expand Down
3 changes: 3 additions & 0 deletions src/projectloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions test/mocks/enginemock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -103,6 +106,7 @@ class EngineMock : public IEngine
MOCK_METHOD(void, addBackdropChangeScript, (std::shared_ptr<Block>, int), (override));
MOCK_METHOD(void, addCloneInitScript, (std::shared_ptr<Block>), (override));
MOCK_METHOD(void, addKeyPressScript, (std::shared_ptr<Block>, int), (override));
MOCK_METHOD(void, addTargetClickScript, (std::shared_ptr<Block>), (override));

MOCK_METHOD(const std::vector<std::shared_ptr<Target>> &, targets, (), (const, override));
MOCK_METHOD(void, setTargets, (const std::vector<std::shared_ptr<Target>> &), (override));
Expand All @@ -122,10 +126,19 @@ class EngineMock : public IEngine
MOCK_METHOD(void, setAddMonitorHandler, (const std::function<void(Monitor *)> &), (override));
MOCK_METHOD(void, setRemoveMonitorHandler, (const std::function<void(Monitor *, IMonitorHandler *)> &), (override));

MOCK_METHOD(const std::function<void(const std::string &)> &, questionAsked, (), (const, override));
MOCK_METHOD(void, setQuestionAsked, (const std::function<void(const std::string &)> &), (override));

MOCK_METHOD(const std::function<void(const std::string &)> &, questionAnswered, (), (const, override));
MOCK_METHOD(void, setQuestionAnswered, (const std::function<void(const std::string &)> &), (override));

MOCK_METHOD(std::vector<std::string> &, extensions, (), (const, override));
MOCK_METHOD(void, setExtensions, (const std::vector<std::string> &), (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
38 changes: 38 additions & 0 deletions test/projectloader/projectloader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ using namespace scratchcpprender;
using namespace libscratchcpp;

using ::testing::Return;
using ::testing::ReturnRef;

class ProjectLoaderTest : public testing::Test
{
Expand Down Expand Up @@ -59,6 +60,11 @@ class ProjectLoaderTest : public testing::Test
}
};

struct AnswerQuestionMock
{
MOCK_METHOD(void, answer, (const std::string &), ());
};

TEST_F(ProjectLoaderTest, Constructors)
{
ProjectLoader loader1;
Expand Down Expand Up @@ -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<void(const std::string &)> 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;
Expand Down