Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqstylekitstyle.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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#include "qqstylekit_p.h"
11
12#include <QtQuickTemplates2/private/qquickdeferredexecute_p_p.h>
13#include <QtQml/private/qqmllist_p.h>
14
15#include <QtGui/QGuiApplication>
16#include <QtGui/QStyleHints>
17
19
20/*!
21 \qmltype Style
22 \inqmlmodule Qt.labs.StyleKit
23 \inherits AbstractStyle
24 \brief The root type for a style definition.
25
26 \l Style is the root type in StyleKit for defining a complete visual style for
27 \l [QtQuickControls] {Qt Quick Controls}. A style lets you customize
28 the appearance of \l {AbstractStylableControls}{every control type}
29 — \l {ControlStateStyle::background}{backgrounds}, \l {ControlStateStyle::indicator}{indicators},
30 \l {ControlStateStyle::handle}{handles}, \l {ControlStateStyle::}{text},
31 \l {ControlStateStyle::}{padding}, and more
32 — as well as how controls respond to states
33 such as \l {ControlStyle::}{hovered},
34 \l {ControlStyle::}{pressed}, and
35 \l {ControlStyle::}{disabled}, including animated
36 \l {ControlStyle::transition}{transitions} between them.
37
38 Styles support \l light and \l dark color schemes through \l {Theme}
39 {themes}, and you can add any number of \l {CustomTheme}{custom themes}
40 as well. \l {StyleVariation}{Style variations} allow you to define
41 alternative styling that can be applied to specific control instances
42 or entire control types. You can also define \l {CustomControl}
43 {custom controls} to extend the style beyond the built-in control set.
44
45 The following example shows a minimal style that defines some structural
46 properties shared by all themes, with separate light and dark themes for colors:
47
48 \snippet PlainStyle.qml 1
49
50 For a more complete example, see the \l{StyleKit Quick Controls Example}.
51
52 \labs
53
54 \sa Theme, CustomTheme, StyleVariation, ControlStyle, DelegateStyle,
55 CustomControl, {StyleKit Property Resolution}
56*/
57
58/*!
59 \qmlproperty list<string> Style::customThemeNames
60 \readonly
61
62 The names of all the \l {CustomTheme}{custom themes} defined in the style. This does not
63 include the \l{themeNames}{built-in themes.}
64
65 \sa themeNames, themeName, CustomTheme
66*/
67
68/*!
69 \qmlproperty Component Style::dark
70
71 The dark theme component. It's instantiated and applied when the system is in
72 dark mode and \l themeName is \c "System", or when \l themeName is
73 explicitly set to \c "Dark".
74
75 \snippet StyleSnippets.qml dark
76
77 \sa light, themeName
78*/
79
80/*!
81 \qmlproperty Style Style::fallbackStyle
82
83 The fallback style used to resolve properties that are not explicitly
84 set in this style. When a property is not found in the style or its
85 active theme, StyleKit looks it up in the fallback style.
86
87 By default, the fallback style is set to an internal style that provides
88 a basic appearance similar to the \l {Basic Style}{Basic} style.
89
90 You can set this to a custom Style, or to \c null to disable
91 fallback resolution entirely. Note that setting it to \c null
92 means starting from a completely clean slate, which requires
93 you to set many more properties than otherwise needed. A
94 reference implementation of a fallback style can be found
95 \l {qtlabsstylekit-fallbackstyle.html}{here.}
96*/
97
98/*!
99 \qmlproperty Component Style::light
100
101 The light theme component. It's instantiated and applied when the system is in
102 light mode and \l themeName is \c "System", or when \l themeName is
103 explicitly set to \c "Light".
104
105 \snippet StyleSnippets.qml light
106
107 \sa dark, themeName
108*/
109
110/*!
111 \qmlproperty palette Style::palette
112 \readonly
113
114 The palette of the control being styled.
115
116 Use this palette to bind colors in the style to the
117 \l {StyleReader::palette}{palette} of the control being styled.
118 If the application assigns a different palette to a control, the
119 style will adapt, and the control will repaint.
120
121 \snippet StyleSnippets.qml palette
122
123 \sa StyleReader::palette
124*/
125
126/*!
127 \qmlproperty Theme Style::theme
128 \readonly
129
130 The currently active theme instance.
131 It's instantiated from either the \l {light}{light theme component}, the
132 \l {dark}{dark theme component}, or one of the \l {CustomTheme}{custom themes},
133 depending on \l themeName.
134
135 When resolving a style property, StyleKit first looks for it in this
136 theme (\l {StyleVariation}{StyleVariations} aside). If the property is not found, it
137 falls back to search for it in the \l Style.
138
139 \sa themeName, light, dark
140*/
141
142/*!
143 \qmlproperty string Style::themeName
144
145 The name of the currently active theme. The default value is \c "System",
146 which automatically choose between \l light or \l dark depending on the
147 color scheme reported by \l QStyleHints::colorScheme.
148
149 You can set this property to change the current theme of this style.
150
151 \snippet StyleSnippets.qml themeName
152
153 Supported values:
154 \list
155 \li \c "System" \mdash follows \l QStyleHints::colorScheme (default)
156 \li \c "Light" \mdash forces the \l light theme
157 \li \c "Dark" \mdash forces the \l dark theme
158 \li \l {customThemeNames}{Any custom theme name}
159 \endlist
160
161 \note Themes are local to the \l Style where they are defined, and can only
162 be set as the current theme for that style. For the current theme to take
163 effect, the style it belongs to must also be the \l{StyleKit::style}{current style}
164 in the application.
165
166 \sa themeNames, theme
167*/
168
169/*!
170 \qmlproperty list<string> Style::themeNames
171 \readonly
172
173 The names of all available themes, including \c "System", \c "Light",
174 \c "Dark", and any \l {customThemeNames}{custom themes.}
175
176 \sa themeName, customThemeNames
177*/
178
179static const QString kSystem = "System"_L1;
180static const QString kLight = "Light"_L1;
181static const QString kDark = "Dark"_L1;
182
183QQStyleKitStyle::QQStyleKitStyle(QObject *parent)
184 : QQStyleKitStyleAndThemeBase(parent)
185 , m_paletteProxy(new QQuickPalette(this))
186 , m_themeName(kSystem)
187{
188}
189
190QQStyleKitStyle::~QQStyleKitStyle()
191{
192 if (m_theme)
193 m_theme->deleteLater();
194}
195
196QQuickPalette *QQStyleKitStyle::palette() const
197{
198 return m_paletteProxy;
199}
200
201QQmlComponent *QQStyleKitStyle::light() const
202{
203 return m_light;
204}
205
206QQStyleKitStyle *QQStyleKitStyle::fallbackStyle() const
207{
208 if (!m_fallbackStyle) {
209 auto *self = const_cast<QQStyleKitStyle *>(this);
210 self->executeFallbackStyle();
211 }
212 return m_fallbackStyle;
213}
214
215void QQStyleKitStyle::setFallbackStyle(QQStyleKitStyle *fallbackStyle)
216{
217 if (m_fallbackStyle == fallbackStyle)
218 return;
219
220 m_fallbackStyle = fallbackStyle;
221 emit fallbackStyleChanged();
222
223 if (palettes())
224 palettes()->setFallbackPalette(m_fallbackStyle ? m_fallbackStyle->palettes() : nullptr);
225
226 if (fonts())
227 fonts()->setFallbackFont(m_fallbackStyle ? m_fallbackStyle->fonts() : nullptr);
228
229}
230
231void QQStyleKitStyle::setLight(QQmlComponent *lightTheme)
232{
233 if (m_light == lightTheme)
234 return;
235
236 m_light = lightTheme;
237
238 emit lightChanged();
239}
240
241QQmlComponent *QQStyleKitStyle::dark() const
242{
243 return m_dark;
244}
245
246void QQStyleKitStyle::setDark(QQmlComponent *darkTheme)
247{
248 if (m_dark == darkTheme)
249 return;
250
251 m_dark = darkTheme;
252
253 emit darkChanged();
254}
255
256QQStyleKitTheme *QQStyleKitStyle::theme() const
257{
258 return m_theme;
259}
260
261QList<QQStyleKitCustomTheme *> QQStyleKitStyle::customThemes() const
262{
263 QList<QQStyleKitCustomTheme *> list;
264 for (auto *obj : children()) {
265 if (auto *customTheme = qobject_cast<QQStyleKitCustomTheme *>(obj))
266 list.append(customTheme);
267 }
268 return list;
269}
270
271void QQStyleKitStyle::parseThemes()
272{
273 m_themeNames = QStringList({kSystem});
274
275 if (m_light)
276 m_themeNames << kLight;
277 if (m_dark)
278 m_themeNames << kDark;
279
280 for (auto *customTheme : customThemes()) {
281 const QString name = customTheme->name();
282 if (name.isEmpty())
283 continue;
284 m_themeNames << name;
285 m_customThemeNames << name;
286 }
287
288 emit themeNamesChanged();
289 emit customThemeNamesChanged();
290}
291
292QString QQStyleKitStyle::themeName() const
293{
294 return m_themeName;
295}
296
297QStringList QQStyleKitStyle::themeNames() const
298{
299 return m_themeNames;
300}
301
302QStringList QQStyleKitStyle::customThemeNames() const
303{
304 return m_customThemeNames;
305}
306
307void QQStyleKitStyle::setThemeName(const QString &themeName)
308{
309 if (m_themeName == themeName)
310 return;
311
312 m_themeName = themeName;
313 if (m_completed)
314 recreateTheme();
315
316 emit themeNameChanged();
317}
318
319void QQStyleKitStyle::recreateTheme()
320{
321 QString effectiveThemeName;
322 QQmlComponent *effectiveThemeComponent = nullptr;
323
324 if (QString::compare(m_themeName, kSystem, Qt::CaseInsensitive) == 0) {
325 switch (QGuiApplication::styleHints()->colorScheme()) {
326 case Qt::ColorScheme::Dark:
327 effectiveThemeName = kDark;
328 effectiveThemeComponent = m_dark;
329 break;
330 case Qt::ColorScheme::Light:
331 case Qt::ColorScheme::Unknown:
332 effectiveThemeName = kLight;
333 effectiveThemeComponent = m_light;
334 break;
335 // Intentionally skipping a default case here, so that if a new scheme is added
336 // in the future, we get a warning about an unhandled enum value, which will prompt
337 // us to consider if we need to add explicit support for it in StyleKit as well.
338 }
339 } else if (QString::compare(m_themeName, kLight, Qt::CaseInsensitive) == 0) {
340 effectiveThemeName = kLight;
341 effectiveThemeComponent = m_light;
342 } else if (QString::compare(m_themeName,kDark, Qt::CaseInsensitive) == 0) {
343 effectiveThemeName =kDark;
344 effectiveThemeComponent = m_dark;
345 } else if (!m_themeName.isEmpty()){
346 for (auto *customTheme : customThemes()) {
347 if (QString::compare(m_themeName, customTheme->name(), Qt::CaseInsensitive) == 0) {
348 effectiveThemeName = customTheme->name();
349 effectiveThemeComponent = customTheme->theme();
350 break;
351 }
352 }
353 if (effectiveThemeName.isEmpty())
354 qmlWarning(this) << "No theme found with themeName '" << m_themeName << "'";
355 else if (!effectiveThemeComponent)
356 qmlWarning(this) << "Custom theme '" << effectiveThemeName << "' has no theme component set";
357 }
358
359 if (m_theme) {
360 if (m_effectiveThemeName == effectiveThemeName)
361 return;
362
363 m_theme->deleteLater();
364 m_theme = nullptr;
365 }
366
367 m_effectiveThemeName = effectiveThemeName;
368 m_currentThemeComponent = effectiveThemeComponent;
369
370 if (effectiveThemeComponent) {
371 if (effectiveThemeComponent->status() != QQmlComponent::Ready) {
372 qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
373 } else {
374 /* The 'createThemeInsideStyle' JS function is a work-around since we haven't found
375 * a way to instantiate a Theme inside the context of a Style from c++. Doing so is
376 * needed in order to allow custom style properties to be added as children of a
377 * Style, and at the same time, be able to access them from within a Theme. For
378 * this to work, the Style also needs to set 'pragma ComponentBehavior: Bound'. */
379 QVariant themeAsVariant;
380 QMetaObject::invokeMethod(this, "createThemeInsideStyle", Qt::DirectConnection,
381 qReturnArg(themeAsVariant), QVariant::fromValue(effectiveThemeComponent));
382 m_theme = qvariant_cast<QQStyleKitTheme *>(themeAsVariant);
383
384 if (!m_theme || !effectiveThemeComponent->errorString().isEmpty()) {
385 qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
386 } else {
387 m_theme->setParent(this);
388 }
389 }
390 }
391
392 if (!m_theme) {
393 // We always require a theme, even if it's empty
394 m_theme = new QQStyleKitTheme(this);
395 m_theme->setObjectName("<empty theme>"_L1);
396 m_theme->m_completed = true;
397 } else {
398 m_theme->setParent(this);
399 }
400
401 if (m_theme->fonts())
402 m_theme->fonts()->setFallbackFont(fonts());
403 if (m_theme->palettes())
404 m_theme->palettes()->setFallbackPalette(palettes());
405
406 reapplyStyle();
407
408 emit themeChanged();
409}
410
411void QQStyleKitStyle::reapplyStyle()
412{
413 if (!loaded())
414 return;
415
416 m_theme->updateThemePalettes();
417 m_theme->updateThemeFonts();
418 QQStyleKitVariation::resetVariationsForStyle(this);
419 QQStyleKitReader::resetReadersForStyle(this);
420}
421
422QQStyleKitStyle* QQStyleKitStyle::current()
423{
424 return QQStyleKit::qmlAttachedProperties()->style();
425}
426
427QPalette QQStyleKitStyle::paletteForControlType(QQStyleKitExtendableControlType type) const
428{
429 return m_theme->paletteForControlType(type);
430}
431
432QFont QQStyleKitStyle::fontForControlType(QQStyleKitExtendableControlType type) const
433{
434 return m_theme->fontForControlType(type);
435}
436
437bool QQStyleKitStyle::loaded() const
438{
439 /* Before both the style and theme has completed loading
440 * we return false. This can be used to avoid unnecessary
441 * property reads when we anyway have to do a full update
442 * in the end. */
443 if (!m_completed)
444 return false;
445 if (!m_theme || !m_theme->m_completed)
446 return false;
447
448 return true;
449}
450
451void QQStyleKitStyle::executeFallbackStyle(bool complete)
452{
453 if (m_fallbackStyle.wasExecuted())
454 return;
455
456 const QString name = "fallbackStyle"_L1;
457 if (!m_fallbackStyle || complete)
458 quickBeginDeferred(this, name, m_fallbackStyle);
459 if (complete)
460 quickCompleteDeferred(this, name, m_fallbackStyle);
461}
462
463void QQStyleKitStyle::syncFromQPalette(const QPalette &palette)
464{
465 if (m_isUpdatingPalette)
466 return;
467 QScopedValueRollback<bool> rb(m_isUpdatingPalette, true);
468 if (palette == m_effectivePalette)
469 return;
470 m_effectivePalette = palette;
471 m_paletteProxy->fromQPalette(m_effectivePalette);
472 emit paletteChanged();
473}
474
475QPalette QQStyleKitStyle::effectivePalette() const
476{
477 return m_effectivePalette;
478}
479
480void QQStyleKitStyle::componentComplete()
481{
482 QQStyleKitControls::componentComplete();
483
484 executeFallbackStyle(true);
485
486 /* If the style is top-level (i.e not a fallback style), we instantiate its theme and
487 * tell all relevant StyleReaders to update.
488 * Note: as an optimization, we currently don't support fallback styles to have their own
489 * themes, simply because it reduces the number of layers that need to be searched during
490 * property resolution. But we might need to reconsider implementing support for this later. */
491 const bool isTopLevelStyle = !qobject_cast<QQStyleKitStyle *>(parent());
492 if (isTopLevelStyle) {
493 parseThemes();
494 recreateTheme();
495 /* It's important to set m_completed to true before resetting the readers, otherwise
496 * QQStyleKitStyle::loaded() will still be false, which will cause property reads to
497 * trigger the "skip read" optimization from QQStyleKitPropertyResolver */
498 m_completed = true;
499 reapplyStyle();
500 }
501
502 m_completed = true;
503}
504
505QT_END_NAMESPACE
506
507#include "moc_qqstylekitstyle_p.cpp"
Combined button and popup list for selecting options.
static const QString kDark
static const QString kLight
static QT_BEGIN_NAMESPACE const QString kSystem
\qmltype Style \inqmlmodule Qt.labs.StyleKit \inherits AbstractStyle