1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "private/qdeclarativerectangle_p.h"
43 #include "private/qdeclarativerectangle_p_p.h"
44
45 #include <QPainter>
46 #include <QStringBuilder>
47 #include <QtCore/qmath.h>
48
49 QT_BEGIN_NAMESPACE
50
51 /*!
52 \internal
53 \class QDeclarativePen
54 \brief The QDeclarativePen class provides a pen used for drawing rectangle borders on a QDeclarativeView.
55
56 By default, the pen is invalid and nothing is drawn. You must either set a color (then the default
57 width is 1) or a width (then the default color is black).
58
59 A width of 1 indicates is a single-pixel line on the border of the item being painted.
60
61 Example:
62 \qml
63 Rectangle {
64 border.width: 2
65 border.color: "red"
66 }
67 \endqml
68 */
69
setColor(const QColor & c)70 void QDeclarativePen::setColor(const QColor &c)
71 {
72 _color = c;
73 _valid = (_color.alpha() && _width >= 1) ? true : false;
74 emit penChanged();
75 }
76
setWidth(int w)77 void QDeclarativePen::setWidth(int w)
78 {
79 if (_width == w && _valid)
80 return;
81
82 _width = w;
83 _valid = (_color.alpha() && _width >= 1) ? true : false;
84 emit penChanged();
85 }
86
87
88 /*!
89 \qmlclass GradientStop QDeclarativeGradientStop
90 \ingroup qml-basic-visual-elements
91 \since 4.7
92 \brief The GradientStop item defines the color at a position in a Gradient.
93
94 \sa Gradient
95 */
96
97 /*!
98 \qmlproperty real GradientStop::position
99 \qmlproperty color GradientStop::color
100
101 The position and color properties describe the color used at a given
102 position in a gradient, as represented by a gradient stop.
103
104 The default position is 0.0; the default color is black.
105
106 \sa Gradient
107 */
108
updateGradient()109 void QDeclarativeGradientStop::updateGradient()
110 {
111 if (QDeclarativeGradient *grad = qobject_cast<QDeclarativeGradient*>(parent()))
112 grad->doUpdate();
113 }
114
115 /*!
116 \qmlclass Gradient QDeclarativeGradient
117 \ingroup qml-basic-visual-elements
118 \since 4.7
119 \brief The Gradient item defines a gradient fill.
120
121 A gradient is defined by two or more colors, which will be blended seamlessly.
122
123 The colors are specified as a set of GradientStop child items, each of
124 which defines a position on the gradient from 0.0 to 1.0 and a color.
125 The position of each GradientStop is defined by setting its
126 \l{GradientStop::}{position} property; its color is defined using its
127 \l{GradientStop::}{color} property.
128
129 A gradient without any gradient stops is rendered as a solid white fill.
130
131 Note that this item is not a visual representation of a gradient. To display a
132 gradient, use a visual element (like \l Rectangle) which supports the use
133 of gradients.
134
135 \section1 Example Usage
136
137 \div {class="float-right"}
138 \inlineimage qml-gradient.png
139 \enddiv
140
141 The following example declares a \l Rectangle item with a gradient starting
142 with red, blending to yellow at one third of the height of the rectangle,
143 and ending with green:
144
145 \snippet doc/src/snippets/declarative/gradient.qml code
146
147 \clearfloat
148 \section1 Performance and Limitations
149
150 Calculating gradients can be computationally expensive compared to the use
151 of solid color fills or images. Consider using gradients for static items
152 in a user interface.
153
154 In Qt 4.7, only vertical, linear gradients can be applied to items. If you
155 need to apply different orientations of gradients, a combination of rotation
156 and clipping will need to be applied to the relevant items. This can
157 introduce additional performance requirements for your application.
158
159 The use of animations involving gradient stops may not give the desired
160 result. An alternative way to animate gradients is to use pre-generated
161 images or SVG drawings containing gradients.
162
163 \sa GradientStop
164 */
165
166 /*!
167 \qmlproperty list<GradientStop> Gradient::stops
168 This property holds the gradient stops describing the gradient.
169
170 By default, this property contains an empty list.
171
172 To set the gradient stops, define them as children of the Gradient element.
173 */
174
gradient() const175 const QGradient *QDeclarativeGradient::gradient() const
176 {
177 if (!m_gradient && !m_stops.isEmpty()) {
178 m_gradient = new QLinearGradient(0,0,0,1.0);
179 for (int i = 0; i < m_stops.count(); ++i) {
180 const QDeclarativeGradientStop *stop = m_stops.at(i);
181 m_gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
182 m_gradient->setColorAt(stop->position(), stop->color());
183 }
184 }
185
186 return m_gradient;
187 }
188
doUpdate()189 void QDeclarativeGradient::doUpdate()
190 {
191 delete m_gradient;
192 m_gradient = 0;
193 emit updated();
194 }
195
196
197 /*!
198 \qmlclass Rectangle QDeclarativeRectangle
199 \ingroup qml-basic-visual-elements
200 \since 4.7
201 \brief The Rectangle item provides a filled rectangle with an optional border.
202 \inherits Item
203
204 Rectangle items are used to fill areas with solid color or gradients, and are
205 often used to hold other items.
206
207 \section1 Appearance
208
209 Each Rectangle item is painted using either a solid fill color, specified using
210 the \l color property, or a gradient, defined using a Gradient element and set
211 using the \l gradient property. If both a color and a gradient are specified,
212 the gradient is used.
213
214 You can add an optional border to a rectangle with its own color and thickness
215 by settting the \l border.color and \l border.width properties.
216
217 You can also create rounded rectangles using the \l radius property. Since this
218 introduces curved edges to the corners of a rectangle, it may be appropriate to
219 set the \l smooth property to improve its appearance.
220
221 \section1 Example Usage
222
223 \div {class="float-right"}
224 \inlineimage declarative-rect.png
225 \enddiv
226
227 The following example shows the effects of some of the common properties on a
228 Rectangle item, which in this case is used to create a square:
229
230 \snippet doc/src/snippets/declarative/rectangle/rectangle.qml document
231
232 \clearfloat
233 \section1 Performance
234
235 Using the \l smooth property improves the appearance of a rounded rectangle at
236 the cost of rendering performance. You should consider unsetting this property
237 for rectangles in motion, and only set it when they are stationary.
238
239 \sa Image
240 */
241
242 int QDeclarativeRectanglePrivate::doUpdateSlotIdx = -1;
243
QDeclarativeRectangle(QDeclarativeItem * parent)244 QDeclarativeRectangle::QDeclarativeRectangle(QDeclarativeItem *parent)
245 : QDeclarativeItem(*(new QDeclarativeRectanglePrivate), parent)
246 {
247 }
248
doUpdate()249 void QDeclarativeRectangle::doUpdate()
250 {
251 Q_D(QDeclarativeRectangle);
252 d->rectImage = QPixmap();
253 const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
254 d->setPaintMargin((pw+1)/2);
255 update();
256 }
257
258 /*!
259 \qmlproperty int Rectangle::border.width
260 \qmlproperty color Rectangle::border.color
261
262 The width and color used to draw the border of the rectangle.
263
264 A width of 1 creates a thin line. For no line, use a width of 0 or a transparent color.
265
266 \note The width of the rectangle's border does not affect the geometry of the
267 rectangle itself or its position relative to other items if anchors are used.
268
269 If \c border.width is an odd number, the rectangle is painted at a half-pixel offset to retain
270 border smoothness. Also, the border is rendered evenly on either side of the
271 rectangle's boundaries, and the spare pixel is rendered to the right and below the
272 rectangle (as documented for QRect rendering). This can cause unintended effects if
273 \c border.width is 1 and the rectangle is \l{Item::clip}{clipped} by a parent item:
274
275 \div {class="float-right"}
276 \inlineimage rect-border-width.png
277 \enddiv
278
279 \snippet doc/src/snippets/declarative/rectangle/rect-border-width.qml 0
280
281 \clearfloat
282 Here, the innermost rectangle's border is clipped on the bottom and right edges by its
283 parent. To avoid this, the border width can be set to two instead of one.
284 */
border()285 QDeclarativePen *QDeclarativeRectangle::border()
286 {
287 Q_D(QDeclarativeRectangle);
288 return d->getPen();
289 }
290
291 /*!
292 \qmlproperty Gradient Rectangle::gradient
293
294 The gradient to use to fill the rectangle.
295
296 This property allows for the construction of simple vertical gradients.
297 Other gradients may by formed by adding rotation to the rectangle.
298
299 \div {class="float-left"}
300 \inlineimage declarative-rect_gradient.png
301 \enddiv
302
303 \snippet doc/src/snippets/declarative/rectangle/rectangle-gradient.qml rectangles
304 \clearfloat
305
306 If both a gradient and a color are specified, the gradient will be used.
307
308 \sa Gradient, color
309 */
gradient() const310 QDeclarativeGradient *QDeclarativeRectangle::gradient() const
311 {
312 Q_D(const QDeclarativeRectangle);
313 return d->gradient;
314 }
315
setGradient(QDeclarativeGradient * gradient)316 void QDeclarativeRectangle::setGradient(QDeclarativeGradient *gradient)
317 {
318 Q_D(QDeclarativeRectangle);
319 if (d->gradient == gradient)
320 return;
321 static int updatedSignalIdx = -1;
322 if (updatedSignalIdx < 0)
323 updatedSignalIdx = QDeclarativeGradient::staticMetaObject.indexOfSignal("updated()");
324 if (d->doUpdateSlotIdx < 0)
325 d->doUpdateSlotIdx = QDeclarativeRectangle::staticMetaObject.indexOfSlot("doUpdate()");
326 if (d->gradient)
327 QMetaObject::disconnect(d->gradient, updatedSignalIdx, this, d->doUpdateSlotIdx);
328 d->gradient = gradient;
329 if (d->gradient)
330 QMetaObject::connect(d->gradient, updatedSignalIdx, this, d->doUpdateSlotIdx);
331 update();
332 }
333
334
335 /*!
336 \qmlproperty real Rectangle::radius
337 This property holds the corner radius used to draw a rounded rectangle.
338
339 If radius is non-zero, the rectangle will be painted as a rounded rectangle, otherwise it will be
340 painted as a normal rectangle. The same radius is used by all 4 corners; there is currently
341 no way to specify different radii for different corners.
342 */
radius() const343 qreal QDeclarativeRectangle::radius() const
344 {
345 Q_D(const QDeclarativeRectangle);
346 return d->radius;
347 }
348
setRadius(qreal radius)349 void QDeclarativeRectangle::setRadius(qreal radius)
350 {
351 Q_D(QDeclarativeRectangle);
352 if (d->radius == radius)
353 return;
354
355 d->radius = radius;
356 d->rectImage = QPixmap();
357 update();
358 emit radiusChanged();
359 }
360
361 /*!
362 \qmlproperty color Rectangle::color
363 This property holds the color used to fill the rectangle.
364
365 The default color is white.
366
367 \div {class="float-right"}
368 \inlineimage rect-color.png
369 \enddiv
370
371 The following example shows rectangles with colors specified
372 using hexadecimal and named color notation:
373
374 \snippet doc/src/snippets/declarative/rectangle/rectangle-colors.qml rectangles
375
376 \clearfloat
377 If both a gradient and a color are specified, the gradient will be used.
378
379 \sa gradient
380 */
color() const381 QColor QDeclarativeRectangle::color() const
382 {
383 Q_D(const QDeclarativeRectangle);
384 return d->color;
385 }
386
setColor(const QColor & c)387 void QDeclarativeRectangle::setColor(const QColor &c)
388 {
389 Q_D(QDeclarativeRectangle);
390 if (d->color == c)
391 return;
392
393 d->color = c;
394 d->rectImage = QPixmap();
395 update();
396 emit colorChanged();
397 }
398
generateRoundedRect()399 void QDeclarativeRectangle::generateRoundedRect()
400 {
401 Q_D(QDeclarativeRectangle);
402 if (d->rectImage.isNull()) {
403 const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
404 const int radius = qCeil(d->radius); //ensure odd numbered width/height so we get 1-pixel center
405
406 QString key = QLatin1String("q_") % QString::number(pw) % d->color.name() % QString::number(d->color.alpha(), 16) % QLatin1Char('_') % QString::number(radius);
407 if (d->pen && d->pen->isValid())
408 key += d->pen->color().name() % QString::number(d->pen->color().alpha(), 16);
409
410 if (!QPixmapCache::find(key, &d->rectImage)) {
411 d->rectImage = QPixmap(radius*2 + 3 + pw*2, radius*2 + 3 + pw*2);
412 d->rectImage.fill(Qt::transparent);
413 QPainter p(&(d->rectImage));
414 p.setRenderHint(QPainter::Antialiasing);
415 if (d->pen && d->pen->isValid()) {
416 QPen pn(QColor(d->pen->color()), d->pen->width());
417 p.setPen(pn);
418 } else {
419 p.setPen(Qt::NoPen);
420 }
421 p.setBrush(d->color);
422 if (pw%2)
423 p.drawRoundedRect(QRectF(qreal(pw)/2+1, qreal(pw)/2+1, d->rectImage.width()-(pw+1), d->rectImage.height()-(pw+1)), d->radius, d->radius);
424 else
425 p.drawRoundedRect(QRectF(qreal(pw)/2, qreal(pw)/2, d->rectImage.width()-pw, d->rectImage.height()-pw), d->radius, d->radius);
426
427 // end painting before inserting pixmap
428 // to pixmap cache to avoid a deep copy
429 p.end();
430 QPixmapCache::insert(key, d->rectImage);
431 }
432 }
433 }
434
generateBorderedRect()435 void QDeclarativeRectangle::generateBorderedRect()
436 {
437 Q_D(QDeclarativeRectangle);
438 if (d->rectImage.isNull()) {
439 const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
440
441 QString key = QLatin1String("q_") % QString::number(pw) % d->color.name() % QString::number(d->color.alpha(), 16);
442 if (d->pen && d->pen->isValid())
443 key += d->pen->color().name() % QString::number(d->pen->color().alpha(), 16);
444
445 if (!QPixmapCache::find(key, &d->rectImage)) {
446 // Adding 5 here makes qDrawBorderPixmap() paint correctly with smooth: true
447 // See QTBUG-7999 and QTBUG-10765 for more details.
448 d->rectImage = QPixmap(pw*2 + 5, pw*2 + 5);
449 d->rectImage.fill(Qt::transparent);
450 QPainter p(&(d->rectImage));
451 p.setRenderHint(QPainter::Antialiasing);
452 if (d->pen && d->pen->isValid()) {
453 QPen pn(QColor(d->pen->color()), d->pen->width());
454 pn.setJoinStyle(Qt::MiterJoin);
455 p.setPen(pn);
456 } else {
457 p.setPen(Qt::NoPen);
458 }
459 p.setBrush(d->color);
460 if (pw%2)
461 p.drawRect(QRectF(qreal(pw)/2+1, qreal(pw)/2+1, d->rectImage.width()-(pw+1), d->rectImage.height()-(pw+1)));
462 else
463 p.drawRect(QRectF(qreal(pw)/2, qreal(pw)/2, d->rectImage.width()-pw, d->rectImage.height()-pw));
464
465 // end painting before inserting pixmap
466 // to pixmap cache to avoid a deep copy
467 p.end();
468 QPixmapCache::insert(key, d->rectImage);
469 }
470 }
471 }
472
paint(QPainter * p,const QStyleOptionGraphicsItem *,QWidget *)473 void QDeclarativeRectangle::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
474 {
475 Q_D(QDeclarativeRectangle);
476 if (width() <= 0 || height() <= 0)
477 return;
478 if (d->radius > 0 || (d->pen && d->pen->isValid())
479 || (d->gradient && d->gradient->gradient()) ) {
480 drawRect(*p);
481 }
482 else {
483 bool oldAA = p->testRenderHint(QPainter::Antialiasing);
484 if (d->smooth)
485 p->setRenderHints(QPainter::Antialiasing, true);
486 p->fillRect(QRectF(0, 0, width(), height()), d->color);
487 if (d->smooth)
488 p->setRenderHint(QPainter::Antialiasing, oldAA);
489 }
490 }
491
drawRect(QPainter & p)492 void QDeclarativeRectangle::drawRect(QPainter &p)
493 {
494 Q_D(QDeclarativeRectangle);
495 if ((d->gradient && d->gradient->gradient())
496 || d->radius > width()/2 || d->radius > height()/2
497 || width() < 3 || height() < 3) {
498 // XXX This path is still slower than the image path
499 // Image path won't work for gradients or invalid radius though
500 bool oldAA = p.testRenderHint(QPainter::Antialiasing);
501 if (d->smooth)
502 p.setRenderHint(QPainter::Antialiasing);
503 if (d->pen && d->pen->isValid()) {
504 QPen pn(QColor(d->pen->color()), d->pen->width());
505 pn.setJoinStyle(Qt::MiterJoin);
506 p.setPen(pn);
507 } else {
508 p.setPen(Qt::NoPen);
509 }
510 if (d->gradient && d->gradient->gradient())
511 p.setBrush(*d->gradient->gradient());
512 else
513 p.setBrush(d->color);
514 const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0;
515 QRectF rect;
516 if (pw%2)
517 rect = QRectF(0.5, 0.5, width()-1, height()-1);
518 else
519 rect = QRectF(0, 0, width(), height());
520 qreal radius = d->radius;
521 if (radius > width()/2 || radius > height()/2)
522 radius = qMin(width()/2, height()/2);
523 if (radius > 0.)
524 p.drawRoundedRect(rect, radius, radius);
525 else
526 p.drawRect(rect);
527 if (d->smooth)
528 p.setRenderHint(QPainter::Antialiasing, oldAA);
529 } else {
530 bool oldAA = p.testRenderHint(QPainter::Antialiasing);
531 bool oldSmooth = p.testRenderHint(QPainter::SmoothPixmapTransform);
532 if (d->smooth)
533 p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth);
534
535 const int pw = d->pen && d->pen->isValid() ? (d->pen->width()+1)/2*2 : 0;
536
537 if (d->radius > 0)
538 generateRoundedRect();
539 else
540 generateBorderedRect();
541
542 int xOffset = (d->rectImage.width()-1)/2;
543 int yOffset = (d->rectImage.height()-1)/2;
544 Q_ASSERT(d->rectImage.width() == 2*xOffset + 1);
545 Q_ASSERT(d->rectImage.height() == 2*yOffset + 1);
546
547 // check whether we've eliminated the center completely
548 if (2*xOffset > width()+pw)
549 xOffset = (width()+pw)/2;
550 if (2*yOffset > height()+pw)
551 yOffset = (height()+pw)/2;
552
553 QMargins margins(xOffset, yOffset, xOffset, yOffset);
554 QTileRules rules(Qt::StretchTile, Qt::StretchTile);
555 //NOTE: even though our item may have qreal-based width and height, qDrawBorderPixmap only supports QRects
556 qDrawBorderPixmap(&p, QRect(-pw/2, -pw/2, width()+pw, height()+pw), margins, d->rectImage, d->rectImage.rect(), margins, rules);
557
558 if (d->smooth) {
559 p.setRenderHint(QPainter::Antialiasing, oldAA);
560 p.setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth);
561 }
562 }
563 }
564
565 /*!
566 \qmlproperty bool Rectangle::smooth
567
568 Set this property if you want the item to be smoothly scaled or
569 transformed. Smooth filtering gives better visual quality, but is slower. If
570 the item is displayed at its natural size, this property has no visual or
571 performance effect.
572
573 \note Generally scaling artifacts are only visible if the item is stationary on
574 the screen. A common pattern when animating an item is to disable smooth
575 filtering at the beginning of the animation and reenable it at the conclusion.
576
577 \image rect-smooth.png
578 On this image, smooth is turned off on the top half and on on the bottom half.
579 */
580
boundingRect() const581 QRectF QDeclarativeRectangle::boundingRect() const
582 {
583 Q_D(const QDeclarativeRectangle);
584 return QRectF(-d->paintmargin, -d->paintmargin, d->width()+d->paintmargin*2, d->height()+d->paintmargin*2);
585 }
586
587 QT_END_NAMESPACE
588