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 | |
18 | QT_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 | |
40 | QQuickPen::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 | |
49 | qreal QQuickPen::width() const |
50 | { |
51 | return m_width; |
52 | } |
53 | |
54 | void 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 | |
65 | QColor QQuickPen::color() const |
66 | { |
67 | return m_color; |
68 | } |
69 | |
70 | void 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 | |
78 | bool QQuickPen::pixelAligned() const |
79 | { |
80 | return m_aligned; |
81 | } |
82 | |
83 | void 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 | |
93 | bool 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 | */ |
119 | QQuickGradientStop::QQuickGradientStop(QObject *parent) |
120 | : QObject(parent) |
121 | { |
122 | } |
123 | |
124 | qreal QQuickGradientStop::position() const |
125 | { |
126 | return m_position; |
127 | } |
128 | |
129 | void QQuickGradientStop::setPosition(qreal position) |
130 | { |
131 | m_position = position; updateGradient(); |
132 | } |
133 | |
134 | QColor QQuickGradientStop::color() const |
135 | { |
136 | return m_color; |
137 | } |
138 | |
139 | void QQuickGradientStop::setColor(const QColor &color) |
140 | { |
141 | m_color = color; updateGradient(); |
142 | } |
143 | |
144 | void 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 | */ |
213 | QQuickGradient::QQuickGradient(QObject *parent) |
214 | : QObject(parent) |
215 | { |
216 | } |
217 | |
218 | QQuickGradient::~QQuickGradient() |
219 | { |
220 | } |
221 | |
222 | QQmlListProperty<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 | */ |
238 | void 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 | |
248 | QGradientStops 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 | |
260 | void QQuickGradient::doUpdate() |
261 | { |
262 | emit updated(); |
263 | } |
264 | |
265 | int 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 | |
314 | QQuickRectangle::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 | |
323 | void 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 | */ |
352 | QQuickPen *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 | */ |
388 | QJSValue QQuickRectangle::gradient() const |
389 | { |
390 | Q_D(const QQuickRectangle); |
391 | return d->gradient; |
392 | } |
393 | |
394 | void 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 | |
456 | void 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 | */ |
469 | qreal QQuickRectangle::radius() const |
470 | { |
471 | Q_D(const QQuickRectangle); |
472 | return d->radius; |
473 | } |
474 | |
475 | void 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 | */ |
508 | QColor QQuickRectangle::color() const |
509 | { |
510 | Q_D(const QQuickRectangle); |
511 | return d->color; |
512 | } |
513 | |
514 | void 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 | |
525 | QSGNode *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 | |
594 | QT_END_NAMESPACE |
595 | |
596 | #include "moc_qquickrectangle_p.cpp" |
597 | |