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