Skip to content

Commit 0aa0537

Browse files
authored
Merge pull request #91 from scratchcpp/question_text_box
Implement question text box
2 parents 2dccd71 + 36d233a commit 0aa0537

File tree

9 files changed

+274
-1
lines changed

9 files changed

+274
-1
lines changed

libscratchcpp

Submodule libscratchcpp updated 48 files

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ qt_add_qml_module(scratchcpp-render
1313
internal/MonitorSlider.qml
1414
internal/ListMonitor.qml
1515
internal/TextBubble.qml
16+
internal/Question.qml
1617
shaders/sprite.vert
1718
shaders/sprite.frag
19+
icons/enter.svg
1820
SOURCES
1921
global.h
2022
projectloader.cpp

src/ProjectPlayer.qml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ ProjectScene {
8181
if(i !== monitors.model.count)
8282
monitors.model.remove(i);
8383
}
84+
85+
onQuestionAsked: (question)=> {
86+
questionLoader.active = true;
87+
questionLoader.item.clear();
88+
questionLoader.item.question = question;
89+
}
8490
}
8591

8692
function start() {
@@ -268,4 +274,22 @@ ProjectScene {
268274
}
269275
}
270276
}
277+
278+
Loader {
279+
id: questionLoader
280+
anchors.left: contentRect.left
281+
anchors.right: contentRect.right
282+
anchors.bottom: contentRect.bottom
283+
anchors.margins: 9
284+
active: false
285+
286+
sourceComponent: Question {
287+
onClosed: {
288+
loader.answerQuestion(answer);
289+
questionLoader.active = false;
290+
}
291+
292+
Component.onCompleted: forceActiveFocus()
293+
}
294+
}
271295
}

src/icons/enter.svg

Lines changed: 81 additions & 0 deletions
Loading

src/internal/Question.qml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
import QtQuick
4+
import QtQuick.Controls
5+
6+
Rectangle {
7+
id: root
8+
property string question: ""
9+
readonly property alias answer: textField.text
10+
signal closed()
11+
12+
QtObject {
13+
id: priv
14+
readonly property int margin: 18
15+
readonly property int spacing: 6
16+
}
17+
18+
color: "white"
19+
border.color: Qt.rgba(0.85, 0.85, 0.85, 1)
20+
border.width: 2
21+
radius: 9
22+
implicitHeight: labelLoader.height + textField.height + 2 * priv.margin + priv.spacing
23+
onActiveFocusChanged: {
24+
if(activeFocus)
25+
textField.forceActiveFocus();
26+
}
27+
28+
function clear() {
29+
textField.clear();
30+
question = "";
31+
}
32+
33+
Loader {
34+
id: labelLoader
35+
anchors.left: parent.left
36+
anchors.right: parent.right
37+
anchors.top: parent.top
38+
anchors.leftMargin: priv.margin
39+
anchors.topMargin: priv.margin
40+
anchors.rightMargin: priv.margin
41+
active: root.question !== ""
42+
43+
sourceComponent: Text {
44+
text: root.question
45+
color: "#575E75"
46+
font.family: "Helvetica"
47+
font.pointSize: 9
48+
font.bold: true
49+
}
50+
}
51+
52+
TextField {
53+
id: textField
54+
anchors.left: parent.left
55+
anchors.right: parent.right
56+
anchors.top: labelLoader.bottom
57+
anchors.leftMargin: priv.margin - leftInset
58+
anchors.topMargin: priv.spacing - topInset
59+
anchors.rightMargin: priv.margin - rightInset
60+
anchors.bottomMargin: priv.margin - bottomInset
61+
color: "#575E75"
62+
font.family: "Helvetica"
63+
font.pointSize: 8
64+
leftInset: 5
65+
topInset: 0
66+
rightInset: 0
67+
bottomInset: 0
68+
padding: 0
69+
Keys.onReturnPressed: closed()
70+
Keys.onEnterPressed: closed()
71+
72+
background: Rectangle {
73+
color: root.color
74+
border.color: root.border.color
75+
radius: height / 2
76+
implicitHeight: 30
77+
}
78+
79+
Rectangle {
80+
anchors.right: parent.right
81+
anchors.top: parent.top
82+
anchors.rightMargin: 4
83+
anchors.topMargin: 3.5
84+
color: "#4D97FF"
85+
width: 25
86+
height: 25
87+
radius: width / 2
88+
89+
Image {
90+
anchors.fill: parent
91+
source: "qrc:/qt/qml/ScratchCPP/Render/icons/enter.svg"
92+
93+
MouseArea {
94+
anchors.fill: parent
95+
onClicked: closed()
96+
}
97+
}
98+
}
99+
}
100+
}

src/projectloader.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ void ProjectLoader::stop()
166166
}
167167
}
168168

169+
void ProjectLoader::answerQuestion(const QString &answer)
170+
{
171+
if (m_engine) {
172+
auto f = m_engine->questionAnswered();
173+
174+
if (f)
175+
f(answer.toStdString());
176+
}
177+
}
178+
169179
void ProjectLoader::timerEvent(QTimerEvent *event)
170180
{
171181
if (m_loadThread.isRunning())
@@ -220,6 +230,8 @@ void ProjectLoader::load()
220230
auto removeMonitorHandler = std::bind(&ProjectLoader::removeMonitor, this, std::placeholders::_1, std::placeholders::_2);
221231
m_engine->setRemoveMonitorHandler(std::function<void(Monitor *, IMonitorHandler *)>(removeMonitorHandler));
222232

233+
m_engine->setQuestionAsked([this](const std::string &question) { emit questionAsked(QString::fromStdString(question)); });
234+
223235
// Load targets
224236
const auto &targets = m_engine->targets();
225237

src/projectloader.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class ProjectLoader : public QObject
6464
Q_INVOKABLE void start();
6565
Q_INVOKABLE void stop();
6666

67+
Q_INVOKABLE void answerQuestion(const QString &answer);
68+
6769
double fps() const;
6870
void setFps(double newFps);
6971

@@ -107,6 +109,7 @@ class ProjectLoader : public QObject
107109
void cloneDeleted(SpriteModel *model);
108110
void monitorAdded(MonitorModel *model);
109111
void monitorRemoved(MonitorModel *model);
112+
void questionAsked(QString question);
110113

111114
protected:
112115
void timerEvent(QTimerEvent *event) override;

test/mocks/enginemock.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class EngineMock : public IEngine
4949
MOCK_METHOD(void, setKeyState, (const KeyEvent &, bool), (override));
5050
MOCK_METHOD(void, setAnyKeyPressed, (bool), (override));
5151

52+
MOCK_METHOD(void, mouseWheelUp, (), (override));
53+
MOCK_METHOD(void, mouseWheelDown, (), (override));
54+
5255
MOCK_METHOD(double, mouseX, (), (const, override));
5356
MOCK_METHOD(void, setMouseX, (double x), (override));
5457

@@ -103,6 +106,7 @@ class EngineMock : public IEngine
103106
MOCK_METHOD(void, addBackdropChangeScript, (std::shared_ptr<Block>, int), (override));
104107
MOCK_METHOD(void, addCloneInitScript, (std::shared_ptr<Block>), (override));
105108
MOCK_METHOD(void, addKeyPressScript, (std::shared_ptr<Block>, int), (override));
109+
MOCK_METHOD(void, addTargetClickScript, (std::shared_ptr<Block>), (override));
106110

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

129+
MOCK_METHOD(const std::function<void(const std::string &)> &, questionAsked, (), (const, override));
130+
MOCK_METHOD(void, setQuestionAsked, (const std::function<void(const std::string &)> &), (override));
131+
132+
MOCK_METHOD(const std::function<void(const std::string &)> &, questionAnswered, (), (const, override));
133+
MOCK_METHOD(void, setQuestionAnswered, (const std::function<void(const std::string &)> &), (override));
134+
125135
MOCK_METHOD(std::vector<std::string> &, extensions, (), (const, override));
126136
MOCK_METHOD(void, setExtensions, (const std::vector<std::string> &), (override));
127137

128138
MOCK_METHOD(const ScriptMap &, scripts, (), (const, override));
139+
140+
MOCK_METHOD(const std::string &, userAgent, (), (const, override));
141+
MOCK_METHOD(void, setUserAgent, (const std::string &), (override));
129142
};
130143

131144
} // namespace scratchcpprender

test/projectloader/projectloader_test.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ using namespace scratchcpprender;
1414
using namespace libscratchcpp;
1515

1616
using ::testing::Return;
17+
using ::testing::ReturnRef;
1718

1819
class ProjectLoaderTest : public testing::Test
1920
{
@@ -59,6 +60,11 @@ class ProjectLoaderTest : public testing::Test
5960
}
6061
};
6162

63+
struct AnswerQuestionMock
64+
{
65+
MOCK_METHOD(void, answer, (const std::string &), ());
66+
};
67+
6268
TEST_F(ProjectLoaderTest, Constructors)
6369
{
6470
ProjectLoader loader1;
@@ -162,6 +168,38 @@ TEST_F(ProjectLoaderTest, TimerEvent)
162168
QCoreApplication::sendEvent(&loader, &event);
163169
}
164170

171+
TEST_F(ProjectLoaderTest, QuestionAsked)
172+
{
173+
ProjectLoader loader;
174+
QSignalSpy spy(&loader, &ProjectLoader::questionAsked);
175+
176+
load(&loader, "load_test.sb3");
177+
178+
auto engine = loader.engine();
179+
auto f = engine->questionAsked();
180+
ASSERT_TRUE(f);
181+
ASSERT_TRUE(spy.isEmpty());
182+
f("test");
183+
ASSERT_EQ(spy.count(), 1);
184+
185+
auto args = spy.takeFirst();
186+
ASSERT_EQ(args.size(), 1);
187+
ASSERT_EQ(args.first().toString(), "test");
188+
}
189+
190+
TEST_F(ProjectLoaderTest, AnswerQuestion)
191+
{
192+
ProjectLoader loader;
193+
EngineMock engine;
194+
loader.setEngine(&engine);
195+
196+
AnswerQuestionMock mock;
197+
std::function<void(const std::string &)> f = std::bind(&AnswerQuestionMock::answer, &mock, std::placeholders::_1);
198+
EXPECT_CALL(engine, questionAnswered()).WillOnce(ReturnRef(f));
199+
EXPECT_CALL(mock, answer("hello"));
200+
loader.answerQuestion("hello");
201+
}
202+
165203
TEST_F(ProjectLoaderTest, Fps)
166204
{
167205
ProjectLoader loader;

0 commit comments

Comments
 (0)