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