Skip to content

Commit 9b05d1a

Browse files
committed
Add PenLayer and PenLayerPainter classes
1 parent 7221fec commit 9b05d1a

File tree

14 files changed

+686
-0
lines changed

14 files changed

+686
-0
lines changed

src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ qt_add_qml_module(scratchcpp-render
4949
mouseeventhandler.h
5050
keyeventhandler.cpp
5151
keyeventhandler.h
52+
ipenlayer.h
53+
penlayer.cpp
54+
penlayer.h
55+
penlayerpainter.cpp
56+
penlayerpainter.h
5257
penattributes.h
5358
)
5459

src/ipenlayer.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <qnanoquickitem.h>
6+
7+
namespace libscratchcpp
8+
{
9+
10+
class IEngine;
11+
12+
}
13+
14+
namespace scratchcpprender
15+
{
16+
17+
struct PenAttributes;
18+
19+
class IPenLayer : public QNanoQuickItem
20+
{
21+
public:
22+
IPenLayer(QNanoQuickItem *parent = nullptr) :
23+
QNanoQuickItem(parent)
24+
{
25+
}
26+
27+
virtual ~IPenLayer() { }
28+
29+
virtual libscratchcpp::IEngine *engine() const = 0;
30+
virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0;
31+
32+
virtual void clear() = 0;
33+
virtual void drawPoint(const PenAttributes &penAttributes, double x, double y) = 0;
34+
virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0;
35+
36+
virtual QOpenGLFramebufferObject *framebufferObject() const = 0;
37+
};
38+
39+
} // namespace scratchcpprender

src/penlayer.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include "penlayer.h"
4+
#include "penlayerpainter.h"
5+
#include "penattributes.h"
6+
7+
using namespace scratchcpprender;
8+
9+
std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> PenLayer::m_projectPenLayers;
10+
11+
PenLayer::PenLayer(QNanoQuickItem *parent) :
12+
IPenLayer(parent)
13+
{
14+
m_fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
15+
m_fboFormat.setSamples(4);
16+
}
17+
18+
PenLayer::~PenLayer()
19+
{
20+
if (m_engine)
21+
m_projectPenLayers.erase(m_engine);
22+
23+
if (m_painter && m_painter->isActive())
24+
m_painter->end();
25+
}
26+
27+
libscratchcpp::IEngine *PenLayer::engine() const
28+
{
29+
return m_engine;
30+
}
31+
32+
void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
33+
{
34+
if (m_engine == newEngine)
35+
return;
36+
37+
if (m_engine)
38+
m_projectPenLayers.erase(m_engine);
39+
40+
m_engine = newEngine;
41+
42+
if (m_engine) {
43+
m_projectPenLayers[m_engine] = this;
44+
m_fbo = std::make_unique<QOpenGLFramebufferObject>(m_engine->stageWidth(), m_engine->stageHeight(), m_fboFormat);
45+
Q_ASSERT(m_fbo->isValid());
46+
47+
if (m_painter && m_painter->isActive())
48+
m_painter->end();
49+
50+
m_paintDevice = std::make_unique<QOpenGLPaintDevice>(m_fbo->size());
51+
m_painter = std::make_unique<QPainter>(m_paintDevice.get());
52+
clear();
53+
}
54+
55+
emit engineChanged();
56+
}
57+
58+
void scratchcpprender::PenLayer::clear()
59+
{
60+
if (!m_fbo)
61+
return;
62+
63+
m_fbo->bind();
64+
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
65+
glClear(GL_COLOR_BUFFER_BIT);
66+
m_fbo->release();
67+
68+
update();
69+
}
70+
71+
void scratchcpprender::PenLayer::drawPoint(const PenAttributes &penAttributes, double x, double y)
72+
{
73+
drawLine(penAttributes, x, y, x, y);
74+
}
75+
76+
void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1)
77+
{
78+
if (!m_fbo || !m_painter || !m_engine)
79+
return;
80+
81+
// Begin painting
82+
m_fbo->bind();
83+
m_painter->beginNativePainting();
84+
m_painter->setRenderHint(QPainter::Antialiasing);
85+
m_painter->setRenderHint(QPainter::SmoothPixmapTransform, false);
86+
87+
// Translate to Scratch coordinate system
88+
double stageWidthHalf = m_engine->stageWidth() / 2;
89+
double stageHeightHalf = m_engine->stageHeight() / 2;
90+
x0 += stageWidthHalf;
91+
y0 = stageHeightHalf - y0;
92+
x1 += stageWidthHalf;
93+
y1 = stageHeightHalf - y1;
94+
95+
// Set pen attributes
96+
QPen pen(penAttributes.color);
97+
pen.setWidthF(penAttributes.diameter);
98+
pen.setCapStyle(Qt::RoundCap);
99+
m_painter->setPen(pen);
100+
101+
// If the start and end coordinates are the same, draw a point, otherwise draw a line
102+
if (x0 == x1 && y0 == y1)
103+
m_painter->drawPoint(x0, y0);
104+
else
105+
m_painter->drawLine(x0, y0, x1, y1);
106+
107+
// End painting
108+
m_painter->endNativePainting();
109+
m_fbo->release();
110+
111+
update();
112+
}
113+
114+
QOpenGLFramebufferObject *PenLayer::framebufferObject() const
115+
{
116+
return m_fbo.get();
117+
}
118+
119+
IPenLayer *PenLayer::getProjectPenLayer(libscratchcpp::IEngine *engine)
120+
{
121+
auto it = m_projectPenLayers.find(engine);
122+
123+
if (it != m_projectPenLayers.cend())
124+
return it->second;
125+
126+
return nullptr;
127+
}
128+
129+
QNanoQuickItemPainter *PenLayer::createItemPainter() const
130+
{
131+
return new PenLayerPainter;
132+
}

src/penlayer.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <ipenlayer.h>
6+
#include <QOpenGLFramebufferObject>
7+
#include <QOpenGLPaintDevice>
8+
#include <scratchcpp/iengine.h>
9+
10+
namespace scratchcpprender
11+
{
12+
13+
class PenLayer : public IPenLayer
14+
{
15+
Q_OBJECT
16+
QML_ELEMENT
17+
Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged)
18+
19+
public:
20+
PenLayer(QNanoQuickItem *parent = nullptr);
21+
~PenLayer();
22+
23+
libscratchcpp::IEngine *engine() const override;
24+
void setEngine(libscratchcpp::IEngine *newEngine) override;
25+
26+
void clear() override;
27+
void drawPoint(const PenAttributes &penAttributes, double x, double y) override;
28+
void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override;
29+
30+
QOpenGLFramebufferObject *framebufferObject() const override;
31+
32+
static IPenLayer *getProjectPenLayer(libscratchcpp::IEngine *engine);
33+
34+
signals:
35+
void engineChanged();
36+
37+
protected:
38+
QNanoQuickItemPainter *createItemPainter() const override;
39+
40+
private:
41+
static std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> m_projectPenLayers;
42+
libscratchcpp::IEngine *m_engine = nullptr;
43+
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
44+
std::unique_ptr<QOpenGLPaintDevice> m_paintDevice;
45+
std::unique_ptr<QPainter> m_painter;
46+
QOpenGLFramebufferObjectFormat m_fboFormat;
47+
};
48+
49+
} // namespace scratchcpprender

src/penlayerpainter.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include "penlayerpainter.h"
4+
#include "penlayer.h"
5+
6+
using namespace scratchcpprender;
7+
8+
PenLayerPainter::PenLayerPainter(QOpenGLFramebufferObject *fbo)
9+
{
10+
m_targetFbo = fbo;
11+
}
12+
13+
void PenLayerPainter::paint(QNanoPainter *painter)
14+
{
15+
if (QThread::currentThread() != qApp->thread()) {
16+
qFatal("Error: Rendering must happen in the GUI thread to work correctly. Please disable threaded render loop using qputenv(\"QSG_RENDER_LOOP\", \"basic\") before constructing your "
17+
"application object.");
18+
}
19+
20+
QOpenGLContext *context = QOpenGLContext::currentContext();
21+
Q_ASSERT(context);
22+
23+
if (!context || !m_fbo)
24+
return;
25+
26+
// Custom FBO - only used for testing
27+
QOpenGLFramebufferObject *targetFbo = m_targetFbo ? m_targetFbo : framebufferObject();
28+
29+
QOpenGLFramebufferObjectFormat format;
30+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
31+
32+
// Blit the FBO to a temporary FBO first (multisampled FBOs can only be blitted to FBOs with the same size)
33+
QOpenGLFramebufferObject tmpFbo(m_fbo->size(), format);
34+
QOpenGLFramebufferObject::blitFramebuffer(&tmpFbo, m_fbo);
35+
QOpenGLFramebufferObject::blitFramebuffer(targetFbo, &tmpFbo);
36+
}
37+
38+
void PenLayerPainter::synchronize(QNanoQuickItem *item)
39+
{
40+
IPenLayer *penLayer = dynamic_cast<IPenLayer *>(item);
41+
Q_ASSERT(penLayer);
42+
43+
if (penLayer)
44+
m_fbo = penLayer->framebufferObject();
45+
}

src/penlayerpainter.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <qnanoquickitempainter.h>
6+
7+
#include "texture.h"
8+
9+
namespace scratchcpprender
10+
{
11+
12+
class PenLayerPainter : public QNanoQuickItemPainter
13+
{
14+
public:
15+
PenLayerPainter(QOpenGLFramebufferObject *fbo = nullptr);
16+
17+
void paint(QNanoPainter *painter) override;
18+
void synchronize(QNanoQuickItem *item) override;
19+
20+
private:
21+
QOpenGLFramebufferObject *m_targetFbo = nullptr;
22+
QOpenGLFramebufferObject *m_fbo = nullptr;
23+
};
24+
25+
} // namespace scratchcpprender

test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ add_subdirectory(monitor_models)
3232
add_subdirectory(texture)
3333
add_subdirectory(skins)
3434
add_subdirectory(penattributes)
35+
add_subdirectory(penlayer)
36+
add_subdirectory(penlayerpainter)

test/lines.png

6.92 KB
Loading

test/mocks/penlayermock.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include <ipenlayer.h>
4+
#include <qnanoquickitem.h>
5+
#include <gmock/gmock.h>
6+
7+
using namespace scratchcpprender;
8+
9+
namespace scratchcpprender
10+
{
11+
12+
class PenLayerMock : public IPenLayer
13+
{
14+
public:
15+
MOCK_METHOD(libscratchcpp::IEngine *, engine, (), (const, override));
16+
MOCK_METHOD(void, setEngine, (libscratchcpp::IEngine *), (override));
17+
18+
MOCK_METHOD(void, clear, (), (override));
19+
MOCK_METHOD(void, drawPoint, (const PenAttributes &, double, double), (override));
20+
MOCK_METHOD(void, drawLine, (const PenAttributes &, double, double, double, double), (override));
21+
22+
MOCK_METHOD(QOpenGLFramebufferObject *, framebufferObject, (), (const, override));
23+
24+
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
25+
};
26+
27+
} // namespace scratchcpprender

test/penlayer/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
add_executable(
2+
penlayer_test
3+
penlayer_test.cpp
4+
)
5+
6+
target_link_libraries(
7+
penlayer_test
8+
GTest::gtest_main
9+
GTest::gmock_main
10+
scratchcpp-render
11+
scratchcpprender_mocks
12+
${QT_LIBS}
13+
)
14+
15+
add_test(penlayer_test)
16+
gtest_discover_tests(penlayer_test)

0 commit comments

Comments
 (0)