aboutsummaryrefslogtreecommitdiffstats
path: root/src/imports/nativestyle/items/qquickstyleitem.h
blob: 3b87faf60ca64a1d10f4d2ee110ddcf1000583ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QQUICKSTYLEITEM_H
#define QQUICKSTYLEITEM_H

#include <QtCore/qdebug.h>
#include <QtQml/qqml.h>
#include <QtQml/qqmlinfo.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuickTemplates2/private/qquickcontrol_p.h>

#include "qquicknativestyle.h"
#include "qquickstyle.h"
#include "qquickstyleoption.h"

// Work-around for now, to avoid creator getting confused
// about missing macros. Should eventually be defined
// in qt declarative somewhere I assume.
#ifndef QML_NAMED_ELEMENT
#define QML_NAMED_ELEMENT(NAME)
#define QML_UNCREATABLE(NAME)
#endif

#ifdef QT_DEBUG
#define qqc2Debug() if (m_debugFlags.testFlag(Debug)) qDebug() << __FUNCTION__ << ":"
#define qqc2Info() if (m_debugFlags.testFlag(Info)) qDebug() << __FUNCTION__ << ":"
#define qqc2InfoHeading(HEADING) if (m_debugFlags.testFlag(Info)) qDebug() << "--------" << HEADING << "--------"
#else
#define qqc2Debug() if (false) qDebug()
#define qqc2Info() if (false) qDebug()
#define qqc2InfoHeading(HEADING) if (false) qDebug()
#endif

QT_BEGIN_NAMESPACE

using namespace QQC2;

class QQuickStyleMargins
{
    Q_GADGET

    Q_PROPERTY(int left READ left())
    Q_PROPERTY(int top READ top())
    Q_PROPERTY(int right READ right())
    Q_PROPERTY(int bottom READ bottom())

    QML_NAMED_ELEMENT(stylemargins)
    QML_UNCREATABLE("")

public:
    QQuickStyleMargins() {}
    QQuickStyleMargins(const QQuickStyleMargins &other) : m_margins(other.m_margins) {}
    QQuickStyleMargins(const QMargins &margins) : m_margins(margins) {}
    QQuickStyleMargins(const QRect &outer, const QRect &inner)
    {
        const int left = inner.left() - outer.left();
        const int top = inner.top() - outer.top();
        const int right = outer.right() - inner.right();
        const int bottom = outer.bottom() - inner.bottom();
        m_margins = QMargins(left, top, right, bottom);
    }

    inline void operator=(const QQuickStyleMargins &other) { m_margins = other.m_margins; }
    inline bool operator==(const QQuickStyleMargins &other) const { return other.m_margins == m_margins; }
    inline bool operator!=(const QQuickStyleMargins &other) const { return other.m_margins != m_margins; }

    inline int left() const { return m_margins.left(); }
    inline int right() const { return m_margins.right(); }
    inline int top() const { return m_margins.top(); }
    inline int bottom() const { return m_margins.bottom(); }

    QMargins m_margins;
};

QDebug operator<<(QDebug debug, const QQuickStyleMargins &padding);

struct StyleItemGeometry
{
    /*
    A QQuickStyleItem is responsible for drawing a control, or a part of it.

    'minimumSize' should be the minimum possible size that the item can
    have _without_ taking content size into consideration (and still render
    correctly). This will also be the size of the image that the item is drawn
    to, unless QQuickStyleItem::useNinePatchImage is set to false. In that
    case, the size of the image will be set to the size of the item instead
    (which is set from QML, and will typically be the same as the size of the control).
    The default way to calculate minimumSize is to call style()->sizeFromContents()
    with an empty content size. This is not always well supported by the legacy QStyle
    implementation, which means that you might e.g get an empty size in return.
    For those cases, the correct solution is to go into the specific platform style
    and change it so that it returns a valid size also for this special case.

    'implicitSize' should reflect the preferred size of the item, taking the
    given content size (as set from QML) into account. But not all controls
    have contents (slider), and for many controls, the content/label is instead
    placed outside the item/background image (radiobutton). In both cases, the
    size of the item will not include the content size, and implicitSize can
    usually be set equal to minimumSize instead.

    'contentRect' should be the free space where the contents can be placed. Note that
    this rect doesn't need to have the same size as the contentSize provided as input
    to the style item. Instead, QStyle can typically calculate a rect that is bigger, to
    e.g center the contents inside the control.

    'layoutRect' can be set to shift the position of the whole control so
    that aligns correctly with the other controls. This is important for
    controls that draws e.g shadows or focus rings. Such adornments should
    be painted, but not be included when aligning the controls.
    */

    QSize minimumSize;
    QSize implicitSize;
    QRect contentRect;
    QRect layoutRect; // If invalid, there are no layout margins!
    QMargins ninePatchMargins;
    qreal focusFrameRadius;
};

QDebug operator<<(QDebug debug, const StyleItemGeometry &cg);

class QQuickStyleItem : public QQuickItem
{
    Q_OBJECT

    // Input
    Q_PROPERTY(QQuickItem *control MEMBER m_control NOTIFY controlChanged)
    Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth)
    Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight)
    Q_PROPERTY(bool useNinePatchImage MEMBER m_useNinePatchImage)
    Q_PROPERTY(OverrideState overrideState MEMBER m_overrideState)

    // Output
    Q_PROPERTY(QQuickStyleMargins contentPadding READ contentPadding() NOTIFY contentPaddingChanged)
    Q_PROPERTY(QQuickStyleMargins layoutMargins READ layoutMargins() NOTIFY layoutMarginsChanged)
    Q_PROPERTY(QSize minimumSize READ minimumSize() NOTIFY minimumSizeChanged)
    Q_PROPERTY(int transitionDuration MEMBER m_transitionDuration CONSTANT)

    QML_NAMED_ELEMENT(StyleItem)
    QML_UNCREATABLE("StyleItem is an abstract base class.")

public:
    enum DirtyFlag {
        Nothing = 0,
        Geometry,
        Image,
        Everything = 255
    };
    Q_DECLARE_FLAGS(DirtyFlags, DirtyFlag)

    enum OverrideState {
        None = 0,
        AlwaysHovered,
        NeverHovered,
        AlwaysSunken
    };
    Q_ENUM(OverrideState)


#ifdef QT_DEBUG
    enum DebugFlag {
        NoDebug = 0x000,
        Debug = 0x001,
        Info = 0x002,
        ImageRect = 0x004,
        ContentRect = 0x008,
        LayoutRect = 0x010,
        Unscaled = 0x020,
        InputContentSize = 0x040,
        DontUseNinePatchImage = 0x080,
        NinePatchMargins = 0x100,
        SaveImage = 0x200,
    };
    Q_FLAG(DebugFlag)
    Q_DECLARE_FLAGS(DebugFlags, DebugFlag)
#endif

    explicit QQuickStyleItem(QQuickItem *parent = nullptr);
    ~QQuickStyleItem() override;

    qreal contentWidth();
    void setContentWidth(qreal contentWidth);
    qreal contentHeight();
    void setContentHeight(qreal contentHeight);

    QQuickStyleMargins contentPadding() const;
    QQuickStyleMargins layoutMargins() const;
    QSize minimumSize() const;
    QSize imageSize() const;
    qreal focusFrameRadius() const;

    Q_INVOKABLE virtual QFont styleFont(QQuickItem *control) const;

    void markGeometryDirty();
    void markImageDirty();

signals:
    void controlChanged();
    void contentPaddingChanged();
    void layoutMarginsChanged();
    void fontChanged();
    void minimumSizeChanged();

protected:
    void componentComplete() override;
    QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
    void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
    void itemChange(ItemChange change, const ItemChangeData &data) override;
    void updatePolish() override;

    virtual void connectToControl() const;
    virtual void paintEvent(QPainter *painter) const = 0;
    virtual StyleItemGeometry calculateGeometry() = 0;

    static QStyle::State controlSize(QQuickItem *item);
    void initStyleOptionBase(QStyleOption &styleOption) const;

    inline QSize contentSize() const { return QSize(qCeil(m_contentSize.width()), qCeil(m_contentSize.height())); }
    inline static QStyle *style() { return QQuickNativeStyle::style(); }

    template <class T> inline const T* control() const {
#ifdef QT_DEBUG
        if (!dynamic_cast<T *>(m_control.data())) {
            qmlWarning(this) << "control property is not of correct type";
            Q_UNREACHABLE();
        }
#endif
        return static_cast<T *>(m_control.data());
    }

#ifdef QT_DEBUG
    DebugFlags m_debugFlags = NoDebug;
#endif
    OverrideState m_overrideState = None;

private:
    inline void updateGeometry();
    inline void paintControlToImage();

    int dprAlignedSize(const int size) const;

#ifdef QT_DEBUG
    void addDebugInfo();
#endif

private:
    QPointer<QQuickItem> m_control;
    QImage m_paintedImage;
    StyleItemGeometry m_styleItemGeometry;
    QSizeF m_contentSize;

    DirtyFlags m_dirty = Everything;
    bool m_useNinePatchImage = true;
    bool m_polishing = false;
    mutable QQuickWindow *m_connectedWindow = nullptr;

#ifdef Q_OS_MACOS
    int m_transitionDuration = 150;
#else
    int m_transitionDuration = 400;
#endif

private:
    friend class QtQuickControls2MacOSStylePlugin;
};

Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickStyleItem::DirtyFlags)

#ifdef QT_DEBUG
Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickStyleItem::DebugFlags)
#endif

QT_END_NAMESPACE

QML_DECLARE_TYPE(QQuickStyleItem)

#endif // QQUICKSTYLEITEM_H