Skip to content

Commit bc689db

Browse files
authored
Merge pull request #55 from scratchcpp/sprite_dragging
Implement sprite dragging
2 parents 080d9fe + aabad57 commit bc689db

File tree

12 files changed

+164
-8
lines changed

12 files changed

+164
-8
lines changed

ScratchCPPGui/ProjectPlayer.qml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ ProjectScene {
4747
id: stageTarget
4848
engine: loader.engine
4949
stageModel: loader.stage
50+
mouseArea: sceneMouseArea
5051
onStageModelChanged: stageModel.renderedTarget = this
5152
}
5253

@@ -66,6 +67,7 @@ ProjectScene {
6667
id: target
6768
engine: loader.engine
6869
spriteModel: modelData
70+
mouseArea: sceneMouseArea
6971
transform: Scale { xScale: mirrorHorizontally ? -1 : 1 }
7072
Component.onCompleted: modelData.renderedTarget = this
7173
}
@@ -112,7 +114,7 @@ ProjectScene {
112114
}
113115

114116
SceneMouseArea {
115-
id: mouseArea
117+
id: sceneMouseArea
116118
anchors.fill: parent
117119
stage: stageTarget
118120
spriteRepeater: sprites

ScratchCPPGui/irenderedtarget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace scratchcppgui
2121

2222
class StageModel;
2323
class SpriteModel;
24+
class SceneMouseArea;
2425

2526
class IRenderedTarget : public QNanoQuickItem
2627
{
@@ -47,6 +48,9 @@ class IRenderedTarget : public QNanoQuickItem
4748

4849
virtual libscratchcpp::Target *scratchTarget() const = 0;
4950

51+
virtual SceneMouseArea *mouseArea() const = 0;
52+
virtual void setMouseArea(SceneMouseArea *newMouseArea) = 0;
53+
5054
virtual qreal width() const = 0;
5155
virtual void setWidth(qreal width) = 0;
5256

ScratchCPPGui/mouseeventhandler.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ bool MouseEventHandler::eventFilter(QObject *obj, QEvent *event)
5757

5858
case QEvent::MouseButtonRelease:
5959
emit mouseReleased();
60-
forwardPointEvent(static_cast<QSinglePointEvent *>(event));
60+
61+
if (m_clickedItem) {
62+
sendPointEventToItem(static_cast<QSinglePointEvent *>(event), m_clickedItem);
63+
m_clickedItem = nullptr;
64+
} else
65+
forwardPointEvent(static_cast<QSinglePointEvent *>(event));
66+
6167
return true;
6268

6369
default:
@@ -137,6 +143,9 @@ void MouseEventHandler::sendPointEventToItem(QSinglePointEvent *event, QQuickIte
137143
case QEvent::MouseButtonPress:
138144
case QEvent::MouseButtonRelease:
139145
case QEvent::MouseMove: {
146+
if (event->type() == QEvent::MouseButtonPress)
147+
m_clickedItem = item;
148+
140149
QMouseEvent itemEvent(
141150
event->type(),
142151
item->mapFromScene(event->scenePosition()),

ScratchCPPGui/mouseeventhandler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class MouseEventHandler : public QObject
4040

4141
IRenderedTarget *m_stage = nullptr;
4242
QQuickItem *m_hoveredItem = nullptr;
43+
QQuickItem *m_clickedItem = nullptr;
4344
QQuickItem *m_spriteRepeater = nullptr;
4445
};
4546

ScratchCPPGui/renderedtarget.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "targetpainter.h"
1010
#include "stagemodel.h"
1111
#include "spritemodel.h"
12+
#include "scenemousearea.h"
1213

1314
using namespace scratchcppgui;
1415
using namespace libscratchcpp;
@@ -196,6 +197,22 @@ Target *RenderedTarget::scratchTarget() const
196197
return nullptr;
197198
}
198199

200+
SceneMouseArea *RenderedTarget::mouseArea() const
201+
{
202+
return m_mouseArea;
203+
}
204+
205+
void RenderedTarget::setMouseArea(SceneMouseArea *newMouseArea)
206+
{
207+
if (m_mouseArea == newMouseArea)
208+
return;
209+
210+
m_mouseArea = newMouseArea;
211+
Q_ASSERT(m_mouseArea);
212+
connect(m_mouseArea, &SceneMouseArea::mouseMoved, this, &RenderedTarget::handleSceneMouseMove);
213+
emit mouseAreaChanged();
214+
}
215+
199216
qreal RenderedTarget::width() const
200217
{
201218
return QNanoQuickItem::width();
@@ -226,6 +243,39 @@ QNanoQuickItemPainter *RenderedTarget::createItemPainter() const
226243
return new TargetPainter();
227244
}
228245

246+
void RenderedTarget::mousePressEvent(QMouseEvent *event)
247+
{
248+
m_clicked = true;
249+
}
250+
251+
void RenderedTarget::mouseReleaseEvent(QMouseEvent *event)
252+
{
253+
m_clicked = false;
254+
Q_ASSERT(m_mouseArea);
255+
256+
// Stop dragging
257+
if (m_mouseArea->draggedSprite() == this)
258+
m_mouseArea->setDraggedSprite(nullptr);
259+
}
260+
261+
void RenderedTarget::mouseMoveEvent(QMouseEvent *event)
262+
{
263+
Q_ASSERT((m_spriteModel && m_spriteModel->sprite()) || m_stageModel);
264+
Q_ASSERT(m_mouseArea);
265+
266+
// Start dragging
267+
if (m_clicked && !m_mouseArea->draggedSprite() && m_spriteModel && m_spriteModel->sprite()->draggable()) {
268+
Q_ASSERT(m_engine);
269+
Sprite *sprite = m_spriteModel->sprite();
270+
m_dragDeltaX = m_engine->mouseX() - sprite->x();
271+
m_dragDeltaY = m_engine->mouseY() - sprite->y();
272+
m_mouseArea->setDraggedSprite(this);
273+
274+
// Move the sprite to the front layer
275+
m_engine->moveSpriteToFront(sprite);
276+
}
277+
}
278+
229279
void RenderedTarget::updateCostumeData()
230280
{
231281
// Costume
@@ -399,6 +449,19 @@ void RenderedTarget::calculateSize(Target *target, double costumeWidth, double c
399449
}
400450
}
401451

452+
void RenderedTarget::handleSceneMouseMove(qreal x, qreal y)
453+
{
454+
Q_ASSERT(m_mouseArea);
455+
456+
if (m_mouseArea->draggedSprite() == this) {
457+
Q_ASSERT(m_spriteModel && m_spriteModel->sprite());
458+
Q_ASSERT(m_engine);
459+
Sprite *sprite = m_spriteModel->sprite();
460+
sprite->setX(x - m_engine->stageWidth() / 2.0 - m_dragDeltaX);
461+
sprite->setY(-y + m_engine->stageHeight() / 2.0 - m_dragDeltaY);
462+
}
463+
}
464+
402465
QBuffer *RenderedTarget::bitmapBuffer()
403466
{
404467
return &m_bitmapBuffer;

ScratchCPPGui/renderedtarget.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
Q_MOC_INCLUDE("stagemodel.h");
1313
Q_MOC_INCLUDE("spritemodel.h");
14+
Q_MOC_INCLUDE("scenemousearea.h");
1415

1516
namespace scratchcppgui
1617
{
@@ -23,6 +24,7 @@ class RenderedTarget : public IRenderedTarget
2324
Q_PROPERTY(StageModel *stageModel READ stageModel WRITE setStageModel NOTIFY stageModelChanged)
2425
Q_PROPERTY(SpriteModel *spriteModel READ spriteModel WRITE setSpriteModel NOTIFY spriteModelChanged)
2526
Q_PROPERTY(bool mirrorHorizontally READ mirrorHorizontally NOTIFY mirrorHorizontallyChanged)
27+
Q_PROPERTY(SceneMouseArea *mouseArea READ mouseArea WRITE setMouseArea NOTIFY mouseAreaChanged)
2628

2729
public:
2830
RenderedTarget(QNanoQuickItem *parent = nullptr);
@@ -42,6 +44,9 @@ class RenderedTarget : public IRenderedTarget
4244

4345
libscratchcpp::Target *scratchTarget() const override;
4446

47+
SceneMouseArea *mouseArea() const override;
48+
void setMouseArea(SceneMouseArea *newMouseArea) override;
49+
4550
qreal width() const override;
4651
void setWidth(qreal width) override;
4752

@@ -70,21 +75,26 @@ class RenderedTarget : public IRenderedTarget
7075
void engineChanged();
7176
void stageModelChanged();
7277
void spriteModelChanged();
73-
78+
void mouseAreaChanged();
7479
void mirrorHorizontallyChanged();
7580

7681
protected:
7782
QNanoQuickItemPainter *createItemPainter() const override;
83+
void mousePressEvent(QMouseEvent *event) override;
84+
void mouseReleaseEvent(QMouseEvent *event) override;
85+
void mouseMoveEvent(QMouseEvent *event) override;
7886

7987
private:
8088
void updateCostumeData();
8189
void doLoadCostume();
8290
void calculateSize(libscratchcpp::Target *target, double costumeWidth, double costumeHeight);
91+
void handleSceneMouseMove(qreal x, qreal y);
8392

8493
libscratchcpp::IEngine *m_engine = nullptr;
8594
libscratchcpp::Costume *m_costume = nullptr;
8695
StageModel *m_stageModel = nullptr;
8796
SpriteModel *m_spriteModel = nullptr;
97+
SceneMouseArea *m_mouseArea = nullptr;
8898
QSvgRenderer m_svgRenderer;
8999
QBuffer m_bitmapBuffer;
90100
QString m_bitmapUniqueKey;
@@ -109,6 +119,9 @@ class RenderedTarget : public IRenderedTarget
109119
qreal m_maximumWidth = std::numeric_limits<double>::infinity();
110120
qreal m_maximumHeight = std::numeric_limits<double>::infinity();
111121
std::vector<QPointF> m_hullPoints;
122+
bool m_clicked = false;
123+
double m_dragDeltaX = 0;
124+
double m_dragDeltaY = 0;
112125
};
113126

114127
} // namespace scratchcppgui

ScratchCPPGui/scenemousearea.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// SPDX-License-Identifier: LGPL-3.0-or-later
22

3+
#include <scratchcpp/sprite.h>
4+
35
#include "scenemousearea.h"
46
#include "mouseeventhandler.h"
57

@@ -40,3 +42,14 @@ void SceneMouseArea::setSpriteRepeater(QQuickItem *newSpriteRepeater)
4042
m_mouseHandler->setSpriteRepeater(newSpriteRepeater);
4143
emit spriteRepeaterChanged();
4244
}
45+
46+
IRenderedTarget *SceneMouseArea::draggedSprite() const
47+
{
48+
return m_draggedSprite;
49+
}
50+
51+
void SceneMouseArea::setDraggedSprite(IRenderedTarget *sprite)
52+
{
53+
Q_ASSERT(sprite);
54+
m_draggedSprite = sprite;
55+
}

ScratchCPPGui/scenemousearea.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class SceneMouseArea : public QQuickItem
2626
QQuickItem *spriteRepeater() const;
2727
void setSpriteRepeater(QQuickItem *newSpriteRepeater);
2828

29+
IRenderedTarget *draggedSprite() const;
30+
void setDraggedSprite(IRenderedTarget *sprite);
31+
2932
signals:
3033
void mouseMoved(qreal x, qreal y);
3134
void mousePressed();
@@ -35,6 +38,7 @@ class SceneMouseArea : public QQuickItem
3538

3639
private:
3740
MouseEventHandler *m_mouseHandler = nullptr;
41+
IRenderedTarget *m_draggedSprite = nullptr;
3842
};
3943

4044
} // namespace scratchcppgui

test/mocks/renderedtargetmock.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class RenderedTargetMock : public IRenderedTarget
2727

2828
MOCK_METHOD(libscratchcpp::Target *, scratchTarget, (), (const, override));
2929

30+
MOCK_METHOD(SceneMouseArea *, mouseArea, (), (const, override));
31+
MOCK_METHOD(void, setMouseArea, (SceneMouseArea *), (override));
32+
3033
MOCK_METHOD(qreal, width, (), (const, override));
3134
MOCK_METHOD(void, setWidth, (qreal), (override));
3235

test/mouseeventhandler/mouseeventhandler_test.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,32 @@ TEST(MouseEventHandlerTest, MousePressReleaseEvent)
303303
pressedSpy.clear();
304304
releasedSpy.clear();
305305

306-
// Send to stage (press)
306+
// Send release (should be sent to sprite 1)
307+
EXPECT_CALL(renderedTarget1, mouseReleaseEvent(_)).WillOnce(WithArgs<0>(Invoke(checkReleaseEvent)));
308+
ASSERT_TRUE(handler.eventFilter(nullptr, &releaseEvent));
309+
ASSERT_EQ(pressedSpy.count(), 0);
310+
ASSERT_EQ(releasedSpy.count(), 1);
311+
pressedSpy.clear();
312+
releasedSpy.clear();
313+
314+
// Send to sprite 1 (press)
307315
EXPECT_CALL(renderedTarget3, contains(localPos)).WillOnce(Return(false));
308-
EXPECT_CALL(renderedTarget1, contains(localPos)).WillOnce(Return(false));
309-
EXPECT_CALL(renderedTarget2, contains(localPos)).WillOnce(Return(false));
310-
EXPECT_CALL(stage, mousePressEvent(_)).WillOnce(WithArgs<0>(Invoke(checkPressEvent)));
316+
EXPECT_CALL(renderedTarget1, contains(localPos)).WillOnce(Return(true));
317+
EXPECT_CALL(renderedTarget1, mousePressEvent(_)).WillOnce(WithArgs<0>(Invoke(checkPressEvent)));
311318
ASSERT_TRUE(handler.eventFilter(nullptr, &pressEvent));
312319
ASSERT_EQ(pressedSpy.count(), 1);
313320
ASSERT_EQ(releasedSpy.count(), 0);
314321
pressedSpy.clear();
315322
releasedSpy.clear();
316323

324+
// Send release (should be sent to sprite 1)
325+
EXPECT_CALL(renderedTarget1, mouseReleaseEvent(_)).WillOnce(WithArgs<0>(Invoke(checkReleaseEvent)));
326+
ASSERT_TRUE(handler.eventFilter(nullptr, &releaseEvent));
327+
ASSERT_EQ(pressedSpy.count(), 0);
328+
ASSERT_EQ(releasedSpy.count(), 1);
329+
pressedSpy.clear();
330+
releasedSpy.clear();
331+
317332
// Send to sprite 1 (release)
318333
EXPECT_CALL(renderedTarget3, contains(localPos)).WillOnce(Return(false));
319334
EXPECT_CALL(renderedTarget1, contains(localPos)).WillOnce(Return(true));
@@ -324,10 +339,18 @@ TEST(MouseEventHandlerTest, MousePressReleaseEvent)
324339
pressedSpy.clear();
325340
releasedSpy.clear();
326341

327-
// Send to stage (release)
342+
// Send to stage (press)
328343
EXPECT_CALL(renderedTarget3, contains(localPos)).WillOnce(Return(false));
329344
EXPECT_CALL(renderedTarget1, contains(localPos)).WillOnce(Return(false));
330345
EXPECT_CALL(renderedTarget2, contains(localPos)).WillOnce(Return(false));
346+
EXPECT_CALL(stage, mousePressEvent(_)).WillOnce(WithArgs<0>(Invoke(checkPressEvent)));
347+
ASSERT_TRUE(handler.eventFilter(nullptr, &pressEvent));
348+
ASSERT_EQ(pressedSpy.count(), 1);
349+
ASSERT_EQ(releasedSpy.count(), 0);
350+
pressedSpy.clear();
351+
releasedSpy.clear();
352+
353+
// Send release (should be sent to stage)
331354
EXPECT_CALL(stage, mouseReleaseEvent(_)).WillOnce(WithArgs<0>(Invoke(checkReleaseEvent)));
332355
ASSERT_TRUE(handler.eventFilter(nullptr, &releaseEvent));
333356
ASSERT_EQ(pressedSpy.count(), 0);

0 commit comments

Comments
 (0)