1 | // Copyright (C) 2020 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 | #ifndef QQUICKSTYLEITEM_H |
5 | #define QQUICKSTYLEITEM_H |
6 | |
7 | #include <QtCore/qdebug.h> |
8 | #include <QtQml/qqml.h> |
9 | #include <QtQml/qqmlinfo.h> |
10 | #include <QtQuick/private/qquickitem_p.h> |
11 | #include <QtQuickTemplates2/private/qquickcontrol_p.h> |
12 | |
13 | #include "qquicknativestyle.h" |
14 | #include "qquickstyle.h" |
15 | #include "qquickstyleoption.h" |
16 | |
17 | #include <QtCore/qpointer.h> |
18 | |
19 | // Work-around for now, to avoid creator getting confused |
20 | // about missing macros. Should eventually be defined |
21 | // in qt declarative somewhere I assume. |
22 | #ifndef QML_NAMED_ELEMENT |
23 | #define QML_NAMED_ELEMENT(NAME) |
24 | #define QML_UNCREATABLE(NAME) |
25 | #endif |
26 | |
27 | #ifdef QT_DEBUG |
28 | #define qqc2Debug() if (m_debugFlags.testFlag(Debug)) qDebug() << __FUNCTION__ << ":" |
29 | #define qqc2Info() if (m_debugFlags.testFlag(Info)) qDebug() << __FUNCTION__ << ":" |
30 | #define qqc2InfoHeading(HEADING) if (m_debugFlags.testFlag(Info)) qDebug() << "--------" << HEADING << "--------" |
31 | #else |
32 | #define qqc2Debug() if (false) qDebug() |
33 | #define qqc2Info() if (false) qDebug() |
34 | #define qqc2InfoHeading(HEADING) if (false) qDebug() |
35 | #endif |
36 | |
37 | QT_BEGIN_NAMESPACE |
38 | |
39 | using namespace QQC2; |
40 | |
41 | class QQuickStyleMargins |
42 | { |
43 | Q_GADGET |
44 | |
45 | Q_PROPERTY(int left READ left()) |
46 | Q_PROPERTY(int top READ top()) |
47 | Q_PROPERTY(int right READ right()) |
48 | Q_PROPERTY(int bottom READ bottom()) |
49 | |
50 | QML_NAMED_ELEMENT(stylemargins) |
51 | QML_UNCREATABLE("" ) |
52 | |
53 | public: |
54 | QQuickStyleMargins() {} |
55 | QQuickStyleMargins(const QQuickStyleMargins &other) : m_margins(other.m_margins) {} |
56 | QQuickStyleMargins(const QMargins &margins) : m_margins(margins) {} |
57 | QQuickStyleMargins(const QRect &outer, const QRect &inner) |
58 | { |
59 | const int left = inner.left() - outer.left(); |
60 | const int top = inner.top() - outer.top(); |
61 | const int right = outer.right() - inner.right(); |
62 | const int bottom = outer.bottom() - inner.bottom(); |
63 | m_margins = QMargins(left, top, right, bottom); |
64 | } |
65 | |
66 | inline void operator=(const QQuickStyleMargins &other) { m_margins = other.m_margins; } |
67 | inline bool operator==(const QQuickStyleMargins &other) const { return other.m_margins == m_margins; } |
68 | inline bool operator!=(const QQuickStyleMargins &other) const { return other.m_margins != m_margins; } |
69 | |
70 | inline int left() const { return m_margins.left(); } |
71 | inline int right() const { return m_margins.right(); } |
72 | inline int top() const { return m_margins.top(); } |
73 | inline int bottom() const { return m_margins.bottom(); } |
74 | |
75 | QMargins m_margins; |
76 | }; |
77 | |
78 | QDebug operator<<(QDebug debug, const QQuickStyleMargins &padding); |
79 | |
80 | struct StyleItemGeometry |
81 | { |
82 | /* |
83 | A QQuickStyleItem is responsible for drawing a control, or a part of it. |
84 | |
85 | 'minimumSize' should be the minimum possible size that the item can |
86 | have _without_ taking content size into consideration (and still render |
87 | correctly). This will also be the size of the image that the item is drawn |
88 | to, unless QQuickStyleItem::useNinePatchImage is set to false. In that |
89 | case, the size of the image will be set to the size of the item instead |
90 | (which is set from QML, and will typically be the same as the size of the control). |
91 | The default way to calculate minimumSize is to call style()->sizeFromContents() |
92 | with an empty content size. This is not always well supported by the legacy QStyle |
93 | implementation, which means that you might e.g get an empty size in return. |
94 | For those cases, the correct solution is to go into the specific platform style |
95 | and change it so that it returns a valid size also for this special case. |
96 | |
97 | 'implicitSize' should reflect the preferred size of the item, taking the |
98 | given content size (as set from QML) into account. But not all controls |
99 | have contents (slider), and for many controls, the content/label is instead |
100 | placed outside the item/background image (radiobutton). In both cases, the |
101 | size of the item will not include the content size, and implicitSize can |
102 | usually be set equal to minimumSize instead. |
103 | |
104 | 'contentRect' should be the free space where the contents can be placed. Note that |
105 | this rect doesn't need to have the same size as the contentSize provided as input |
106 | to the style item. Instead, QStyle can typically calculate a rect that is bigger, to |
107 | e.g center the contents inside the control. |
108 | |
109 | 'layoutRect' can be set to shift the position of the whole control so |
110 | that aligns correctly with the other controls. This is important for |
111 | controls that draws e.g shadows or focus rings. Such adornments should |
112 | be painted, but not be included when aligning the controls. |
113 | */ |
114 | |
115 | QSize minimumSize; |
116 | QSize implicitSize; |
117 | QRect contentRect; |
118 | QRect layoutRect; // If invalid, there are no layout margins! |
119 | QMargins ninePatchMargins; |
120 | qreal focusFrameRadius; |
121 | }; |
122 | |
123 | QDebug operator<<(QDebug debug, const StyleItemGeometry &cg); |
124 | |
125 | class QQuickStyleItem : public QQuickItem |
126 | { |
127 | Q_OBJECT |
128 | |
129 | // Input |
130 | Q_PROPERTY(QQuickItem *control MEMBER m_control NOTIFY controlChanged) |
131 | Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth) |
132 | Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight) |
133 | Q_PROPERTY(bool useNinePatchImage MEMBER m_useNinePatchImage) |
134 | Q_PROPERTY(OverrideState overrideState MEMBER m_overrideState) |
135 | |
136 | // Output |
137 | Q_PROPERTY(QQuickStyleMargins contentPadding READ contentPadding() NOTIFY contentPaddingChanged) |
138 | Q_PROPERTY(QQuickStyleMargins layoutMargins READ layoutMargins() NOTIFY layoutMarginsChanged) |
139 | Q_PROPERTY(QSize minimumSize READ minimumSize() NOTIFY minimumSizeChanged) |
140 | Q_PROPERTY(int transitionDuration MEMBER m_transitionDuration CONSTANT) |
141 | |
142 | QML_NAMED_ELEMENT(StyleItem) |
143 | QML_UNCREATABLE("StyleItem is an abstract base class." ) |
144 | |
145 | public: |
146 | enum DirtyFlag { |
147 | Nothing = 0, |
148 | Geometry, |
149 | Image, |
150 | Everything = 255 |
151 | }; |
152 | Q_DECLARE_FLAGS(DirtyFlags, DirtyFlag) |
153 | |
154 | enum OverrideState { |
155 | None = 0, |
156 | AlwaysHovered, |
157 | NeverHovered, |
158 | AlwaysSunken |
159 | }; |
160 | Q_ENUM(OverrideState) |
161 | |
162 | |
163 | #ifdef QT_DEBUG |
164 | enum DebugFlag { |
165 | NoDebug = 0x000, |
166 | Debug = 0x001, |
167 | Info = 0x002, |
168 | ImageRect = 0x004, |
169 | ContentRect = 0x008, |
170 | LayoutRect = 0x010, |
171 | Unscaled = 0x020, |
172 | InputContentSize = 0x040, |
173 | DontUseNinePatchImage = 0x080, |
174 | NinePatchMargins = 0x100, |
175 | SaveImage = 0x200, |
176 | }; |
177 | Q_DECLARE_FLAGS(DebugFlags, DebugFlag) |
178 | Q_FLAG(DebugFlags) |
179 | #endif |
180 | |
181 | explicit QQuickStyleItem(QQuickItem *parent = nullptr); |
182 | ~QQuickStyleItem() override; |
183 | |
184 | qreal contentWidth(); |
185 | void setContentWidth(qreal contentWidth); |
186 | qreal contentHeight(); |
187 | void setContentHeight(qreal contentHeight); |
188 | |
189 | QQuickStyleMargins contentPadding() const; |
190 | QQuickStyleMargins layoutMargins() const; |
191 | QSize minimumSize() const; |
192 | QSize imageSize() const; |
193 | qreal focusFrameRadius() const; |
194 | |
195 | Q_INVOKABLE virtual QFont styleFont(QQuickItem *control) const; |
196 | |
197 | void markGeometryDirty(); |
198 | void markImageDirty(); |
199 | |
200 | Q_SIGNALS: |
201 | void controlChanged(); |
202 | void contentPaddingChanged(); |
203 | void layoutMarginsChanged(); |
204 | void fontChanged(); |
205 | void minimumSizeChanged(); |
206 | |
207 | protected: |
208 | void componentComplete() override; |
209 | QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override; |
210 | void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; |
211 | void itemChange(ItemChange change, const ItemChangeData &data) override; |
212 | void updatePolish() override; |
213 | |
214 | virtual void connectToControl() const; |
215 | virtual void paintEvent(QPainter *painter) const = 0; |
216 | virtual StyleItemGeometry calculateGeometry() = 0; |
217 | |
218 | static QStyle::State controlSize(QQuickItem *item); |
219 | void initStyleOptionBase(QStyleOption &styleOption) const; |
220 | |
221 | inline QSize contentSize() const { return QSize(qCeil(v: m_contentSize.width()), qCeil(v: m_contentSize.height())); } |
222 | inline static QStyle *style() { return QQuickNativeStyle::style(); } |
223 | |
224 | template <class T> inline const T* control() const { |
225 | #ifdef QT_DEBUG |
226 | if (!dynamic_cast<T *>(m_control.data())) { |
227 | qmlWarning(me: this) << "control property is not of correct type" ; |
228 | Q_UNREACHABLE(); |
229 | } |
230 | #endif |
231 | return static_cast<T *>(m_control.data()); |
232 | } |
233 | |
234 | #ifdef QT_DEBUG |
235 | DebugFlags m_debugFlags = NoDebug; |
236 | #endif |
237 | OverrideState m_overrideState = None; |
238 | |
239 | private: |
240 | bool event(QEvent *event) override; |
241 | inline void updateGeometry(); |
242 | inline void paintControlToImage(); |
243 | |
244 | int dprAlignedSize(const int size) const; |
245 | |
246 | #ifdef QT_DEBUG |
247 | void addDebugInfo(); |
248 | #endif |
249 | |
250 | private: |
251 | QPointer<QQuickItem> m_control; |
252 | QImage m_paintedImage; |
253 | StyleItemGeometry m_styleItemGeometry; |
254 | QSizeF m_contentSize; |
255 | |
256 | DirtyFlags m_dirty = Everything; |
257 | bool m_useNinePatchImage = true; |
258 | bool m_polishing = false; |
259 | mutable QQuickWindow *m_connectedWindow = nullptr; |
260 | |
261 | #ifdef Q_OS_MACOS |
262 | int m_transitionDuration = 150; |
263 | #else |
264 | int m_transitionDuration = 400; |
265 | #endif |
266 | |
267 | private: |
268 | friend class QtQuickControls2MacOSStylePlugin; |
269 | }; |
270 | |
271 | Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickStyleItem::DirtyFlags) |
272 | |
273 | #ifdef QT_DEBUG |
274 | Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickStyleItem::DebugFlags) |
275 | #endif |
276 | |
277 | QT_END_NAMESPACE |
278 | |
279 | #endif // QQUICKSTYLEITEM_H |
280 | |