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