I'm using this solution to animate ellipses along a QPainterPath.
But I need to slowly increase or decrease the speed of the animation.
I create a timer and set a new duration for the animation but the result is a choppy animation because the ellipses start from the beginning.
Is there a better way to accomplish this?
class AnimatedEllipses: public QGraphicsObject
{
Q_OBJECT
Q_PROPERTY(int progress READ progress WRITE setProgress)
private:
QGraphicsPathItem path;
QList<QGraphicsEllipseItem*> ellipses;
int propProgress;
QPropertyAnimation* animation;
public:
int progress() const { return propProgress;}
void setProgress(int value)
{
propProgress = value;
int index = 0;
for (QGraphicsEllipseItem* ellipse: ellipses)
{
// Keep value between 0 and length.
int lgt = (propProgress + index * 40) % int(path.path().length());
qreal percent = path.path().percentAtLength(lgt);
++index;
ellipse->setPos(path.path().pointAtPercent(percent));
}
}
AnimatedEllipses(QPainterPath const& path): QGraphicsObject(), path(path), propProgress(0)
{
qreal pos = 0;
qreal length = path.length();
while (pos < length)
{
qreal percent = path.percentAtLength(pos);
QPointF pointAtPercent = path.pointAtPercent(percent);
pos += 40;
QGraphicsEllipseItem * item = new QGraphicsEllipseItem(-10, -10, 20, 20, this);
item->setPos(pointAtPercent);
ellipses << item;
}
animation = new QPropertyAnimation(this, "progress");
animation->setStartValue(0);
animation->setEndValue(length);
animation->setDuration(10000);
animation->setLoopCount(-1);
animation->start();
QTimer *timer = new QTimer();
connect(timer, SIGNAL(timeout()), this, SLOT(SlotTimeOut()));
timer->start(1000);
}
void SlotTimeOut() {
int newDuration = GetRandomDuration();
animation->setDuration(newDuration);
}
// QGraphicsItem interface
public:
QRectF boundingRect() const { return path.boundingRect();}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget){}
};
You can do this with custom QEasingCurve. Here is a small example for progressbar value.
MainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void OnChangeDurationTimer();
private:
Ui::MainWindow *ui;
QPropertyAnimation m_animProgress;
};
Initialization
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_animProgress.setTargetObject(ui->progressBar);
m_animProgress.setPropertyName("value");
m_animProgress.setStartValue(0);
m_animProgress.setEndValue(100);
m_animProgress.setDuration(10000);
m_animProgress.setLoopCount(-1);
//setting custom QEasingCurve
QEasingCurve eCurve;
eCurve.setCustomType(myEasingFunction);
m_animProgress.setEasingCurve(eCurve);
m_animProgress.start();
QTimer::singleShot(3000, this, &MainWindow::OnChangeDurationTimer); //timer to change duration
}
Most interesting is function myEasingFunction for custom EasingCurve
qreal g_offset = 0; //value last animation stopped with
qreal g_offsetLast = 0; //keep current value of animation
qreal myEasingFunction(qreal progress)
{
qreal val = g_offset + progress;
while (val > 1) {
val -= 1; //normalize
}
g_offsetLast = val;
return val;
}
And changing duration on timer
void MainWindow::OnChangeDurationTimer()
{
g_offset = g_offsetLast; //remember stopped value
m_animProgress.stop();
m_animProgress.setDuration((rand() % 10 + 1) * 1000);
m_animProgress.start();
QTimer::singleShot(3000, this, &MainWindow::OnChangeDurationTimer); //next changing
}
I have a bunch of ellipses that initially are lined on top of a path and should move along the QPainterPath. I have it working for the first ellipse but I can't figure out how to get the correct position for the other ellipses.
Is there a way to check if it passed the end of the path and move it back to the beginning?
class Animation : public QAbstractAnimation
{
public:
Animation(const QPainterPath& path, QObject *parent = Q_NULLPTR);
virtual void updateCurrentTime(int ms) override;
virtual int duration() const override;
QPainterPath mPath;
QVector<EllipseGraphicsItem*> mAnimationElements;
};
Animation::Animation (const QPainterPath& path, QObject *parent) : QAbstractAnimation(parent)
, mPath(path)
{
qreal pos = 0;
qreal length = mPath.length();
while (pos < length)
{
qreal percent = path.percentAtLength(pos);
QPointF pointAtPercent = path.pointAtPercent(percent);
pos += 40;
EllipseGraphicsItem * item = new EllipseGraphicsItem(parentItem());
mAnimationElements.append(item);
item->setPos(pointAtPercent);
}
}
void Animation::updateCurrentTime(int ms)
{
QPointF point = mPath.pointAtPercent(qreal(ms) / 6000);
if (mAnimationElements.size() > 0)
mAnimationElements[0]->setPos(point);
for (int i = 0; i < mAnimationElements.size(); i++) {
// how to update each circle's position?
}
}
Start the animation:
QPainterPath path;
path.moveTo(10, 10);
path.lineTo(QPointF(500, 10));
path.lineTo(QPointF(500, 700));
path.lineTo(QPointF(10, 700));
Animation *animation = new Animation(path, this);
animation->setLoopCount(-1);
animation->start();
Imho, it would be easier to use a QGraphicsObject with a QPropertyAnimation:
Use a property that varies between 0 and the length of the path and place your elements by calculating their positions from its value and their position in the list.
A quick example :
class AnimatedEllipses: public QGraphicsObject
{
Q_OBJECT
Q_PROPERTY(int progress READ progress WRITE setProgress)
private:
QGraphicsPathItem path;
QList<QGraphicsEllipseItem*> ellipses;
int propProgress;
public:
int progress() const { return propProgress;}
void setProgress(int value)
{
propProgress = value;
int index = 0;
for (QGraphicsEllipseItem* ellipse: ellipses)
{
// Keep value between 0 and length.
int lgt = (propProgress + index * 40) % int(path.path().length());
qreal percent = path.path().percentAtLength(lgt);
++index;
ellipse->setPos(path.path().pointAtPercent(percent));
}
}
AnimatedEllipses(QPainterPath const& path): QGraphicsObject(), path(path), propProgress(0)
{
qreal pos = 0;
qreal length = path.length();
while (pos < length)
{
qreal percent = path.percentAtLength(pos);
QPointF pointAtPercent = path.pointAtPercent(percent);
pos += 40;
QGraphicsEllipseItem * item = new QGraphicsEllipseItem(-10, -10, 20, 20, this);
item->setPos(pointAtPercent);
ellipses << item;
}
QPropertyAnimation* animation = new QPropertyAnimation(this, "progress");
animation->setStartValue(0);
animation->setEndValue(length);
animation->setDuration(10000);
animation->setLoopCount(-1);
animation->start();
}
// QGraphicsItem interface
public:
QRectF boundingRect() const { return path.boundingRect();}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget){}
};
The modulo allows you to create an infinte loop for each ellipse.
When I want to create a new Qt Quick Item in C++ in I extend QQuickItem. It happens that I want my new item to have the same basic properties of a Rectangle. Is there any class I can extend to have a Rectangle directly?
Rectangle is actually QQuickRectangle, but it is not exported to be used in C++. The header file is QtQuick\private\qquickrectangle_p.h.
Check my version of an AdvancedRectangle that can have a different radius for each corner. I had to re-implement Rectangle's behavior.
advancedrectangle.h
#ifndef ADVANCEDRECTANGLE_H
#define ADVANCEDRECTANGLE_H
#include <QQuickPaintedItem>
#include <QPainter>
#include <QPen>
class AdvancedRectangle : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit AdvancedRectangle(QQuickItem *parent = 0);
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
QColor color() const;
void setColor(QColor color);
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
qreal radius() const;
void setRadius(qreal r);
Q_PROPERTY(qreal radiusTopLeft READ radiusTopLeft WRITE setRadiusTopLeft NOTIFY radiusTopLeftChanged)
qreal radiusTopLeft() const;
void setRadiusTopLeft(qreal r);
Q_PROPERTY(qreal radiusTopRight READ radiusTopRight WRITE setRadiusTopRight NOTIFY radiusTopRightChanged)
qreal radiusTopRight() const;
void setRadiusTopRight(qreal r);
Q_PROPERTY(qreal radiusBottomRight READ radiusBottomRight WRITE setRadiusBottomRight NOTIFY radiusBottomRightChanged)
qreal radiusBottomRight() const;
void setRadiusBottomRight(qreal r);
Q_PROPERTY(qreal radiusBottomLeft READ radiusBottomLeft WRITE setRadiusBottomLeft NOTIFY radiusBottomLeftChanged)
qreal radiusBottomLeft() const;
void setRadiusBottomLeft(qreal r);
void paint(QPainter *painter);
enum START_ANGELS {
START_E = 0,
START_N = 90,
START_W = 180,
START_S = 270
};
signals:
void colorChanged();
void radiusChanged();
void radiusTopLeftChanged();
void radiusTopRightChanged();
void radiusBottomRightChanged();
void radiusBottomLeftChanged();
public slots:
private:
QColor color_ = QColor(255, 255, 255);
qreal radius_ = 0.0;
qreal radiusTopLeft_ = -1;
qreal radiusTopRight_ = -1;
qreal radiusBottomRight_ = -1;
qreal radiusBottomLeft_ = -1;
private slots:
void onAppearanceChanged();
};
#endif // ADVANCEDRECTANGLE_H
advancedrectangle.cpp
#include "advancedrectangle.h"
AdvancedRectangle::AdvancedRectangle(QQuickItem *parent) :
QQuickPaintedItem(parent)
{
connect(this, SIGNAL(widthChanged()), SLOT(onAppearanceChanged()));
connect(this, SIGNAL(heightChanged()), SLOT(onAppearanceChanged()));
connect(this, SIGNAL(colorChanged()), SLOT(onAppearanceChanged()));
}
QColor AdvancedRectangle::color() const
{
return color_;
}
void AdvancedRectangle::setColor(QColor color)
{
if (color_ == color) return;
color_ = color;
emit colorChanged();
}
qreal AdvancedRectangle::radius() const
{
return radius_;
}
void AdvancedRectangle::setRadius(qreal r)
{
if (r == radius_) return;
radius_ = r;
emit radiusChanged();
}
qreal AdvancedRectangle::radiusTopLeft() const
{
if (radiusTopLeft_ >= 0) return radiusTopLeft_;
else return radius_;
}
void AdvancedRectangle::setRadiusTopLeft(qreal r)
{
if (r == radiusTopLeft_) return;
radiusTopLeft_ = r;
emit radiusTopLeftChanged();
}
qreal AdvancedRectangle::radiusTopRight() const
{
if (radiusTopRight_ >= 0) return radiusTopRight_;
else return radius_;
}
void AdvancedRectangle::setRadiusTopRight(qreal r)
{
if (r == radiusTopRight_) return;
radiusTopRight_ = r;
emit radiusTopRightChanged();
}
qreal AdvancedRectangle::radiusBottomRight() const
{
if (radiusBottomRight_ >= 0) return radiusBottomRight_;
else return radius_;
}
void AdvancedRectangle::setRadiusBottomRight(qreal r)
{
if (r == radiusBottomRight_) return;
radiusBottomRight_ = r;
emit radiusBottomRightChanged();
}
qreal AdvancedRectangle::radiusBottomLeft() const
{
if (radiusBottomLeft_ >= 0) return radiusBottomLeft_;
else return radius_;
}
void AdvancedRectangle::setRadiusBottomLeft(qreal r)
{
if (r == radiusBottomLeft_) return;
radiusBottomLeft_ = r;
emit radiusBottomLeftChanged();
}
void AdvancedRectangle::paint(QPainter *painter)
{
QPen pen(Qt::NoPen);
painter->setPen(pen);
QBrush brush(color_);
painter->setBrush(brush);
painter->setRenderHints(QPainter::Antialiasing, true);
int _width = width();
int _height = height();
QRectF rectangle;
// top-left
rectangle = QRectF(0, 0, 2*radiusTopLeft(), 2*radiusTopLeft());
painter->drawPie(rectangle, START_W * 16, 90 * -1 * 16);
// top-right
rectangle = QRectF(_width-2*radiusTopRight(), 0, 2*radiusTopRight(), 2*radiusTopRight());
painter->drawPie(rectangle, START_N * 16, 90 * -1 * 16);
// bottom-right
rectangle = QRectF(_width-2*radiusBottomRight(), _height-2*radiusBottomRight(), 2*radiusBottomRight(), 2*radiusBottomRight());
painter->drawPie(rectangle, START_E * 16, 90 * -1 * 16);
// bottom-left
rectangle = QRectF(0, _height-2*radiusBottomLeft(), 2*radiusBottomLeft(), 2*radiusBottomLeft());
painter->drawPie(rectangle, START_S * 16, 90 * -1 * 16);
QPointF points[12] = {
QPointF(radiusTopLeft(), 0),
QPointF(radiusTopLeft(), radiusTopLeft()),
QPointF(0, radiusTopLeft()),
QPointF(0, _height-radiusBottomLeft()),
QPointF(radiusBottomLeft(), _height-radiusBottomLeft()),
QPointF(radiusBottomLeft(), _height),
QPointF(_width-radiusBottomRight(), _height),
QPointF(_width-radiusBottomRight(), _height-radiusBottomRight()),
QPointF(_width, _height-radiusBottomRight()),
QPointF(_width, radiusTopRight()),
QPointF(_width-radiusTopRight(), radiusTopRight()),
QPointF(_width-radiusTopRight(), 0)
};
painter->drawPolygon(points, 12);
}
void AdvancedRectangle::onAppearanceChanged()
{
int new_width = width();
int new_height = height();
update(QRect(0, 0, new_width, new_height));
}
Let me know if you need that MIT licensed.
The application compiles and runs with no compiler errors however no Q logo is displayed.
I have taken the QT Logo OpenGL example http://qt.developpez.com/doc/4.7/opengl-hellogl/
and attempted to compile in QT version 5.2.1 (MSVC 2010, 32bit) . I am aware that there is a change in the way OpenGL is used in version 5 from the older version 4. I have modified some of the code from a working GL example and it still does not work. My bet is the problem exists in the QtLogo because I have changed nothing in those files. So I am completely puzzled being a newbie to OpenGL.
I hope to have provided a descent problem statement. Answering this will help so many others unlock the mysteries of QT OpenGL changes. It is so frustrating.
1) QT version 5.2.1 (MSVC 2010, 32bit) - Installed with Qt 5.2.1 for Windows 32-bit (MinGW 4.8, OpenGL, 634 MB) installer
2) Windows 7, tried 32bit and 64bit
3) Intel CPUs (used two different Intel based computers)
4) The application does work correctly in QT version 4
5) The code is available for download http://1drv.ms/1g88ScS
Here is the code:
hellogl.pro
# HELLOGL
#VPATH += ../shared
#INCLUDEPATH += ../shared
QT += core gui opengl
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = hellogl
TEMPLATE = app
SOURCES += main.cpp \
window.cpp \
glwidget.cpp \
qtlogo.cpp
HEADERS += window.h \
glwidget.h \
qtlogo.h
FORMS += window.ui
GLWIDGET.H
// glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QGLWidget>
class QtLogo;
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
explicit GLWidget(QWidget *parent = 0);
~GLWidget();
QSize minimumSizeHint() const;
QSize sizeHint() const;
public slots:
void setXRotation(int angle);
void setYRotation(int angle);
void setZRotation(int angle);
signals:
void xRotationChanged(int angle);
void yRotationChanged(int angle);
void zRotationChanged(int angle);
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
QtLogo *logo;
int xRot;
int yRot;
int zRot;
QPoint lastPos;
QColor qtGreen;
QColor qtPurple;
};
#endif
qtlogo.h
//qtlogo.h
#ifndef QTLOGO_H
#define QTLOGO_H
//#include <QObject>
//#include <QtOpenGL>
#include <QColor>
#include <GL/gl.h>
//#include <QGLWidget>
//#include <QtWidgets>
#include <QMatrix4x4>
#include <QVector3D>
#include <qmath.h>
class Patch;
class Geometry;
//! [0]
class QtLogo : public QObject
{
public:
QtLogo(QObject *parent, int d = 64, qreal s = 1.0);
~QtLogo();
void setColor(QColor c);
void draw() const;
private:
void buildGeometry(int d, qreal s);
QList<Patch *> parts;
Geometry *geom;
};
//! [0]
#endif // QTLOGO_H
window.h
// window.h
#ifndef WINDOW_H
#define WINDOW_H
//#include <QObject>
//#include <QApplication>
//#include <QHBoxLayout>
//#include <QSpinBox>
//#include <QKeyEvent>
#include <QWidget>
#include <QSlider>
class QSlider;
class GLWidget;
namespace Ui {
class Window;
}
class Window : public QWidget
{
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
~Window();
protected:
void keyPressEvent(QKeyEvent *event);
private:
Ui::Window *ui;
QSlider *createSlider();
GLWidget *glWidget;
QSlider *xSlider;
QSlider *ySlider;
QSlider *zSlider;
};
#endif
glwidget.cpp
// glwidget.cpp
#include <math.h>
#include <QtWidgets>
#include <QtOpenGL>
#include "glwidget.h"
#include "qtlogo.h"
//#include <QColor>
/*
#ifndef GL_MULTISAMPLE
#define GL_MULTISAMPLE 0x809D
#endif
*/
GLWidget::GLWidget(QWidget *parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{
logo = 0;
xRot = 0;
yRot = 0;
zRot = 0;
qtGreen = QColor::fromCmykF(0.40, 0.0, 1.0, 0.0);
qtPurple = QColor::fromCmykF(0.39, 0.39, 0.0, 0.0);
}
GLWidget::~GLWidget()
{
}
QSize GLWidget::minimumSizeHint() const
{
return QSize(50, 50); //
}
QSize GLWidget::sizeHint() const
{
return QSize(800, 800);
}
static void qNormalizeAngle(int &angle)
{
while (angle < 0)
angle += 360 * 16;
while (angle > 360 * 16)
angle -= 360 * 16;
}
void GLWidget::setXRotation(int angle)
{
qNormalizeAngle(angle);
if (angle != xRot) {
xRot = angle;
emit xRotationChanged(angle);
updateGL();
}
}
void GLWidget::setYRotation(int angle)
{
qNormalizeAngle(angle);
if (angle != yRot) {
yRot = angle;
emit yRotationChanged(angle);
updateGL();
}
}
void GLWidget::setZRotation(int angle)
{
qNormalizeAngle(angle);
if (angle != zRot) {
zRot = angle;
emit zRotationChanged(angle);
updateGL();
}
}
void GLWidget::initializeGL()
{
qglClearColor(qtPurple.dark());
logo = new QtLogo(this, 64);
logo->setColor(qtGreen.dark());
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_MULTISAMPLE);
static GLfloat lightPosition[4] = { 0.5, 5.0, 7.0, 1.0 };
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0, 0.0, -10.0);
glRotatef(xRot / 16.0, 1.0, 0.0, 0.0);
glRotatef(yRot / 16.0, 0.0, 1.0, 0.0);
glRotatef(zRot / 16.0, 0.0, 0.0, 1.0);
logo->draw();
//logo->setColor(QColor(0,0,0));
}
void GLWidget::resizeGL(int width, int height)
{
int side = qMin(width, height);
glViewport((width - side) / 2, (height - side) / 2, side, side);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
#ifdef QT_OPENGL_ES_1
glOrthof(-0.5, +0.5, -0.5, +0.5, 4.0, 15.0);
#else
glOrtho(-0.5, +0.5, -0.5, +0.5, 4.0, 15.0);
#endif
glMatrixMode(GL_MODELVIEW);
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
int dx = event->x() - lastPos.x();
int dy = event->y() - lastPos.y();
if (event->buttons() & Qt::LeftButton) {
setXRotation(xRot + 8 * dy);
setYRotation(yRot + 8 * dx);
} else if (event->buttons() & Qt::RightButton) {
setXRotation(xRot + 8 * dy);
setZRotation(zRot + 8 * dx);
}
lastPos = event->pos();
}
main.cpp
// main.cpp
#include <QtOpenGL>
#include <QtGui>
#include <QApplication>
#include <QDesktopWidget>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.resize(window.sizeHint());
int desktopArea = QApplication::desktop()->width() *
QApplication::desktop()->height();
int widgetArea = window.width() * window.height();
if (((float)widgetArea / (float)desktopArea) < 0.75f)
window.show();
else
window.showMaximized();
return app.exec();
}
qtlogo.cpp
// qtlogo.cpp
#include "qtlogo.h"
static const qreal tee_height = 0.311126;
static const qreal cross_width = 0.25;
static const qreal bar_thickness = 0.113137;
static const qreal inside_diam = 0.20;
static const qreal outside_diam = 0.30;
static const qreal logo_depth = 0.10;
static const int num_divisions = 32;
//! [0]
struct Geometry
{
QVector<GLushort> faces;
QVector<QVector3D> vertices;
QVector<QVector3D> normals;
void appendSmooth(const QVector3D &a, const QVector3D &n, int from);
void appendFaceted(const QVector3D &a, const QVector3D &n);
void finalize();
void loadArrays() const;
};
//! [0]
//! [1]
class Patch
{
public:
enum Smoothing { Faceted, Smooth };
Patch(Geometry *);
void setSmoothing(Smoothing s) { sm = s; }
void translate(const QVector3D &t);
void rotate(qreal deg, QVector3D axis);
void draw() const;
void addTri(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &n);
void addQuad(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &d);
GLushort start;
GLushort count;
GLushort initv;
GLfloat faceColor[4];
QMatrix4x4 mat;
Smoothing sm;
Geometry *geom;
};
//! [1]
static inline void qSetColor(float colorVec[], QColor c)
{
colorVec[0] = c.redF();
colorVec[1] = c.greenF();
colorVec[2] = c.blueF();
colorVec[3] = c.alphaF();
}
void Geometry::loadArrays() const
{
glVertexPointer(3, GL_FLOAT, 0, vertices.constData());
glNormalPointer(GL_FLOAT, 0, normals.constData());
}
void Geometry::finalize()
{
// TODO: add vertex buffer uploading here
// Finish smoothing normals by ensuring accumulated normals are returned
// to length 1.0.
for (int i = 0; i < normals.count(); ++i)
normals[i].normalize();
}
void Geometry::appendSmooth(const QVector3D &a, const QVector3D &n, int from)
{
// Smooth normals are acheived by averaging the normals for faces meeting
// at a point. First find the point in geometry already generated
// (working backwards, since most often the points shared are between faces
// recently added).
int v = vertices.count() - 1;
for ( ; v >= from; --v)
if (qFuzzyCompare(vertices[v], a))
break;
if (v < from)
{
// The vert was not found so add it as a new one, and initialize
// its corresponding normal
v = vertices.count();
vertices.append(a);
normals.append(n);
}
else
{
// Vert found, accumulate normals into corresponding normal slot.
// Must call finalize once finished accumulating normals
normals[v] += n;
}
// In both cases (found or not) reference the vert via its index
faces.append(v);
}
void Geometry::appendFaceted(const QVector3D &a, const QVector3D &n)
{
// Faceted normals are achieved by duplicating the vert for every
// normal, so that faces meeting at a vert get a sharp edge.
int v = vertices.count();
vertices.append(a);
normals.append(n);
faces.append(v);
}
Patch::Patch(Geometry *g)
: start(g->faces.count())
, count(0)
, initv(g->vertices.count())
, sm(Patch::Smooth)
, geom(g)
{
qSetColor(faceColor, QColor(Qt::darkGray));
}
void Patch::rotate(qreal deg, QVector3D axis)
{
mat.rotate(deg, axis);
}
void Patch::translate(const QVector3D &t)
{
mat.translate(t);
}
static inline void qMultMatrix(const QMatrix4x4 &mat)
{
if (sizeof(qreal) == sizeof(GLfloat))
glMultMatrixf((GLfloat*)mat.constData());
#ifndef QT_OPENGL_ES
else if (sizeof(qreal) == sizeof(GLdouble))
glMultMatrixd((GLdouble*)mat.constData());
#endif
else
{
GLfloat fmat[16];
GLfloat const *r = mat.constData();
for (int i = 0; i < 16; ++i)
fmat[i] = r[i];
glMultMatrixf(fmat);
}
}
//! [2]
void Patch::draw() const
{
glPushMatrix();
qMultMatrix(mat);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, faceColor);
const GLushort *indices = geom->faces.constData();
glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, indices + start);
glPopMatrix();
}
//! [2]
void Patch::addTri(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &n)
{
QVector3D norm = n.isNull() ? QVector3D::normal(a, b, c) : n;
if (sm == Smooth)
{
geom->appendSmooth(a, norm, initv);
geom->appendSmooth(b, norm, initv);
geom->appendSmooth(c, norm, initv);
}
else
{
geom->appendFaceted(a, norm);
geom->appendFaceted(b, norm);
geom->appendFaceted(c, norm);
}
count += 3;
}
void Patch::addQuad(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &d)
{
QVector3D norm = QVector3D::normal(a, b, c);
if (sm == Smooth)
{
addTri(a, b, c, norm);
addTri(a, c, d, norm);
}
else
{
// If faceted share the two common verts
addTri(a, b, c, norm);
int k = geom->vertices.count();
geom->appendSmooth(a, norm, k);
geom->appendSmooth(c, norm, k);
geom->appendFaceted(d, norm);
count += 3;
}
}
static inline QVector<QVector3D> extrude(const QVector<QVector3D> &verts, qreal depth)
{
QVector<QVector3D> extr = verts;
for (int v = 0; v < extr.count(); ++v)
extr[v].setZ(extr[v].z() - depth);
return extr;
}
class Rectoid
{
public:
void translate(const QVector3D &t)
{
for (int i = 0; i < parts.count(); ++i)
parts[i]->translate(t);
}
void rotate(qreal deg, QVector3D axis)
{
for (int i = 0; i < parts.count(); ++i)
parts[i]->rotate(deg, axis);
}
// No special Rectoid destructor - the parts are fetched out of this member
// variable, and destroyed by the new owner
QList<Patch*> parts;
};
class RectPrism : public Rectoid
{
public:
RectPrism(Geometry *g, qreal width, qreal height, qreal depth);
};
RectPrism::RectPrism(Geometry *g, qreal width, qreal height, qreal depth)
{
enum { bl, br, tr, tl };
Patch *fb = new Patch(g);
fb->setSmoothing(Patch::Faceted);
// front face
QVector<QVector3D> r(4);
r[br].setX(width);
r[tr].setX(width);
r[tr].setY(height);
r[tl].setY(height);
QVector3D adjToCenter(-width / 2.0, -height / 2.0, depth / 2.0);
for (int i = 0; i < 4; ++i)
r[i] += adjToCenter;
fb->addQuad(r[bl], r[br], r[tr], r[tl]);
// back face
QVector<QVector3D> s = extrude(r, depth);
fb->addQuad(s[tl], s[tr], s[br], s[bl]);
// side faces
Patch *sides = new Patch(g);
sides->setSmoothing(Patch::Faceted);
sides->addQuad(s[bl], s[br], r[br], r[bl]);
sides->addQuad(s[br], s[tr], r[tr], r[br]);
sides->addQuad(s[tr], s[tl], r[tl], r[tr]);
sides->addQuad(s[tl], s[bl], r[bl], r[tl]);
parts << fb << sides;
}
class RectTorus : public Rectoid
{
public:
RectTorus(Geometry *g, qreal iRad, qreal oRad, qreal depth, int numSectors);
};
RectTorus::RectTorus(Geometry *g, qreal iRad, qreal oRad, qreal depth, int k)
{
QVector<QVector3D> inside;
QVector<QVector3D> outside;
for (int i = 0; i < k; ++i) {
qreal angle = (i * 2 * M_PI) / k;
inside << QVector3D(iRad * qSin(angle), iRad * qCos(angle), depth / 2.0);
outside << QVector3D(oRad * qSin(angle), oRad * qCos(angle), depth / 2.0);
}
inside << QVector3D(0.0, iRad, 0.0);
outside << QVector3D(0.0, oRad, 0.0);
QVector<QVector3D> in_back = extrude(inside, depth);
QVector<QVector3D> out_back = extrude(outside, depth);
// Create front, back and sides as separate patches so that smooth normals
// are generated for the curving sides, but a faceted edge is created between
// sides and front/back
Patch *front = new Patch(g);
for (int i = 0; i < k; ++i)
front->addQuad(outside[i], inside[i],
inside[(i + 1) % k], outside[(i + 1) % k]);
Patch *back = new Patch(g);
for (int i = 0; i < k; ++i)
back->addQuad(in_back[i], out_back[i],
out_back[(i + 1) % k], in_back[(i + 1) % k]);
Patch *is = new Patch(g);
for (int i = 0; i < k; ++i)
is->addQuad(in_back[i], in_back[(i + 1) % k],
inside[(i + 1) % k], inside[i]);
Patch *os = new Patch(g);
for (int i = 0; i < k; ++i)
os->addQuad(out_back[(i + 1) % k], out_back[i],
outside[i], outside[(i + 1) % k]);
parts << front << back << is << os;
}
QtLogo::QtLogo(QObject *parent, int divisions, qreal scale)
: QObject(parent)
, geom(new Geometry())
{
buildGeometry(divisions, scale);
}
QtLogo::~QtLogo()
{
qDeleteAll(parts);
delete geom;
}
void QtLogo::setColor(QColor c)
{
for (int i = 0; i < parts.count(); ++i)
qSetColor(parts[i]->faceColor, c);
}
//! [3]
void QtLogo::buildGeometry(int divisions, qreal scale)
{
qreal cw = cross_width * scale;
qreal bt = bar_thickness * scale;
qreal ld = logo_depth * scale;
qreal th = tee_height *scale;
RectPrism cross(geom, cw, bt, ld);
RectPrism stem(geom, bt, th, ld);
QVector3D z(0.0, 0.0, 1.0);
cross.rotate(45.0, z);
stem.rotate(45.0, z);
qreal stem_downshift = (th + bt) / 2.0;
stem.translate(QVector3D(0.0, -stem_downshift, 0.0));
RectTorus body(geom, 0.20, 0.30, 0.1, divisions);
parts << stem.parts << cross.parts << body.parts;
geom->finalize();
}
//! [3]
//! [4]
void QtLogo::draw() const
{
geom->loadArrays();
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
for (int i = 0; i < parts.count(); ++i)
parts[i]->draw();
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
}
//! [4]
window.cpp
// window.cpp
#include <QtWidgets>
#include "window.h"
#include "ui_window.h"
#include "glwidget.h"
#include <QSlider>
Window::Window(QWidget *parent) :
QWidget(parent),
ui(new Ui::Window)
{
ui->setupUi(this);
//glWidget = new GLWidget;
glWidget = ui->widget;
xSlider = createSlider();
ySlider = createSlider();
zSlider = createSlider();
connect(xSlider, SIGNAL(valueChanged(int)), glWidget, SLOT(setXRotation(int)));
connect(glWidget, SIGNAL(xRotationChanged(int)), xSlider, SLOT(setValue(int)));
connect(ySlider, SIGNAL(valueChanged(int)), glWidget, SLOT(setYRotation(int)));
connect(glWidget, SIGNAL(yRotationChanged(int)), ySlider, SLOT(setValue(int)));
connect(zSlider, SIGNAL(valueChanged(int)), glWidget, SLOT(setZRotation(int)));
connect(glWidget, SIGNAL(zRotationChanged(int)), zSlider, SLOT(setValue(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(glWidget);
mainLayout->addWidget(xSlider);
mainLayout->addWidget(ySlider);
mainLayout->addWidget(zSlider);
setLayout(mainLayout);
xSlider->setValue(15 * 16);
ySlider->setValue(345 * 16);
zSlider->setValue(0 * 16);
setWindowTitle(tr("Hello GL"));
}
QSlider *Window::createSlider()
{
QSlider *slider = new QSlider(Qt::Vertical);
slider->setRange(0, 360 * 16);
slider->setSingleStep(16);
slider->setPageStep(15 * 16);
slider->setTickInterval(15 * 16);
slider->setTickPosition(QSlider::TicksRight);
return slider;
}
Window::~Window()
{
delete ui;
}
void Window::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Escape)
close();
else
QWidget::keyPressEvent(e);
}
:-) I have nailed it!
The problem was: The Q Logo does not appear in QT5. Although it does work in QT4.
After spending weeks on this problem, googling for hours, going through the hassle of compiling QT many many times, I have finally solved the problem with this code and the mystery of opengl_es and ANGLE
So for those of us completely baffled as to why QT OpenGL examples code prior to QT5 may not work or work properly, it could be the same issue as this code.
Basically comment out or omit references to QT_OPENGL_ES.
In this hello world Q example:
Comment this out as shown contained in the Qlogo.cpp file
/*
#ifndef QT_OPENGL_ES
else if (sizeof(qreal) == sizeof(GLdouble))
glMultMatrixd((GLdouble*)mat.constData());
#endif
*/
Lots of code lovin' to you all.
I'm studying WidgetMarqueeLabel class:
#include "WidgetMarqueeLabel.h"
#include <QPainter>
#include <QWidget>
WidgetMarqueeLabel::WidgetMarqueeLabel(QWidget *parent)//*parent)
{
px = 0;
py = 10;
speed = 1;
direction = RightToLeft;
connect(&timer3, SIGNAL(timeout()), this, SLOT(refreshLabel()));
timer3.start(10);
}
void WidgetMarqueeLabel::refreshLabel()
{
repaint();
}
WidgetMarqueeLabel::~WidgetMarqueeLabel()
{}
void WidgetMarqueeLabel::show()
{
QLabel::show();
}
void WidgetMarqueeLabel::setAlignment(Qt::Alignment al)
{
m_align = al;
updateCoordinates();
QLabel::setAlignment(al);
}
void WidgetMarqueeLabel::paintEvent(QPaintEvent *evt)
{
QPainter p(this);
if(direction==RightToLeft)
{
px -= speed;
if(px <= (-textLength))
px = width();
}
else
{
px += speed;
if(px >= width())
px = - textLength;
}
p.drawText(px, py+fontPointSize, text());
p.translate(px,0);
}
void WidgetMarqueeLabel::resizeEvent(QResizeEvent *evt)
{
updateCoordinates();
QLabel::resizeEvent(evt);
}
void WidgetMarqueeLabel::updateCoordinates()
{
switch(m_align)
{
case Qt::AlignTop:
py = 10;
break;
case Qt::AlignBottom:
py = height()-10;
break;
case Qt::AlignVCenter:
py = height()/2;
break;
}
fontPointSize = font().pointSize()/2;
textLength = fontMetrics().width(text());
}
void WidgetMarqueeLabel::setSpeed(int s)
{
speed = s;
}
int WidgetMarqueeLabel::getSpeed()
{
return speed;
}
void WidgetMarqueeLabel::setDirection(int d)
{
direction = d;
if (direction==RightToLeft)
px = width() - textLength;
else
px = 0;
refreshLabel();
}
void WidgetMarqueeLabel::close()
{
QLabel::close();
}
I was wondering if it was possible to make the text reappear before the text that reaches the end of the last letter on the right. I want something like this: for example (white space are 25):
WidgetMarqueeLabel
tMarqueeLabel Widge
eLabel WidgetMarque
el WidgetMarqueeLa
WidgetMarqueeLabel
WidgetMarqueeLabel
WidgetMarqueeLabel
WidgetMarqueeLabel
Is this possible?
For this purpose, I once wrote a class.
Example screenshot showing the text "This is an example text. It will be scrolled horizontally.". Note the alpha blending at both sides.
The code:
scrolltext.h:
#ifndef SCROLLTEXT_H
#define SCROLLTEXT_H
#include <QWidget>
#include <QStaticText>
#include <QTimer>
class ScrollText : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(QString separator READ separator WRITE setSeparator)
public:
explicit ScrollText(QWidget *parent = 0);
public slots:
QString text() const;
void setText(QString text);
QString separator() const;
void setSeparator(QString separator);
protected:
virtual void paintEvent(QPaintEvent *);
virtual void resizeEvent(QResizeEvent *);
private:
void updateText();
QString _text;
QString _separator;
QStaticText staticText;
int singleTextWidth;
QSize wholeTextSize;
int leftMargin;
bool scrollEnabled;
int scrollPos;
QImage alphaChannel;
QImage buffer;
QTimer timer;
private slots:
virtual void timer_timeout();
};
#endif // SCROLLTEXT_H
scrolltext.cpp:
#include "scrolltext.h"
#include <QPainter>
ScrollText::ScrollText(QWidget *parent) :
QWidget(parent), scrollPos(0)
{
staticText.setTextFormat(Qt::PlainText);
setFixedHeight(fontMetrics().height());
leftMargin = height() / 3;
setSeparator(" --- ");
connect(&timer, SIGNAL(timeout()), this, SLOT(timer_timeout()));
timer.setInterval(50);
}
QString ScrollText::text() const
{
return _text;
}
void ScrollText::setText(QString text)
{
_text = text;
updateText();
update();
}
QString ScrollText::separator() const
{
return _separator;
}
void ScrollText::setSeparator(QString separator)
{
_separator = separator;
updateText();
update();
}
void ScrollText::updateText()
{
timer.stop();
singleTextWidth = fontMetrics().width(_text);
scrollEnabled = (singleTextWidth > width() - leftMargin);
if(scrollEnabled)
{
scrollPos = -64;
staticText.setText(_text + _separator);
timer.start();
}
else
staticText.setText(_text);
staticText.prepare(QTransform(), font());
wholeTextSize = QSize(fontMetrics().width(staticText.text()), fontMetrics().height());
}
void ScrollText::paintEvent(QPaintEvent*)
{
QPainter p(this);
if(scrollEnabled)
{
buffer.fill(qRgba(0, 0, 0, 0));
QPainter pb(&buffer);
pb.setPen(p.pen());
pb.setFont(p.font());
int x = qMin(-scrollPos, 0) + leftMargin;
while(x < width())
{
pb.drawStaticText(QPointF(x, (height() - wholeTextSize.height()) / 2) + QPoint(2, 2), staticText);
x += wholeTextSize.width();
}
//Apply Alpha Channel
pb.setCompositionMode(QPainter::CompositionMode_DestinationIn);
pb.setClipRect(width() - 15, 0, 15, height());
pb.drawImage(0, 0, alphaChannel);
pb.setClipRect(0, 0, 15, height());
//initial situation: don't apply alpha channel in the left half of the image at all; apply it more and more until scrollPos gets positive
if(scrollPos < 0)
pb.setOpacity((qreal)(qMax(-8, scrollPos) + 8) / 8.0);
pb.drawImage(0, 0, alphaChannel);
//pb.end();
p.drawImage(0, 0, buffer);
}
else
{
p.drawStaticText(QPointF(leftMargin, (height() - wholeTextSize.height()) / 2), staticText);
}
}
void ScrollText::resizeEvent(QResizeEvent*)
{
//When the widget is resized, we need to update the alpha channel.
alphaChannel = QImage(size(), QImage::Format_ARGB32_Premultiplied);
buffer = QImage(size(), QImage::Format_ARGB32_Premultiplied);
//Create Alpha Channel:
if(width() > 64)
{
//create first scanline
QRgb* scanline1 = (QRgb*)alphaChannel.scanLine(0);
for(int x = 1; x < 16; ++x)
scanline1[x - 1] = scanline1[width() - x] = qRgba(0, 0, 0, x << 4);
for(int x = 15; x < width() - 15; ++x)
scanline1[x] = qRgb(0, 0, 0);
//copy scanline to the other ones
for(int y = 1; y < height(); ++y)
memcpy(alphaChannel.scanLine(y), (uchar*)scanline1, width() * 4);
}
else
alphaChannel.fill(qRgb(0, 0, 0));
//Update scrolling state
bool newScrollEnabled = (singleTextWidth > width() - leftMargin);
if(newScrollEnabled != scrollEnabled)
updateText();
}
void ScrollText::timer_timeout()
{
scrollPos = (scrollPos + 2)
% wholeTextSize.width();
update();
}
Really easy. Simply repaint the text displaced by the width of the control:
void WidgetMarqueeLabel::paintEvent(QPaintEvent *evt)
{
QPainter p(this);
if(direction==RightToLeft)
{
px -= speed;
if(px <= (-textLength))
px = width();
}
else
{
px += speed;
if(px >= width())
px = - textLength;
}
p.drawText(px, py+fontPointSize, text());
__p.drawText(px-width(), py+fontPointSize, text());
p.drawText(px+width(), py+fontPointSize, text());
p.translate(px,0);
}
Something like the following should work. The padding is hard-coded at 25, which is what is sounded like you wanted. If you wanted the label to always be a certain size, you could use something like QString::leftJustified.
class MarqueeLabel : public QLabel {
public:
explicit MarqueeLabel(const QString &text) : QLabel(text), pos_(0) {
QString pad(25, ' ');
actual_text_ = text + pad;
startTimer(100);
}
protected:
void timerEvent(QTimerEvent *) {
pos_ = ++pos_ % actual_text_.length();
setText(actual_text_.mid(pos_).append(actual_text_.left(pos_)));
}
private:
QString actual_text_;
int pos_;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MarqueeLabel lbl("WidgetMarqueeLabel");
lbl.show();
return a.exec();
}