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
qqstylekitpropertyresolver.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
11
12#include <QtQuickTemplates2/private/qquickcontrol_p.h>
13#include <QtCore/QScopedValueRollback>
14
16
17bool QQStyleKitPropertyResolver::s_styleWarningsIssued = false;
18bool QQStyleKitPropertyResolver::s_isReadingProperty = false;
19QQSK::State QQStyleKitPropertyResolver::s_cachedState = QQSK::StateFlag::Unspecified;
20QVarLengthArray<QQSK::StateFlag, 10> QQStyleKitPropertyResolver::s_cachedStateList;
21
22const QList<QQStyleKitExtendableControlType> QQStyleKitPropertyResolver::baseTypesForType(
23 QQStyleKitExtendableControlType exactType)
24{
25 /* By default, the base types should mirror the class hierarchy in Qt Quick Controls.
26 * However, to make it possible to style a base type without having to "undo" it again
27 * in a sub type, we choose to diverge in som cases:
28 *
29 * ItemDelegate — Normally used as a menu item in a ComboBox or as an item in a ListView.
30 * Although it behaves similarly to a button, it is typically styled very differently
31 * (e.g., without borders, drop shadows, gradients, etc.). For that reason, it falls
32 * back to Control rather than AbstractButton.
33 *
34 * MenuBarItem — Although it is technically a button, it is typically styled very differently
35 * from other buttons (e.g., without borders, drop shadows, gradients, etc.). For that
36 * reason, it falls back to Control rather than AbstractButton.
37 *
38 * MenuItem — In Qt Quick Controls, MenuItem inherits from AbstractButton. Although it
39 * behaves similarly to a button, it is typically styled very differently
40 * (e.g., without borders, drop shadows, gradients, etc.). For that reason, it falls
41 * back to Control rather than AbstractButton.
42 *
43 * TabBar — In Qt Quick Controls, ToolBar inherits Pane, while TabBar inherits Container.
44 * Since it is desirable for a TabBar to share styling characteristics (such as
45 * background color) with ToolBar and Pane, we let it fall back to Pane instead of
46 * Control.
47 */
48 switch (exactType) {
49 case QQStyleKitReader::ApplicationWindow: {
50 static QList<QQStyleKitExtendableControlType> t =
51 { QQStyleKitReader::ApplicationWindow };
52 return t; }
53 case QQStyleKitReader::Button:
54 case QQStyleKitReader::DelayButton:
55 case QQStyleKitReader::FlatButton:
56 case QQStyleKitReader::ToolButton:
57 case QQStyleKitReader::TabButton:
58 case QQStyleKitReader::RadioButton:
59 case QQStyleKitReader::RoundButton:
60 case QQStyleKitReader::CheckBox:
61 case QQStyleKitReader::SwitchControl: {
62 static QList<QQStyleKitExtendableControlType> t =
63 { QQStyleKitReader::AbstractButton, QQStyleKitReader::Control };
64 return t; }
65 case QQStyleKitReader::CheckDelegate:
66 case QQStyleKitReader::RadioDelegate:
67 case QQStyleKitReader::SwipeDelegate:
68 case QQStyleKitReader::SwitchDelegate: {
69 static QList<QQStyleKitExtendableControlType> t =
70 { QQStyleKitReader::ItemDelegate, QQStyleKitReader::Control };
71 return t; }
72 case QQStyleKitReader::Dialog:
73 case QQStyleKitReader::Drawer:
74 case QQStyleKitReader::Menu:
75 case QQStyleKitReader::ToolTip: {
76 static QList<QQStyleKitExtendableControlType> t =
77 { QQStyleKitReader::Popup, QQStyleKitReader::Control };
78 return t; }
79 case QQStyleKitReader::Page:
80 case QQStyleKitReader::DialogButtonBox:
81 case QQStyleKitReader::Frame:
82 case QQStyleKitReader::TabBar:
83 case QQStyleKitReader::ToolBar: {
84 static QList<QQStyleKitExtendableControlType> t =
85 { QQStyleKitReader::Pane, QQStyleKitReader::Control };
86 return t; }
87 case QQStyleKitReader::GroupBox: {
88 static QList<QQStyleKitExtendableControlType> t =
89 { QQStyleKitReader::Frame, QQStyleKitReader::Pane, QQStyleKitReader::Control };
90 return t;
91 }
92 case QQStyleKitReader::TextField:
93 case QQStyleKitReader::TextArea: {
94 static QList<QQStyleKitExtendableControlType> t =
95 { QQStyleKitReader::TextInput, QQStyleKitReader::Control };
96 return t; }
97 default: {
98 static QList<QQStyleKitExtendableControlType> t =
99 { QQStyleKitReader::Control };
100 return t; }
101 }
102
103 Q_UNREACHABLE();
104 return {};
105}
106
107void QQStyleKitPropertyResolver::cacheReaderState(QQSK::State state)
108{
109 Q_ASSERT(state != QQSK::StateFlag::Unspecified);
110 if (state == s_cachedState)
111 return;
112
113 s_cachedState = state;
114
115 /* Note: The order in which we add the states below matters.
116 * The reason is that the s_cachedStateList that we build is used by QQStyleKitPropertyResolver
117 * later to generate all the different state combinations that should be tested when
118 * searching for a property. And the states added first to the list will "win" if the
119 * same property is set in several of the states. */
120 s_cachedStateList.clear();
121 if (state.testFlag(QQSK::StateFlag::Pressed))
122 s_cachedStateList.append(QQSK::StateFlag::Pressed);
123 if (state.testFlag(QQSK::StateFlag::Hovered))
124 s_cachedStateList.append(QQSK::StateFlag::Hovered);
125 if (state.testFlag(QQSK::StateFlag::Highlighted))
126 s_cachedStateList.append(QQSK::StateFlag::Highlighted);
127 if (state.testFlag(QQSK::StateFlag::Focused))
128 s_cachedStateList.append(QQSK::StateFlag::Focused);
129 if (state.testFlag(QQSK::StateFlag::Checked))
130 s_cachedStateList.append(QQSK::StateFlag::Checked);
131 if (state.testFlag(QQSK::StateFlag::Vertical))
132 s_cachedStateList.append(QQSK::StateFlag::Vertical);
133 if (state.testFlag(QQSK::StateFlag::Disabled))
134 s_cachedStateList.append(QQSK::StateFlag::Disabled);
135}
136
137void QQStyleKitPropertyResolver::addVariationToReader(
138 QQStyleKitReader *styleReader,
139 QQStyleKitStyleAndThemeBase *styleOrTheme,
140 QQStyleKitVariation *variation)
141{
142 /* Add the variation to the StyleReader's list of effective variations.
143 * A variation may appear more than once in a type variation list or in
144 * an instance variation list, but it only needs to be added once - the
145 * first in the list will shadow the other ones anyway. */
146 if (!styleReader->m_effectiveVariations.contains(variation))
147 styleReader->m_effectiveVariations.append(variation);
148
149 /* We also record in which Style or Theme the variation was found, so that
150 * it only takes effect for that specific Style or Theme during property
151 * propagation. Note that the same type variation can be used from both the
152 * Style and the Theme (its ID can be added to several variation lists). */
153 if (!variation->m_usageContext.contains(styleOrTheme))
154 variation->m_usageContext.append(styleOrTheme);
155}
156
157void QQStyleKitPropertyResolver::addTypeVariationsToReader(
158 QQStyleKitReader *styleReader,
159 QQStyleKitStyleAndThemeBase *styleOrTheme,
160 const AttachedVariationList &attachedVariations)
161{
162 const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType();
163 const auto styleReaderBaseType = baseTypesForType(styleReaderType);
164
165 static PropertyPathIds ids;
166 if (ids.property.property() == QQSK::Property::NoProperty) {
167 /* ids is made static, since the 'variations' path will be the same for all
168 * StyleKitControls. Also, since subtypes are only possible for delegates,
169 * and 'variations' is a control property, we can exclude subtypes. */
170 ids.property = styleReader->propertyPathId(QQSK::Property::Variations, PropertyPathId::Flag::ExcludeSubtype);
171 ids.alternative = styleReader->propertyPathId(QQSK::Property::NoProperty, PropertyPathId::Flag::ExcludeSubtype);
172 ids.subTypeProperty = PropertyPathId();
173 ids.subTypeAlternative = PropertyPathId();
174 }
175
176 for (const QQStyleKitVariationAttached *attached : attachedVariations) {
177 const auto parentType = attached->controlType();
178 const auto parentBaseTypes = baseTypesForType(parentType);
179
180 /* Search for the 'variations' property set on the parentType, or any of its base
181 * types, using normal propagation and fallback logic. Unlike when resolving other style
182 * properties, we limit the search to the style or theme we're processing, since it matters which
183 * style or theme a variation belongs when they're used to resolve other style properties later. */
184 const QVariant typeVariationsVariant = readPropertyInRelevantControls(styleOrTheme, ids, parentType, parentBaseTypes);
185 if (!typeVariationsVariant.isValid())
186 continue;
187
188 const auto typeVariations = *qvariant_cast<QList<QQStyleKitVariation *> *>(typeVariationsVariant);
189
190 for (QQStyleKitVariation *variation : typeVariations) {
191 /* Inside each type variation, check if the control type that styleReader represents has
192 * been defined. If so, it means that the variation _might_ affect it, and should therefore
193 * be added to the style readers list of effective variations. */
194 if (!variation) {
195 /* The variation will be nullptr if non-QQStyleKitVariation elements
196 * are added to the 'variations' list from QML (such as strings). */
197 continue;
198 }
199
200 if (variation->getControlStyle(styleReaderType)) {
201 addVariationToReader(styleReader, styleOrTheme, variation);
202 } else {
203 for (int type : styleReaderBaseType) {
204 if (variation->getControlStyle(type))
205 addVariationToReader(styleReader, styleOrTheme, variation);
206 }
207 }
208 }
209 }
210}
211
212void QQStyleKitPropertyResolver::addInstanceVariationsToReader(
213 QQStyleKitReader *styleReader,
214 QQStyleKitStyleAndThemeBase *styleOrTheme,
215 const AttachedVariationList &attachedVariations)
216{
217 /* Add the variations set from the application to the list of effective variations
218 * in the styleReader. But, to speed up property look-up later on, we only add the
219 * variations that has the potential to affect the control type, or its base types,
220 * that the styleReader represents. The variations that are closest to styleReader
221 * in the hierarchy will be added first and take precendence over the ones added last. */
222 const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType();
223 const auto styleReaderBaseTypes = baseTypesForType(styleReaderType);
224
225 for (const QQStyleKitVariationAttached *attached : attachedVariations) {
226 for (const QString &instanceVariationName : attached->variations()) {
227 for (QQStyleKitVariation *variation : styleOrTheme->m_styleVariations) {
228 if (variation->name() != instanceVariationName)
229 continue;
230
231 /* Invariant: we found a variation in the given Style or a Theme with a name that matches
232 * a name in the attached variation list. Check if the found variation contains the
233 * type, or the subtypes, of the style reader. If not, it doesn't affect it and can
234 * therefore be skipped. */
235 if (variation->getControlStyle(styleReaderType)) {
236 addVariationToReader(styleReader, styleOrTheme, variation);
237 } else {
238 for (int baseType : styleReaderBaseTypes) {
239 if (variation->getControlStyle(baseType))
240 addVariationToReader(styleReader, styleOrTheme, variation);
241 }
242 }
243 }
244 }
245 }
246}
247
248void QQStyleKitPropertyResolver::rebuildVariationsForReader(
249 QQStyleKitReader *styleReader, QQStyleKitStyle *style)
250{
251 /* Traverse up the parent chain of \a styleReader, and for each parent, look for an
252 * instance of QQStyleKitVariationAttached. And for each attached object, check if it
253 * has variations that can potentially affect the style reader. If so, add the
254 * variations to the style readers list of effective variations.
255 * A QQStyleKitVariationAttached can specify both Instance Variations and Type Variations.
256 * The former should affect all descendant StyleKitReaders of the parent, while the
257 * latter should only affect descendant StyleKitReaders of a specific type. */
258 Q_ASSERT(styleReader->m_effectiveVariationsDirty);
259 styleReader->m_effectiveVariationsDirty = false;
260 styleReader->m_effectiveVariations.clear();
261
262 if (!style->m_hasVariations)
263 return;
264
265 /* Walk up the parent chain and collect all attached StyleVariation objects that
266 * may affect this StyleReader. Their variation lists affect instance variations,
267 * and their control type may affect type variations. */
268 AttachedVariationList attachedVariations;
269 for (QObject *current = styleReader; current; current = current->parent()) {
270 if (const QObject *attachedObject = qmlAttachedPropertiesObject<QQStyleKitVariation>(current, false)) {
271 const auto *attached = static_cast<const QQStyleKitVariationAttached *>(attachedObject);
272 attachedVariations.append(attached);
273 }
274 }
275
276 if (attachedVariations.isEmpty())
277 return;
278
279 for (QQStyleKitStyle *current = style; current; current = current->fallbackStyle()) {
280 if (QQStyleKitTheme *theme = current->theme()) {
281 addInstanceVariationsToReader(styleReader, theme, attachedVariations);
282 addTypeVariationsToReader(styleReader, theme, attachedVariations);
283 }
284 addInstanceVariationsToReader(styleReader, current, attachedVariations);
285 addTypeVariationsToReader(styleReader, current, attachedVariations);
286 }
287}
288
289template <class T>
290QVariant QQStyleKitPropertyResolver::readPropertyInStorageForState(
291 const PropertyPathId main, const PropertyPathId alternative,
292 const T *storageProvider, QQSK::State state)
293{
294 /* If either the main property or its alternative is set in the storage,
295 * we’ve found the best match and can return the value to the application.
296 * The reason we support an alternative property is that some properties can be
297 * specified in more than one way. For example, 'topLeftRadius' can be set either
298 * directly ('topLeftRadius', the main property) or indirectly via 'radius'
299 * (the alternative). Whichever one is encountered first in the propagation chain
300 * takes precedence.
301 * This means that when resolving 'topLeftRadius' for a Button, if 'radius' is set
302 * on the Button and 'topLeftRadius' is set on AbstractButton, then 'radius' will
303 * override 'topLeftRadius' and be used as the final value. */
304 Q_ASSERT(qlonglong(state) <= qlonglong(QQSK::StateFlag::MAX_STATE));
305
306 const PropertyStorageId propertyKey = main.storageId(state);
307
308 if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
309 QQStyleKitDebug::trace(main, storageProvider, state, propertyKey);
310
311 const QVariant propertyValue = storageProvider->readStyleProperty(propertyKey);
312 if (propertyValue.isValid()) {
313 if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
314 QQStyleKitDebug::notifyPropertyRead(main, storageProvider, state, propertyValue);
315 return propertyValue;
316 }
317
318 const PropertyStorageId altPropertyKey = alternative.storageId(state);
319
320 if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
321 QQStyleKitDebug::trace(alternative, storageProvider, state, altPropertyKey);
322
323 const QVariant altValue = storageProvider->readStyleProperty(altPropertyKey);
324 if (altValue.isValid()) {
325 if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
326 QQStyleKitDebug::notifyPropertyRead(main, storageProvider, state, altValue);
327 return altValue;
328 }
329
330 return {};
331}
332
333template <class INDICES_CONTAINER>
334QVariant QQStyleKitPropertyResolver::readPropertyInControlForStates(
335 const PropertyPathId main, const PropertyPathId alternative,
336 const QQStyleKitControl *control, INDICES_CONTAINER &stateListIndices,
337 int startIndex, int recursionLevel)
338{
339 for (int i = startIndex; i < s_cachedStateList.length(); ++i) {
340 /* stateListIndices is a helper list to track which index in the state list
341 * each recursion level is currently processing. The first recursion level
342 * will iterate through all of the states. The second recursion level will
343 * only the iterate through the states that comes after the state at the
344 * previous recursion. And so on recursively. The end result will be a list of
345 * state combinations (depth first) where no state is repeated more than
346 * once. And for each combination, we check if the storage has a value assigned
347 * for the given property for the given state combination. */
348 stateListIndices[recursionLevel] = i;
349 const QQSK::StateFlag stateFlag = s_cachedStateList[i];
350
351 /* Optimization: check if the control stores values for any properties for
352 * the state we're processing. Otherwise, skip the state. */
353 if (!control->m_writtenStates.testFlag(stateFlag))
354 continue;
355
356 /* Optimization: check if the style/theme/variation stores a value for the
357 * property in the state we're processing. Otherwise, skip the state. */
358 const QQStyleKitControls *controls = control->controls();
359 const QQSK::State statesAffectingProperty = controls->m_writtenPropertyPaths[main.pathId()];
360 if (!statesAffectingProperty.testFlag(stateFlag)) {
361 if (alternative.property() == QQSK::Property::NoProperty) {
362 continue;
363 } else {
364 const QQSK::State statesAffectingAlternative = controls->m_writtenPropertyPaths[alternative.pathId()];
365 if (!statesAffectingAlternative.testFlag(stateFlag))
366 continue;
367 }
368 }
369
370 if (recursionLevel < s_cachedStateList.length() - 1) {
371 // Continue the recursion towards the longest possible nested state
372 const QVariant value = readPropertyInControlForStates(
373 main, alternative, control, stateListIndices, i + 1, recursionLevel + 1);
374 if (value.isValid())
375 return value;
376 }
377
378 // Check the current combination
379 QQSK::State storageState = QQSK::StateFlag::Unspecified;
380 for (int j = 0; j <= recursionLevel; ++j)
381 storageState.setFlag(s_cachedStateList[stateListIndices[j]]);
382 const QVariant value = readPropertyInStorageForState(main, alternative, control, storageState);
383 if (value.isValid())
384 return value;
385 }
386
387 return {};
388}
389
390QVariant QQStyleKitPropertyResolver::readPropertyInControl(
391 const PropertyPathIds &ids, const QQStyleKitControl *control)
392{
393 /* Find the most specific state combination (based on the state of the reader) that
394 * has a value set for the property in the contol. In case several state combinations
395 * could be found, the order of the states in the stateList decides the priority.
396 * If we're reading a property in a subtype, try all state combinations in the subtype
397 * first, before trying all the state combinations in the super type. */
398 QVarLengthArray<int, 10> stateListIndices(s_cachedStateList.length());
399
400 if (ids.subTypeProperty.property() != QQSK::Property::NoProperty) {
401 if (s_cachedState != QQSK::StateFlag::Normal) {
402 QVariant value = readPropertyInControlForStates(
403 ids.subTypeProperty, ids.subTypeAlternative, control, stateListIndices, 0, 0);
404 if (value.isValid())
405 return value;
406 }
407
408 if (control->m_writtenStates.testFlag(QQSK::StateFlag::Normal)) {
409 const QVariant value = readPropertyInStorageForState(
410 ids.subTypeProperty, ids.subTypeAlternative, control, QQSK::StateFlag::Normal);
411 if (value.isValid())
412 return value;
413 }
414 }
415
416 if (s_cachedState != QQSK::StateFlag::Normal) {
417 const QVariant value = readPropertyInControlForStates(
418 ids.property, ids.alternative, control, stateListIndices, 0, 0);
419 if (value.isValid())
420 return value;
421 }
422
423 /* The normal state is the propagation fall back for all state combinations.
424 * If the normal state has the property set, it'll return a valid QVariant,
425 * which will cause the propagation to stop. Otherwise we'll return an invalid
426 * variant which will cause the search to continue. */
427 if (control->m_writtenStates.testFlag(QQSK::StateFlag::Normal))
428 return readPropertyInStorageForState(ids.property, ids.alternative, control, QQSK::StateFlag::Normal);
429
430 return {};
431}
432
433QVariant QQStyleKitPropertyResolver::readPropertyInRelevantControls(
434 const QQStyleKitControls *controls, const PropertyPathIds &ids,
435 const QQStyleKitExtendableControlType exactType,
436 const QList<QQStyleKitExtendableControlType> baseTypes)
437{
438 if (!controls)
439 return {};
440
441 /* Optimization: check if the style/theme/variation stores a value for
442 * the property, regardless of state. Otherwise we can just return. */
443 while (true) {
444 const auto writtenProperties = controls->m_writtenPropertyPaths;
445 if (writtenProperties.contains(ids.property.pathId()))
446 break;
447 const bool hasAlternative = ids.alternative.property() != QQSK::Property::NoProperty;
448 if (hasAlternative && writtenProperties.contains(ids.alternative.pathId()))
449 break;
450 if (ids.subTypeProperty.property() == QQSK::Property::NoProperty)
451 return {};
452 if (writtenProperties.contains(ids.subTypeProperty.pathId()))
453 break;
454 if (hasAlternative && writtenProperties.contains(ids.subTypeAlternative.pathId()))
455 break;
456 return {};
457 }
458
459 if (const QQStyleKitControl *control = controls->getControlStyle(exactType)) {
460 const QVariant value = readPropertyInControl(ids, control);
461 if (value.isValid())
462 return value;
463 }
464
465 for (const int type : baseTypes) {
466 if (const QQStyleKitControl *control = controls->getControlStyle(type)) {
467 const QVariant value = readPropertyInControl(ids, control);
468 if (value.isValid())
469 return value;
470 }
471 }
472
473 return {};
474}
475
476QVariant QQStyleKitPropertyResolver::readPropertyInVariations(
477 const QList<QPointer<QQStyleKitVariation>> &variations,
478 const QQStyleKitStyleAndThemeBase *styleOrTheme,
479 const PropertyPathIds &ids,
480 const QQStyleKitExtendableControlType exactType,
481 const QList<QQStyleKitExtendableControlType> baseTypes)
482{
483 bool foundAtLeastOneVariation = false;
484 for (const QPointer<QQStyleKitVariation> &variation : variations) {
485 if (!variation)
486 continue;
487 if (!variation->m_usageContext.contains(styleOrTheme)) {
488 if (foundAtLeastOneVariation) {
489 /* Optimization: We have found at least one effective variation in the list, but this
490 * one has a different usage context. This means that the remaining variations will also
491 * have a different usage context, since the variations were added to the list sorted on context.
492 * So we can end the iteration. */
493 break;
494 }
495 /* The variations list is sorted on usage context. And the first variation in the list with
496 * the correct usage context has yet to be found. So continue to the next variation. */
497 continue;
498 }
499 foundAtLeastOneVariation = true;
500 const QVariant value = readPropertyInRelevantControls(variation, ids, exactType, baseTypes);
501 if (value.isValid())
502 return value;
503 }
504 return {};
505}
506
507QVariant QQStyleKitPropertyResolver::readPropertyInStyle(
508 QQStyleKitStyle *style, const PropertyPathIds &ids, QQStyleKitReader *styleReader)
509{
510 /* Sync the palette of the style with the palette of the current reader. Note
511 * that this can cause palette bindings in the style to change, which will
512 * result in calls to writeStyleProperty(). */
513 style->syncFromQPalette(styleReader->effectivePalette());
514
515 /* Cache the state of the style reader to avoid rebuilding the same helper
516 * structures on subsequent reads. In practice, a single style reader
517 * typically processes many properties in sequence rather than just one. */
518 cacheReaderState(styleReader->controlState());
519
520 if (styleReader->m_effectiveVariationsDirty)
521 rebuildVariationsForReader(styleReader, style);
522
523 const QQStyleKitExtendableControlType exactType = styleReader->controlType();
524 const QList<QQStyleKitExtendableControlType> baseTypes = baseTypesForType(exactType);
525
526 QVariant value;
527
528 while (true) {
529 value = readPropertyInVariations(styleReader->m_effectiveVariations, style->theme(), ids, exactType, baseTypes);
530 if (value.isValid())
531 break;
532
533 value = readPropertyInRelevantControls(style->theme(), ids, exactType, baseTypes);
534 if (value.isValid())
535 break;
536
537 value = readPropertyInVariations(styleReader->m_effectiveVariations, style, ids, exactType, baseTypes);
538 if (value.isValid())
539 break;
540
541 value = readPropertyInRelevantControls(style, ids, exactType, baseTypes);
542 if (value.isValid())
543 break;
544
545 if (auto *fallbackStyle = style->fallbackStyle()) {
546 /* Recurse into the fallback style, and search for the property there. If not
547 * found, and the fallback style has a fallback style, the recursion continues. */
548 value = readPropertyInStyle(fallbackStyle, ids, styleReader);
549 if (value.isValid())
550 break;
551 }
552
553 break;
554 }
555
556 if (Q_UNLIKELY(QQStyleKitDebug::enabled())) {
557 if (!value.isValid())
558 QQStyleKitDebug::notifyPropertyNotResolved(ids.property);
559 }
560
561 return value;
562}
563
564QVariant QQStyleKitPropertyResolver::readStyleProperty(
565 const QQStyleKitPropertyGroup *group,
566 const QQSK::Property property,
567 const QQSK::Property alternative)
568{
569 const QQStyleKitControlProperties *controlProperties = group->controlProperties();
570 const QQSK::PropertyPathFlags pathFlags = group->pathFlags();
571 const QQSK::Subclass subclass = controlProperties->subclass();
572
573 if (subclass != QQSK::Subclass::QQStyleKitReader) {
574 /* Due to propagation, we must know the reader’s state in order to resolve the value
575 * of a given property. For example, the background delegate’s color may follow a
576 * completely different lookup path—and therefore produce a different result—depending
577 * on whether the reader is a Slider or a Button, and whether it is hovered, pressed, etc.
578 * For this reason, when a property is not read via a QQStyleKitReader, propagation and
579 * fall back logic cannot be supported.
580 * However, to still allow static (i.e., non-propagating) bindings between properties
581 * _inside_ a Style, we fall back to return the value set directly on the accessed control. */
582 Q_ASSERT(subclass == QQSK::Subclass::QQStyleKitState);
583 const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState();
584 const QQStyleKitControl *control = controlState->control();
585 const PropertyPathId propertyPathId = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype);
586 const PropertyStorageId key = propertyPathId.storageId(controlState->nestedState());
587 return control->readStyleProperty(key);
588 }
589
590 QQStyleKitStyle *style = controlProperties->style();
591 if (!style || !style->loaded()) {
592 /* No style has been set yet, or the style is still loading. The application should set
593 * a style early (e.g. at the top of main.qml) before creating any controls, to avoid
594 * premature property reads that end up here. However, some controls like the menu bar
595 * are instantiated very early and may try to read properties before a style is available. */
596 return {};
597 }
598
599 QQStyleKitDebug::groupBeingRead = group;
600 QQStyleKitReader *styleReader = controlProperties->asQQStyleKitReader();
601
602 if (s_isReadingProperty) {
603 if (!s_styleWarningsIssued) {
604 s_styleWarningsIssued = true;
605 qmlWarning(styleReader) << "The style property '" << property << "' was read "
606 << "before finishing the read of another style property. "
607 << "This is likely to cause a style glitch.";
608 }
609 }
610 QScopedValueRollback rollback(s_isReadingProperty, true);
611
612 PropertyPathIds ids;
613 ids.property = group->propertyPathId(property, PropertyPathId::Flag::ExcludeSubtype);
614 ids.alternative = group->propertyPathId(alternative, PropertyPathId::Flag::ExcludeSubtype);
615 const bool insideSubType = pathFlags &
616 (QQSK::PropertyPathFlag::DelegateSubtype1 | QQSK::PropertyPathFlag::DelegateSubtype2);
617
618 if (insideSubType) {
619 ids.subTypeProperty = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype);
620 ids.subTypeAlternative = group->propertyPathId(alternative, PropertyPathId::Flag::IncludeSubtype);
621 } else {
622 ids.subTypeProperty = PropertyPathId();
623 ids.subTypeAlternative = PropertyPathId();
624 }
625
626 if (!pathFlags.testFlag(QQSK::PropertyPathFlag::Global)) {
627 /* A style reader can have a storage that contains local property overrides (that is,
628 * interpolated values from an ongoing transition). When searching for a property, we
629 * therefore need to check this storage first. The exception is if the property was read
630 * inside the 'global' group, which means that we should read the values directly
631 * from the style. */
632 if (insideSubType) {
633 const QVariant value = readPropertyInStorageForState(
634 ids.subTypeProperty, ids.subTypeAlternative, styleReader, QQSK::StateFlag::Normal);
635 if (value.isValid())
636 return value;
637 }
638 const QVariant value = readPropertyInStorageForState(
639 ids.property, ids.alternative, styleReader, QQSK::StateFlag::Normal);
640 if (value.isValid())
641 return value;
642 }
643
644 return readPropertyInStyle(style, ids, styleReader);
645}
646
647bool QQStyleKitPropertyResolver::writeStyleProperty(
648 const QQStyleKitPropertyGroup *group,
649 const QQSK::Property property,
650 const QVariant &value)
651{
652 // While readStyleProperty() takes propagation into account, writeStyleProperty() doesn't.
653 // Instead it writes \a value directly to the storage that the group belongs to.
654 Q_ASSERT(group);
655 const QQStyleKitControlProperties *controlProperties = group->controlProperties();
656 const QQSK::PropertyPathFlags pathFlags = group->pathFlags();
657 const QQSK::Subclass subclass = controlProperties->subclass();
658 const PropertyPathId propertyPathId = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype);
659
660 if (pathFlags.testFlag(QQSK::PropertyPathFlag::Global)) {
661 qmlWarning(controlProperties) << "Properties inside 'global' are read-only!";
662 return false;
663 }
664
665 if (subclass == QQSK::Subclass::QQStyleKitReader) {
666 // This is a write to a StyleKitReader, probably from an ongoing transition
667 QQStyleKitReader *reader = controlProperties->asQQStyleKitReader();
668 const PropertyStorageId key = propertyPathId.storageId(QQSK::StateFlag::Normal);
669 const QVariant currentValue = reader->readStyleProperty(key);
670 const bool valueChanged = currentValue != value;
671 if (valueChanged) {
672 reader->writeStyleProperty(key, value);
673 QQStyleKitDebug::notifyPropertyWrite(group, property, reader, QQSK::StateFlag::Normal, key, value);
674 }
675 return valueChanged;
676 }
677
678 if (subclass == QQSK::Subclass::QQStyleKitState) {
679 // This is a write to a control inside the StyleKit style
680 const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState();
681 QQStyleKitControl *control = controlState->control();
682 const QQSK::State nestedState = controlState->nestedState();
683 const PropertyStorageId key = propertyPathId.storageId(nestedState);
684 const QVariant currentValue = control->readStyleProperty(key);
685 const bool valueChanged = currentValue != value;
686 if (valueChanged) {
687 /* Optimization: Allow a control to track which states it stores property values for.
688 * When later reading a property via propagation, we can skip that control entirely
689 * if it has no stored values corresponding to the state requested by the StyleKitReader. */
690 control->m_writtenStates |= nestedState;
691 /* Optimization: Track which properties a style, theme, or variation defines
692 * values for, and for which states. When reading a property later, we can skip
693 * the style/theme/variation entirely if it has no stored values for it, or if
694 * none match the state requested by the StyleKitReader. */
695 QQStyleKitControls *controls = control->controls();
696 const QQSK::State alreadyWrittenStates = controls->m_writtenPropertyPaths[propertyPathId.pathId()];
697 controls->m_writtenPropertyPaths[propertyPathId.pathId()] = alreadyWrittenStates | nestedState;
698
699 control->writeStyleProperty(key, value);
700 QQStyleKitDebug::notifyPropertyWrite(group, property, control, nestedState, key, value);
701 }
702 return valueChanged;
703 }
704
705 Q_UNREACHABLE();
706 return false;
707}
708
709bool QQStyleKitPropertyResolver::hasLocalStyleProperty(
710 const QQStyleKitPropertyGroup *group,
711 const QQSK::Property property)
712{
713 Q_ASSERT(group);
714 const QQStyleKitControlProperties *controlProperties = group->controlProperties();
715 const QQSK::PropertyPathFlags pathFlags = group->pathFlags();
716 const PropertyPathId propertyPathId = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype);
717 const QQSK::Subclass subclass = controlProperties->subclass();
718
719 if (pathFlags.testFlag(QQSK::PropertyPathFlag::Global))
720 return false;
721
722 if (subclass == QQSK::Subclass::QQStyleKitReader) {
723 const PropertyStorageId key = propertyPathId.storageId(QQSK::StateFlag::Normal);
724 return controlProperties->asQQStyleKitReader()->readStyleProperty(key).isValid();
725 }
726
727 if (subclass == QQSK::Subclass::QQStyleKitState) {
728 const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState();
729 const QQStyleKitControl *control = controlState->control();
730 const PropertyStorageId key = propertyPathId.storageId(controlState->nestedState());
731 return control->readStyleProperty(key).isValid();
732 }
733
734 Q_UNREACHABLE();
735 return false;
736}
737
738QT_END_NAMESPACE
739
740#include "moc_qqstylekitpropertyresolver_p.cpp"
QQStyleKitControl * control() const
QQStyleKitControlProperties * controlProperties() const
Combined button and popup list for selecting options.