Skip to content

Commit 2527f4f

Browse files
committed
Add MouseEventHandler class
1 parent 19a343b commit 2527f4f

File tree

12 files changed

+635
-0
lines changed

12 files changed

+635
-0
lines changed

ScratchCPPGui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ qt_add_qml_module(libscratchcpp-gui
2222
renderedtarget.h
2323
targetpainter.cpp
2424
targetpainter.h
25+
mouseeventhandler.cpp
26+
mouseeventhandler.h
2527
keyeventhandler.cpp
2628
keyeventhandler.h
2729
)

ScratchCPPGui/irenderedtarget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class IRenderedTarget : public QNanoQuickItem
5353
virtual qreal height() const = 0;
5454
virtual void setHeight(qreal width) = 0;
5555

56+
virtual QPointF mapFromScene(const QPointF &point) const = 0;
57+
5658
virtual QBuffer *bitmapBuffer() = 0;
5759
virtual const QString &bitmapUniqueKey() const = 0;
5860

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include <QMouseEvent>
4+
#include <scratchcpp/target.h>
5+
6+
#include "mouseeventhandler.h"
7+
#include "renderedtarget.h"
8+
9+
using namespace scratchcppgui;
10+
11+
MouseEventHandler::MouseEventHandler(QObject *parent) :
12+
QObject(parent)
13+
{
14+
}
15+
16+
IRenderedTarget *MouseEventHandler::stage() const
17+
{
18+
return m_stage;
19+
}
20+
21+
void MouseEventHandler::setStage(IRenderedTarget *stage)
22+
{
23+
m_stage = stage;
24+
}
25+
26+
QQuickItem *MouseEventHandler::spriteRepeater() const
27+
{
28+
return m_spriteRepeater;
29+
}
30+
31+
void MouseEventHandler::setSpriteRepeater(QQuickItem *repeater)
32+
{
33+
m_spriteRepeater = repeater;
34+
}
35+
36+
bool MouseEventHandler::eventFilter(QObject *obj, QEvent *event)
37+
{
38+
switch (event->type()) {
39+
case QEvent::HoverEnter:
40+
case QEvent::HoverLeave:
41+
forwardPointEvent(static_cast<QSinglePointEvent *>(event));
42+
return true;
43+
44+
case QEvent::HoverMove:
45+
case QEvent::MouseMove: {
46+
QSinglePointEvent *pointEvent = static_cast<QSinglePointEvent *>(event);
47+
forwardPointEvent(pointEvent);
48+
QPointF pos = pointEvent->position();
49+
emit mouseMoved(pos.x(), pos.y());
50+
return true;
51+
}
52+
53+
case QEvent::MouseButtonPress:
54+
emit mousePressed();
55+
forwardPointEvent(static_cast<QSinglePointEvent *>(event));
56+
return true;
57+
58+
case QEvent::MouseButtonRelease:
59+
emit mouseReleased();
60+
forwardPointEvent(static_cast<QSinglePointEvent *>(event));
61+
return true;
62+
63+
default:
64+
break;
65+
}
66+
67+
return QObject::eventFilter(obj, event);
68+
}
69+
70+
void MouseEventHandler::forwardPointEvent(QSinglePointEvent *event)
71+
{
72+
Q_ASSERT(m_spriteRepeater);
73+
74+
if (!m_spriteRepeater)
75+
return;
76+
77+
// Create list of sprites
78+
std::vector<IRenderedTarget *> sprites;
79+
int count = m_spriteRepeater->property("count").toInt();
80+
sprites.reserve(count);
81+
82+
for (int i = 0; i < count; i++) {
83+
QQuickItem *sprite = nullptr;
84+
QMetaObject::invokeMethod(m_spriteRepeater, "itemAt", Qt::DirectConnection, Q_RETURN_ARG(QQuickItem *, sprite), Q_ARG(int, i));
85+
Q_ASSERT(sprite);
86+
Q_ASSERT(dynamic_cast<IRenderedTarget *>(sprite));
87+
Q_ASSERT(dynamic_cast<IRenderedTarget *>(sprite)->scratchTarget());
88+
sprites.push_back(dynamic_cast<IRenderedTarget *>(sprite));
89+
}
90+
91+
// Sort the list by layer order
92+
std::sort(sprites.begin(), sprites.end(), [](IRenderedTarget *t1, IRenderedTarget *t2) { return t1->scratchTarget()->layerOrder() > t2->scratchTarget()->layerOrder(); });
93+
94+
// Send the event to the hovered sprite
95+
for (IRenderedTarget *sprite : sprites) {
96+
// contains() expects position in the item's coordinate system
97+
QPointF localPos = sprite->mapFromScene(event->scenePosition());
98+
99+
if (sprite->contains(localPos)) {
100+
sendPointEventToItem(event, sprite);
101+
return;
102+
}
103+
}
104+
105+
// If there wasn't any hovered sprite, send the event to the stage
106+
Q_ASSERT(m_stage);
107+
108+
if (m_stage)
109+
sendPointEventToItem(event, m_stage);
110+
}
111+
112+
void MouseEventHandler::sendPointEventToItem(QSinglePointEvent *event, QQuickItem *item)
113+
{
114+
Q_ASSERT(event);
115+
Q_ASSERT(item);
116+
Q_ASSERT(m_stage);
117+
118+
switch (event->type()) {
119+
case QEvent::HoverEnter:
120+
case QEvent::HoverLeave:
121+
QCoreApplication::sendEvent(item, event);
122+
break;
123+
124+
case QEvent::HoverMove:
125+
// Send HoverLeave to the previously hovered item and HoverEnter to the hovered item
126+
if (item != m_hoveredItem) {
127+
if (m_hoveredItem)
128+
sendHoverEventToItem(static_cast<QHoverEvent *>(event), QEvent::HoverLeave, m_hoveredItem);
129+
130+
sendHoverEventToItem(static_cast<QHoverEvent *>(event), QEvent::HoverEnter, item);
131+
m_hoveredItem = item;
132+
}
133+
134+
QCoreApplication::sendEvent(item, event);
135+
break;
136+
137+
case QEvent::MouseButtonPress:
138+
case QEvent::MouseButtonRelease:
139+
case QEvent::MouseMove: {
140+
QMouseEvent itemEvent(
141+
event->type(),
142+
item->mapFromScene(event->scenePosition()),
143+
event->scenePosition(),
144+
event->globalPosition(),
145+
event->button(),
146+
event->buttons(),
147+
event->modifiers(),
148+
static_cast<const QPointingDevice *>(event->device()));
149+
150+
QCoreApplication::sendEvent(item, &itemEvent);
151+
break;
152+
}
153+
154+
default:
155+
Q_ASSERT(false);
156+
break;
157+
}
158+
}
159+
160+
void MouseEventHandler::sendHoverEventToItem(QHoverEvent *originalEvent, QEvent::Type newType, QQuickItem *item)
161+
{
162+
Q_ASSERT(newType == QEvent::HoverEnter || newType == QEvent::HoverLeave || newType == QEvent::HoverMove);
163+
164+
QHoverEvent event(
165+
newType,
166+
originalEvent->scenePosition(),
167+
originalEvent->globalPosition(),
168+
originalEvent->oldPosF(),
169+
originalEvent->modifiers(),
170+
static_cast<const QPointingDevice *>(originalEvent->device()));
171+
172+
QCoreApplication::sendEvent(item, &event);
173+
}

ScratchCPPGui/mouseeventhandler.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <QObject>
6+
#include <QEvent>
7+
8+
class QQuickItem;
9+
class QSinglePointEvent;
10+
class QHoverEvent;
11+
12+
namespace scratchcppgui
13+
{
14+
15+
class IRenderedTarget;
16+
17+
class MouseEventHandler : public QObject
18+
{
19+
Q_OBJECT
20+
public:
21+
explicit MouseEventHandler(QObject *parent = nullptr);
22+
23+
IRenderedTarget *stage() const;
24+
void setStage(IRenderedTarget *stage);
25+
26+
QQuickItem *spriteRepeater() const;
27+
void setSpriteRepeater(QQuickItem *repeater);
28+
29+
bool eventFilter(QObject *obj, QEvent *event) override;
30+
31+
signals:
32+
void mouseMoved(qreal x, qreal y);
33+
void mousePressed();
34+
void mouseReleased();
35+
36+
private:
37+
void forwardPointEvent(QSinglePointEvent *event);
38+
void sendPointEventToItem(QSinglePointEvent *event, QQuickItem *item);
39+
void sendHoverEventToItem(QHoverEvent *originalEvent, QEvent::Type newType, QQuickItem *item);
40+
41+
IRenderedTarget *m_stage = nullptr;
42+
QQuickItem *m_hoveredItem = nullptr;
43+
QQuickItem *m_spriteRepeater = nullptr;
44+
};
45+
46+
} // namespace scratchcppgui

ScratchCPPGui/renderedtarget.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ void RenderedTarget::setHeight(qreal height)
216216
QNanoQuickItem::setHeight(height);
217217
}
218218

219+
QPointF RenderedTarget::mapFromScene(const QPointF &point) const
220+
{
221+
return QNanoQuickItem::mapFromScene(point);
222+
}
223+
219224
QNanoQuickItemPainter *RenderedTarget::createItemPainter() const
220225
{
221226
return new TargetPainter();

ScratchCPPGui/renderedtarget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class RenderedTarget : public IRenderedTarget
4848
qreal height() const override;
4949
void setHeight(qreal height) override;
5050

51+
QPointF mapFromScene(const QPointF &point) const override;
52+
5153
QBuffer *bitmapBuffer() override;
5254
const QString &bitmapUniqueKey() const override;
5355

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ add_subdirectory(targetpainter)
2626
add_subdirectory(target_models)
2727
add_subdirectory(projectscene)
2828
add_subdirectory(keyeventhandler)
29+
add_subdirectory(mouseeventhandler)

test/mocks/renderedtargetmock.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class RenderedTargetMock : public IRenderedTarget
3333
MOCK_METHOD(qreal, height, (), (const, override));
3434
MOCK_METHOD(void, setHeight, (qreal), (override));
3535

36+
MOCK_METHOD(QPointF, mapFromScene, (const QPointF &), (const, override));
37+
3638
MOCK_METHOD(QBuffer *, bitmapBuffer, (), (override));
3739
MOCK_METHOD(const QString &, bitmapUniqueKey, (), (const, override));
3840

@@ -49,6 +51,12 @@ class RenderedTargetMock : public IRenderedTarget
4951

5052
MOCK_METHOD(bool, contains, (const QPointF &), (const, override));
5153
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
54+
MOCK_METHOD(void, hoverEnterEvent, (QHoverEvent *), (override));
55+
MOCK_METHOD(void, hoverLeaveEvent, (QHoverEvent *), (override));
56+
MOCK_METHOD(void, hoverMoveEvent, (QHoverEvent *), (override));
57+
MOCK_METHOD(void, mouseMoveEvent, (QMouseEvent *), (override));
58+
MOCK_METHOD(void, mousePressEvent, (QMouseEvent *), (override));
59+
MOCK_METHOD(void, mouseReleaseEvent, (QMouseEvent *), (override));
5260
};
5361

5462
} // namespace scratchcppgui
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
add_executable(
2+
mouseeventhandler_test
3+
mouseeventhandler_test.cpp
4+
fakerepeater.cpp
5+
fakerepeater.h
6+
)
7+
8+
target_link_libraries(
9+
mouseeventhandler_test
10+
GTest::gtest_main
11+
GTest::gmock_main
12+
libscratchcpp-gui
13+
scratchcppgui_mocks
14+
${QT_LIBS}
15+
Qt6::Test
16+
)
17+
18+
add_test(mouseeventhandler_test)
19+
gtest_discover_tests(mouseeventhandler_test)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "fakerepeater.h"
2+
3+
using namespace scratchcppgui;
4+
5+
FakeRepeater::FakeRepeater(QQuickItem *parent) :
6+
QQuickItem(parent)
7+
{
8+
}
9+
10+
int FakeRepeater::count() const
11+
{
12+
return items.size();
13+
}
14+
15+
QQuickItem *FakeRepeater::itemAt(int index) const
16+
{
17+
Q_ASSERT(index >= 0 && index < items.size());
18+
return items[index];
19+
}

0 commit comments

Comments
 (0)