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