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