1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickrectangle_p.h"
5#include "qquickrectangle_p_p.h"
6
7#include <QtQml/qqmlinfo.h>
8
9#include <QtQuick/private/qsgcontext_p.h>
10#include <private/qsgadaptationlayer_p.h>
11
12#include <private/qqmlmetatype_p.h>
13
14#include <QtGui/qpixmapcache.h>
15#include <QtCore/qmath.h>
16#include <QtCore/qmetaobject.h>
17
18QT_BEGIN_NAMESPACE
19
20// XXX todo - should we change rectangle to draw entirely within its width/height?
21/*!
22 \internal
23 \class QQuickPen
24 \brief For specifying a pen used for drawing rectangle borders on a QQuickView
25
26 By default, the pen is invalid and nothing is drawn. You must either set a color (then the default
27 width is 1) or a width (then the default color is black).
28
29 A width of 1 indicates is a single-pixel line on the border of the item being painted.
30
31 Example:
32 \qml
33 Rectangle {
34 border.width: 2
35 border.color: "red"
36 }
37 \endqml
38*/
39
40QQuickPen::QQuickPen(QObject *parent)
41 : QObject(parent)
42 , m_width(1)
43 , m_color(Qt::black)
44 , m_aligned(true)
45 , m_valid(false)
46{
47}
48
49qreal QQuickPen::width() const
50{
51 return m_width;
52}
53
54void QQuickPen::setWidth(qreal w)
55{
56 if (m_width == w && m_valid)
57 return;
58
59 m_width = w;
60 m_valid = m_color.alpha() && (qRound(d: m_width) >= 1 || (!m_aligned && m_width > 0));
61 static_cast<QQuickItem*>(parent())->update();
62 emit widthChanged();
63}
64
65QColor QQuickPen::color() const
66{
67 return m_color;
68}
69
70void QQuickPen::setColor(const QColor &c)
71{
72 m_color = c;
73 m_valid = m_color.alpha() && (qRound(d: m_width) >= 1 || (!m_aligned && m_width > 0));
74 static_cast<QQuickItem*>(parent())->update();
75 emit colorChanged();
76}
77
78bool QQuickPen::pixelAligned() const
79{
80 return m_aligned;
81}
82
83void QQuickPen::setPixelAligned(bool aligned)
84{
85 if (aligned == m_aligned)
86 return;
87 m_aligned = aligned;
88 m_valid = m_color.alpha() && (qRound(d: m_width) >= 1 || (!m_aligned && m_width > 0));
89 static_cast<QQuickItem*>(parent())->update();
90 emit pixelAlignedChanged();
91}
92
93bool QQuickPen::isValid() const
94{
95 return m_valid;
96}
97
98/*!
99 \qmltype GradientStop
100 \instantiates QQuickGradientStop
101 \inqmlmodule QtQuick
102 \ingroup qtquick-visual-utility
103 \brief Defines the color at a position in a Gradient.
104
105 \sa Gradient
106*/
107
108/*!
109 \qmlproperty real QtQuick::GradientStop::position
110 \qmlproperty color QtQuick::GradientStop::color
111
112 The position and color properties describe the color used at a given
113 position in a gradient, as represented by a gradient stop.
114
115 The default position is 0.0; the default color is black.
116
117 \sa Gradient
118*/
119QQuickGradientStop::QQuickGradientStop(QObject *parent)
120 : QObject(parent)
121{
122}
123
124qreal QQuickGradientStop::position() const
125{
126 return m_position;
127}
128
129void QQuickGradientStop::setPosition(qreal position)
130{
131 m_position = position; updateGradient();
132}
133
134QColor QQuickGradientStop::color() const
135{
136 return m_color;
137}
138
139void QQuickGradientStop::setColor(const QColor &color)
140{
141 m_color = color; updateGradient();
142}
143
144void QQuickGradientStop::updateGradient()
145{
146 if (QQuickGradient *grad = qobject_cast<QQuickGradient*>(object: parent()))
147 grad->doUpdate();
148}
149
150/*!
151 \qmltype Gradient
152 \instantiates QQuickGradient
153 \inqmlmodule QtQuick
154 \ingroup qtquick-visual-utility
155 \brief Defines a gradient fill.
156
157 A gradient is defined by two or more colors, which will be blended seamlessly.
158
159 The colors are specified as a set of GradientStop child items, each of
160 which defines a position on the gradient from 0.0 to 1.0 and a color.
161 The position of each GradientStop is defined by setting its
162 \l{GradientStop::}{position} property; its color is defined using its
163 \l{GradientStop::}{color} property.
164
165 A gradient without any gradient stops is rendered as a solid white fill.
166
167 Note that this item is not a visual representation of a gradient. To display a
168 gradient, use a visual item (like \l Rectangle) which supports the use
169 of gradients.
170
171 \section1 Example Usage
172
173 \div {class="float-right"}
174 \inlineimage qml-gradient.png
175 \enddiv
176
177 The following example declares a \l Rectangle item with a gradient starting
178 with red, blending to yellow at one third of the height of the rectangle,
179 and ending with green:
180
181 \snippet qml/gradient.qml code
182
183 \clearfloat
184 \section1 Performance and Limitations
185
186 Calculating gradients can be computationally expensive compared to the use
187 of solid color fills or images. Consider using gradients for static items
188 in a user interface.
189
190 Since Qt 5.12, vertical and horizontal linear gradients can be applied to items.
191 If you need to apply angled gradients, a combination of rotation and clipping
192 can be applied to the relevant items. Alternatively, consider using
193 QtQuick.Shapes::LinearGradient or QtGraphicalEffects::LinearGradient. These
194 approaches can all introduce additional performance requirements for your application.
195
196 The use of animations involving gradient stops may not give the desired
197 result. An alternative way to animate gradients is to use pre-generated
198 images or SVG drawings containing gradients.
199
200 \sa GradientStop
201*/
202
203/*!
204 \qmlproperty list<GradientStop> QtQuick::Gradient::stops
205 \qmldefault
206
207 This property holds the gradient stops describing the gradient.
208
209 By default, this property contains an empty list.
210
211 To set the gradient stops, define them as children of the Gradient.
212*/
213QQuickGradient::QQuickGradient(QObject *parent)
214: QObject(parent)
215{
216}
217
218QQuickGradient::~QQuickGradient()
219{
220}
221
222QQmlListProperty<QQuickGradientStop> QQuickGradient::stops()
223{
224 return QQmlListProperty<QQuickGradientStop>(this, &m_stops);
225}
226
227/*!
228 \qmlproperty enumeration QtQuick::Gradient::orientation
229 \since 5.12
230
231 Set this property to define the direction of the gradient.
232
233 \value Gradient.Vertical a vertical gradient
234 \value Gradient.Horizontal a horizontal gradient
235
236 The default is Gradient.Vertical.
237*/
238void QQuickGradient::setOrientation(Orientation orientation)
239{
240 if (m_orientation == orientation)
241 return;
242
243 m_orientation = orientation;
244 emit orientationChanged();
245 emit updated();
246}
247
248QGradientStops QQuickGradient::gradientStops() const
249{
250 QGradientStops stops;
251 for (int i = 0; i < m_stops.size(); ++i){
252 int j = 0;
253 while (j < stops.size() && stops.at(i: j).first < m_stops[i]->position())
254 j++;
255 stops.insert(i: j, t: QGradientStop(m_stops.at(i)->position(), m_stops.at(i)->color()));
256 }
257 return stops;
258}
259
260void QQuickGradient::doUpdate()
261{
262 emit updated();
263}
264
265int QQuickRectanglePrivate::doUpdateSlotIdx = -1;
266
267/*!
268 \qmltype Rectangle
269 \instantiates QQuickRectangle
270 \inqmlmodule QtQuick
271 \inherits Item
272 \ingroup qtquick-visual
273 \brief Paints a filled rectangle with an optional border.
274
275 Rectangle items are used to fill areas with solid color or gradients, and/or
276 to provide a rectangular border.
277
278 \section1 Appearance
279
280 Each Rectangle item is painted using either a solid fill color, specified using
281 the \l color property, or a gradient, defined using a Gradient type and set
282 using the \l gradient property. If both a color and a gradient are specified,
283 the gradient is used.
284
285 You can add an optional border to a rectangle with its own color and thickness
286 by setting the \l border.color and \l border.width properties. Set the color
287 to "transparent" to paint a border without a fill color.
288
289 You can also create rounded rectangles using the \l radius property. Since this
290 introduces curved edges to the corners of a rectangle, it may be appropriate to
291 set the \l Item::antialiasing property to improve its appearance.
292
293 \section1 Example Usage
294
295 \div {class="float-right"}
296 \inlineimage declarative-rect.png
297 \enddiv
298
299 The following example shows the effects of some of the common properties on a
300 Rectangle item, which in this case is used to create a square:
301
302 \snippet qml/rectangle/rectangle.qml document
303
304 \clearfloat
305 \section1 Performance
306
307 Using the \l Item::antialiasing property improves the appearance of a rounded rectangle at
308 the cost of rendering performance. You should consider unsetting this property
309 for rectangles in motion, and only set it when they are stationary.
310
311 \sa Image
312*/
313
314QQuickRectangle::QQuickRectangle(QQuickItem *parent)
315: QQuickItem(*(new QQuickRectanglePrivate), parent)
316{
317 setFlag(flag: ItemHasContents);
318#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
319 setAcceptTouchEvents(false);
320#endif
321}
322
323void QQuickRectangle::doUpdate()
324{
325 update();
326}
327
328/*!
329 \qmlproperty bool QtQuick::Rectangle::antialiasing
330
331 Used to decide if the Rectangle should use antialiasing or not.
332 \l {Antialiasing} provides information on the performance implications
333 of this property.
334
335 The default is true for Rectangles with a radius, and false otherwise.
336*/
337
338/*!
339 \qmlpropertygroup QtQuick::Rectangle::border
340 \qmlproperty int QtQuick::Rectangle::border.width
341 \qmlproperty color QtQuick::Rectangle::border.color
342
343 The width and color used to draw the border of the rectangle.
344
345 A width of 1 creates a thin line. For no line, use a width of 0 or a transparent color.
346
347 \note The width of the rectangle's border does not affect the geometry of the
348 rectangle itself or its position relative to other items if anchors are used.
349
350 The border is rendered within the rectangle's boundaries.
351*/
352QQuickPen *QQuickRectangle::border()
353{
354 Q_D(QQuickRectangle);
355 if (!d->pen) {
356 d->pen = new QQuickPen;
357 QQml_setParent_noEvent(object: d->pen, parent: this);
358 }
359 return d->pen;
360}
361
362/*!
363 \qmlproperty var QtQuick::Rectangle::gradient
364
365 The gradient to use to fill the rectangle.
366
367 This property allows for the construction of simple vertical or horizontal gradients.
368 Other gradients may be formed by adding rotation to the rectangle.
369
370 \div {class="float-left"}
371 \inlineimage declarative-rect_gradient.png
372 \enddiv
373
374 \snippet qml/rectangle/rectangle-gradient.qml rectangles
375 \clearfloat
376
377 The property also accepts gradient presets from QGradient::Preset. Note however
378 that due to Rectangle only supporting simple vertical or horizontal gradients,
379 any preset with an unsupported angle will revert to the closest representation.
380
381 \snippet qml/rectangle/rectangle-gradient.qml presets
382 \clearfloat
383
384 If both a gradient and a color are specified, the gradient will be used.
385
386 \sa Gradient, color
387*/
388QJSValue QQuickRectangle::gradient() const
389{
390 Q_D(const QQuickRectangle);
391 return d->gradient;
392}
393
394void QQuickRectangle::setGradient(const QJSValue &gradient)
395{
396 Q_D(QQuickRectangle);
397 if (d->gradient.equals(other: gradient))
398 return;
399
400 static int updatedSignalIdx = QMetaMethod::fromSignal(signal: &QQuickGradient::updated).methodIndex();
401 if (d->doUpdateSlotIdx < 0)
402 d->doUpdateSlotIdx = QQuickRectangle::staticMetaObject.indexOfSlot(slot: "doUpdate()");
403
404 if (auto oldGradient = qobject_cast<QQuickGradient*>(object: d->gradient.toQObject()))
405 QMetaObject::disconnect(sender: oldGradient, signal_index: updatedSignalIdx, receiver: this, method_index: d->doUpdateSlotIdx);
406
407 if (gradient.isQObject()) {
408 if (auto newGradient = qobject_cast<QQuickGradient*>(object: gradient.toQObject())) {
409 d->gradient = gradient;
410 QMetaObject::connect(sender: newGradient, signal_index: updatedSignalIdx, receiver: this, method_index: d->doUpdateSlotIdx);
411 } else {
412 qmlWarning(me: this) << "Can't assign "
413 << QQmlMetaType::prettyTypeName(object: gradient.toQObject()) << " to gradient property";
414 d->gradient = QJSValue();
415 }
416 } else if (gradient.isNumber() || gradient.isString()) {
417 static const QMetaEnum gradientPresetMetaEnum = QMetaEnum::fromType<QGradient::Preset>();
418 Q_ASSERT(gradientPresetMetaEnum.isValid());
419
420 QGradient result;
421
422 // This code could simply use gradient.toVariant().convert<QGradient::Preset>(),
423 // but QTBUG-76377 prevents us from doing error checks. So we need to
424 // do them manually. Also, NumPresets cannot be used.
425
426 if (gradient.isNumber()) {
427 const auto preset = QGradient::Preset(gradient.toInt());
428 if (preset != QGradient::NumPresets && gradientPresetMetaEnum.valueToKey(value: preset))
429 result = QGradient(preset);
430 } else if (gradient.isString()) {
431 const auto presetName = gradient.toString();
432 if (presetName != QLatin1String("NumPresets")) {
433 bool ok;
434 const auto presetInt = gradientPresetMetaEnum.keyToValue(qPrintable(presetName), ok: &ok);
435 if (ok)
436 result = QGradient(QGradient::Preset(presetInt));
437 }
438 }
439
440 if (result.type() != QGradient::NoGradient) {
441 d->gradient = gradient;
442 } else {
443 qmlWarning(me: this) << "No such gradient preset '" << gradient.toString() << "'";
444 d->gradient = QJSValue();
445 }
446 } else if (gradient.isNull() || gradient.isUndefined()) {
447 d->gradient = gradient;
448 } else {
449 qmlWarning(me: this) << "Unknown gradient type. Expected int, string, or Gradient";
450 d->gradient = QJSValue();
451 }
452
453 update();
454}
455
456void QQuickRectangle::resetGradient()
457{
458 setGradient(QJSValue());
459}
460
461/*!
462 \qmlproperty real QtQuick::Rectangle::radius
463 This property holds the corner radius used to draw a rounded rectangle.
464
465 If radius is non-zero, the rectangle will be painted as a rounded rectangle, otherwise it will be
466 painted as a normal rectangle. The same radius is used by all 4 corners; there is currently
467 no way to specify different radii for different corners.
468*/
469qreal QQuickRectangle::radius() const
470{
471 Q_D(const QQuickRectangle);
472 return d->radius;
473}
474
475void QQuickRectangle::setRadius(qreal radius)
476{
477 Q_D(QQuickRectangle);
478 if (d->radius == radius)
479 return;
480
481 d->radius = radius;
482 d->setImplicitAntialiasing(radius != 0.0);
483
484 update();
485 emit radiusChanged();
486}
487
488/*!
489 \qmlproperty color QtQuick::Rectangle::color
490 This property holds the color used to fill the rectangle.
491
492 The default color is white.
493
494 \div {class="float-right"}
495 \inlineimage rect-color.png
496 \enddiv
497
498 The following example shows rectangles with colors specified
499 using hexadecimal and named color notation:
500
501 \snippet qml/rectangle/rectangle-colors.qml rectangles
502
503 \clearfloat
504 If both a gradient and a color are specified, the gradient will be used.
505
506 \sa gradient
507*/
508QColor QQuickRectangle::color() const
509{
510 Q_D(const QQuickRectangle);
511 return d->color;
512}
513
514void QQuickRectangle::setColor(const QColor &c)
515{
516 Q_D(QQuickRectangle);
517 if (d->color == c)
518 return;
519
520 d->color = c;
521 update();
522 emit colorChanged();
523}
524
525QSGNode *QQuickRectangle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
526{
527 Q_UNUSED(data);
528 Q_D(QQuickRectangle);
529
530 if (width() <= 0 || height() <= 0
531 || (d->color.alpha() == 0 && (!d->pen || d->pen->width() == 0 || d->pen->color().alpha() == 0))) {
532 delete oldNode;
533 return nullptr;
534 }
535
536 QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode);
537 if (!rectangle) rectangle = d->sceneGraphContext()->createInternalRectangleNode();
538
539 rectangle->setRect(QRectF(0, 0, width(), height()));
540 rectangle->setColor(d->color);
541
542 if (d->pen && d->pen->isValid()) {
543 rectangle->setPenColor(d->pen->color());
544 qreal penWidth = d->pen->width();
545 if (d->pen->pixelAligned()) {
546 qreal dpr = window() ? window()->effectiveDevicePixelRatio() : 1.0;
547 penWidth = qRound(d: penWidth * dpr) / dpr; // Ensures integer width after dpr scaling
548 }
549 rectangle->setPenWidth(penWidth);
550 rectangle->setAligned(false); // width rounding already done, so the Node should not do it
551 } else {
552 rectangle->setPenWidth(0);
553 }
554
555 rectangle->setRadius(d->radius);
556 rectangle->setAntialiasing(antialiasing());
557
558 QGradientStops stops;
559 bool vertical = true;
560 if (d->gradient.isQObject()) {
561 auto gradient = qobject_cast<QQuickGradient*>(object: d->gradient.toQObject());
562 Q_ASSERT(gradient);
563 stops = gradient->gradientStops();
564 vertical = gradient->orientation() == QQuickGradient::Vertical;
565 } else if (d->gradient.isNumber() || d->gradient.isString()) {
566 QGradient preset(d->gradient.toVariant().value<QGradient::Preset>());
567 if (preset.type() == QGradient::LinearGradient) {
568 auto linearGradient = static_cast<QLinearGradient&>(preset);
569 const QPointF start = linearGradient.start();
570 const QPointF end = linearGradient.finalStop();
571 vertical = qAbs(t: start.y() - end.y()) >= qAbs(t: start.x() - end.x());
572 stops = linearGradient.stops();
573 if ((vertical && start.y() > end.y()) || (!vertical && start.x() > end.x())) {
574 // QSGInternalRectangleNode doesn't support stops in the wrong order,
575 // so we need to manually reverse them here.
576 QGradientStops reverseStops;
577 for (auto it = stops.rbegin(); it != stops.rend(); ++it) {
578 auto stop = *it;
579 stop.first = 1 - stop.first;
580 reverseStops.append(t: stop);
581 }
582 stops = reverseStops;
583 }
584 }
585 }
586 rectangle->setGradientStops(stops);
587 rectangle->setGradientVertical(vertical);
588
589 rectangle->update();
590
591 return rectangle;
592}
593
594QT_END_NAMESPACE
595
596#include "moc_qquickrectangle_p.cpp"
597

source code of qtdeclarative/src/quick/items/qquickrectangle.cpp