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<QObject *> QQStyleKitStyle::customThemesAsList()
262{
263 QList<QObject *> list;
264 for (auto *customTheme : customThemes())
265 list.append(customTheme);
266 return list;
267}
268
269QList<QQStyleKitCustomTheme *> QQStyleKitStyle::customThemes() const
270{
271 QList<QQStyleKitCustomTheme *> list;
272 for (auto *obj : children()) {
273 if (auto *customTheme = qobject_cast<QQStyleKitCustomTheme *>(obj))
274 list.append(customTheme);
275 }
276 return list;
277}
278
279void QQStyleKitStyle::parseThemes()
280{
281 m_themeNames = QStringList({kSystem});
282
283 if (m_light)
284 m_themeNames << kLight;
285 if (m_dark)
286 m_themeNames << kDark;
287
288 for (auto *customTheme : customThemes()) {
289 const QString name = customTheme->name();
290 if (name.isEmpty())
291 continue;
292 m_themeNames << name;
293 m_customThemeNames << name;
294 }
295
296 emit themeNamesChanged();
297 emit customThemeNamesChanged();
298}
299
300QString QQStyleKitStyle::themeName() const
301{
302 return m_themeName;
303}
304
305QStringList QQStyleKitStyle::themeNames() const
306{
307 return m_themeNames;
308}
309
310QStringList QQStyleKitStyle::customThemeNames() const
311{
312 return m_customThemeNames;
313}
314
315void QQStyleKitStyle::setThemeName(const QString &themeName)
316{
317 if (m_themeName == themeName)
318 return;
319
320 m_themeName = themeName;
321 if (m_completed)
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 switch (QGuiApplication::styleHints()->colorScheme()) {
334 case Qt::ColorScheme::Dark:
335 effectiveThemeName = kDark;
336 effectiveThemeComponent = m_dark;
337 break;
338 case Qt::ColorScheme::Light:
339 case Qt::ColorScheme::Unknown:
340 effectiveThemeName = kLight;
341 effectiveThemeComponent = m_light;
342 break;
343 // Intentionally skipping a default case here, so that if a new scheme is added
344 // in the future, we get a warning about an unhandled enum value, which will prompt
345 // us to consider if we need to add explicit support for it in StyleKit as well.
346 }
347 } else if (QString::compare(m_themeName, kLight, Qt::CaseInsensitive) == 0) {
348 effectiveThemeName = kLight;
349 effectiveThemeComponent = m_light;
350 } else if (QString::compare(m_themeName,kDark, Qt::CaseInsensitive) == 0) {
351 effectiveThemeName =kDark;
352 effectiveThemeComponent = m_dark;
353 } else if (!m_themeName.isEmpty()){
354 for (auto *customTheme : customThemes()) {
355 if (QString::compare(m_themeName, customTheme->name(), Qt::CaseInsensitive) == 0) {
356 effectiveThemeName = customTheme->name();
357 effectiveThemeComponent = customTheme->theme();
358 break;
359 }
360 }
361 if (effectiveThemeName.isEmpty())
362 qmlWarning(this) << "No theme found with themeName '" << m_themeName << "'";
363 else if (!effectiveThemeComponent)
364 qmlWarning(this) << "Custom theme '" << effectiveThemeName << "' has no theme component set";
365 }
366
367 if (m_theme) {
368 if (m_effectiveThemeName == effectiveThemeName)
369 return;
370
371 m_theme->deleteLater();
372 m_theme = nullptr;
373 }
374
375 m_effectiveThemeName = effectiveThemeName;
376 m_currentThemeComponent = effectiveThemeComponent;
377
378 if (effectiveThemeComponent) {
379 if (effectiveThemeComponent->status() != QQmlComponent::Ready) {
380 qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
381 } else {
382 /* The 'createThemeInsideStyle' JS function is a work-around since we haven't found
383 * a way to instantiate a Theme inside the context of a Style from c++. Doing so is
384 * needed in order to allow custom style properties to be added as children of a
385 * Style, and at the same time, be able to access them from within a Theme. For
386 * this to work, the Style also needs to set 'pragma ComponentBehavior: Bound'. */
387 QVariant themeAsVariant;
388 QMetaObject::invokeMethod(this, "createThemeInsideStyle", Qt::DirectConnection,
389 qReturnArg(themeAsVariant), QVariant::fromValue(effectiveThemeComponent));
390 m_theme = qvariant_cast<QQStyleKitTheme *>(themeAsVariant);
391
392 if (!m_theme || !effectiveThemeComponent->errorString().isEmpty()) {
393 qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
394 } else {
395 m_theme->setParent(this);
396 }
397 }
398 }
399
400 if (!m_theme) {
401 // We always require a theme, even if it's empty
402 m_theme = new QQStyleKitTheme(this);
403 m_theme->setObjectName("<empty theme>"_L1);
404 m_theme->m_completed = true;
405 } else {
406 m_theme->setParent(this);
407 }
408
409 if (m_theme->fonts())
410 m_theme->fonts()->setFallbackFont(fonts());
411 if (m_theme->palettes())
412 m_theme->palettes()->setFallbackPalette(palettes());
413
414 reapplyStyle();
415
416 emit themeChanged();
417}
418
419void QQStyleKitStyle::reapplyStyle()
420{
421 if (!loaded())
422 return;
423
424 m_theme->updateThemePalettes();
425 m_theme->updateThemeFonts();
426 QQStyleKitVariation::resetVariationsForStyle(this);
427 QQStyleKitReader::resetReadersForStyle(this);
428}
429
430QQStyleKitStyle* QQStyleKitStyle::current()
431{
432 return QQStyleKit::qmlAttachedProperties()->style();
433}
434
435QPalette QQStyleKitStyle::paletteForControlType(QQStyleKitExtendableControlType type) const
436{
437 return m_theme->paletteForControlType(type);
438}
439
440QFont QQStyleKitStyle::fontForControlType(QQStyleKitExtendableControlType type) const
441{
442 return m_theme->fontForControlType(type);
443}
444
445bool QQStyleKitStyle::loaded() const
446{
447 /* Before both the style and theme has completed loading
448 * we return false. This can be used to avoid unnecessary
449 * property reads when we anyway have to do a full update
450 * in the end. */
451 if (!m_completed)
452 return false;
453 if (!m_theme || !m_theme->m_completed)
454 return false;
455
456 return true;
457}
458
459void QQStyleKitStyle::executeFallbackStyle(bool complete)
460{
461 if (m_fallbackStyle.wasExecuted())
462 return;
463
464 const QString name = "fallbackStyle"_L1;
465 if (!m_fallbackStyle || complete)
466 quickBeginDeferred(this, name, m_fallbackStyle);
467 if (complete)
468 quickCompleteDeferred(this, name, m_fallbackStyle);
469}
470
471void QQStyleKitStyle::syncFromQPalette(const QPalette &palette)
472{
473 if (m_isUpdatingPalette)
474 return;
475 QScopedValueRollback<bool> rb(m_isUpdatingPalette, true);
476 if (palette == m_effectivePalette)
477 return;
478 m_effectivePalette = palette;
479 m_paletteProxy->fromQPalette(m_effectivePalette);
480 emit paletteChanged();
481}
482
483QPalette QQStyleKitStyle::effectivePalette() const
484{
485 return m_effectivePalette;
486}
487
488void QQStyleKitStyle::componentComplete()
489{
490 QQStyleKitControls::componentComplete();
491
492 executeFallbackStyle(true);
493
494 /* If the style is top-level (i.e not a fallback style), we instantiate its theme and
495 * tell all relevant StyleReaders to update.
496 * Note: as an optimization, we currently don't support fallback styles to have their own
497 * themes, simply because it reduces the number of layers that need to be searched during
498 * property resolution. But we might need to reconsider implementing support for this later. */
499 const bool isTopLevelStyle = !qobject_cast<QQStyleKitStyle *>(parent());
500 if (isTopLevelStyle) {
501 parseThemes();
502 recreateTheme();
503 /* It's important to set m_completed to true before resetting the readers, otherwise
504 * QQStyleKitStyle::loaded() will still be false, which will cause property reads to
505 * trigger the "skip read" optimization from QQStyleKitPropertyResolver */
506 m_completed = true;
507 reapplyStyle();
508 }
509
510 m_completed = true;
511}
512
513QT_END_NAMESPACE
514
515#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