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