Remove "All rights reserved" line from license headers.
[profile/ivi/qtdeclarative.git] / src / quick / scenegraph / qsgdefaultrectanglenode.cpp
1
2 /****************************************************************************
3 **
4 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 **
39 ** $QT_END_LICENSE$
40 **
41 ****************************************************************************/
42
43
44
45 #include "qsgdefaultrectanglenode_p.h"
46
47 #include <QtQuick/qsgvertexcolormaterial.h>
48 #include <QtQuick/qsgtexturematerial.h>
49
50 #include <QtQuick/private/qsgcontext_p.h>
51
52 #include <QtCore/qmath.h>
53 #include <QtCore/qvarlengtharray.h>
54
55 QT_BEGIN_NAMESPACE
56
57 QSGDefaultRectangleNode::QSGDefaultRectangleNode(QSGContext *context)
58     : m_border(0)
59     , m_radius(0)
60     , m_pen_width(0)
61     , m_aligned(true)
62     , m_gradient_is_opaque(true)
63     , m_dirty_geometry(false)
64     , m_default_geometry(QSGGeometry::defaultAttributes_Point2D(), 4)
65     , m_context(context)
66 {
67     setGeometry(&m_default_geometry);
68     setMaterial(&m_fill_material);
69     m_border_material.setColor(QColor(0, 0, 0));
70
71     m_material_type = TypeFlat;
72
73 #ifdef QML_RUNTIME_TESTING
74     description = QLatin1String("rectangle");
75 #endif
76 }
77
78 QSGDefaultRectangleNode::~QSGDefaultRectangleNode()
79 {
80     if (m_material_type == TypeVertexGradient)
81         delete material();
82     delete m_border;
83 }
84
85 QSGGeometryNode *QSGDefaultRectangleNode::border()
86 {
87     if (!m_border) {
88         m_border = new QSGGeometryNode;
89         m_border->setMaterial(&m_border_material);
90         QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
91         m_border->setGeometry(geometry);
92         m_border->setFlag(QSGNode::OwnsGeometry);
93     }
94     return m_border;
95 }
96
97 void QSGDefaultRectangleNode::setRect(const QRectF &rect)
98 {
99     if (rect == m_rect)
100         return;
101     m_rect = rect;
102     m_dirty_geometry = true;
103 }
104
105 void QSGDefaultRectangleNode::setColor(const QColor &color)
106 {
107     if (color == m_fill_material.color())
108         return;
109     m_fill_material.setColor(color);
110     if (m_gradient_stops.isEmpty()) {
111         Q_ASSERT(m_material_type == TypeFlat);
112         markDirty(DirtyMaterial);
113     }
114 }
115
116 void QSGDefaultRectangleNode::setPenColor(const QColor &color)
117 {
118     if (color == m_border_material.color())
119         return;
120     m_border_material.setColor(color);
121     if (m_border)
122         m_border->markDirty(DirtyMaterial);
123 }
124
125 void QSGDefaultRectangleNode::setPenWidth(qreal width)
126 {
127     if (width == m_pen_width)
128         return;
129     m_pen_width = width;
130     if (m_pen_width <= 0 && m_border && m_border->parent())
131         removeChildNode(m_border);
132     else if (m_pen_width > 0 && !border()->parent())
133         appendChildNode(m_border);
134     m_dirty_geometry = true;
135 }
136
137
138 void QSGDefaultRectangleNode::setGradientStops(const QGradientStops &stops)
139 {
140     if (stops.constData() == m_gradient_stops.constData())
141         return;
142
143     m_gradient_stops = stops;
144
145     m_gradient_is_opaque = true;
146     for (int i = 0; i < stops.size(); ++i)
147         m_gradient_is_opaque &= stops.at(i).second.alpha() == 0xff;
148
149     if (stops.isEmpty()) {
150         // No gradient specified, use flat color.
151         if (m_material_type != TypeFlat) {
152             delete material();
153
154             setMaterial(&m_fill_material);
155             m_material_type = TypeFlat;
156
157             setGeometry(&m_default_geometry);
158             setFlag(OwnsGeometry, false);
159         }
160     } else {
161         if (m_material_type == TypeFlat) {
162             QSGVertexColorMaterial *material = new QSGVertexColorMaterial;
163             setMaterial(material);
164             m_material_type = TypeVertexGradient;
165             QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0);
166             setGeometry(g);
167             setFlag(OwnsGeometry);
168         }
169         static_cast<QSGVertexColorMaterial *>(material())->setFlag(QSGMaterial::Blending, !m_gradient_is_opaque);
170     }
171
172     m_dirty_geometry = true;
173 }
174
175 void QSGDefaultRectangleNode::setRadius(qreal radius)
176 {
177     if (radius == m_radius)
178         return;
179     m_radius = radius;
180     m_dirty_geometry = true;
181 }
182
183 void QSGDefaultRectangleNode::setAligned(bool aligned)
184 {
185     if (aligned == m_aligned)
186         return;
187     m_aligned = aligned;
188     m_dirty_geometry = true;
189 }
190
191 void QSGDefaultRectangleNode::update()
192 {
193     if (m_dirty_geometry) {
194         updateGeometry();
195         m_dirty_geometry = false;
196     }
197 }
198
199 struct Color4ub
200 {
201     unsigned char r, g, b, a;
202 };
203
204 Color4ub operator *(Color4ub c, float t) { c.a *= t; c.r *= t; c.g *= t; c.b *= t; return c; }
205 Color4ub operator +(Color4ub a, Color4ub b) {  a.a += b.a; a.r += b.r; a.g += b.g; a.b += b.b; return a; }
206
207 static inline Color4ub colorToColor4ub(const QColor &c)
208 {
209     Color4ub color = { uchar(c.redF() * c.alphaF() * 255),
210                        uchar(c.greenF() * c.alphaF() * 255),
211                        uchar(c.blueF() * c.alphaF() * 255),
212                        uchar(c.alphaF() * 255)
213                      };
214     return color;
215 }
216
217 struct Vertex
218 {
219     QVector2D position;
220 };
221
222 struct ColorVertex
223 {
224     QVector2D position;
225     Color4ub color;
226 };
227
228 void QSGDefaultRectangleNode::updateGeometry()
229 {
230     qreal penWidth = m_aligned ? qreal(qRound(m_pen_width)) : m_pen_width;
231
232     // fast path for the simple case...
233     if ((penWidth == 0 || m_border_material.color().alpha() == 0)
234             && m_radius == 0
235             && m_material_type == TypeFlat) {
236         QSGGeometry::updateRectGeometry(&m_default_geometry, m_rect);
237         return;
238     }
239
240     QSGGeometry *fill = geometry();
241
242     // Check that the vertex type matches the material.
243     Q_ASSERT(m_material_type != TypeFlat || fill->sizeOfVertex() == sizeof(Vertex));
244     Q_ASSERT(m_material_type != TypeVertexGradient || fill->sizeOfVertex() == sizeof(ColorVertex));
245
246     QSGGeometry *borderGeometry = 0;
247     if (m_border) {
248         borderGeometry = m_border->geometry();
249         Q_ASSERT(borderGeometry->sizeOfVertex() == sizeof(Vertex));
250     }
251
252     int fillVertexCount = 0;
253
254     // Preallocate arrays for a rectangle with 18 segments per corner and 3 gradient stops.
255     uchar *fillVertices = 0;
256     Vertex *borderVertices = 0;
257
258     Color4ub fillColor = colorToColor4ub(m_fill_material.color());
259     const QGradientStops &stops = m_gradient_stops;
260
261     if (m_radius > 0) {
262         // Rounded corners.
263
264         // Radius should never exceeds half of the width or half of the height
265         qreal radius = qMin(qMin(m_rect.width() * qreal(0.5), m_rect.height() * qreal(0.5)), m_radius);
266         QRectF innerRect = m_rect;
267         innerRect.adjust(radius, radius, -radius, -radius);
268         if (m_aligned && (int(penWidth) & 1)) {
269             // Pen width is odd, so add the offset as documented.
270             innerRect.moveLeft(innerRect.left() + qreal(0.5));
271             innerRect.moveTop(innerRect.top() + qreal(0.5));
272         }
273
274         qreal innerRadius = radius - penWidth * qreal(0.5);
275         qreal outerRadius = radius + penWidth * qreal(0.5);
276
277         // Number of segments per corner, approximately one per 3 pixels.
278         int segments = qBound(3, qCeil(outerRadius * (M_PI / 6)), 18);
279
280         /*
281
282         --+-__
283           | segment
284           |       _+
285         --+-__  _-   \
286               -+  segment
287         --------+      \        <- gradient line
288                  +-----+
289                  |     |
290
291         */
292
293         int nextGradientStop = 0;
294         qreal gradientPos = (radius - innerRadius) / (innerRect.height() + 2 * radius);
295         while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
296             ++nextGradientStop;
297         int lastGradientStop = stops.size() - 1;
298         qreal lastGradientPos = (innerRect.height() + radius + innerRadius) / (innerRect.height() + 2 * radius);
299         while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
300             --lastGradientStop;
301
302         int borderVertexHead = 0;
303         int borderVertexTail = 0;
304         if (penWidth) {
305             // The reason I add extra vertices where the gradient lines intersect the border is
306             // to avoid pixel sized gaps between the fill and the border caused by floating point
307             // inaccuracies.
308             borderGeometry->allocate((segments + 1) * 2 * 4 + (lastGradientStop - nextGradientStop + 1) * 4 + 2);
309             borderVertexHead = borderVertexTail = (borderGeometry->vertexCount() >> 1) - 1;
310             borderVertices = (Vertex *)borderGeometry->vertexData();
311         }
312
313         fill->allocate((segments + 1) * 4 + (lastGradientStop - nextGradientStop + 1) * 2);
314         fillVertices = (uchar *)fill->vertexData();
315
316         qreal py = 0; // previous inner y-coordinate.
317         qreal plx = 0; // previous inner left x-coordinate.
318         qreal prx = 0; // previous inner right x-coordinate.
319
320         qreal angle = qreal(0.5) * M_PI / qreal(segments);
321         qreal cosStep = qFastCos(angle);
322         qreal sinStep = qFastSin(angle);
323
324         for (int part = 0; part < 2; ++part) {
325             qreal c = 1 - part;
326             qreal s = part;
327             for (int i = 0; i <= segments; ++i) {
328                 qreal y, lx, rx;
329                 if (innerRadius > 0) {
330                     y = (part ? innerRect.bottom() : innerRect.top()) - innerRadius * c; // current inner y-coordinate.
331                     lx = innerRect.left() - innerRadius * s; // current inner left x-coordinate.
332                     rx = innerRect.right() + innerRadius * s; // current inner right x-coordinate.
333                     gradientPos = ((part ? innerRect.height() : 0) + radius - innerRadius * c) / (innerRect.height() + 2 * radius);
334                 } else {
335                     y = (part ? innerRect.bottom() + innerRadius : innerRect.top() - innerRadius); // current inner y-coordinate.
336                     lx = innerRect.left() - innerRadius; // current inner left x-coordinate.
337                     rx = innerRect.right() + innerRadius; // current inner right x-coordinate.
338                     gradientPos = ((part ? innerRect.height() + innerRadius : -innerRadius) + radius) / (innerRect.height() + 2 * radius);
339                 }
340                 qreal Y = (part ? innerRect.bottom() : innerRect.top()) - outerRadius * c; // current outer y-coordinate.
341                 qreal lX = innerRect.left() - outerRadius * s; // current outer left x-coordinate.
342                 qreal rX = innerRect.right() + outerRadius * s; // current outer right x-coordinate.
343
344                 while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
345                     // Insert vertices at gradient stops.
346                     qreal gy = (innerRect.top() - radius) + stops.at(nextGradientStop).first * (innerRect.height() + 2 * radius);
347                     Q_ASSERT(fillVertexCount >= 2);
348                     qreal t = (gy - py) / (y - py);
349                     qreal glx = plx * (1 - t) + t * lx;
350                     qreal grx = prx * (1 - t) + t * rx;
351
352                     if (penWidth) {
353                         const Vertex &first = borderVertices[borderVertexHead];
354                         borderVertices[--borderVertexHead].position = QVector2D(glx, gy);
355                         borderVertices[--borderVertexHead] = first;
356
357                         const Vertex &last = borderVertices[borderVertexTail - 2];
358                         borderVertices[borderVertexTail++] = last;
359                         borderVertices[borderVertexTail++].position = QVector2D(grx, gy);
360                     }
361
362                     ColorVertex *vertices = (ColorVertex *)fillVertices;
363
364                     fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
365                     vertices[fillVertexCount].position = QVector2D(grx, gy);
366                     vertices[fillVertexCount].color = fillColor;
367                     ++fillVertexCount;
368                     vertices[fillVertexCount].position = QVector2D(glx, gy);
369                     vertices[fillVertexCount].color = fillColor;
370                     ++fillVertexCount;
371
372                     ++nextGradientStop;
373                 }
374
375                 if (penWidth) {
376                     borderVertices[--borderVertexHead].position = QVector2D(lx, y);
377                     borderVertices[--borderVertexHead].position = QVector2D(lX, Y);
378                     borderVertices[borderVertexTail++].position = QVector2D(rX, Y);
379                     borderVertices[borderVertexTail++].position = QVector2D(rx, y);
380                 }
381
382                 if (stops.isEmpty()) {
383                     Q_ASSERT(m_material_type == TypeFlat);
384                     Vertex *vertices = (Vertex *)fillVertices;
385                     vertices[fillVertexCount++].position = QVector2D(rx, y);
386                     vertices[fillVertexCount++].position = QVector2D(lx, y);
387                 } else {
388                     if (nextGradientStop == 0) {
389                         fillColor = colorToColor4ub(stops.at(0).second);
390                     } else if (nextGradientStop == stops.size()) {
391                         fillColor = colorToColor4ub(stops.last().second);
392                     } else {
393                         const QGradientStop &prev = stops.at(nextGradientStop - 1);
394                         const QGradientStop &next = stops.at(nextGradientStop);
395                         qreal t = (gradientPos - prev.first) / (next.first - prev.first);
396                         fillColor = (colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t);
397                     }
398
399                     ColorVertex *vertices = (ColorVertex *)fillVertices;
400                     vertices[fillVertexCount].position = QVector2D(rx, y);
401                     vertices[fillVertexCount].color = fillColor;
402                     ++fillVertexCount;
403                     vertices[fillVertexCount].position = QVector2D(lx, y);
404                     vertices[fillVertexCount].color = fillColor;
405                     ++fillVertexCount;
406                 }
407                 py = y;
408                 plx = lx;
409                 prx = rx;
410
411                 // Rotate
412                 qreal tmp = c;
413                 c = c * cosStep - s * sinStep;
414                 s = s * cosStep + tmp * sinStep;
415             }
416         }
417
418         if (penWidth) {
419             // Close border.
420             const Vertex &first = borderVertices[borderVertexHead];
421             const Vertex &second = borderVertices[borderVertexHead + 1];
422             borderVertices[borderVertexTail++] = first;
423             borderVertices[borderVertexTail++] = second;
424
425             Q_ASSERT(borderVertexHead == 0 && borderVertexTail == borderGeometry->vertexCount());
426         }
427         Q_ASSERT(fillVertexCount == fill->vertexCount());
428
429     } else {
430
431         // Straight corners.
432         QRectF innerRect = m_rect;
433         QRectF outerRect = m_rect;
434
435         qreal halfPenWidth = 0;
436         if (penWidth) {
437             if (m_aligned && (int(penWidth) & 1)) {
438                 // Pen width is odd, so add the offset as documented.
439                 innerRect.moveLeft(innerRect.left() + qreal(0.5));
440                 innerRect.moveTop(innerRect.top() + qreal(0.5));
441                 outerRect = innerRect;
442             }
443             halfPenWidth = penWidth * qreal(0.5);
444             innerRect.adjust(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth);
445             outerRect.adjust(-halfPenWidth, -halfPenWidth, halfPenWidth, halfPenWidth);
446         }
447
448         int nextGradientStop = 0;
449         qreal gradientPos = halfPenWidth / m_rect.height();
450         while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
451             ++nextGradientStop;
452         int lastGradientStop = stops.size() - 1;
453         qreal lastGradientPos = (m_rect.height() - halfPenWidth) / m_rect.height();
454         while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
455             --lastGradientStop;
456
457         int borderVertexCount = 0;
458         if (penWidth) {
459             borderGeometry->allocate((1 + lastGradientStop - nextGradientStop) * 4 + 10);
460             borderVertices = (Vertex *)borderGeometry->vertexData();
461         }
462         fill->allocate((3 + lastGradientStop - nextGradientStop) * 2);
463         fillVertices = (uchar *)fill->vertexData();
464
465         QVarLengthArray<qreal, 16> ys(3 + lastGradientStop - nextGradientStop);
466         int yCount = 0;
467
468         for (int part = 0; part < 2; ++part) {
469             qreal y = (part ? innerRect.bottom() : innerRect.top());
470             gradientPos = (y - innerRect.top() + halfPenWidth) / m_rect.height();
471
472             while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
473                 // Insert vertices at gradient stops.
474                 qreal gy = (innerRect.top() - halfPenWidth) + stops.at(nextGradientStop).first * m_rect.height();
475                 Q_ASSERT(fillVertexCount >= 2);
476
477                 ColorVertex *vertices = (ColorVertex *)fillVertices;
478
479                 fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
480                 vertices[fillVertexCount].position = QVector2D(innerRect.right(), gy);
481                 vertices[fillVertexCount].color = fillColor;
482                 ++fillVertexCount;
483                 vertices[fillVertexCount].position = QVector2D(innerRect.left(), gy);
484                 vertices[fillVertexCount].color = fillColor;
485                 ++fillVertexCount;
486
487                 ys[yCount++] = gy;
488
489                 ++nextGradientStop;
490             }
491
492             if (stops.isEmpty()) {
493                 Q_ASSERT(m_material_type == TypeFlat);
494                 Vertex *vertices = (Vertex *)fillVertices;
495                 vertices[fillVertexCount++].position = QVector2D(innerRect.right(), y);
496                 vertices[fillVertexCount++].position = QVector2D(innerRect.left(), y);
497             } else {
498                 if (nextGradientStop == 0) {
499                     fillColor = colorToColor4ub(stops.at(0).second);
500                 } else if (nextGradientStop == stops.size()) {
501                     fillColor = colorToColor4ub(stops.last().second);
502                 } else {
503                     const QGradientStop &prev = stops.at(nextGradientStop - 1);
504                     const QGradientStop &next = stops.at(nextGradientStop);
505                     qreal t = (gradientPos - prev.first) / (next.first - prev.first);
506                     fillColor = (colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t);
507                 }
508
509                 ColorVertex *vertices = (ColorVertex *)fillVertices;
510                 vertices[fillVertexCount].position = QVector2D(innerRect.right(), y);
511                 vertices[fillVertexCount].color = fillColor;
512                 ++fillVertexCount;
513                 vertices[fillVertexCount].position = QVector2D(innerRect.left(), y);
514                 vertices[fillVertexCount].color = fillColor;
515                 ++fillVertexCount;
516             }
517
518             ys[yCount++] = y;
519         }
520
521         if (penWidth) {
522             borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.top());
523             borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), ys[0]);
524             for (int i = 1; i < fillVertexCount / 2; ++i) {
525                 borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.bottom());
526                 borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), ys[i]);
527             }
528
529             borderVertices[borderVertexCount++].position = QVector2D(outerRect.left(), outerRect.bottom());
530             borderVertices[borderVertexCount++].position = QVector2D(innerRect.left(), ys[fillVertexCount / 2 - 1]);
531             for (int i = fillVertexCount / 2 - 2; i >= 0; --i) {
532                 borderVertices[borderVertexCount++].position = QVector2D(outerRect.left(), outerRect.top());
533                 borderVertices[borderVertexCount++].position = QVector2D(innerRect.left(), ys[i]);
534             }
535
536             borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.top());
537             borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), innerRect.top());
538
539             Q_ASSERT(borderVertexCount == borderGeometry->vertexCount());
540         }
541         Q_ASSERT(fillVertexCount == fill->vertexCount());
542     }
543
544     markDirty(DirtyGeometry);
545 }
546
547
548 QT_END_NAMESPACE