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