Volker Hilsheimer 7c59c5ed13 Examples: remove OpenGL code paths from painting examples
The extra code for using the OpenGL paint engine is significant enough
to be distracting from what the examples are supposed to show. If we
want to show how to use QPainter on an OpenGL widget, then we can make
dedicated and documented examples for that, in the OpenGL category. And
we have such examples in the Qt OpenGL module anyway.

As is, the examples feel more like manual tests of the OpenGL paint
engine; if we need more coverage there, then we can add it there.

Change-Id: I7b56ea6d08c02cd0a1050ab03991656a0538498d
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
2023-10-27 16:22:59 +02:00

535 lines
17 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "pathdeform.h"
#include <QGuiApplication>
#include <QScreen>
#include <QtDebug>
#include <QMouseEvent>
#include <QTimerEvent>
#include <QLayout>
#include <QLineEdit>
#include <QPainter>
#include <QSlider>
#include <QLabel>
#include <qmath.h>
PathDeformControls::PathDeformControls(QWidget *parent,
PathDeformRenderer* renderer, bool smallScreen)
: QWidget(parent)
{
m_renderer = renderer;
if (smallScreen)
layoutForSmallScreen();
else
layoutForDesktop();
}
void PathDeformControls::layoutForDesktop()
{
QGroupBox* mainGroup = new QGroupBox(this);
mainGroup->setTitle(tr("Controls"));
QGroupBox *radiusGroup = new QGroupBox(mainGroup);
radiusGroup->setTitle(tr("Lens Radius"));
QSlider *radiusSlider = new QSlider(Qt::Horizontal, radiusGroup);
radiusSlider->setRange(15, 150);
radiusSlider->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QGroupBox *deformGroup = new QGroupBox(mainGroup);
deformGroup->setTitle(tr("Deformation"));
QSlider *deformSlider = new QSlider(Qt::Horizontal, deformGroup);
deformSlider->setRange(-100, 100);
deformSlider->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QGroupBox *fontSizeGroup = new QGroupBox(mainGroup);
fontSizeGroup->setTitle(tr("Font Size"));
QSlider *fontSizeSlider = new QSlider(Qt::Horizontal, fontSizeGroup);
fontSizeSlider->setRange(16, 200);
fontSizeSlider->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QGroupBox *textGroup = new QGroupBox(mainGroup);
textGroup->setTitle(tr("Text"));
QLineEdit *textInput = new QLineEdit(textGroup);
QPushButton *animateButton = new QPushButton(mainGroup);
animateButton->setText(tr("Animated"));
animateButton->setCheckable(true);
QPushButton *showSourceButton = new QPushButton(mainGroup);
showSourceButton->setText(tr("Show Source"));
QPushButton *whatsThisButton = new QPushButton(mainGroup);
whatsThisButton->setText(tr("What's This?"));
whatsThisButton->setCheckable(true);
mainGroup->setFixedWidth(180);
QVBoxLayout *mainGroupLayout = new QVBoxLayout(mainGroup);
mainGroupLayout->addWidget(radiusGroup);
mainGroupLayout->addWidget(deformGroup);
mainGroupLayout->addWidget(fontSizeGroup);
mainGroupLayout->addWidget(textGroup);
mainGroupLayout->addWidget(animateButton);
mainGroupLayout->addStretch(1);
mainGroupLayout->addWidget(showSourceButton);
mainGroupLayout->addWidget(whatsThisButton);
QVBoxLayout *radiusGroupLayout = new QVBoxLayout(radiusGroup);
radiusGroupLayout->addWidget(radiusSlider);
QVBoxLayout *deformGroupLayout = new QVBoxLayout(deformGroup);
deformGroupLayout->addWidget(deformSlider);
QVBoxLayout *fontSizeGroupLayout = new QVBoxLayout(fontSizeGroup);
fontSizeGroupLayout->addWidget(fontSizeSlider);
QVBoxLayout *textGroupLayout = new QVBoxLayout(textGroup);
textGroupLayout->addWidget(textInput);
QVBoxLayout * mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(mainGroup);
mainLayout->setContentsMargins(QMargins());
connect(radiusSlider, &QAbstractSlider::valueChanged, m_renderer, &PathDeformRenderer::setRadius);
connect(deformSlider, &QAbstractSlider::valueChanged, m_renderer, &PathDeformRenderer::setIntensity);
connect(fontSizeSlider, &QAbstractSlider::valueChanged, m_renderer, &PathDeformRenderer::setFontSize);
connect(animateButton, &QAbstractButton::clicked, m_renderer, &PathDeformRenderer::setAnimated);
connect(textInput, &QLineEdit::textChanged, m_renderer, &PathDeformRenderer::setText);
connect(m_renderer, &ArthurFrame::descriptionEnabledChanged,
whatsThisButton, &QAbstractButton::setChecked);
connect(whatsThisButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::setDescriptionEnabled);
connect(showSourceButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::showSource);
animateButton->animateClick();
deformSlider->setValue(80);
fontSizeSlider->setValue(120);
radiusSlider->setValue(100);
textInput->setText(tr("Qt"));
}
void PathDeformControls::layoutForSmallScreen()
{
QGroupBox* mainGroup = new QGroupBox(this);
mainGroup->setTitle(tr("Controls"));
QLabel *radiusLabel = new QLabel(mainGroup);
radiusLabel->setText(tr("Lens Radius:"));
QSlider *radiusSlider = new QSlider(Qt::Horizontal, mainGroup);
radiusSlider->setRange(15, 150);
radiusSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QLabel *deformLabel = new QLabel(mainGroup);
deformLabel->setText(tr("Deformation:"));
QSlider *deformSlider = new QSlider(Qt::Horizontal, mainGroup);
deformSlider->setRange(-100, 100);
deformSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QLabel *fontSizeLabel = new QLabel(mainGroup);
fontSizeLabel->setText(tr("Font Size:"));
QSlider *fontSizeSlider = new QSlider(Qt::Horizontal, mainGroup);
fontSizeSlider->setRange(16, 200);
fontSizeSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QPushButton *animateButton = new QPushButton(tr("Animated"), mainGroup);
animateButton->setCheckable(true);
QPushButton *quitButton = new QPushButton(tr("Quit"), mainGroup);
QPushButton *okButton = new QPushButton(tr("OK"), mainGroup);
QGridLayout *mainGroupLayout = new QGridLayout(mainGroup);
mainGroupLayout->setContentsMargins(QMargins());
mainGroupLayout->addWidget(radiusLabel, 0, 0, Qt::AlignRight);
mainGroupLayout->addWidget(radiusSlider, 0, 1);
mainGroupLayout->addWidget(deformLabel, 1, 0, Qt::AlignRight);
mainGroupLayout->addWidget(deformSlider, 1, 1);
mainGroupLayout->addWidget(fontSizeLabel, 2, 0, Qt::AlignRight);
mainGroupLayout->addWidget(fontSizeSlider, 2, 1);
mainGroupLayout->addWidget(animateButton, 3,0, 1,2);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(mainGroup);
mainLayout->addStretch(1);
mainLayout->addWidget(okButton);
mainLayout->addWidget(quitButton);
connect(quitButton, &QAbstractButton::clicked, this, &PathDeformControls::quitPressed);
connect(okButton, &QAbstractButton::clicked, this, &PathDeformControls::okPressed);
connect(radiusSlider, &QAbstractSlider::valueChanged, m_renderer, &PathDeformRenderer::setRadius);
connect(deformSlider, &QAbstractSlider::valueChanged, m_renderer, &PathDeformRenderer::setIntensity);
connect(fontSizeSlider, &QAbstractSlider::valueChanged, m_renderer, &PathDeformRenderer::setFontSize);
connect(animateButton, &QAbstractButton::clicked, m_renderer, &PathDeformRenderer::setAnimated);
animateButton->animateClick();
deformSlider->setValue(80);
fontSizeSlider->setValue(120);
QRect screen_size = QGuiApplication::primaryScreen()->geometry();
radiusSlider->setValue(qMin(screen_size.width(), screen_size.height())/5);
m_renderer->setText(tr("Qt"));
}
PathDeformWidget::PathDeformWidget(QWidget *parent, bool smallScreen)
: QWidget(parent)
{
setWindowTitle(tr("Vector Deformation"));
m_renderer = new PathDeformRenderer(this, smallScreen);
m_renderer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// Layouts
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(m_renderer);
m_controls = new PathDeformControls(nullptr, m_renderer, smallScreen);
m_controls->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
if (!smallScreen)
mainLayout->addWidget(m_controls);
m_renderer->loadSourceFile(":res/deform/pathdeform.cpp");
m_renderer->loadDescription(":res/deform/pathdeform.html");
m_renderer->setDescriptionEnabled(false);
connect(m_renderer, &PathDeformRenderer::clicked,
this, &PathDeformWidget::showControls);
connect(m_controls, &PathDeformControls::okPressed,
this, &PathDeformWidget::hideControls);
connect(m_controls, &PathDeformControls::quitPressed,
qApp, &QCoreApplication::quit);
}
void PathDeformWidget::showControls()
{
m_controls->showFullScreen();
}
void PathDeformWidget::hideControls()
{
m_controls->hide();
}
void PathDeformWidget::setStyle(QStyle *style)
{
QWidget::setStyle(style);
if (!m_controls)
return;
m_controls->setStyle(style);
const QList<QWidget *> widgets = m_controls->findChildren<QWidget *>();
for (QWidget *w : widgets)
w->setStyle(style);
}
static inline QRect circle_bounds(const QPointF &center, qreal radius, qreal compensation)
{
return QRect(qRound(center.x() - radius - compensation),
qRound(center.y() - radius - compensation),
qRound((radius + compensation) * 2),
qRound((radius + compensation) * 2));
}
const int LENS_EXTENT = 10;
PathDeformRenderer::PathDeformRenderer(QWidget *widget, bool smallScreen)
: ArthurFrame(widget)
{
m_radius = 100;
m_pos = QPointF(m_radius, m_radius);
m_direction = QPointF(1, 1);
m_fontSize = 24;
m_animated = true;
m_repaintTimer.start(25, this);
m_repaintTracker.start();
m_intensity = 100;
m_smallScreen = smallScreen;
// m_fpsTimer.start(1000, this);
// m_fpsCounter = 0;
generateLensPixmap();
}
void PathDeformRenderer::setText(const QString &text)
{
m_text = text;
QFont f("times new roman,utopia");
f.setStyleStrategy(QFont::ForceOutline);
f.setPointSize(m_fontSize);
f.setStyleHint(QFont::Times);
QFontMetrics fm(f);
m_paths.clear();
m_pathBounds = QRect();
QPointF advance(0, 0);
bool do_quick = true;
for (int i=0; i<text.size(); ++i) {
if (text.at(i).unicode() >= 0x4ff && text.at(i).unicode() <= 0x1e00) {
do_quick = false;
break;
}
}
if (do_quick) {
for (int i=0; i<text.size(); ++i) {
QPainterPath path;
path.addText(advance, f, text.mid(i, 1));
m_pathBounds |= path.boundingRect();
m_paths << path;
advance += QPointF(fm.horizontalAdvance(text.mid(i, 1)), 0);
}
} else {
QPainterPath path;
path.addText(advance, f, text);
m_pathBounds |= path.boundingRect();
m_paths << path;
}
for (int i=0; i<m_paths.size(); ++i)
m_paths[i] = m_paths[i] * QTransform(1, 0, 0, 1, -m_pathBounds.x(), -m_pathBounds.y());
update();
}
void PathDeformRenderer::generateLensPixmap()
{
qreal rad = m_radius + LENS_EXTENT;
QRect bounds = circle_bounds(QPointF(), rad, 0);
QPainter painter;
if (preferImage()) {
m_lens_image = QImage(bounds.size(), QImage::Format_ARGB32_Premultiplied);
m_lens_image.fill(0);
painter.begin(&m_lens_image);
} else {
m_lens_pixmap = QPixmap(bounds.size());
m_lens_pixmap.fill(Qt::transparent);
painter.begin(&m_lens_pixmap);
}
QRadialGradient gr(rad, rad, rad, 3 * rad / 5, 3 * rad / 5);
gr.setColorAt(0.0, QColor(255, 255, 255, 191));
gr.setColorAt(0.2, QColor(255, 255, 127, 191));
gr.setColorAt(0.9, QColor(150, 150, 200, 63));
gr.setColorAt(0.95, QColor(0, 0, 0, 127));
gr.setColorAt(1, QColor(0, 0, 0, 0));
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(gr);
painter.setPen(Qt::NoPen);
painter.drawEllipse(0, 0, bounds.width(), bounds.height());
}
void PathDeformRenderer::setAnimated(bool animated)
{
m_animated = animated;
if (m_animated) {
// m_fpsTimer.start(1000, this);
// m_fpsCounter = 0;
m_repaintTimer.start(25, this);
m_repaintTracker.start();
} else {
// m_fpsTimer.stop();
m_repaintTimer.stop();
}
}
void PathDeformRenderer::timerEvent(QTimerEvent *e)
{
if (e->timerId() == m_repaintTimer.timerId()) {
if (QLineF(QPointF(0,0), m_direction).length() > 1)
m_direction *= 0.995;
qreal time = m_repaintTracker.restart();
QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
qreal dx = m_direction.x();
qreal dy = m_direction.y();
if (time > 0) {
dx = dx * time * .1;
dy = dy * time * .1;
}
m_pos += QPointF(dx, dy);
if (m_pos.x() - m_radius < 0) {
m_direction.setX(-m_direction.x());
m_pos.setX(m_radius);
} else if (m_pos.x() + m_radius > width()) {
m_direction.setX(-m_direction.x());
m_pos.setX(width() - m_radius);
}
if (m_pos.y() - m_radius < 0) {
m_direction.setY(-m_direction.y());
m_pos.setY(m_radius);
} else if (m_pos.y() + m_radius > height()) {
m_direction.setY(-m_direction.y());
m_pos.setY(height() - m_radius);
}
QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
update(rectAfter | rectBefore);
}
}
void PathDeformRenderer::mousePressEvent(QMouseEvent *e)
{
if (m_showDoc) {
setDescriptionEnabled(false);
return;
}
setDescriptionEnabled(false);
m_repaintTimer.stop();
m_offset = QPointF();
if (QLineF(m_pos, e->position().toPoint()).length() <= m_radius)
m_offset = m_pos - e->position().toPoint();
m_mousePress = e->position().toPoint();
// If we're not running in small screen mode, always assume we're dragging
m_mouseDrag = !m_smallScreen;
mouseMoveEvent(e);
}
void PathDeformRenderer::mouseReleaseEvent(QMouseEvent *e)
{
if (e->buttons() == Qt::NoButton && m_animated) {
m_repaintTimer.start(10, this);
m_repaintTracker.start();
}
if (!m_mouseDrag && m_smallScreen)
emit clicked();
}
void PathDeformRenderer::mouseMoveEvent(QMouseEvent *e)
{
if (!m_mouseDrag && (QLineF(m_mousePress, e->position().toPoint()).length() > 25.0) )
m_mouseDrag = true;
if (m_mouseDrag) {
QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
if (e->type() == QEvent::MouseMove) {
QLineF line(m_pos, e->position().toPoint() + m_offset);
line.setLength(line.length() * .1);
QPointF dir(line.dx(), line.dy());
m_direction = (m_direction + dir) / 2;
}
m_pos = e->position().toPoint() + m_offset;
QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
update(rectBefore | rectAfter);
}
}
QPainterPath PathDeformRenderer::lensDeform(const QPainterPath &source, const QPointF &offset)
{
QPainterPath path;
path.addPath(source);
qreal flip = m_intensity / qreal(100);
for (int i=0; i<path.elementCount(); ++i) {
const QPainterPath::Element &e = path.elementAt(i);
qreal x = e.x + offset.x();
qreal y = e.y + offset.y();
qreal dx = x - m_pos.x();
qreal dy = y - m_pos.y();
qreal len = m_radius - qSqrt(dx * dx + dy * dy);
if (len > 0) {
path.setElementPositionAt(i,
x + flip * dx * len / m_radius,
y + flip * dy * len / m_radius);
} else {
path.setElementPositionAt(i, x, y);
}
}
return path;
}
void PathDeformRenderer::paint(QPainter *painter)
{
int pad_x = 5;
int pad_y = 5;
int skip_x = qRound(m_pathBounds.width() + pad_x + m_fontSize/2);
int skip_y = qRound(m_pathBounds.height() + pad_y);
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::black);
QRectF clip(painter->clipPath().boundingRect());
int overlap = pad_x / 2;
for (int start_y=0; start_y < height(); start_y += skip_y) {
if (start_y > clip.bottom())
break;
int start_x = -overlap;
for (; start_x < width(); start_x += skip_x) {
if (start_y + skip_y >= clip.top() &&
start_x + skip_x >= clip.left() &&
start_x <= clip.right()) {
for (int i=0; i<m_paths.size(); ++i) {
QPainterPath path = lensDeform(m_paths[i], QPointF(start_x, start_y));
painter->drawPath(path);
}
}
}
overlap = skip_x - (start_x - width());
}
if (preferImage()) {
painter->drawImage(m_pos - QPointF(m_radius + LENS_EXTENT, m_radius + LENS_EXTENT),
m_lens_image);
} else {
painter->drawPixmap(m_pos - QPointF(m_radius + LENS_EXTENT, m_radius + LENS_EXTENT),
m_lens_pixmap);
}
}
void PathDeformRenderer::setRadius(int radius)
{
qreal max = qMax(m_radius, (qreal)radius);
m_radius = radius;
generateLensPixmap();
if (!m_animated || m_radius < max)
update(circle_bounds(m_pos, max, m_fontSize));
}
void PathDeformRenderer::setIntensity(int intensity)
{
m_intensity = intensity;
if (!m_animated)
update(circle_bounds(m_pos, m_radius, m_fontSize));
}