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