Skip to content

Commit d7645d5

Browse files
committed
Add clone method to Sprite
1 parent af49e8b commit d7645d5

File tree

4 files changed

+176
-1
lines changed

4 files changed

+176
-1
lines changed

include/scratchcpp/sprite.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
2626

2727
void setInterface(ISpriteHandler *newInterface);
2828

29+
std::shared_ptr<Sprite> clone();
30+
2931
bool isClone() const;
3032

3133
Sprite *cloneRoot() const;

src/scratch/sprite.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
#include <scratchcpp/sprite.h>
44
#include <scratchcpp/ispritehandler.h>
5-
#include <string>
5+
#include <scratchcpp/iengine.h>
6+
#include <scratchcpp/variable.h>
7+
#include <scratchcpp/list.h>
68
#include <cassert>
79

810
#include "sprite_p.h"
@@ -33,6 +35,62 @@ void Sprite::setInterface(ISpriteHandler *newInterface)
3335
impl->iface->onSpriteChanged(this);
3436
}
3537

38+
/*! Creates a clone of the sprite. */
39+
std::shared_ptr<Sprite> Sprite::clone()
40+
{
41+
IEngine *eng = engine();
42+
43+
if (eng) {
44+
std::shared_ptr<Sprite> clone = std::make_shared<Sprite>();
45+
46+
if (impl->cloneRoot == nullptr)
47+
clone->impl->cloneRoot = this;
48+
else
49+
clone->impl->cloneRoot = impl->cloneRoot;
50+
51+
clone->impl->cloneParent = this;
52+
impl->childClones.push_back(clone);
53+
54+
// Copy data
55+
clone->setName(name());
56+
57+
const auto &vars = variables();
58+
59+
for (auto var : vars)
60+
clone->addVariable(std::make_shared<Variable>(var->id(), var->name(), var->value()));
61+
62+
const auto &l = lists();
63+
64+
for (auto list : l) {
65+
auto newList = std::make_shared<List>(list->id(), list->name());
66+
clone->addList(newList);
67+
68+
for (const Value &item : *list)
69+
newList->push_back(item);
70+
}
71+
72+
clone->setCurrentCostume(currentCostume());
73+
clone->setLayerOrder(layerOrder());
74+
clone->setVolume(volume());
75+
clone->setEngine(engine());
76+
77+
clone->impl->visible = impl->visible;
78+
clone->impl->x = impl->x;
79+
clone->impl->y = impl->y;
80+
clone->impl->size = impl->size;
81+
clone->impl->direction = impl->direction;
82+
clone->impl->draggable = impl->draggable;
83+
clone->impl->rotationStyle = impl->rotationStyle;
84+
85+
// Call "when I start as clone" scripts
86+
eng->initClone(clone.get());
87+
88+
return clone;
89+
}
90+
91+
return nullptr;
92+
}
93+
3694
/*! Returns true if this is a clone. */
3795
bool Sprite::isClone() const
3896
{

test/scratch_classes/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ add_executable(
3535
target_link_libraries(
3636
sprite_test
3737
GTest::gtest_main
38+
GTest::gmock_main
3839
scratchcpp
40+
scratchcpp_mocks
3941
)
4042

4143
gtest_discover_tests(sprite_test)

test/scratch_classes/sprite_test.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
#include <scratchcpp/sprite.h>
2+
#include <scratchcpp/variable.h>
3+
#include <scratchcpp/list.h>
4+
#include <enginemock.h>
25

36
#include "../common.h"
47

58
using namespace libscratchcpp;
69

10+
using ::testing::_;
11+
using ::testing::SaveArg;
12+
713
TEST(SpriteTest, IsStage)
814
{
915
Sprite sprite;
@@ -18,6 +24,113 @@ TEST(SpriteTest, Visible)
1824
ASSERT_FALSE(sprite.visible());
1925
}
2026

27+
TEST(SpriteTest, Clone)
28+
{
29+
Sprite sprite;
30+
sprite.setName("Sprite1");
31+
auto var1 = std::make_shared<Variable>("a", "var1", "hello");
32+
auto var2 = std::make_shared<Variable>("b", "var2", "world");
33+
sprite.addVariable(var1);
34+
sprite.addVariable(var2);
35+
36+
auto list1 = std::make_shared<List>("c", "list1");
37+
list1->push_back("item1");
38+
list1->push_back("item2");
39+
auto list2 = std::make_shared<List>("d", "list2");
40+
list2->push_back("test");
41+
sprite.addList(list1);
42+
sprite.addList(list2);
43+
44+
sprite.setCurrentCostume(2);
45+
sprite.setLayerOrder(5);
46+
sprite.setVolume(50);
47+
48+
sprite.setVisible(false);
49+
sprite.setX(100.25);
50+
sprite.setY(-45.43);
51+
sprite.setSize(54.121);
52+
sprite.setDirection(179.4);
53+
sprite.setDraggable(true);
54+
sprite.setRotationStyle(Sprite::RotationStyle::DoNotRotate);
55+
56+
auto checkCloneData = [](Sprite *clone) {
57+
ASSERT_TRUE(clone);
58+
Sprite *root = clone->cloneRoot();
59+
60+
ASSERT_EQ(clone->name(), "Sprite1");
61+
ASSERT_EQ(clone->variables().size(), 2);
62+
ASSERT_NE(clone->variables(), root->variables());
63+
ASSERT_EQ(clone->variableAt(0)->id(), "a");
64+
ASSERT_EQ(clone->variableAt(0)->name(), "var1");
65+
ASSERT_EQ(clone->variableAt(0)->value().toString(), "hello");
66+
ASSERT_EQ(clone->variableAt(1)->id(), "b");
67+
ASSERT_EQ(clone->variableAt(1)->name(), "var2");
68+
ASSERT_EQ(clone->variableAt(1)->value().toString(), "world");
69+
70+
ASSERT_EQ(clone->lists().size(), 2);
71+
ASSERT_NE(clone->lists(), root->lists());
72+
ASSERT_EQ(clone->listAt(0)->id(), "c");
73+
ASSERT_EQ(clone->listAt(0)->name(), "list1");
74+
ASSERT_EQ(*clone->listAt(0), std::deque<Value>({ "item1", "item2" }));
75+
ASSERT_EQ(clone->listAt(1)->id(), "d");
76+
ASSERT_EQ(clone->listAt(1)->name(), "list2");
77+
ASSERT_EQ(*clone->listAt(1), std::deque<Value>({ "test" }));
78+
79+
ASSERT_EQ(clone->currentCostume(), 2);
80+
ASSERT_EQ(clone->layerOrder(), 5);
81+
ASSERT_EQ(clone->volume(), 50);
82+
ASSERT_EQ(clone->engine(), root->engine());
83+
84+
ASSERT_EQ(clone->visible(), false);
85+
ASSERT_EQ(clone->x(), 100.25);
86+
ASSERT_EQ(clone->y(), -45.43);
87+
ASSERT_EQ(clone->size(), 54.121);
88+
ASSERT_EQ(clone->direction(), 179.4);
89+
ASSERT_EQ(clone->draggable(), true);
90+
ASSERT_EQ(clone->rotationStyle(), Sprite::RotationStyle::DoNotRotate);
91+
};
92+
93+
ASSERT_FALSE(sprite.isClone());
94+
ASSERT_EQ(sprite.cloneRoot(), nullptr);
95+
ASSERT_EQ(sprite.cloneParent(), nullptr);
96+
97+
ASSERT_EQ(sprite.clone(), nullptr);
98+
ASSERT_FALSE(sprite.isClone());
99+
ASSERT_EQ(sprite.cloneRoot(), nullptr);
100+
ASSERT_EQ(sprite.cloneParent(), nullptr);
101+
102+
EngineMock engine;
103+
sprite.setEngine(&engine);
104+
105+
Sprite *clone1;
106+
EXPECT_CALL(engine, initClone(_)).WillOnce(SaveArg<0>(&clone1));
107+
ASSERT_EQ(sprite.clone().get(), clone1);
108+
ASSERT_FALSE(sprite.isClone());
109+
ASSERT_EQ(sprite.cloneRoot(), nullptr);
110+
ASSERT_EQ(sprite.cloneParent(), nullptr);
111+
112+
ASSERT_TRUE(clone1->isClone());
113+
ASSERT_EQ(clone1->cloneRoot(), &sprite);
114+
ASSERT_EQ(clone1->cloneParent(), &sprite);
115+
116+
checkCloneData(clone1);
117+
118+
// Modify root sprite data to make sure parent is used
119+
sprite.setLayerOrder(3);
120+
121+
Sprite *clone2;
122+
EXPECT_CALL(engine, initClone(_)).WillOnce(SaveArg<0>(&clone2));
123+
ASSERT_EQ(clone1->clone().get(), clone2);
124+
ASSERT_TRUE(clone1->isClone());
125+
ASSERT_EQ(clone1->cloneRoot(), &sprite);
126+
ASSERT_EQ(clone1->cloneParent(), &sprite);
127+
ASSERT_TRUE(clone2->isClone());
128+
ASSERT_EQ(clone2->cloneRoot(), &sprite);
129+
ASSERT_EQ(clone2->cloneParent(), clone1);
130+
131+
checkCloneData(clone2);
132+
}
133+
21134
TEST(SpriteTest, X)
22135
{
23136
Sprite sprite;

0 commit comments

Comments
 (0)