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