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