1 | /* |
2 | * SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org> |
3 | * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com> |
4 | * SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com> |
5 | * |
6 | * SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | |
9 | #pragma once |
10 | |
11 | #include <QIcon> |
12 | #include <QPointer> |
13 | #include <QQuickItem> |
14 | #include <QVariant> |
15 | |
16 | #include <QQmlEngine> |
17 | |
18 | class QNetworkReply; |
19 | class QQuickWindow; |
20 | class QPropertyAnimation; |
21 | |
22 | namespace Kirigami |
23 | { |
24 | namespace Platform |
25 | { |
26 | class PlatformTheme; |
27 | class Units; |
28 | } |
29 | } |
30 | |
31 | /** |
32 | * Class for rendering an icon in UI. |
33 | */ |
34 | class Icon : public QQuickItem |
35 | { |
36 | Q_OBJECT |
37 | QML_ELEMENT |
38 | |
39 | /** |
40 | * The source of this icon. An `Icon` can pull from: |
41 | * |
42 | * * The icon theme: |
43 | * @include icon/IconThemeSource.qml |
44 | * * The filesystem: |
45 | * @include icon/FilesystemSource.qml |
46 | * * Remote URIs: |
47 | * @include icon/InternetSource.qml |
48 | * * Custom providers: |
49 | * @include icon/CustomSource.qml |
50 | * * Your application's bundled resources: |
51 | * @include icon/ResourceSource.qml |
52 | * |
53 | * @note See https://doc.qt.io/qt-5/qtquickcontrols2-icons.html for how to |
54 | * bundle icon themes in your application to refer to them by name instead of |
55 | * by resource URL. |
56 | * |
57 | * @note Use `fallback` to provide a fallback theme name for icons. |
58 | * |
59 | * @note Cuttlefish is a KDE application that lets you view all the icons that |
60 | * you can use for your application. It offers a number of useful features such |
61 | * as previews of their appearance across different installed themes, previews |
62 | * at different sizes, and more. You might find it a useful tool when deciding |
63 | * on which icons to use in your application. |
64 | */ |
65 | Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged FINAL) |
66 | |
67 | /** |
68 | * The name of a fallback icon to load from the icon theme when the `source` |
69 | * cannot be found. The default fallback icon is `"unknown"`. |
70 | * |
71 | * @include icon/Fallback.qml |
72 | * |
73 | * @note This will only be loaded if source is unavailable (e.g. it doesn't exist, or network issues have prevented loading). |
74 | */ |
75 | Q_PROPERTY(QString fallback READ fallback WRITE setFallback NOTIFY fallbackChanged FINAL) |
76 | |
77 | /** |
78 | * The name of an icon from the icon theme to show while the icon set in `source` is |
79 | * being loaded. This is primarily relevant for remote sources, or those using slow- |
80 | * loading image providers. The default temporary icon is `"image-x-icon"` |
81 | * |
82 | * @note This will only be loaded if the source is a type which can be so long-loading |
83 | * that a temporary image makes sense (e.g. a remote image, or from an ImageProvider |
84 | * of the type QQmlImageProviderBase::ImageResponse) |
85 | * |
86 | * @since 5.15 |
87 | */ |
88 | Q_PROPERTY(QString placeholder READ placeholder WRITE setPlaceholder NOTIFY placeholderChanged FINAL) |
89 | |
90 | /** |
91 | * Whether this icon will use the QIcon::Active mode when drawing the icon, |
92 | * resulting in a graphical effect being applied to the icon to indicate that |
93 | * it is currently active. |
94 | * |
95 | * This is typically used to indicate when an item is being hovered or pressed. |
96 | * |
97 | * @image html icon/active.png |
98 | * |
99 | * The color differences under the default KDE color palette, Breeze. Note |
100 | * that a dull highlight background is typically displayed behind active icons and |
101 | * it is recommended to add one if you are creating a custom component. |
102 | */ |
103 | Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged FINAL) |
104 | |
105 | /** |
106 | * Whether this icon's `source` is valid and it is being used. |
107 | */ |
108 | Q_PROPERTY(bool valid READ valid NOTIFY validChanged FINAL) |
109 | |
110 | /** |
111 | * Whether this icon will use the QIcon::Selected mode when drawing the icon, |
112 | * resulting in a graphical effect being applied to the icon to indicate that |
113 | * it is currently selected. |
114 | * |
115 | * This is typically used to indicate when a list item is currently selected. |
116 | * |
117 | * @image html icon/selected.png |
118 | * |
119 | * The color differences under the default KDE color palette, Breeze. Note |
120 | * that a blue background is typically displayed behind selected elements. |
121 | */ |
122 | Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged FINAL) |
123 | |
124 | /** |
125 | * Whether this icon will be treated as a mask. When an icon is being used |
126 | * as a mask, all non-transparent colors are replaced with the color provided in the Icon's |
127 | * @link Icon::color color @endlink property. |
128 | * |
129 | * @see color |
130 | */ |
131 | Q_PROPERTY(bool isMask READ isMask WRITE setIsMask NOTIFY isMaskChanged FINAL) |
132 | |
133 | /** |
134 | * The color to use when drawing this icon when `isMask` is enabled. |
135 | * If this property is not set or is `Qt::transparent`, the icon will use |
136 | * the text or the selected text color, depending on if `selected` is set to |
137 | * true. |
138 | */ |
139 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) |
140 | |
141 | /** |
142 | * Whether the icon is correctly loaded, is asynchronously loading or there was an error. |
143 | * Note that image loading will not be initiated until the item is shown, so if the Icon is not visible, |
144 | * it can only have Null or Loading states. |
145 | * @since 5.15 |
146 | */ |
147 | Q_PROPERTY(Icon::Status status READ status NOTIFY statusChanged FINAL) |
148 | |
149 | /** |
150 | * The width of the painted area measured in pixels. This will be smaller than or |
151 | * equal to the width of the area taken up by the Item itself. This can be 0. |
152 | * |
153 | * @since 5.15 |
154 | */ |
155 | Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedAreaChanged FINAL) |
156 | |
157 | /** |
158 | * The height of the painted area measured in pixels. This will be smaller than or |
159 | * equal to the height of the area taken up by the Item itself. This can be 0. |
160 | * |
161 | * @since 5.15 |
162 | */ |
163 | Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedAreaChanged FINAL) |
164 | |
165 | /** |
166 | * If set, icon will blend when the source is changed |
167 | */ |
168 | Q_PROPERTY(bool animated READ isAnimated WRITE setAnimated NOTIFY animatedChanged FINAL) |
169 | |
170 | /** |
171 | * If set, icon will round the painted size to defined icon sizes. Default is true. |
172 | */ |
173 | Q_PROPERTY(bool roundToIconSize READ roundToIconSize WRITE setRoundToIconSize NOTIFY roundToIconSizeChanged FINAL) |
174 | |
175 | public: |
176 | enum Status { |
177 | Null = 0, /// No icon has been set |
178 | Ready, /// The icon loaded correctly |
179 | Loading, // The icon is being loaded, but not ready yet |
180 | Error, /// There was an error while loading the icon, for instance a non existent themed name, or an invalid url |
181 | }; |
182 | Q_ENUM(Status) |
183 | |
184 | Icon(QQuickItem *parent = nullptr); |
185 | ~Icon() override; |
186 | |
187 | void componentComplete() override; |
188 | |
189 | void setSource(const QVariant &source); |
190 | QVariant source() const; |
191 | |
192 | void setActive(bool active = true); |
193 | bool active() const; |
194 | |
195 | bool valid() const; |
196 | |
197 | void setSelected(bool selected = true); |
198 | bool selected() const; |
199 | |
200 | void setIsMask(bool mask); |
201 | bool isMask() const; |
202 | |
203 | void setColor(const QColor &color); |
204 | QColor color() const; |
205 | |
206 | QString fallback() const; |
207 | void setFallback(const QString &fallback); |
208 | |
209 | QString placeholder() const; |
210 | void setPlaceholder(const QString &placeholder); |
211 | |
212 | Status status() const; |
213 | |
214 | qreal paintedWidth() const; |
215 | qreal paintedHeight() const; |
216 | |
217 | bool isAnimated() const; |
218 | void setAnimated(bool animated); |
219 | |
220 | bool roundToIconSize() const; |
221 | void setRoundToIconSize(bool roundToIconSize); |
222 | |
223 | QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override; |
224 | |
225 | Q_SIGNALS: |
226 | void sourceChanged(); |
227 | void activeChanged(); |
228 | void validChanged(); |
229 | void selectedChanged(); |
230 | void isMaskChanged(); |
231 | void colorChanged(); |
232 | void fallbackChanged(const QString &fallback); |
233 | void placeholderChanged(const QString &placeholder); |
234 | void statusChanged(); |
235 | void paintedAreaChanged(); |
236 | void animatedChanged(); |
237 | void roundToIconSizeChanged(); |
238 | |
239 | protected: |
240 | void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; |
241 | QImage findIcon(const QSize &size); |
242 | void handleFinished(QNetworkReply *reply); |
243 | void handleRedirect(QNetworkReply *reply); |
244 | QIcon::Mode iconMode() const; |
245 | bool guessMonochrome(const QImage &img); |
246 | void setStatus(Status status); |
247 | void updatePolish() override; |
248 | void updatePaintedGeometry(); |
249 | void updateIsMaskHeuristic(const QString &iconSource); |
250 | void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override; |
251 | |
252 | private: |
253 | void valueChanged(const QVariant &value); |
254 | void windowVisibleChanged(bool visible); |
255 | QSGNode *createSubtree(qreal initialOpacity); |
256 | void updateSubtree(QSGNode *node, qreal opacity); |
257 | QSize iconSizeHint() const; |
258 | inline QImage iconPixmap(const QIcon &icon) const; |
259 | |
260 | Kirigami::Platform::PlatformTheme *m_theme = nullptr; |
261 | Kirigami::Platform::Units *m_units = nullptr; |
262 | QPointer<QNetworkReply> m_networkReply; |
263 | QHash<int, bool> m_monochromeHeuristics; |
264 | QVariant m_source; |
265 | qreal m_devicePixelRatio = 1.0; |
266 | Status m_status = Null; |
267 | bool m_textureChanged = false; |
268 | bool m_sizeChanged = false; |
269 | bool m_active; |
270 | bool m_selected; |
271 | bool m_isMask; |
272 | bool m_isMaskHeuristic = false; |
273 | QImage m_loadedImage; |
274 | QColor m_color = Qt::transparent; |
275 | QString m_fallback = QStringLiteral("unknown" ); |
276 | QString m_placeholder = QStringLiteral("image-png" ); |
277 | QSizeF m_paintedSize; |
278 | |
279 | QImage m_oldIcon; |
280 | QImage m_icon; |
281 | |
282 | // animation on image change |
283 | QPropertyAnimation *m_animation = nullptr; |
284 | qreal m_animValue = 1.0; |
285 | bool m_animated = false; |
286 | bool m_roundToIconSize = true; |
287 | bool m_allowNextAnimation = false; |
288 | bool m_blockNextAnimation = false; |
289 | QPointer<QQuickWindow> m_window; |
290 | }; |
291 | |