Commit 8befa85d authored by Morten Sorvig's avatar Morten Sorvig

Add Qt for WebAssembly examples and demos

Initial batch of four:

	localfiles
	multicanvas
	mandelbtrot
	slate

See README.md for further description.
parents
Pipeline #2127 failed with stages
Qt for WebAssembly Examples
===========================
This repository contains sample code for the Qt port to the HTML/Web platform.
In general, the existing Qt examples also work on WebAssembly. The examples
provided here demonstrate topics specific to this platform.
Examples
--------
localfiles/ : local file access
multicanvas/ : rendering to multiple canvases
mandelbrot/ : multithtreading
Applications
------------
slate/ : The Slate pixel editor (fork of github.com/mitchcurtis/slate)
TEMPLATE = app
TARGET = localfiles
QT += widgets
SOURCES += \
$$PWD/main.cpp
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the demonstration applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtWidgets>
#include <emscripten/val.h>
// Save file function. Will be added to Qt 5.14 :)
void saveFileContent(const QString &fileName, const QByteArray &fileContent);
class LoadSaveWidget : public QWidget
{
public:
LoadSaveWidget() {
QVBoxLayout *layout = new QVBoxLayout();
setLayout(layout);
QHBoxLayout *buttonLayout = new QHBoxLayout();
layout->addLayout(buttonLayout);
QPushButton *loadFile = new QPushButton("Load File");
buttonLayout->addWidget(loadFile);
QPushButton *saveFile = new QPushButton("Save File");
buttonLayout->addWidget(saveFile);
QLabel *fileInfo = new QLabel("File");
layout->addWidget(fileInfo);
saveFile->setEnabled(false);
auto fileReady = [=](const QString &name, const QByteArray &content) {
fileName = name;
fileContent = content;
fileInfo->setText(QString("File: %1 Size: %2").arg(fileName).arg(fileContent.size()));
saveFile->setEnabled(true);
};
QObject::connect(loadFile, &QPushButton::clicked, [=]() {
QFileDialog::getOpenFileContent("*.*", fileReady);
});
QObject::connect(saveFile, &QPushButton::clicked, [=]() {
saveFileContent(fileName, fileContent);
});
}
private:
QString fileName;
QByteArray fileContent;
};
void saveFileContent(const QString &fileName, const QByteArray &fileContent)
{
auto saveFileImpl = [=]() {
// Create file data Blob
emscripten::val Blob = emscripten::val::global("Blob");
emscripten::val content = emscripten::val(
emscripten::typed_memory_view(fileContent.size(), fileContent.constData()));
emscripten::val type = emscripten::val::object();
type.set("type","octet/stream");
emscripten::val fileBlob = Blob.new_(content, type);
// Create Blob download link
emscripten::val document = emscripten::val::global("document");
emscripten::val link = document.call<emscripten::val>("createElement", std::string("a"));
link.set("download", fileName.toStdString());
emscripten::val window = emscripten::val::global("window");
emscripten::val URL = window["URL"];
emscripten::val objectUrl = URL.call<emscripten::val>("createObjectURL", fileBlob);
link.set("href", objectUrl);
link.set("style", "display:none");
qDebug() << "savefileImpl 3";
// Programatically click link
emscripten::val body = document["body"];
body.call<void>("appendChild", link);
link.call<void>("click");
// Clean up
body.call<void>("removeChild", link);
URL.call<void>("revokeObjectURL", objectUrl);
};
// Give the app some time to finish its current event processing,
// before starting the potentially time consuming save.a
QTimer::singleShot(10, saveFileImpl);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
LoadSaveWidget loadSaveWidget;
loadSaveWidget.showNormal();
return app.exec();
}
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mandelbrotwidget.h"
#include <QApplication>
#include <QThreadPool>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// we're managing the number of threads elsewhere; set the global
// thread limit to a large value so it won't interfere.
QThreadPool::globalInstance()->setMaxThreadCount(1024);
MandelbrotWidget widget;
widget.show();
return app.exec();
}
QT += widgets
CONFIG += c++14
HEADERS = mandelbrotwidget.h \
renderthread.h
SOURCES = main.cpp \
mandelbrotwidget.cpp \
renderthread.cpp
unix:!mac:!vxworks:!integrity:!haiku:LIBS += -lm
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mandelbrotwidget.h"
#include <QPainter>
#include <QKeyEvent>
#include <QDebug>
#include <math.h>
//! [0]
const double DefaultCenterX = -0.637011f;
const double DefaultCenterY = -0.0395159f;
const double DefaultScale = 0.00403897f;
const double ZoomInFactor = 0.8f;
const double ZoomOutFactor = 1 / ZoomInFactor;
const int ScrollStep = 20;
bool renderAtDeviceResolution = false;
//! [0]
int deviceIndependentWidth(const QPixmap &pixmap)
{
return pixmap.width() / pixmap.devicePixelRatio();
}
int deviceIndependentHeight(const QPixmap &pixmap)
{
return pixmap.height() / pixmap.devicePixelRatio();
}
//! [1]
MandelbrotWidget::MandelbrotWidget(QWidget *parent)
: QWidget(parent)
, thread(QThread::idealThreadCount(), parent)
{
centerX = DefaultCenterX;
centerY = DefaultCenterY;
pixmapScale = DefaultScale;
curScale = DefaultScale;
connect(&thread, &RenderThread::renderedImage,
this, &MandelbrotWidget::updatePixmap);
setWindowTitle(tr("Mandelbrot"));
#ifndef QT_NO_CURSOR
setCursor(Qt::CrossCursor);
#endif
resize(550, 400);
}
//! [1]
//! [2]
void MandelbrotWidget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.fillRect(rect(), Qt::black);
if (pixmap.isNull()) {
painter.setPen(Qt::white);
painter.drawText(rect(), Qt::AlignCenter, tr("Rendering initial image, please wait..."));
//! [2] //! [3]
return;
//! [3] //! [4]
}
//! [4]
//! [5]
if (curScale == pixmapScale) {
//! [5] //! [6]
painter.drawPixmap(pixmapOffset, pixmap);
//! [6] //! [7]
} else {
//! [7] //! [8]
double scaleFactor = pixmapScale / curScale;
int newWidth = int(deviceIndependentWidth(pixmap) * scaleFactor);
int newHeight = int(deviceIndependentHeight(pixmap) * scaleFactor);
int newX = pixmapOffset.x() + (deviceIndependentWidth(pixmap) - newWidth) / 2;
int newY = pixmapOffset.y() + (deviceIndependentHeight(pixmap) - newHeight) / 2;
painter.save();
painter.translate(newX, newY);
painter.scale(scaleFactor, scaleFactor);
QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1);
QRectF deviceExposed(exposed.topLeft() * pixmap.devicePixelRatio(), exposed.size() * pixmap.devicePixelRatio()); // meh
painter.drawPixmap(exposed, pixmap, deviceExposed);
painter.restore();
}
//! [8] //! [9]
QString text = tr("Use mouse wheel or the '+' and '-' keys to zoom. "
"Press and hold left mouse button to scroll.");
QFontMetrics metrics = painter.fontMetrics();
int textWidth = metrics.horizontalAdvance(text);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 127));
painter.drawRect((width() - textWidth) / 2 - 5, 0, textWidth + 10, metrics.lineSpacing() + 5);
painter.setPen(Qt::white);
painter.drawText((width() - textWidth) / 2, metrics.leading() + metrics.ascent(), text);
}
//! [9]
//! [10]
void MandelbrotWidget::resizeEvent(QResizeEvent * /* event */)
{
thread.render(centerX, centerY, curScale, size(), renderAtDeviceResolution ? devicePixelRatioF() : 1.0);
}
//! [10]
//! [11]
void MandelbrotWidget::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Plus:
zoom(ZoomInFactor);
break;
case Qt::Key_Minus:
zoom(ZoomOutFactor);
break;
case Qt::Key_Left:
scroll(-ScrollStep, 0);
break;
case Qt::Key_Right:
scroll(+ScrollStep, 0);
break;
case Qt::Key_Down:
scroll(0, -ScrollStep);
break;
case Qt::Key_Up:
scroll(0, +ScrollStep);
break;
default:
QWidget::keyPressEvent(event);
}
}
//! [11]
#if QT_CONFIG(wheelevent)
//! [12]
void MandelbrotWidget::wheelEvent(QWheelEvent *event)
{
int numDegrees = event->delta() / 8;
double numSteps = numDegrees / 15.0f;
zoom(pow(ZoomInFactor, numSteps));
}
//! [12]
#endif
//! [13]
void MandelbrotWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
lastDragPos = event->pos();
}
//! [13]
//! [14]
void MandelbrotWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
pixmapOffset += event->pos() - lastDragPos;
lastDragPos = event->pos();
update();
}
}
//! [14]
//! [15]
void MandelbrotWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
pixmapOffset += event->pos() - lastDragPos;
lastDragPos = QPoint();
int deltaX = (width() - deviceIndependentWidth(pixmap)) / 2 - pixmapOffset.x();
int deltaY = (height() - deviceIndependentHeight(pixmap)) / 2 - pixmapOffset.y();
scroll(deltaX, deltaY);
}
}
//! [15]
//! [16]
void MandelbrotWidget::updatePixmap(const QImage &image, double scaleFactor)
{
if (!lastDragPos.isNull())
return;
pixmap = QPixmap::fromImage(image);
pixmapOffset = QPoint();
lastDragPos = QPoint();
pixmapScale = scaleFactor;
update();
}
//! [16]
//! [17]
void MandelbrotWidget::zoom(double zoomFactor)
{
curScale *= zoomFactor;
update();
thread.render(centerX, centerY, curScale, size(), renderAtDeviceResolution ? devicePixelRatioF() : 1.0);
}
//! [17]
//! [18]
void MandelbrotWidget::scroll(int deltaX, int deltaY)
{
centerX += deltaX * curScale;
centerY += deltaY * curScale;
update();
thread.render(centerX, centerY, curScale, size(), renderAtDeviceResolution ? devicePixelRatioF() : 1.0);
}
//! [18]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef MANDELBROTWIDGET_H
#define MANDELBROTWIDGET_H
#include <QPixmap>
#include <QWidget>
#include "renderthread.h"
//! [0]
class MandelbrotWidget : public QWidget
{
Q_OBJECT
public:
MandelbrotWidget(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
#if QT_CONFIG(wheelevent)
void wheelEvent(QWheelEvent *event) override;
#endif
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private slots:
void updatePixmap(const QImage &image, double scaleFactor);
void zoom(double zoomFactor);
private:
void scroll(int deltaX, int deltaY);