1/****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** BSD License Usage
18 ** Alternatively, you may use this file under the terms of the BSD license
19 ** as follows:
20 **
21 ** "Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions are
23 ** met:
24 ** * Redistributions of source code must retain the above copyright
25 ** notice, this list of conditions and the following disclaimer.
26 ** * Redistributions in binary form must reproduce the above copyright
27 ** notice, this list of conditions and the following disclaimer in
28 ** the documentation and/or other materials provided with the
29 ** distribution.
30 ** * Neither the name of The Qt Company Ltd nor the names of its
31 ** contributors may be used to endorse or promote products derived
32 ** from this software without specific prior written permission.
33 **
34 **
35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46 **
47 ** $QT_END_LICENSE$
48 **
49 ****************************************************************************/
50
51#include "glwidget.h"
52#include <QPainter>
53#include <QPaintEngine>
54#include <QOpenGLShaderProgram>
55#include <QOpenGLTexture>
56#include <QRandomGenerator>
57#include <QCoreApplication>
58#include <qmath.h>
59
60#include "mainwindow.h"
61#include "bubble.h"
62
63const int bubbleNum = 8;
64
65#ifndef GL_SRGB8_ALPHA8
66#define GL_SRGB8_ALPHA8 0x8C43
67#endif
68
69GLWidget::GLWidget(MainWindow *mw, bool button, const QColor &background)
70 : m_mainWindow(mw),
71 m_hasButton(button),
72 m_background(background)
73{
74 setMinimumSize(minw: 300, minh: 250);
75 if (QCoreApplication::arguments().contains(QStringLiteral("--srgb")))
76 setTextureFormat(GL_SRGB8_ALPHA8);
77}
78
79GLWidget::~GLWidget()
80{
81 qDeleteAll(c: m_bubbles);
82
83 // And now release all OpenGL resources.
84 makeCurrent();
85 delete m_texture;
86 delete m_program1;
87 delete m_program2;
88 delete m_vshader1;
89 delete m_fshader1;
90 delete m_vshader2;
91 delete m_fshader2;
92 m_vbo1.destroy();
93 m_vbo2.destroy();
94 doneCurrent();
95}
96
97void GLWidget::setScaling(int scale)
98{
99 if (scale > 30)
100 m_fScale = 1 + qreal(scale - 30) / 30 * 0.25;
101 else if (scale < 30)
102 m_fScale = 1 - (qreal(30 - scale) / 30 * 0.25);
103 else
104 m_fScale = 1;
105}
106
107void GLWidget::setLogo()
108{
109 m_qtLogo = true;
110}
111
112void GLWidget::setTexture()
113{
114 m_qtLogo = false;
115}
116
117void GLWidget::setShowBubbles(bool bubbles)
118{
119 m_showBubbles = bubbles;
120}
121
122void GLWidget::paintQtLogo()
123{
124 m_program1->enableAttributeArray(location: m_vertexAttr1);
125 m_program1->enableAttributeArray(location: m_normalAttr1);
126
127 m_vbo1.bind();
128 // The data in the buffer is placed like this:
129 // vertex1.x, vertex1.y, vertex1.z, normal1.x, normal1.y, normal1.z, vertex2.x, ...
130 m_program1->setAttributeBuffer(location: m_vertexAttr1, GL_FLOAT, offset: 0, tupleSize: 3, stride: 6 * sizeof(GLfloat));
131 m_program1->setAttributeBuffer(location: m_normalAttr1, GL_FLOAT, offset: 3 * sizeof(GLfloat), tupleSize: 3, stride: 6 * sizeof(GLfloat));
132 m_vbo1.release();
133
134 glDrawArrays(GL_TRIANGLES, first: 0, count: m_vertices.size());
135
136 m_program1->disableAttributeArray(location: m_normalAttr1);
137 m_program1->disableAttributeArray(location: m_vertexAttr1);
138}
139
140void GLWidget::paintTexturedCube()
141{
142 m_texture->bind();
143
144 if (!m_vbo2.isCreated()) {
145 static GLfloat afVertices[] = {
146 -0.5, 0.5, 0.5, 0.5,-0.5,0.5,-0.5,-0.5,0.5,
147 0.5, -0.5, 0.5, -0.5,0.5,0.5,0.5,0.5,0.5,
148 -0.5, -0.5, -0.5, 0.5,-0.5,-0.5,-0.5,0.5,-0.5,
149 0.5, 0.5, -0.5, -0.5,0.5,-0.5,0.5,-0.5,-0.5,
150
151 0.5, -0.5, -0.5, 0.5,-0.5,0.5,0.5,0.5,-0.5,
152 0.5, 0.5, 0.5, 0.5,0.5,-0.5,0.5,-0.5,0.5,
153 -0.5, 0.5, -0.5, -0.5,-0.5,0.5,-0.5,-0.5,-0.5,
154 -0.5, -0.5, 0.5, -0.5,0.5,-0.5,-0.5,0.5,0.5,
155
156 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
157 -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
158 -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5,
159 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5
160 };
161
162 static GLfloat afTexCoord[] = {
163 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f,
164 1.0f,1.0f, 0.0f,0.0f, 0.0f,1.0f,
165 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f,
166 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f,
167
168 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f,
169 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f,
170 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f,
171 1.0f,1.0f, 0.0f,0.0f, 0.0f,1.0f,
172
173 0.0f,1.0f, 1.0f,0.0f, 1.0f,1.0f,
174 1.0f,0.0f, 0.0f,1.0f, 0.0f,0.0f,
175 1.0f,0.0f, 1.0f,1.0f, 0.0f,0.0f,
176 0.0f,1.0f, 0.0f,0.0f, 1.0f,1.0f
177 };
178
179 GLfloat afNormals[] = {
180
181 0,0,-1, 0,0,-1, 0,0,-1,
182 0,0,-1, 0,0,-1, 0,0,-1,
183 0,0,1, 0,0,1, 0,0,1,
184 0,0,1, 0,0,1, 0,0,1,
185
186 -1,0,0, -1,0,0, -1,0,0,
187 -1,0,0, -1,0,0, -1,0,0,
188 1,0,0, 1,0,0, 1,0,0,
189 1,0,0, 1,0,0, 1,0,0,
190
191 0,-1,0, 0,-1,0, 0,-1,0,
192 0,-1,0, 0,-1,0, 0,-1,0,
193 0,1,0, 0,1,0, 0,1,0,
194 0,1,0, 0,1,0, 0,1,0
195 };
196
197 m_vbo2.create();
198 m_vbo2.bind();
199 m_vbo2.allocate(count: 36 * 8 * sizeof(GLfloat));
200 m_vbo2.write(offset: 0, data: afVertices, count: sizeof(afVertices));
201 m_vbo2.write(offset: sizeof(afVertices), data: afTexCoord, count: sizeof(afTexCoord));
202 m_vbo2.write(offset: sizeof(afVertices) + sizeof(afTexCoord), data: afNormals, count: sizeof(afNormals));
203 m_vbo2.release();
204 }
205
206 m_program2->setUniformValue(location: m_textureUniform2, value: 0); // use texture unit 0
207
208 m_program2->enableAttributeArray(location: m_vertexAttr2);
209 m_program2->enableAttributeArray(location: m_normalAttr2);
210 m_program2->enableAttributeArray(location: m_texCoordAttr2);
211
212 m_vbo2.bind();
213 // In the buffer we first have 36 vertices (3 floats for each), then 36 texture
214 // coordinates (2 floats for each), then 36 normals (3 floats for each).
215 m_program2->setAttributeBuffer(location: m_vertexAttr2, GL_FLOAT, offset: 0, tupleSize: 3);
216 m_program2->setAttributeBuffer(location: m_texCoordAttr2, GL_FLOAT, offset: 36 * 3 * sizeof(GLfloat), tupleSize: 2);
217 m_program2->setAttributeBuffer(location: m_normalAttr2, GL_FLOAT, offset: 36 * 5 * sizeof(GLfloat), tupleSize: 3);
218 m_vbo2.release();
219
220 glDrawArrays(GL_TRIANGLES, first: 0, count: 36);
221
222 m_program2->disableAttributeArray(location: m_vertexAttr2);
223 m_program2->disableAttributeArray(location: m_normalAttr2);
224 m_program2->disableAttributeArray(location: m_texCoordAttr2);
225}
226
227void GLWidget::initializeGL()
228{
229 initializeOpenGLFunctions();
230
231 m_texture = new QOpenGLTexture(QImage(":/qt.png"));
232
233 m_vshader1 = new QOpenGLShader(QOpenGLShader::Vertex);
234 const char *vsrc1 =
235 "attribute highp vec4 vertex;\n"
236 "attribute mediump vec3 normal;\n"
237 "uniform mediump mat4 matrix;\n"
238 "varying mediump vec4 color;\n"
239 "void main(void)\n"
240 "{\n"
241 " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
242 " float angle = max(dot(normal, toLight), 0.0);\n"
243 " vec3 col = vec3(0.40, 1.0, 0.0);\n"
244 " color = vec4(col * 0.2 + col * 0.8 * angle, 1.0);\n"
245 " color = clamp(color, 0.0, 1.0);\n"
246 " gl_Position = matrix * vertex;\n"
247 "}\n";
248 m_vshader1->compileSourceCode(source: vsrc1);
249
250 m_fshader1 = new QOpenGLShader(QOpenGLShader::Fragment);
251 const char *fsrc1 =
252 "varying mediump vec4 color;\n"
253 "void main(void)\n"
254 "{\n"
255 " gl_FragColor = color;\n"
256 "}\n";
257 m_fshader1->compileSourceCode(source: fsrc1);
258
259 m_program1 = new QOpenGLShaderProgram;
260 m_program1->addShader(shader: m_vshader1);
261 m_program1->addShader(shader: m_fshader1);
262 m_program1->link();
263
264 m_vertexAttr1 = m_program1->attributeLocation(name: "vertex");
265 m_normalAttr1 = m_program1->attributeLocation(name: "normal");
266 m_matrixUniform1 = m_program1->uniformLocation(name: "matrix");
267
268 m_vshader2 = new QOpenGLShader(QOpenGLShader::Vertex);
269 const char *vsrc2 =
270 "attribute highp vec4 vertex;\n"
271 "attribute highp vec4 texCoord;\n"
272 "attribute mediump vec3 normal;\n"
273 "uniform mediump mat4 matrix;\n"
274 "varying highp vec4 texc;\n"
275 "varying mediump float angle;\n"
276 "void main(void)\n"
277 "{\n"
278 " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
279 " angle = max(dot(normal, toLight), 0.0);\n"
280 " gl_Position = matrix * vertex;\n"
281 " texc = texCoord;\n"
282 "}\n";
283 m_vshader2->compileSourceCode(source: vsrc2);
284
285 m_fshader2 = new QOpenGLShader(QOpenGLShader::Fragment);
286 const char *fsrc2 =
287 "varying highp vec4 texc;\n"
288 "uniform sampler2D tex;\n"
289 "varying mediump float angle;\n"
290 "void main(void)\n"
291 "{\n"
292 " highp vec3 color = texture2D(tex, texc.st).rgb;\n"
293 " color = color * 0.2 + color * 0.8 * angle;\n"
294 " gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n"
295 "}\n";
296 m_fshader2->compileSourceCode(source: fsrc2);
297
298 m_program2 = new QOpenGLShaderProgram;
299 m_program2->addShader(shader: m_vshader2);
300 m_program2->addShader(shader: m_fshader2);
301 m_program2->link();
302
303 m_vertexAttr2 = m_program2->attributeLocation(name: "vertex");
304 m_normalAttr2 = m_program2->attributeLocation(name: "normal");
305 m_texCoordAttr2 = m_program2->attributeLocation(name: "texCoord");
306 m_matrixUniform2 = m_program2->uniformLocation(name: "matrix");
307 m_textureUniform2 = m_program2->uniformLocation(name: "tex");
308
309 m_fAngle = 0;
310 m_fScale = 1;
311
312 createGeometry();
313
314 // Use a vertex buffer object. Client-side pointers are old-school and should be avoided.
315 m_vbo1.create();
316 m_vbo1.bind();
317 // For the cube all the data belonging to the texture coordinates and
318 // normals is placed separately, after the vertices. Here, for the Qt logo,
319 // let's do something different and potentially more efficient: create a
320 // properly interleaved data set.
321 const int vertexCount = m_vertices.count();
322 QVector<GLfloat> buf;
323 buf.resize(asize: vertexCount * 3 * 2);
324 GLfloat *p = buf.data();
325 for (int i = 0; i < vertexCount; ++i) {
326 *p++ = m_vertices[i].x();
327 *p++ = m_vertices[i].y();
328 *p++ = m_vertices[i].z();
329 *p++ = m_normals[i].x();
330 *p++ = m_normals[i].y();
331 *p++ = m_normals[i].z();
332 }
333 m_vbo1.allocate(data: buf.constData(), count: buf.count() * sizeof(GLfloat));
334 m_vbo1.release();
335
336 createBubbles(number: bubbleNum - m_bubbles.count());
337}
338
339void GLWidget::paintGL()
340{
341 createBubbles(number: bubbleNum - m_bubbles.count());
342
343 QPainter painter;
344 painter.begin(this);
345
346 painter.beginNativePainting();
347
348 glClearColor(red: m_background.redF(), green: m_background.greenF(), blue: m_background.blueF(), alpha: m_transparent ? 0.0f : 1.0f);
349 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
350
351 glFrontFace(GL_CW);
352 glCullFace(GL_FRONT);
353 glEnable(GL_CULL_FACE);
354 glEnable(GL_DEPTH_TEST);
355
356 QMatrix4x4 modelview;
357 modelview.rotate(angle: m_fAngle, x: 0.0f, y: 1.0f, z: 0.0f);
358 modelview.rotate(angle: m_fAngle, x: 1.0f, y: 0.0f, z: 0.0f);
359 modelview.rotate(angle: m_fAngle, x: 0.0f, y: 0.0f, z: 1.0f);
360 modelview.scale(factor: m_fScale);
361 modelview.translate(x: 0.0f, y: -0.2f, z: 0.0f);
362
363 if (m_qtLogo) {
364 m_program1->bind();
365 m_program1->setUniformValue(location: m_matrixUniform1, value: modelview);
366 paintQtLogo();
367 m_program1->release();
368 } else {
369 m_program2->bind();
370 m_program2->setUniformValue(location: m_matrixUniform2, value: modelview);
371 paintTexturedCube();
372 m_program2->release();
373 }
374
375 glDisable(GL_DEPTH_TEST);
376 glDisable(GL_CULL_FACE);
377
378 painter.endNativePainting();
379
380 if (m_showBubbles) {
381 for (Bubble *bubble : qAsConst(t&: m_bubbles))
382 bubble->drawBubble(painter: &painter);
383 }
384
385 if (const int elapsed = m_time.elapsed()) {
386 QString framesPerSecond;
387 framesPerSecond.setNum(m_frames /(elapsed / 1000.0), f: 'f', prec: 2);
388 painter.setPen(m_transparent ? Qt::black : Qt::white);
389 painter.drawText(x: 20, y: 40, s: framesPerSecond + " paintGL calls / s");
390 }
391
392 painter.end();
393
394 for (Bubble *bubble : qAsConst(t&: m_bubbles))
395 bubble->move(bbox: rect());
396
397 if (!(m_frames % 100)) {
398 m_time.start();
399 m_frames = 0;
400 }
401 m_fAngle += 1.0f;
402 ++m_frames;
403
404 // When requested, follow the ideal way to animate: Rely on
405 // blocking swap and just schedule updates continuously.
406 if (!m_mainWindow->timerEnabled())
407 update();
408}
409
410void GLWidget::createBubbles(int number)
411{
412 for (int i = 0; i < number; ++i) {
413 QPointF position(width()*(0.1 + QRandomGenerator::global()->bounded(highest: 0.8)),
414 height()*(0.1 + QRandomGenerator::global()->bounded(highest: 0.8)));
415 qreal radius = qMin(a: width(), b: height())*(0.0175 + QRandomGenerator::global()->bounded(highest: 0.0875));
416 QPointF velocity(width()*0.0175*(-0.5 + QRandomGenerator::global()->bounded(highest: 1.0)),
417 height()*0.0175*(-0.5 + QRandomGenerator::global()->bounded(highest: 1.0)));
418
419 m_bubbles.append(t: new Bubble(position, radius, velocity));
420 }
421}
422
423void GLWidget::createGeometry()
424{
425 m_vertices.clear();
426 m_normals.clear();
427
428 qreal x1 = +0.06f;
429 qreal y1 = -0.14f;
430 qreal x2 = +0.14f;
431 qreal y2 = -0.06f;
432 qreal x3 = +0.08f;
433 qreal y3 = +0.00f;
434 qreal x4 = +0.30f;
435 qreal y4 = +0.22f;
436
437 quad(x1, y1, x2, y2, x3: y2, y3: x2, x4: y1, y4: x1);
438 quad(x1: x3, y1: y3, x2: x4, y2: y4, x3: y4, y3: x4, x4: y3, y4: x3);
439
440 extrude(x1, y1, x2, y2);
441 extrude(x1: x2, y1: y2, x2: y2, y2: x2);
442 extrude(x1: y2, y1: x2, x2: y1, y2: x1);
443 extrude(x1: y1, y1: x1, x2: x1, y2: y1);
444 extrude(x1: x3, y1: y3, x2: x4, y2: y4);
445 extrude(x1: x4, y1: y4, x2: y4, y2: x4);
446 extrude(x1: y4, y1: x4, x2: y3, y2: x3);
447
448 const int NumSectors = 100;
449 const qreal sectorAngle = 2 * qreal(M_PI) / NumSectors;
450
451 for (int i = 0; i < NumSectors; ++i) {
452 qreal angle = i * sectorAngle;
453 qreal x5 = 0.30 * sin(x: angle);
454 qreal y5 = 0.30 * cos(x: angle);
455 qreal x6 = 0.20 * sin(x: angle);
456 qreal y6 = 0.20 * cos(x: angle);
457
458 angle += sectorAngle;
459 qreal x7 = 0.20 * sin(x: angle);
460 qreal y7 = 0.20 * cos(x: angle);
461 qreal x8 = 0.30 * sin(x: angle);
462 qreal y8 = 0.30 * cos(x: angle);
463
464 quad(x1: x5, y1: y5, x2: x6, y2: y6, x3: x7, y3: y7, x4: x8, y4: y8);
465
466 extrude(x1: x6, y1: y6, x2: x7, y2: y7);
467 extrude(x1: x8, y1: y8, x2: x5, y2: y5);
468 }
469
470 for (int i = 0;i < m_vertices.size();i++)
471 m_vertices[i] *= 2.0f;
472}
473
474void GLWidget::quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4)
475{
476 m_vertices << QVector3D(x1, y1, -0.05f);
477 m_vertices << QVector3D(x2, y2, -0.05f);
478 m_vertices << QVector3D(x4, y4, -0.05f);
479
480 m_vertices << QVector3D(x3, y3, -0.05f);
481 m_vertices << QVector3D(x4, y4, -0.05f);
482 m_vertices << QVector3D(x2, y2, -0.05f);
483
484 QVector3D n = QVector3D::normal
485 (v1: QVector3D(x2 - x1, y2 - y1, 0.0f), v2: QVector3D(x4 - x1, y4 - y1, 0.0f));
486
487 m_normals << n;
488 m_normals << n;
489 m_normals << n;
490
491 m_normals << n;
492 m_normals << n;
493 m_normals << n;
494
495 m_vertices << QVector3D(x4, y4, 0.05f);
496 m_vertices << QVector3D(x2, y2, 0.05f);
497 m_vertices << QVector3D(x1, y1, 0.05f);
498
499 m_vertices << QVector3D(x2, y2, 0.05f);
500 m_vertices << QVector3D(x4, y4, 0.05f);
501 m_vertices << QVector3D(x3, y3, 0.05f);
502
503 n = QVector3D::normal
504 (v1: QVector3D(x2 - x4, y2 - y4, 0.0f), v2: QVector3D(x1 - x4, y1 - y4, 0.0f));
505
506 m_normals << n;
507 m_normals << n;
508 m_normals << n;
509
510 m_normals << n;
511 m_normals << n;
512 m_normals << n;
513}
514
515void GLWidget::extrude(qreal x1, qreal y1, qreal x2, qreal y2)
516{
517 m_vertices << QVector3D(x1, y1, +0.05f);
518 m_vertices << QVector3D(x2, y2, +0.05f);
519 m_vertices << QVector3D(x1, y1, -0.05f);
520
521 m_vertices << QVector3D(x2, y2, -0.05f);
522 m_vertices << QVector3D(x1, y1, -0.05f);
523 m_vertices << QVector3D(x2, y2, +0.05f);
524
525 QVector3D n = QVector3D::normal
526 (v1: QVector3D(x2 - x1, y2 - y1, 0.0f), v2: QVector3D(0.0f, 0.0f, -0.1f));
527
528 m_normals << n;
529 m_normals << n;
530 m_normals << n;
531
532 m_normals << n;
533 m_normals << n;
534 m_normals << n;
535}
536
537void GLWidget::setTransparent(bool transparent)
538{
539 setAttribute(Qt::WA_AlwaysStackOnTop, on: transparent);
540 m_transparent = transparent;
541 // Call update() on the top-level window after toggling AlwayStackOnTop to make sure
542 // the entire backingstore is updated accordingly.
543 window()->update();
544}
545
546void GLWidget::resizeGL(int, int)
547{
548 if (m_hasButton) {
549 if (!m_btn) {
550 m_btn = new QPushButton("A widget on top.\nPress for more widgets.", this);
551 connect(sender: m_btn, signal: &QPushButton::clicked, receiver: this, slot: &GLWidget::handleButtonPress);
552 }
553 m_btn->move(ax: 20, ay: 80);
554 }
555}
556
557void GLWidget::handleButtonPress()
558{
559 m_mainWindow->addNew();
560}
561

source code of qtbase/examples/opengl/qopenglwidget/glwidget.cpp