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 Example}.
51
52 \labs
53
54 \sa Theme, CustomTheme, StyleVariation, ControlStyle, DelegateStyle,
55 CustomControl
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 const auto scheme = QGuiApplication::styleHints()->colorScheme();
352 if (scheme == Qt::ColorScheme::Light) {
353 effectiveThemeName = kLight;
354 effectiveThemeComponent = m_light;
355 }
356 else if (scheme == Qt::ColorScheme::Dark) {
357 effectiveThemeName = kDark;
358 effectiveThemeComponent = m_dark;
359 }
360 } else if (QString::compare(m_themeName, kLight, Qt::CaseInsensitive) == 0) {
361 effectiveThemeName = kLight;
362 effectiveThemeComponent = m_light;
363 } else if (QString::compare(m_themeName,kDark, Qt::CaseInsensitive) == 0) {
364 effectiveThemeName =kDark;
365 effectiveThemeComponent = m_dark;
366 } else if (!m_themeName.isEmpty()){
367 for (auto *customTheme : customThemes()) {
368 if (QString::compare(m_themeName, customTheme->name(), Qt::CaseInsensitive) == 0) {
369 effectiveThemeName = customTheme->name();
370 effectiveThemeComponent = customTheme->theme();
371 break;
372 }
373 }
374 if (effectiveThemeName.isEmpty())
375 qmlWarning(this) << "No theme found with name:" << m_themeName;
376 else if (!effectiveThemeComponent)
377 qmlWarning(this) << "Custom theme '" << effectiveThemeName << "' has no theme component set";
378 }
379
380 if (m_effectiveThemeName == effectiveThemeName)
381 return;
382
383 if (m_theme) {
384 m_theme->deleteLater();
385 m_theme = nullptr;
386 }
387
388 m_effectiveThemeName = effectiveThemeName;
389 m_currentThemeComponent = effectiveThemeComponent;
390
391 if (effectiveThemeComponent) {
392 if (effectiveThemeComponent->status() != QQmlComponent::Ready) {
393 qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
394 } else {
395 /* The 'createThemeInsideStyle' JS function is a work-around since we haven't found
396 * a way to instantiate a Theme inside the context of a Style from c++. Doing so is
397 * needed in order to allow custom style properties to be added as children of a
398 * Style, and at the same time, be able to access them from within a Theme. For
399 * this to work, the Style also needs to set 'pragma ComponentBehavior: Bound'. */
400 QVariant themeAsVariant;
401 QMetaObject::invokeMethod(this, "createThemeInsideStyle", Qt::DirectConnection,
402 qReturnArg(themeAsVariant), QVariant::fromValue(effectiveThemeComponent));
403 m_theme = qvariant_cast<QQStyleKitTheme *>(themeAsVariant);
404
405 if (!m_theme || !effectiveThemeComponent->errorString().isEmpty()) {
406 qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
407 } else {
408 m_theme->setParent(this);
409 }
410 }
411 }
412
413 if (!m_theme) {
414 // We always require a theme, even if it's empty
415 m_theme = new QQStyleKitTheme(this);
416 m_theme->setObjectName("<empty theme>"_L1);
417 m_theme->m_completed = true;
418 } else {
419 m_theme->setParent(this);
420 }
421
422 if (m_theme->fonts())
423 m_theme->fonts()->setFallbackFont(fonts());
424 if (m_theme->palettes())
425 m_theme->palettes()->setFallbackPalette(palettes());
426
427 m_theme->updateThemePalettes();
428 m_theme->updateThemeFonts();
429
430 QQStyleKitVariation::resetVariationsForStyle(this);
431 QQStyleKitReader::resetReadersForStyle(this);
432
433 emit themeChanged();
434}
435
436QQStyleKitStyle* QQStyleKitStyle::current()
437{
438 return QQStyleKit::qmlAttachedProperties()->style();
439}
440
441QPalette QQStyleKitStyle::paletteForControlType(QQStyleKitExtendableControlType type) const
442{
443 return m_theme->paletteForControlType(type);
444}
445
446QFont QQStyleKitStyle::fontForControlType(QQStyleKitExtendableControlType type) const
447{
448 return m_theme->fontForControlType(type);
449}
450
451bool QQStyleKitStyle::loaded() const
452{
453 /* Before both the style and theme has completed loading
454 * we return false. This can be used to avoid unnecessary
455 * property reads when we anyway have to do a full update
456 * in the end. */
457 if (!m_completed)
458 return false;
459 if (!m_theme || !m_theme->m_completed)
460 return false;
461
462 return true;
463}
464
465void QQStyleKitStyle::executeFallbackStyle(bool complete)
466{
467 if (m_fallbackStyle.wasExecuted())
468 return;
469
470 const QString name = "fallbackStyle"_L1;
471 if (!m_fallbackStyle || complete)
472 quickBeginDeferred(this, name, m_fallbackStyle);
473 if (complete)
474 quickCompleteDeferred(this, name, m_fallbackStyle);
475}
476
477void QQStyleKitStyle::syncFromQPalette(const QPalette &palette)
478{
479 if (m_isUpdatingPalette)
480 return;
481 QScopedValueRollback<bool> rb(m_isUpdatingPalette, true);
482 if (palette == m_effectivePalette)
483 return;
484 m_effectivePalette = palette;
485 m_paletteProxy->fromQPalette(m_effectivePalette);
486 emit paletteChanged();
487}
488
489QPalette QQStyleKitStyle::effectivePalette() const
490{
491 return m_effectivePalette;
492}
493
494void QQStyleKitStyle::componentComplete()
495{
496 QQStyleKitControls::componentComplete();
497
498 /* It's important to set m_completed before creating the theme, otherwise
499 * styleAndThemeFinishedLoading() will still be false, which will e.g cause
500 * property reads to return early from QQStyleKitPropertyResolver */
501 m_completed = true;
502
503 executeFallbackStyle(true);
504 parseThemes();
505 recreateTheme();
506}
507
508QT_END_NAMESPACE
509
510#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