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