Skip to content

Commit dd38f8f

Browse files
authored
Merge pull request #66 from scratchcpp/clones
Implement sprite cloning
2 parents 7dab0ba + fd0a553 commit dd38f8f

22 files changed

+371
-113
lines changed

src/ProjectPlayer.qml

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ProjectScene {
3636
fileName: root.fileName
3737
stageWidth: root.stageWidth
3838
stageHeight: root.stageHeight
39+
3940
onLoadingFinished: {
4041
priv.loading = false;
4142

@@ -44,7 +45,25 @@ ProjectScene {
4445
else
4546
failedToLoad();
4647
}
48+
4749
onStageChanged: stage.loadCostume();
50+
51+
onCloneCreated: (cloneModel)=> clones.model.append({"spriteModel": cloneModel})
52+
53+
onCloneDeleted: (cloneModel)=> {
54+
// TODO: Removing the clone from C++ would probably be faster
55+
let i;
56+
57+
for(i = 0; i < clones.model.count; i++) {
58+
if(clones.model.get(i).spriteModel === cloneModel)
59+
break;
60+
}
61+
62+
if(i === clones.model.count)
63+
console.error("error: deleted clone doesn't exist");
64+
else
65+
clones.model.remove(i);
66+
}
4867
}
4968

5069
function start() {
@@ -73,21 +92,34 @@ ProjectScene {
7392
onStageModelChanged: stageModel.renderedTarget = this
7493
}
7594

76-
Repeater {
77-
id: sprites
78-
model: loader.sprites
95+
Component {
96+
id: renderedSprite
7997

8098
RenderedTarget {
8199
id: target
82-
engine: loader.engine
83-
spriteModel: modelData
84100
mouseArea: sceneMouseArea
85101
stageScale: root.stageScale
86102
transform: Scale { xScale: mirrorHorizontally ? -1 : 1 }
87-
Component.onCompleted: modelData.renderedTarget = this
103+
Component.onCompleted: {
104+
engine = loader.engine;
105+
spriteModel = modelData;
106+
spriteModel.renderedTarget = this;
107+
}
88108
}
89109
}
90110

111+
Repeater {
112+
id: sprites
113+
model: loader.sprites
114+
delegate: renderedSprite
115+
}
116+
117+
Repeater {
118+
id: clones
119+
model: ListModel {}
120+
delegate: renderedSprite
121+
}
122+
91123
Loader {
92124
anchors.fill: parent
93125
active: showLoadingProgress && loading
@@ -132,7 +164,7 @@ ProjectScene {
132164
id: sceneMouseArea
133165
anchors.fill: parent
134166
stage: stageTarget
135-
spriteRepeater: sprites
167+
projectLoader: loader
136168
onMouseMoved: (x, y)=> root.handleMouseMove(x, y)
137169
onMousePressed: root.handleMousePress()
138170
onMouseReleased: root.handleMouseRelease()

src/irenderedtarget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class IRenderedTarget : public QNanoQuickItem
3838

3939
virtual void beforeRedraw() = 0;
4040

41+
virtual void deinitClone() = 0;
42+
4143
virtual libscratchcpp::IEngine *engine() const = 0;
4244
virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0;
4345

src/mouseeventhandler.cpp

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include "mouseeventhandler.h"
77
#include "renderedtarget.h"
8+
#include "projectloader.h"
9+
#include "spritemodel.h"
810

911
using namespace scratchcpprender;
1012

@@ -23,14 +25,20 @@ void MouseEventHandler::setStage(IRenderedTarget *stage)
2325
m_stage = stage;
2426
}
2527

26-
QQuickItem *MouseEventHandler::spriteRepeater() const
28+
ProjectLoader *MouseEventHandler::projectLoader() const
2729
{
28-
return m_spriteRepeater;
30+
return m_projectLoader;
2931
}
3032

31-
void MouseEventHandler::setSpriteRepeater(QQuickItem *repeater)
33+
void MouseEventHandler::setProjectLoader(ProjectLoader *newProjectLoader)
3234
{
33-
m_spriteRepeater = repeater;
35+
m_projectLoader = newProjectLoader;
36+
37+
if (m_projectLoader) {
38+
connect(m_projectLoader, &ProjectLoader::spritesChanged, this, &MouseEventHandler::getSprites);
39+
connect(m_projectLoader, &ProjectLoader::cloneCreated, this, &MouseEventHandler::addClone);
40+
connect(m_projectLoader, &ProjectLoader::cloneDeleted, this, &MouseEventHandler::removeClone);
41+
}
3442
}
3543

3644
bool MouseEventHandler::eventFilter(QObject *obj, QEvent *event)
@@ -76,34 +84,64 @@ bool MouseEventHandler::eventFilter(QObject *obj, QEvent *event)
7684
return QObject::eventFilter(obj, event);
7785
}
7886

79-
void MouseEventHandler::forwardPointEvent(QSinglePointEvent *event, QQuickItem *oldClickedItem)
87+
void scratchcpprender::MouseEventHandler::getSprites()
8088
{
81-
Q_ASSERT(m_spriteRepeater);
89+
Q_ASSERT(m_projectLoader);
8290

83-
if (!m_spriteRepeater)
91+
if (!m_projectLoader)
8492
return;
8593

86-
// Create list of sprites
87-
std::vector<IRenderedTarget *> sprites;
88-
int count = m_spriteRepeater->property("count").toInt();
89-
sprites.reserve(count);
94+
m_sprites.clear();
95+
const auto &spriteModels = m_projectLoader->spriteList();
9096

91-
for (int i = 0; i < count; i++) {
92-
QQuickItem *sprite = nullptr;
93-
QMetaObject::invokeMethod(m_spriteRepeater, "itemAt", Qt::DirectConnection, Q_RETURN_ARG(QQuickItem *, sprite), Q_ARG(int, i));
97+
for (SpriteModel *model : spriteModels) {
98+
Q_ASSERT(model);
99+
IRenderedTarget *sprite = model->renderedTarget();
94100
Q_ASSERT(sprite);
95-
Q_ASSERT(dynamic_cast<IRenderedTarget *>(sprite));
96-
Q_ASSERT(dynamic_cast<IRenderedTarget *>(sprite)->scratchTarget());
97-
sprites.push_back(dynamic_cast<IRenderedTarget *>(sprite));
101+
Q_ASSERT(sprite->scratchTarget());
102+
m_sprites.push_back(sprite);
98103
}
104+
}
105+
106+
void scratchcpprender::MouseEventHandler::addClone(SpriteModel *model)
107+
{
108+
Q_ASSERT(model);
109+
IRenderedTarget *sprite = model->renderedTarget();
110+
Q_ASSERT(sprite);
111+
Q_ASSERT(std::find_if(m_sprites.begin(), m_sprites.end(), [sprite](IRenderedTarget *renderedTarget) { return renderedTarget == sprite; }) == m_sprites.end());
112+
m_sprites.push_back(sprite);
113+
}
114+
115+
void scratchcpprender::MouseEventHandler::removeClone(SpriteModel *model)
116+
{
117+
Q_ASSERT(model);
118+
IRenderedTarget *sprite = model->renderedTarget();
119+
Q_ASSERT(sprite);
120+
m_sprites.erase(std::remove_if(m_sprites.begin(), m_sprites.end(), [sprite](IRenderedTarget *renderedTarget) { return renderedTarget == sprite; }), m_sprites.end());
121+
Q_ASSERT(std::find_if(m_sprites.begin(), m_sprites.end(), [sprite](IRenderedTarget *renderedTarget) { return renderedTarget == sprite; }) == m_sprites.end());
122+
123+
// Make sure the pointer is never used again after it becomes "dangling"
124+
if (m_clickedItem == sprite)
125+
m_clickedItem = nullptr;
126+
127+
if (m_hoveredItem == sprite)
128+
m_hoveredItem = nullptr;
129+
}
130+
131+
void MouseEventHandler::forwardPointEvent(QSinglePointEvent *event, QQuickItem *oldClickedItem)
132+
{
133+
Q_ASSERT(m_projectLoader);
134+
135+
if (!m_projectLoader)
136+
return;
99137

100-
// Sort the list by layer order
101-
std::sort(sprites.begin(), sprites.end(), [](IRenderedTarget *t1, IRenderedTarget *t2) { return t1->scratchTarget()->layerOrder() > t2->scratchTarget()->layerOrder(); });
138+
// Sort sprite list by layer order
139+
std::sort(m_sprites.begin(), m_sprites.end(), [](IRenderedTarget *t1, IRenderedTarget *t2) { return t1->scratchTarget()->layerOrder() > t2->scratchTarget()->layerOrder(); });
102140

103141
// Find hovered sprite
104142
QQuickItem *hoveredItem = nullptr;
105143

106-
for (IRenderedTarget *sprite : sprites) {
144+
for (IRenderedTarget *sprite : m_sprites) {
107145
// contains() expects position in the item's coordinate system
108146
QPointF localPos = sprite->mapFromScene(event->scenePosition());
109147

src/mouseeventhandler.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ namespace scratchcpprender
1313
{
1414

1515
class IRenderedTarget;
16+
class ProjectLoader;
17+
class SpriteModel;
1618

1719
class MouseEventHandler : public QObject
1820
{
@@ -23,25 +25,29 @@ class MouseEventHandler : public QObject
2325
IRenderedTarget *stage() const;
2426
void setStage(IRenderedTarget *stage);
2527

26-
QQuickItem *spriteRepeater() const;
27-
void setSpriteRepeater(QQuickItem *repeater);
28-
2928
bool eventFilter(QObject *obj, QEvent *event) override;
3029

30+
ProjectLoader *projectLoader() const;
31+
void setProjectLoader(ProjectLoader *newProjectLoader);
32+
3133
signals:
3234
void mouseMoved(qreal x, qreal y);
3335
void mousePressed();
3436
void mouseReleased();
3537

3638
private:
39+
void getSprites();
40+
void addClone(SpriteModel *model);
41+
void removeClone(SpriteModel *model);
3742
void forwardPointEvent(QSinglePointEvent *event, QQuickItem *oldClickedItem = nullptr);
3843
void sendPointEventToItem(QSinglePointEvent *event, QQuickItem *item);
3944
void sendHoverEventToItem(QHoverEvent *originalEvent, QEvent::Type newType, QQuickItem *item);
4045

4146
IRenderedTarget *m_stage = nullptr;
47+
std::vector<IRenderedTarget *> m_sprites;
4248
QQuickItem *m_hoveredItem = nullptr;
4349
QQuickItem *m_clickedItem = nullptr;
44-
QQuickItem *m_spriteRepeater = nullptr;
50+
ProjectLoader *m_projectLoader = nullptr;
4551
};
4652

4753
} // namespace scratchcpprender

src/projectloader.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ const QList<SpriteModel *> &ProjectLoader::spriteList() const
116116
return m_sprites;
117117
}
118118

119+
QQmlListProperty<SpriteModel> ProjectLoader::clones()
120+
{
121+
return QQmlListProperty<SpriteModel>(this, &m_clones);
122+
}
123+
124+
const QList<SpriteModel *> &ProjectLoader::cloneList() const
125+
{
126+
return m_clones;
127+
}
128+
119129
void ProjectLoader::start()
120130
{
121131
if (m_loadThread.isRunning())
@@ -196,6 +206,7 @@ void ProjectLoader::load()
196206
SpriteModel *sprite = new SpriteModel;
197207
sprite->moveToThread(qApp->thread());
198208
dynamic_cast<Sprite *>(target.get())->setInterface(sprite);
209+
connect(sprite, &SpriteModel::cloned, this, &ProjectLoader::addClone);
199210
m_sprites.push_back(sprite);
200211
}
201212
}
@@ -240,6 +251,31 @@ void ProjectLoader::redraw()
240251
if (renderedTarget)
241252
renderedTarget->beforeRedraw();
242253
}
254+
255+
for (auto sprite : m_clones) {
256+
auto renderedTarget = sprite->renderedTarget();
257+
258+
if (renderedTarget)
259+
renderedTarget->beforeRedraw();
260+
}
261+
}
262+
263+
void ProjectLoader::addClone(SpriteModel *model)
264+
{
265+
connect(model, &SpriteModel::cloneDeleted, this, &ProjectLoader::deleteClone);
266+
m_clones.push_back(model);
267+
emit cloneCreated(model);
268+
emit clonesChanged();
269+
}
270+
271+
void ProjectLoader::deleteClone(SpriteModel *model)
272+
{
273+
m_clones.removeAll(model);
274+
emit cloneDeleted(model);
275+
Q_ASSERT(model->renderedTarget());
276+
model->renderedTarget()->deinitClone();
277+
model->deleteLater();
278+
emit clonesChanged();
243279
}
244280

245281
double ProjectLoader::fps() const

src/projectloader.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include "stagemodel.h"
1111

12+
Q_MOC_INCLUDE("spritemodel.h");
13+
1214
namespace scratchcpprender
1315
{
1416

@@ -23,6 +25,7 @@ class ProjectLoader : public QObject
2325
Q_PROPERTY(libscratchcpp::IEngine *engine READ engine NOTIFY engineChanged)
2426
Q_PROPERTY(StageModel *stage READ stage NOTIFY stageChanged)
2527
Q_PROPERTY(QQmlListProperty<SpriteModel> sprites READ sprites NOTIFY spritesChanged)
28+
Q_PROPERTY(QQmlListProperty<SpriteModel> clones READ clones NOTIFY clonesChanged)
2629
Q_PROPERTY(double fps READ fps WRITE setFps NOTIFY fpsChanged)
2730
Q_PROPERTY(bool turboMode READ turboMode WRITE setTurboMode NOTIFY turboModeChanged)
2831
Q_PROPERTY(unsigned int stageWidth READ stageWidth WRITE setStageWidth NOTIFY stageWidthChanged)
@@ -49,6 +52,9 @@ class ProjectLoader : public QObject
4952
QQmlListProperty<SpriteModel> sprites();
5053
const QList<SpriteModel *> &spriteList() const;
5154

55+
QQmlListProperty<SpriteModel> clones();
56+
const QList<SpriteModel *> &cloneList() const;
57+
5258
Q_INVOKABLE void start();
5359
Q_INVOKABLE void stop();
5460

@@ -81,6 +87,7 @@ class ProjectLoader : public QObject
8187
void engineChanged();
8288
void stageChanged();
8389
void spritesChanged();
90+
void clonesChanged();
8491
void fpsChanged();
8592
void turboModeChanged();
8693
void stageWidthChanged();
@@ -89,6 +96,8 @@ class ProjectLoader : public QObject
8996
void spriteFencingChanged();
9097
void downloadedAssetsChanged();
9198
void assetCountChanged();
99+
void cloneCreated(SpriteModel *model);
100+
void cloneDeleted(SpriteModel *model);
92101

93102
protected:
94103
void timerEvent(QTimerEvent *event) override;
@@ -98,6 +107,8 @@ class ProjectLoader : public QObject
98107
void load();
99108
void initTimer();
100109
void redraw();
110+
void addClone(SpriteModel *model);
111+
void deleteClone(SpriteModel *model);
101112

102113
int m_timerId = -1;
103114
QString m_fileName;
@@ -108,6 +119,7 @@ class ProjectLoader : public QObject
108119
bool m_loadStatus = false;
109120
StageModel m_stage;
110121
QList<SpriteModel *> m_sprites;
122+
QList<SpriteModel *> m_clones;
111123
double m_fps = 30;
112124
bool m_turboMode = false;
113125
unsigned int m_stageWidth = 480;

src/renderedtarget.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ void RenderedTarget::beforeRedraw()
149149
setHeight(m_height);
150150
}
151151

152+
void RenderedTarget::deinitClone()
153+
{
154+
// Do not process mouse move events after the clone has been deleted
155+
disconnect(m_mouseArea, &SceneMouseArea::mouseMoved, this, &RenderedTarget::handleSceneMouseMove);
156+
157+
// Release drag lock
158+
if (m_mouseArea->draggedSprite() == this)
159+
m_mouseArea->setDraggedSprite(nullptr);
160+
}
161+
152162
IEngine *RenderedTarget::engine() const
153163
{
154164
return m_engine;

src/renderedtarget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class RenderedTarget : public IRenderedTarget
4343

4444
void beforeRedraw() override;
4545

46+
void deinitClone() override;
47+
4648
libscratchcpp::IEngine *engine() const override;
4749
void setEngine(libscratchcpp::IEngine *newEngine) override;
4850

0 commit comments

Comments
 (0)