diff options
Diffstat (limited to 'src/quick/scenegraph/qsgdefaultrectanglenode.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgdefaultrectanglenode.cpp | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgdefaultrectanglenode.cpp b/src/quick/scenegraph/qsgdefaultrectanglenode.cpp new file mode 100644 index 0000000000..bb89b4a9f8 --- /dev/null +++ b/src/quick/scenegraph/qsgdefaultrectanglenode.cpp @@ -0,0 +1,548 @@ + +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation ([email protected]) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +#include "qsgdefaultrectanglenode_p.h" + +#include <QtQuick/qsgvertexcolormaterial.h> +#include <QtQuick/qsgtexturematerial.h> + +#include <QtQuick/private/qsgcontext_p.h> + +#include <QtCore/qmath.h> +#include <QtCore/qvarlengtharray.h> + +QT_BEGIN_NAMESPACE + +QSGDefaultRectangleNode::QSGDefaultRectangleNode(QSGContext *context) + : m_border(0) + , m_radius(0) + , m_pen_width(0) + , m_aligned(true) + , m_gradient_is_opaque(true) + , m_dirty_geometry(false) + , m_default_geometry(QSGGeometry::defaultAttributes_Point2D(), 4) + , m_context(context) +{ + setGeometry(&m_default_geometry); + setMaterial(&m_fill_material); + m_border_material.setColor(QColor(0, 0, 0)); + + m_material_type = TypeFlat; + +#ifdef QML_RUNTIME_TESTING + description = QLatin1String("rectangle"); +#endif +} + +QSGDefaultRectangleNode::~QSGDefaultRectangleNode() +{ + if (m_material_type == TypeVertexGradient) + delete material(); + delete m_border; +} + +QSGGeometryNode *QSGDefaultRectangleNode::border() +{ + if (!m_border) { + m_border = new QSGGeometryNode; + m_border->setMaterial(&m_border_material); + QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0); + m_border->setGeometry(geometry); + m_border->setFlag(QSGNode::OwnsGeometry); + } + return m_border; +} + +void QSGDefaultRectangleNode::setRect(const QRectF &rect) +{ + if (rect == m_rect) + return; + m_rect = rect; + m_dirty_geometry = true; +} + +void QSGDefaultRectangleNode::setColor(const QColor &color) +{ + if (color == m_fill_material.color()) + return; + m_fill_material.setColor(color); + if (m_gradient_stops.isEmpty()) { + Q_ASSERT(m_material_type == TypeFlat); + markDirty(DirtyMaterial); + } +} + +void QSGDefaultRectangleNode::setPenColor(const QColor &color) +{ + if (color == m_border_material.color()) + return; + m_border_material.setColor(color); + if (m_border) + m_border->markDirty(DirtyMaterial); +} + +void QSGDefaultRectangleNode::setPenWidth(qreal width) +{ + if (width == m_pen_width) + return; + m_pen_width = width; + if (m_pen_width <= 0 && m_border && m_border->parent()) + removeChildNode(m_border); + else if (m_pen_width > 0 && !border()->parent()) + appendChildNode(m_border); + m_dirty_geometry = true; +} + + +void QSGDefaultRectangleNode::setGradientStops(const QGradientStops &stops) +{ + if (stops.constData() == m_gradient_stops.constData()) + return; + + m_gradient_stops = stops; + + m_gradient_is_opaque = true; + for (int i = 0; i < stops.size(); ++i) + m_gradient_is_opaque &= stops.at(i).second.alpha() == 0xff; + + if (stops.isEmpty()) { + // No gradient specified, use flat color. + if (m_material_type != TypeFlat) { + delete material(); + + setMaterial(&m_fill_material); + m_material_type = TypeFlat; + + setGeometry(&m_default_geometry); + setFlag(OwnsGeometry, false); + } + } else { + if (m_material_type == TypeFlat) { + QSGVertexColorMaterial *material = new QSGVertexColorMaterial; + setMaterial(material); + m_material_type = TypeVertexGradient; + QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0); + setGeometry(g); + setFlag(OwnsGeometry); + } + static_cast<QSGVertexColorMaterial *>(material())->setFlag(QSGMaterial::Blending, !m_gradient_is_opaque); + } + + m_dirty_geometry = true; +} + +void QSGDefaultRectangleNode::setRadius(qreal radius) +{ + if (radius == m_radius) + return; + m_radius = radius; + m_dirty_geometry = true; +} + +void QSGDefaultRectangleNode::setAligned(bool aligned) +{ + if (aligned == m_aligned) + return; + m_aligned = aligned; + m_dirty_geometry = true; +} + +void QSGDefaultRectangleNode::update() +{ + if (m_dirty_geometry) { + updateGeometry(); + m_dirty_geometry = false; + } +} + +struct Color4ub +{ + unsigned char r, g, b, a; +}; + +Color4ub operator *(Color4ub c, float t) { c.a *= t; c.r *= t; c.g *= t; c.b *= t; return c; } +Color4ub operator +(Color4ub a, Color4ub b) { a.a += b.a; a.r += b.r; a.g += b.g; a.b += b.b; return a; } + +static inline Color4ub colorToColor4ub(const QColor &c) +{ + Color4ub color = { uchar(c.redF() * c.alphaF() * 255), + uchar(c.greenF() * c.alphaF() * 255), + uchar(c.blueF() * c.alphaF() * 255), + uchar(c.alphaF() * 255) + }; + return color; +} + +struct Vertex +{ + QVector2D position; +}; + +struct ColorVertex +{ + QVector2D position; + Color4ub color; +}; + +void QSGDefaultRectangleNode::updateGeometry() +{ + qreal penWidth = m_aligned ? qreal(qRound(m_pen_width)) : m_pen_width; + + // fast path for the simple case... + if ((penWidth == 0 || m_border_material.color().alpha() == 0) + && m_radius == 0 + && m_material_type == TypeFlat) { + QSGGeometry::updateRectGeometry(&m_default_geometry, m_rect); + return; + } + + QSGGeometry *fill = geometry(); + + // Check that the vertex type matches the material. + Q_ASSERT(m_material_type != TypeFlat || fill->sizeOfVertex() == sizeof(Vertex)); + Q_ASSERT(m_material_type != TypeVertexGradient || fill->sizeOfVertex() == sizeof(ColorVertex)); + + QSGGeometry *borderGeometry = 0; + if (m_border) { + borderGeometry = m_border->geometry(); + Q_ASSERT(borderGeometry->sizeOfVertex() == sizeof(Vertex)); + } + + int fillVertexCount = 0; + + // Preallocate arrays for a rectangle with 18 segments per corner and 3 gradient stops. + uchar *fillVertices = 0; + Vertex *borderVertices = 0; + + Color4ub fillColor = colorToColor4ub(m_fill_material.color()); + const QGradientStops &stops = m_gradient_stops; + + if (m_radius > 0) { + // Rounded corners. + + // Radius should never exceeds half of the width or half of the height + qreal radius = qMin(qMin(m_rect.width() * qreal(0.5), m_rect.height() * qreal(0.5)), m_radius); + QRectF innerRect = m_rect; + innerRect.adjust(radius, radius, -radius, -radius); + if (m_aligned && (int(penWidth) & 1)) { + // Pen width is odd, so add the offset as documented. + innerRect.moveLeft(innerRect.left() + qreal(0.5)); + innerRect.moveTop(innerRect.top() + qreal(0.5)); + } + + qreal innerRadius = radius - penWidth * qreal(0.5); + qreal outerRadius = radius + penWidth * qreal(0.5); + + // Number of segments per corner, approximately one per 3 pixels. + int segments = qBound(3, qCeil(outerRadius * (M_PI / 6)), 18); + + /* + + --+-__ + | segment + | _+ + --+-__ _- \ + -+ segment + --------+ \ <- gradient line + +-----+ + | | + + */ + + int nextGradientStop = 0; + qreal gradientPos = (radius - innerRadius) / (innerRect.height() + 2 * radius); + while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos) + ++nextGradientStop; + int lastGradientStop = stops.size() - 1; + qreal lastGradientPos = (innerRect.height() + radius + innerRadius) / (innerRect.height() + 2 * radius); + while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos) + --lastGradientStop; + + int borderVertexHead = 0; + int borderVertexTail = 0; + if (penWidth) { + // The reason I add extra vertices where the gradient lines intersect the border is + // to avoid pixel sized gaps between the fill and the border caused by floating point + // inaccuracies. + borderGeometry->allocate((segments + 1) * 2 * 4 + (lastGradientStop - nextGradientStop + 1) * 4 + 2); + borderVertexHead = borderVertexTail = (borderGeometry->vertexCount() >> 1) - 1; + borderVertices = (Vertex *)borderGeometry->vertexData(); + } + + fill->allocate((segments + 1) * 4 + (lastGradientStop - nextGradientStop + 1) * 2); + fillVertices = (uchar *)fill->vertexData(); + + qreal py = 0; // previous inner y-coordinate. + qreal plx = 0; // previous inner left x-coordinate. + qreal prx = 0; // previous inner right x-coordinate. + + qreal angle = qreal(0.5) * M_PI / qreal(segments); + qreal cosStep = qFastCos(angle); + qreal sinStep = qFastSin(angle); + + for (int part = 0; part < 2; ++part) { + qreal c = 1 - part; + qreal s = part; + for (int i = 0; i <= segments; ++i) { + qreal y, lx, rx; + if (innerRadius > 0) { + y = (part ? innerRect.bottom() : innerRect.top()) - innerRadius * c; // current inner y-coordinate. + lx = innerRect.left() - innerRadius * s; // current inner left x-coordinate. + rx = innerRect.right() + innerRadius * s; // current inner right x-coordinate. + gradientPos = ((part ? innerRect.height() : 0) + radius - innerRadius * c) / (innerRect.height() + 2 * radius); + } else { + y = (part ? innerRect.bottom() + innerRadius : innerRect.top() - innerRadius); // current inner y-coordinate. + lx = innerRect.left() - innerRadius; // current inner left x-coordinate. + rx = innerRect.right() + innerRadius; // current inner right x-coordinate. + gradientPos = ((part ? innerRect.height() + innerRadius : -innerRadius) + radius) / (innerRect.height() + 2 * radius); + } + qreal Y = (part ? innerRect.bottom() : innerRect.top()) - outerRadius * c; // current outer y-coordinate. + qreal lX = innerRect.left() - outerRadius * s; // current outer left x-coordinate. + qreal rX = innerRect.right() + outerRadius * s; // current outer right x-coordinate. + + while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) { + // Insert vertices at gradient stops. + qreal gy = (innerRect.top() - radius) + stops.at(nextGradientStop).first * (innerRect.height() + 2 * radius); + Q_ASSERT(fillVertexCount >= 2); + qreal t = (gy - py) / (y - py); + qreal glx = plx * (1 - t) + t * lx; + qreal grx = prx * (1 - t) + t * rx; + + if (penWidth) { + const Vertex &first = borderVertices[borderVertexHead]; + borderVertices[--borderVertexHead].position = QVector2D(glx, gy); + borderVertices[--borderVertexHead] = first; + + const Vertex &last = borderVertices[borderVertexTail - 2]; + borderVertices[borderVertexTail++] = last; + borderVertices[borderVertexTail++].position = QVector2D(grx, gy); + } + + ColorVertex *vertices = (ColorVertex *)fillVertices; + + fillColor = colorToColor4ub(stops.at(nextGradientStop).second); + vertices[fillVertexCount].position = QVector2D(grx, gy); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + vertices[fillVertexCount].position = QVector2D(glx, gy); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + + ++nextGradientStop; + } + + if (penWidth) { + borderVertices[--borderVertexHead].position = QVector2D(lx, y); + borderVertices[--borderVertexHead].position = QVector2D(lX, Y); + borderVertices[borderVertexTail++].position = QVector2D(rX, Y); + borderVertices[borderVertexTail++].position = QVector2D(rx, y); + } + + if (stops.isEmpty()) { + Q_ASSERT(m_material_type == TypeFlat); + Vertex *vertices = (Vertex *)fillVertices; + vertices[fillVertexCount++].position = QVector2D(rx, y); + vertices[fillVertexCount++].position = QVector2D(lx, y); + } else { + if (nextGradientStop == 0) { + fillColor = colorToColor4ub(stops.at(0).second); + } else if (nextGradientStop == stops.size()) { + fillColor = colorToColor4ub(stops.last().second); + } else { + const QGradientStop &prev = stops.at(nextGradientStop - 1); + const QGradientStop &next = stops.at(nextGradientStop); + qreal t = (gradientPos - prev.first) / (next.first - prev.first); + fillColor = (colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t); + } + + ColorVertex *vertices = (ColorVertex *)fillVertices; + vertices[fillVertexCount].position = QVector2D(rx, y); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + vertices[fillVertexCount].position = QVector2D(lx, y); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + } + py = y; + plx = lx; + prx = rx; + + // Rotate + qreal tmp = c; + c = c * cosStep - s * sinStep; + s = s * cosStep + tmp * sinStep; + } + } + + if (penWidth) { + // Close border. + const Vertex &first = borderVertices[borderVertexHead]; + const Vertex &second = borderVertices[borderVertexHead + 1]; + borderVertices[borderVertexTail++] = first; + borderVertices[borderVertexTail++] = second; + + Q_ASSERT(borderVertexHead == 0 && borderVertexTail == borderGeometry->vertexCount()); + } + Q_ASSERT(fillVertexCount == fill->vertexCount()); + + } else { + + // Straight corners. + QRectF innerRect = m_rect; + QRectF outerRect = m_rect; + + qreal halfPenWidth = 0; + if (penWidth) { + if (m_aligned && (int(penWidth) & 1)) { + // Pen width is odd, so add the offset as documented. + innerRect.moveLeft(innerRect.left() + qreal(0.5)); + innerRect.moveTop(innerRect.top() + qreal(0.5)); + outerRect = innerRect; + } + halfPenWidth = penWidth * qreal(0.5); + innerRect.adjust(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth); + outerRect.adjust(-halfPenWidth, -halfPenWidth, halfPenWidth, halfPenWidth); + } + + int nextGradientStop = 0; + qreal gradientPos = halfPenWidth / m_rect.height(); + while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos) + ++nextGradientStop; + int lastGradientStop = stops.size() - 1; + qreal lastGradientPos = (m_rect.height() - halfPenWidth) / m_rect.height(); + while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos) + --lastGradientStop; + + int borderVertexCount = 0; + if (penWidth) { + borderGeometry->allocate((1 + lastGradientStop - nextGradientStop) * 4 + 10); + borderVertices = (Vertex *)borderGeometry->vertexData(); + } + fill->allocate((3 + lastGradientStop - nextGradientStop) * 2); + fillVertices = (uchar *)fill->vertexData(); + + QVarLengthArray<qreal, 16> ys(3 + lastGradientStop - nextGradientStop); + int yCount = 0; + + for (int part = 0; part < 2; ++part) { + qreal y = (part ? innerRect.bottom() : innerRect.top()); + gradientPos = (y - innerRect.top() + halfPenWidth) / m_rect.height(); + + while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) { + // Insert vertices at gradient stops. + qreal gy = (innerRect.top() - halfPenWidth) + stops.at(nextGradientStop).first * m_rect.height(); + Q_ASSERT(fillVertexCount >= 2); + + ColorVertex *vertices = (ColorVertex *)fillVertices; + + fillColor = colorToColor4ub(stops.at(nextGradientStop).second); + vertices[fillVertexCount].position = QVector2D(innerRect.right(), gy); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + vertices[fillVertexCount].position = QVector2D(innerRect.left(), gy); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + + ys[yCount++] = gy; + + ++nextGradientStop; + } + + if (stops.isEmpty()) { + Q_ASSERT(m_material_type == TypeFlat); + Vertex *vertices = (Vertex *)fillVertices; + vertices[fillVertexCount++].position = QVector2D(innerRect.right(), y); + vertices[fillVertexCount++].position = QVector2D(innerRect.left(), y); + } else { + if (nextGradientStop == 0) { + fillColor = colorToColor4ub(stops.at(0).second); + } else if (nextGradientStop == stops.size()) { + fillColor = colorToColor4ub(stops.last().second); + } else { + const QGradientStop &prev = stops.at(nextGradientStop - 1); + const QGradientStop &next = stops.at(nextGradientStop); + qreal t = (gradientPos - prev.first) / (next.first - prev.first); + fillColor = (colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t); + } + + ColorVertex *vertices = (ColorVertex *)fillVertices; + vertices[fillVertexCount].position = QVector2D(innerRect.right(), y); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + vertices[fillVertexCount].position = QVector2D(innerRect.left(), y); + vertices[fillVertexCount].color = fillColor; + ++fillVertexCount; + } + + ys[yCount++] = y; + } + + if (penWidth) { + borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.top()); + borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), ys[0]); + for (int i = 1; i < fillVertexCount / 2; ++i) { + borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.bottom()); + borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), ys[i]); + } + + borderVertices[borderVertexCount++].position = QVector2D(outerRect.left(), outerRect.bottom()); + borderVertices[borderVertexCount++].position = QVector2D(innerRect.left(), ys[fillVertexCount / 2 - 1]); + for (int i = fillVertexCount / 2 - 2; i >= 0; --i) { + borderVertices[borderVertexCount++].position = QVector2D(outerRect.left(), outerRect.top()); + borderVertices[borderVertexCount++].position = QVector2D(innerRect.left(), ys[i]); + } + + borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.top()); + borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), innerRect.top()); + + Q_ASSERT(borderVertexCount == borderGeometry->vertexCount()); + } + Q_ASSERT(fillVertexCount == fill->vertexCount()); + } + + markDirty(DirtyGeometry); +} + + +QT_END_NAMESPACE |