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