diff options
Diffstat (limited to 'src/imports/controls/imagine/impl')
13 files changed, 1307 insertions, 0 deletions
diff --git a/src/imports/controls/imagine/impl/OpacityMask.qml b/src/imports/controls/imagine/impl/OpacityMask.qml new file mode 100644 index 00000000..90175386 --- /dev/null +++ b/src/imports/controls/imagine/impl/OpacityMask.qml @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 + +/* + A cross-graphics API implementation of QtGraphicalEffects' OpacityMask. + */ +Item { + id: rootItem + + property variant source + property variant maskSource + property bool cached: false + + ShaderEffectSource { + id: cacheItem + anchors.fill: parent + visible: rootItem.cached + smooth: true + sourceItem: shaderItem + live: true + hideSource: visible + } + + ShaderEffect { + id: shaderItem + property variant source: rootItem.source + property variant maskSource: rootItem.maskSource + + anchors.fill: parent + + fragmentShader: "qrc:/qt-project.org/imports/QtQuick/Controls/Imagine/impl/shaders/OpacityMask.frag" + } +} diff --git a/src/imports/controls/imagine/impl/imagine-impl.pro b/src/imports/controls/imagine/impl/imagine-impl.pro new file mode 100644 index 00000000..cb0f7043 --- /dev/null +++ b/src/imports/controls/imagine/impl/imagine-impl.pro @@ -0,0 +1,39 @@ +TARGET = qtquickcontrols2imaginestyleimplplugin +TARGETPATH = QtQuick/Controls.2/Imagine/impl + +QML_IMPORT_NAME = QtQuick.Controls.Imagine.impl +QML_IMPORT_VERSION = 2.$$QT_MINOR_VERSION + +QT += qml quick +QT_PRIVATE += core-private gui qml-private quick-private quicktemplates2-private quickcontrols2impl-private +QT_FOR_CONFIG = quickcontrols2-private + +DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII + +QML_FILES += \ + $$PWD/OpacityMask.qml + +OTHER_FILES += \ + qmldir \ + $$QML_FILES + +HEADERS += \ + $$PWD/qquickimageselector_p.h \ + $$PWD/qquickninepatchimage_p.h + +SOURCES += \ + $$PWD/qquickimageselector.cpp \ + $$PWD/qquickninepatchimage.cpp \ + $$PWD/qtquickcontrols2imaginestyleimplplugin.cpp + +qtquickcontrols2imaginestyleimpl.prefix = qt-project.org/imports/QtQuick/Controls/Imagine/impl +qtquickcontrols2imaginestyleimpl.files += \ + $$files($$PWD/shaders/OpacityMask.frag) \ + $$files($$PWD/shaders/+glslcore/OpacityMask.frag) \ + $$files($$PWD/shaders/+qsb/OpacityMask.frag) +RESOURCES += qtquickcontrols2imaginestyleimpl + +CONFIG += qmltypes install_qmltypes no_cxx_module builtin_resources qtquickcompiler +load(qml_plugin) + +requires(qtConfig(quickcontrols2-imagine)) diff --git a/src/imports/controls/imagine/impl/qmldir b/src/imports/controls/imagine/impl/qmldir new file mode 100644 index 00000000..9febda6d --- /dev/null +++ b/src/imports/controls/imagine/impl/qmldir @@ -0,0 +1,6 @@ +module QtQuick.Controls.Imagine.impl +plugin qtquickcontrols2imaginestyleimplplugin +classname QtQuickControls2ImagineStyleImplPlugin + +#TODO: update to 6.0 +OpacityMask 2.0 OpacityMask.qml diff --git a/src/imports/controls/imagine/impl/qquickimageselector.cpp b/src/imports/controls/imagine/impl/qquickimageselector.cpp new file mode 100644 index 00000000..dcb7738c --- /dev/null +++ b/src/imports/controls/imagine/impl/qquickimageselector.cpp @@ -0,0 +1,338 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickimageselector_p.h" + +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qcache.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qfileselector.h> +#include <QtQml/qqmlfile.h> +#include <QtQml/private/qqmlproperty_p.h> +#include <algorithm> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQtQuickControlsImagine, "qt.quick.controls.imagine") + +static const int DEFAULT_CACHE = 500; + +static inline int cacheSize() +{ + static bool ok = false; + static const int size = qEnvironmentVariableIntValue("QT_QUICK_CONTROLS_IMAGINE_CACHE", &ok); + return ok ? size : DEFAULT_CACHE; +} + +// input: [focused, pressed] +// => [[focused, pressed], [pressed, focused], [focused], [pressed]] +static QList<QStringList> permutations(const QStringList &input, int count = -1) +{ + if (count == -1) + count = input.count(); + + QList<QStringList> output; + for (int i = 0; i < input.count(); ++i) { + QStringList sub = input.mid(i, count); + + if (count > 1) { + if (i + count > input.count()) + sub += input.mid(0, count - i + 1); + + std::sort(sub.begin(), sub.end()); + do { + if (!sub.isEmpty()) + output += sub; + } while (std::next_permutation(sub.begin(), sub.end())); + } else { + output += sub; + } + + if (count == input.count()) + break; + } + + if (count > 1) + output += permutations(input, --count); + + return output; +} + +static QString findFile(const QDir &dir, const QString &baseName, const QStringList &extensions) +{ + for (const QString &ext : extensions) { + QString filePath = dir.filePath(baseName + QLatin1Char('.') + ext); + if (QFile::exists(filePath)) + return QFileSelector().select(filePath); + } + // return an empty string to indicate that the lookup has been done + // even if no matching asset was found + return QLatin1String(""); +} + +QQuickImageSelector::QQuickImageSelector(QObject *parent) + : QObject(parent), + m_cache(cacheSize() > 0) +{ +} + +QUrl QQuickImageSelector::source() const +{ + return m_source; +} + +void QQuickImageSelector::setSource(const QUrl &source) +{ + if (m_property.isValid()) + QQmlPropertyPrivate::write(m_property, source, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); + if (m_source == source) + return; + + m_source = source; + emit sourceChanged(); +} + +QString QQuickImageSelector::name() const +{ + return m_name; +} + +void QQuickImageSelector::setName(const QString &name) +{ + if (m_name == name) + return; + + m_name = name; + if (m_complete) + updateSource(); +} + +QString QQuickImageSelector::path() const +{ + return m_path; +} + +void QQuickImageSelector::setPath(const QString &path) +{ + if (m_path == path) + return; + + m_path = path; + if (m_complete) + updateSource(); +} + +QVariantList QQuickImageSelector::states() const +{ + return m_allStates; +} + +void QQuickImageSelector::setStates(const QVariantList &states) +{ + if (m_allStates == states) + return; + + m_allStates = states; + if (updateActiveStates() && m_complete) + updateSource(); +} + +QString QQuickImageSelector::separator() const +{ + return m_separator; +} + +void QQuickImageSelector::setSeparator(const QString &separator) +{ + if (m_separator == separator) + return; + + m_separator = separator; + if (m_complete) + updateSource(); +} + +bool QQuickImageSelector::cache() const +{ + return m_cache; +} + +void QQuickImageSelector::setCache(bool cache) +{ + m_cache = cache; +} + +void QQuickImageSelector::write(const QVariant &value) +{ + setUrl(value.toUrl()); +} + +void QQuickImageSelector::setTarget(const QQmlProperty &property) +{ + m_property = property; +} + +void QQuickImageSelector::classBegin() +{ +} + +void QQuickImageSelector::componentComplete() +{ + setUrl(m_property.read().toUrl()); + m_complete = true; + updateSource(); +} + +QStringList QQuickImageSelector::fileExtensions() const +{ + static const QStringList extensions = QStringList() << QStringLiteral("png"); + return extensions; +} + +QString QQuickImageSelector::cacheKey() const +{ + if (!m_cache) + return QString(); + + return m_path + m_name + m_activeStates.join(m_separator); +} + +void QQuickImageSelector::updateSource() +{ + static QCache<QString, QString> cache(cacheSize()); + + const QString key = cacheKey(); + + QString bestFilePath; + + if (m_cache) { + QString *cachedPath = cache.object(key); + if (cachedPath) + bestFilePath = *cachedPath; + } + + // note: a cached file path may be empty + if (bestFilePath.isNull()) { + QDir dir(m_path); + int bestScore = -1; + + const QStringList extensions = fileExtensions(); + + const QList<QStringList> statePerms = permutations(m_activeStates); + for (const QStringList &perm : statePerms) { + const QString filePath = findFile(dir, m_name + m_separator + perm.join(m_separator), extensions); + if (!filePath.isEmpty()) { + int score = calculateScore(perm); + if (score > bestScore) { + bestScore = score; + bestFilePath = filePath; + } + } + } + + if (bestFilePath.isEmpty()) + bestFilePath = findFile(dir, m_name, extensions); + + if (m_cache) + cache.insert(key, new QString(bestFilePath)); + } + + qCDebug(lcQtQuickControlsImagine) << m_name << m_activeStates << "->" << bestFilePath; + + if (bestFilePath.startsWith(QLatin1Char(':'))) + setSource(QUrl(QLatin1String("qrc") + bestFilePath)); + else + setSource(QUrl::fromLocalFile(bestFilePath)); +} + +void QQuickImageSelector::setUrl(const QUrl &url) +{ + QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(url)); + setName(fileInfo.fileName()); + setPath(fileInfo.path()); +} + +bool QQuickImageSelector::updateActiveStates() +{ + QStringList active; + for (const QVariant &v : qAsConst(m_allStates)) { + const QVariantMap state = v.toMap(); + if (state.isEmpty()) + continue; + auto it = state.begin(); + if (it.value().toBool()) + active += it.key(); + } + + if (m_activeStates == active) + return false; + + m_activeStates = active; + return true; +} + +int QQuickImageSelector::calculateScore(const QStringList &states) const +{ + int score = 0; + for (int i = 0; i < states.count(); ++i) + score += (m_activeStates.count() - m_activeStates.indexOf(states.at(i))) << 1; + return score; +} + +QQuickNinePatchImageSelector::QQuickNinePatchImageSelector(QObject *parent) + : QQuickImageSelector(parent) +{ +} + +QStringList QQuickNinePatchImageSelector::fileExtensions() const +{ + static const QStringList extensions = QStringList() << QStringLiteral("9.png") << QStringLiteral("png"); + return extensions; +} + +QQuickAnimatedImageSelector::QQuickAnimatedImageSelector(QObject *parent) + : QQuickImageSelector(parent) +{ +} + +QStringList QQuickAnimatedImageSelector::fileExtensions() const +{ + static const QStringList extensions = QStringList() << QStringLiteral("webp") << QStringLiteral("gif"); + return extensions; +} + +QT_END_NAMESPACE diff --git a/src/imports/controls/imagine/impl/qquickimageselector_p.h b/src/imports/controls/imagine/impl/qquickimageselector_p.h new file mode 100644 index 00000000..351caefb --- /dev/null +++ b/src/imports/controls/imagine/impl/qquickimageselector_p.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKIMAGESELECTOR_P_H +#define QQUICKIMAGESELECTOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qurl.h> +#include <QtCore/qobject.h> +#include <QtCore/qvariant.h> +#include <QtQml/qqmlproperty.h> +#include <QtQml/qqmlparserstatus.h> +#include <QtQml/private/qqmlpropertyvalueinterceptor_p.h> +#include <QtQml/qqmlproperty.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickImageSelector : public QObject, public QQmlParserStatus, public QQmlPropertyValueInterceptor +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source NOTIFY sourceChanged FINAL) + Q_PROPERTY(QString name READ name WRITE setName FINAL) + Q_PROPERTY(QString path READ path WRITE setPath FINAL) + Q_PROPERTY(QVariantList states READ states WRITE setStates FINAL) + Q_PROPERTY(QString separator READ separator WRITE setSeparator FINAL) + Q_PROPERTY(bool cache READ cache WRITE setCache FINAL) + Q_INTERFACES(QQmlParserStatus QQmlPropertyValueInterceptor) + QML_NAMED_ELEMENT(ImageSelector) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickImageSelector(QObject *parent = nullptr); + + QUrl source() const; + void setSource(const QUrl &source); + + QString name() const; + void setName(const QString &name); + + QString path() const; + void setPath(const QString &path); + + QVariantList states() const; + void setStates(const QVariantList &states); + + QString separator() const; + void setSeparator(const QString &separator); + + bool cache() const; + void setCache(bool cache); + + void write(const QVariant &value) override; + void setTarget(const QQmlProperty &property) override; + +Q_SIGNALS: + void sourceChanged(); + +protected: + void classBegin() override; + void componentComplete() override; + + virtual QStringList fileExtensions() const; + + QString cacheKey() const; + void updateSource(); + void setUrl(const QUrl &url); + bool updateActiveStates(); + int calculateScore(const QStringList &states) const; + +private: + bool m_cache = false; + bool m_complete = false; + QUrl m_source; + QString m_path; + QString m_name; + QString m_separator = QLatin1String("-"); + QVariantList m_allStates; + QStringList m_activeStates; + QQmlProperty m_property; +}; + +class QQuickNinePatchImageSelector : public QQuickImageSelector +{ + Q_OBJECT + QML_NAMED_ELEMENT(NinePatchImageSelector) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickNinePatchImageSelector(QObject *parent = nullptr); + +protected: + QStringList fileExtensions() const override; +}; + +class QQuickAnimatedImageSelector : public QQuickImageSelector +{ + Q_OBJECT + QML_NAMED_ELEMENT(AnimatedImageSelector) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickAnimatedImageSelector(QObject *parent = nullptr); + +protected: + QStringList fileExtensions() const override; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickImageSelector) +QML_DECLARE_TYPE(QQuickAnimatedImageSelector) + +#endif // QQUICKIMAGESELECTOR_P_H diff --git a/src/imports/controls/imagine/impl/qquickninepatchimage.cpp b/src/imports/controls/imagine/impl/qquickninepatchimage.cpp new file mode 100644 index 00000000..1d6d60dd --- /dev/null +++ b/src/imports/controls/imagine/impl/qquickninepatchimage.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickninepatchimage_p.h" + +#include <QtCore/qfileinfo.h> +#include <QtQuick/qsggeometry.h> +#include <QtQuick/qsgtexturematerial.h> +#include <QtQuick/private/qsgnode_p.h> +#include <QtQuick/private/qquickimage_p_p.h> + +QT_BEGIN_NAMESPACE + +struct QQuickNinePatchData +{ + QList<qreal> coordsForSize(qreal count) const; + + inline bool isNull() const { return data.isEmpty(); } + inline int count() const { return data.size(); } + inline qreal at(int index) const { return data.at(index); } + inline qreal size() const { return data.last(); } + + void fill(const QList<qreal> &coords, qreal count); + void clear(); + +private: + bool inverted = false; + QList<qreal> data; +}; + +QList<qreal> QQuickNinePatchData::coordsForSize(qreal size) const +{ + // n = number of stretchable sections + // We have to compensate when adding 0 and/or + // the source image width to the divs vector. + const int l = data.size(); + const int n = (inverted ? l - 1 : l) / 2; + const qreal stretch = (size - data.last()) / n; + + QList<qreal> coords; + coords.reserve(l); + coords.append(0); + + bool stretched = !inverted; + for (int i = 1; i < l; ++i) { + qreal advance = data[i] - data[i - 1]; + if (stretched) + advance += stretch; + coords.append(coords.last() + advance); + + stretched = !stretched; + } + + return coords; +} + +void QQuickNinePatchData::fill(const QList<qreal> &coords, qreal size) +{ + data.clear(); + inverted = coords.isEmpty() || coords.first() != 0; + + // Reserve an extra item in case we need to add the image width/height + if (inverted) { + data.reserve(coords.size() + 2); + data.append(0); + } else { + data.reserve(coords.size() + 1); + } + + data += coords; + data.append(size); +} + +void QQuickNinePatchData::clear() +{ + data.clear(); +} + +class QQuickNinePatchNode : public QSGGeometryNode +{ +public: + QQuickNinePatchNode(); + ~QQuickNinePatchNode(); + + void initialize(QSGTexture *texture, const QSizeF &targetSize, const QSize &sourceSize, + const QQuickNinePatchData &xDivs, const QQuickNinePatchData &yDivs, qreal dpr); + +private: + QSGGeometry m_geometry; + QSGTextureMaterial m_material; +}; + +QQuickNinePatchNode::QQuickNinePatchNode() + : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4) +{ + m_geometry.setDrawingMode(QSGGeometry::DrawTriangles); + setGeometry(&m_geometry); + setMaterial(&m_material); +} + +QQuickNinePatchNode::~QQuickNinePatchNode() +{ + delete m_material.texture(); +} + +void QQuickNinePatchNode::initialize(QSGTexture *texture, const QSizeF &targetSize, const QSize &sourceSize, + const QQuickNinePatchData &xDivs, const QQuickNinePatchData &yDivs, qreal dpr) +{ + delete m_material.texture(); + m_material.setTexture(texture); + + const int xlen = xDivs.count(); + const int ylen = yDivs.count(); + + if (xlen > 0 && ylen > 0) { + const int quads = (xlen - 1) * (ylen - 1); + static const int verticesPerQuad = 6; + m_geometry.allocate(xlen * ylen, verticesPerQuad * quads); + + QSGGeometry::TexturedPoint2D *vertices = m_geometry.vertexDataAsTexturedPoint2D(); + QList<qreal> xCoords = xDivs.coordsForSize(targetSize.width()); + QList<qreal> yCoords = yDivs.coordsForSize(targetSize.height()); + + for (int y = 0; y < ylen; ++y) { + for (int x = 0; x < xlen; ++x, ++vertices) + vertices->set(xCoords[x] / dpr, yCoords[y] / dpr, + xDivs.at(x) / sourceSize.width(), + yDivs.at(y) / sourceSize.height()); + } + + quint16 *indices = m_geometry.indexDataAsUShort(); + int n = quads; + for (int q = 0; n--; ++q) { + if ((q + 1) % xlen == 0) // next row + ++q; + // Bottom-left half quad triangle + indices[0] = q; + indices[1] = q + xlen; + indices[2] = q + xlen + 1; + + // Top-right half quad triangle + indices[3] = q; + indices[4] = q + xlen + 1; + indices[5] = q + 1; + + indices += verticesPerQuad; + } + } + + markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); +} + +class QQuickNinePatchImagePrivate : public QQuickImagePrivate +{ + Q_DECLARE_PUBLIC(QQuickNinePatchImage) + +public: + void updatePatches(); + void updatePaddings(const QSizeF &size, const QList<qreal> &horizontal, const QList<qreal> &vertical); + void updateInsets(const QList<qreal> &horizontal, const QList<qreal> &vertical); + + bool resetNode = false; + qreal topPadding = 0; + qreal leftPadding = 0; + qreal rightPadding = 0; + qreal bottomPadding = 0; + qreal topInset = 0; + qreal leftInset = 0; + qreal rightInset = 0; + qreal bottomInset = 0; + + QImage ninePatch; + QQuickNinePatchData xDivs; + QQuickNinePatchData yDivs; +}; + +static QList<qreal> readCoords(const QRgb *data, int from, int count, int offset, QRgb color) +{ + int p1 = -1; + QList<qreal> coords; + for (int i = 0; i < count; ++i) { + int p2 = from + i * offset; + if (data[p2] == color) { + // colored pixel + if (p1 == -1) + p1 = i; + } else { + // empty pixel + if (p1 != -1) { + coords << p1 << i; + p1 = -1; + } + } + } + return coords; +} + +void QQuickNinePatchImagePrivate::updatePatches() +{ + if (ninePatch.isNull()) + return; + + int w = ninePatch.width(); + int h = ninePatch.height(); + const QRgb *data = reinterpret_cast<const QRgb *>(ninePatch.constBits()); + + const QRgb black = qRgb(0,0,0); + const QRgb red = qRgb(255,0,0); + + xDivs.fill(readCoords(data, 1, w - 1, 1, black), w - 2); // top left -> top right + yDivs.fill(readCoords(data, w, h - 1, w, black), h - 2); // top left -> bottom left + + QList<qreal> hInsets = readCoords(data, (h - 1) * w + 1, w - 1, 1, red); // bottom left -> bottom right + QList<qreal> vInsets = readCoords(data, 2 * w - 1, h - 1, w, red); // top right -> bottom right + updateInsets(hInsets, vInsets); + + const QSizeF sz(w - leftInset - rightInset, h - topInset - bottomInset); + QList<qreal> hPaddings = readCoords(data, (h - 1) * w + leftInset + 1, sz.width() - 2, 1, black); // bottom left -> bottom right + QList<qreal> vPaddings = readCoords(data, (2 + topInset) * w - 1, sz.height() - 2, w, black); // top right -> bottom right + updatePaddings(sz, hPaddings, vPaddings); +} + +void QQuickNinePatchImagePrivate::updatePaddings(const QSizeF &size, const QList<qreal> &horizontal, const QList<qreal> &vertical) +{ + Q_Q(QQuickNinePatchImage); + qreal oldTopPadding = topPadding; + qreal oldLeftPadding = leftPadding; + qreal oldRightPadding = rightPadding; + qreal oldBottomPadding = bottomPadding; + + if (horizontal.count() >= 2) { + leftPadding = horizontal.first(); + rightPadding = size.width() - horizontal.last() - 2; + } else { + leftPadding = 0; + rightPadding = 0; + } + + if (vertical.count() >= 2) { + topPadding = vertical.first(); + bottomPadding = size.height() - vertical.last() - 2; + } else { + topPadding = 0; + bottomPadding = 0; + } + + if (!qFuzzyCompare(oldTopPadding, topPadding)) + emit q->topPaddingChanged(); + if (!qFuzzyCompare(oldBottomPadding, bottomPadding)) + emit q->bottomPaddingChanged(); + if (!qFuzzyCompare(oldLeftPadding, leftPadding)) + emit q->leftPaddingChanged(); + if (!qFuzzyCompare(oldRightPadding, rightPadding)) + emit q->rightPaddingChanged(); +} + +void QQuickNinePatchImagePrivate::updateInsets(const QList<qreal> &horizontal, const QList<qreal> &vertical) +{ + Q_Q(QQuickNinePatchImage); + qreal oldTopInset = topInset; + qreal oldLeftInset = leftInset; + qreal oldRightInset = rightInset; + qreal oldBottomInset = bottomInset; + + if (horizontal.count() >= 2 && horizontal.first() == 0) + leftInset = horizontal.at(1); + else + leftInset = 0; + + if (horizontal.count() == 2 && horizontal.first() > 0) + rightInset = horizontal.last() - horizontal.first(); + else if (horizontal.count() == 4) + rightInset = horizontal.last() - horizontal.at(2); + else + rightInset = 0; + + if (vertical.count() >= 2 && vertical.first() == 0) + topInset = vertical.at(1); + else + topInset = 0; + + if (vertical.count() == 2 && vertical.first() > 0) + bottomInset = vertical.last() - vertical.first(); + else if (vertical.count() == 4) + bottomInset = vertical.last() - vertical.at(2); + else + bottomInset = 0; + + if (!qFuzzyCompare(oldTopInset, topInset)) + emit q->topInsetChanged(); + if (!qFuzzyCompare(oldBottomInset, bottomInset)) + emit q->bottomInsetChanged(); + if (!qFuzzyCompare(oldLeftInset, leftInset)) + emit q->leftInsetChanged(); + if (!qFuzzyCompare(oldRightInset, rightInset)) + emit q->rightInsetChanged(); +} + +QQuickNinePatchImage::QQuickNinePatchImage(QQuickItem *parent) + : QQuickImage(*(new QQuickNinePatchImagePrivate), parent) +{ +} + +qreal QQuickNinePatchImage::topPadding() const +{ + Q_D(const QQuickNinePatchImage); + return d->topPadding / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::leftPadding() const +{ + Q_D(const QQuickNinePatchImage); + return d->leftPadding / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::rightPadding() const +{ + Q_D(const QQuickNinePatchImage); + return d->rightPadding / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::bottomPadding() const +{ + Q_D(const QQuickNinePatchImage); + return d->bottomPadding / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::topInset() const +{ + Q_D(const QQuickNinePatchImage); + return d->topInset / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::leftInset() const +{ + Q_D(const QQuickNinePatchImage); + return d->leftInset / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::rightInset() const +{ + Q_D(const QQuickNinePatchImage); + return d->rightInset / d->devicePixelRatio; +} + +qreal QQuickNinePatchImage::bottomInset() const +{ + Q_D(const QQuickNinePatchImage); + return d->bottomInset / d->devicePixelRatio; +} + +void QQuickNinePatchImage::pixmapChange() +{ + Q_D(QQuickNinePatchImage); + if (QFileInfo(d->url.fileName()).completeSuffix().toLower() == QLatin1String("9.png")) { + d->resetNode = d->ninePatch.isNull(); + d->ninePatch = d->pix.image(); + if (d->ninePatch.depth() != 32) + d->ninePatch = d->ninePatch.convertToFormat(QImage::Format_ARGB32); + + int w = d->ninePatch.width(); + int h = d->ninePatch.height(); + d->pix.setImage(QImage(d->ninePatch.constBits() + 4 * (w + 1), w - 2, h - 2, d->ninePatch.bytesPerLine(), d->ninePatch.format())); + + d->updatePatches(); + } else { + /* + Only change resetNode when it's false; i.e. when no reset is pending. + updatePaintNode() will take care of setting it to false if it's true. + + Consider the following changes in source: + + normal.png => press.9.png => normal.png => focus.png + + If the last two events happen quickly, pixmapChange() can be called + twice with no call to updatePaintNode() inbetween. On the first call, + resetNode will be true (because ninePatch is not null since it is still + in the process of going from a 9-patch image to a regular image), + and on the second call, resetNode would be false if we didn't have this check. + This results in the oldNode never being deleted, and QQuickImage + tries to static_cast a QQuickNinePatchImage to a QSGInternalImageNode. + */ + if (!d->resetNode) + d->resetNode = !d->ninePatch.isNull(); + d->ninePatch = QImage(); + } + QQuickImage::pixmapChange(); +} + +QSGNode *QQuickNinePatchImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_D(QQuickNinePatchImage); + Q_UNUSED(data); + + if (d->resetNode) { + delete oldNode; + oldNode = nullptr; + d->resetNode = false; + } + + QSizeF sz = size(); + QImage image = d->pix.image(); + if (!sz.isValid() || image.isNull()) { + delete oldNode; + return nullptr; + } + + if (d->ninePatch.isNull()) + return QQuickImage::updatePaintNode(oldNode, data); + + QQuickNinePatchNode *patchNode = static_cast<QQuickNinePatchNode *>(oldNode); + if (!patchNode) + patchNode = new QQuickNinePatchNode; + +#ifdef QSG_RUNTIME_DESCRIPTION + qsgnode_set_description(patchNode, QString::fromLatin1("QQuickNinePatchImage: '%1'").arg(d->url.toString())); +#endif + + QSGTexture *texture = window()->createTextureFromImage(image); + patchNode->initialize(texture, sz * d->devicePixelRatio, image.size(), d->xDivs, d->yDivs, d->devicePixelRatio); + return patchNode; +} + +QT_END_NAMESPACE diff --git a/src/imports/controls/imagine/impl/qquickninepatchimage_p.h b/src/imports/controls/imagine/impl/qquickninepatchimage_p.h new file mode 100644 index 00000000..78cc3068 --- /dev/null +++ b/src/imports/controls/imagine/impl/qquickninepatchimage_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKNINEPATCHIMAGE_P_H +#define QQUICKNINEPATCHIMAGE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquickimage_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickNinePatchImagePrivate; + +class QQuickNinePatchImage : public QQuickImage +{ + Q_OBJECT + Q_PROPERTY(qreal topPadding READ topPadding NOTIFY topPaddingChanged FINAL) + Q_PROPERTY(qreal leftPadding READ leftPadding NOTIFY leftPaddingChanged FINAL) + Q_PROPERTY(qreal rightPadding READ rightPadding NOTIFY rightPaddingChanged FINAL) + Q_PROPERTY(qreal bottomPadding READ bottomPadding NOTIFY bottomPaddingChanged FINAL) + Q_PROPERTY(qreal topInset READ topInset NOTIFY topInsetChanged FINAL) + Q_PROPERTY(qreal leftInset READ leftInset NOTIFY leftInsetChanged FINAL) + Q_PROPERTY(qreal rightInset READ rightInset NOTIFY rightInsetChanged FINAL) + Q_PROPERTY(qreal bottomInset READ bottomInset NOTIFY bottomInsetChanged FINAL) + QML_NAMED_ELEMENT(NinePatchImage) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickNinePatchImage(QQuickItem *parent = nullptr); + + qreal topPadding() const; + qreal leftPadding() const; + qreal rightPadding() const; + qreal bottomPadding() const; + + qreal topInset() const; + qreal leftInset() const; + qreal rightInset() const; + qreal bottomInset() const; + +Q_SIGNALS: + void topPaddingChanged(); + void leftPaddingChanged(); + void rightPaddingChanged(); + void bottomPaddingChanged(); + + void topInsetChanged(); + void leftInsetChanged(); + void rightInsetChanged(); + void bottomInsetChanged(); + +protected: + void pixmapChange() override; + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) override; + +private: + Q_DISABLE_COPY(QQuickNinePatchImage) + Q_DECLARE_PRIVATE(QQuickNinePatchImage) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickNinePatchImage) + +#endif // QQUICKNINEPATCHIMAGE_P_H diff --git a/src/imports/controls/imagine/impl/qtquickcontrols2imaginestyleimplplugin.cpp b/src/imports/controls/imagine/impl/qtquickcontrols2imaginestyleimplplugin.cpp new file mode 100644 index 00000000..514d9ed8 --- /dev/null +++ b/src/imports/controls/imagine/impl/qtquickcontrols2imaginestyleimplplugin.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqmlextensionplugin.h> + +QT_BEGIN_NAMESPACE + +class QtQuickControls2ImagineStyleImplPlugin : public QQmlEngineExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + QtQuickControls2ImagineStyleImplPlugin(QObject *parent = nullptr); +}; + +QtQuickControls2ImagineStyleImplPlugin::QtQuickControls2ImagineStyleImplPlugin(QObject *parent) + : QQmlEngineExtensionPlugin(parent) +{ +} + +QT_END_NAMESPACE + +#include "qtquickcontrols2imaginestyleimplplugin.moc" diff --git a/src/imports/controls/imagine/impl/shaders/+glslcore/OpacityMask.frag b/src/imports/controls/imagine/impl/shaders/+glslcore/OpacityMask.frag new file mode 100644 index 00000000..529e2696 --- /dev/null +++ b/src/imports/controls/imagine/impl/shaders/+glslcore/OpacityMask.frag @@ -0,0 +1,13 @@ +#version 150 + +uniform float qt_Opacity; +uniform sampler2D source; +uniform sampler2D maskSource; + +in vec2 qt_TexCoord0; +out vec4 fragColor; + +void main() +{ + fragColor = texture(source, qt_TexCoord0.st) * (texture(maskSource, qt_TexCoord0.st).a) * qt_Opacity; +} diff --git a/src/imports/controls/imagine/impl/shaders/+qsb/OpacityMask.frag b/src/imports/controls/imagine/impl/shaders/+qsb/OpacityMask.frag Binary files differnew file mode 100644 index 00000000..331b30b8 --- /dev/null +++ b/src/imports/controls/imagine/impl/shaders/+qsb/OpacityMask.frag diff --git a/src/imports/controls/imagine/impl/shaders/OpacityMask.frag b/src/imports/controls/imagine/impl/shaders/OpacityMask.frag new file mode 100644 index 00000000..84f9bc3e --- /dev/null +++ b/src/imports/controls/imagine/impl/shaders/OpacityMask.frag @@ -0,0 +1,7 @@ +varying highp vec2 qt_TexCoord0; +uniform highp float qt_Opacity; +uniform lowp sampler2D source; +uniform lowp sampler2D maskSource; +void main(void) { + gl_FragColor = texture2D(source, qt_TexCoord0.st) * (texture2D(maskSource, qt_TexCoord0.st).a) * qt_Opacity; +} diff --git a/src/imports/controls/imagine/impl/shaders/OpacityMask_rhi.frag b/src/imports/controls/imagine/impl/shaders/OpacityMask_rhi.frag new file mode 100644 index 00000000..9ae32499 --- /dev/null +++ b/src/imports/controls/imagine/impl/shaders/OpacityMask_rhi.frag @@ -0,0 +1,17 @@ +#version 440 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; +} ubuf; + +layout(binding = 1) uniform sampler2D source; +layout(binding = 2) uniform sampler2D maskSource; + +void main() +{ + fragColor = texture(source, qt_TexCoord0.st) * (texture(maskSource, qt_TexCoord0.st).a) * ubuf.qt_Opacity; +} diff --git a/src/imports/controls/imagine/impl/shaders/compile.bat b/src/imports/controls/imagine/impl/shaders/compile.bat new file mode 100644 index 00000000..8f16d7ac --- /dev/null +++ b/src/imports/controls/imagine/impl/shaders/compile.bat @@ -0,0 +1,40 @@ +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Copyright (C) 2020 The Qt Company Ltd. +:: Contact: https://www.qt.io/licensing/ +:: +:: This file is part of the QtQuick module of the Qt Toolkit. +:: +:: $QT_BEGIN_LICENSE:LGPL$ +:: 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. +:: +:: GNU Lesser General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU Lesser +:: General Public License version 3 as published by the Free Software +:: Foundation and appearing in the file LICENSE.LGPL3 included in the +:: packaging of this file. Please review the following information to +:: ensure the GNU Lesser General Public License version 3 requirements +:: will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +:: +:: GNU General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU +:: General Public License version 2.0 or (at your option) the GNU General +:: Public license version 3 or any later version approved by the KDE Free +:: Qt Foundation. The licenses are as published by the Free Software +:: Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +:: included in the packaging of this file. Please review the following +:: information to ensure the GNU General Public License requirements will +:: be met: https://www.gnu.org/licenses/gpl-2.0.html and +:: https://www.gnu.org/licenses/gpl-3.0.html. +:: +:: $QT_END_LICENSE$ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o +qsb/OpacityMask.frag OpacityMask_rhi.frag |