| 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 | |