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
qqstylekitreader.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
4#include <QtQuick/private/qquickpalette_p.h>
5#include <QtQuick/private/qquickwindow_p.h>
6#include <QtQuick/private/qquickstategroup_p.h>
7#include <QtQuick/private/qquickpropertychanges_p.h>
8
9#include "qqstylekit_p.h"
13
15
16/*!
17 \qmltype StyleReader
18 \inqmlmodule Qt.labs.StyleKit
19 \inherits ControlStyleProperties
20 \brief Reads properties from the active Style for a specific control.
21
22 StyleReader is the bridge between a control in \l {Qt Quick Controls}
23 and the \l {StyleKit::style}{active style}. It exposes all the style
24 properties — \l {ControlStyleProperties::background}{backgrounds},
25 \l {ControlStyleProperties::indicator}{indicators},
26 \l {ControlStyleProperties::handle}{handles},
27 \l {ControlStyleProperties::text}{text},
28 \l {ControlStyleProperties::padding}{padding},
29 and more — that a control, and its delegates, should bind to.
30 All built-in \c StyleKit controls use a StyleReader
31 internally for this purpose.
32
33 The state properties — \l hovered, \l pressed, \l focused, \l checked,
34 \l highlighted, etc. — tell StyleReader which state the control is in.
35 It uses these to resolve and read the correct property values from
36 the \l Style, taking \l {Theme}{Themes}, \l {StyleVariation}{StyleVariations},
37 \l {StylableControls}{fallback types}, and property propagation
38 into account.
39
40 When implementing a \l CustomControl, you can follow the same approach as
41 the built-in controls:
42
43 \snippet StyleReaderSnippets.qml custom control
44
45 \note The style properties inherited from \l ControlStyleProperties do not map
46 directly to the \l Style. Instead, they reflect a cache of potentially interpolated
47 values during an active \l {ControlStyle::}{transition}. Writing to them will \e not
48 update the corresponding properties in the \l Style and may interfere with ongoing
49 transitions. Write directly to the \l Style when you need to change a style property.
50
51 \labs
52
53 \sa CustomControl, Style, {StyleVariation::controlType}{StyleVariation.controlType},
54 {StyleKit Property Resolution}
55*/
56
57/*!
58 \qmlproperty enumeration StyleReader::controlType
59
60 Identifies which control type in the \l Style this reader reads
61 properties from. This can either be set to one of the predefined values
62 below, or to a \l {CustomControl::controlType}{custom control type} defined in the
63 \l Style.
64
65 \value StyleReader.BusyIndicator \l {BusyIndicator}
66 \value StyleReader.Button \l {Button}
67 \value StyleReader.CheckBox \l {CheckBox}
68 \value StyleReader.CheckDelegate \l {CheckDelegate}
69 \value StyleReader.ComboBox \l {ComboBox}
70 \value StyleReader.DelayButton \l {DelayButton}
71 \value StyleReader.Dial \l {Dial}
72 \value StyleReader.Dialog \l {Dialog}
73 \value StyleReader.DialogButtonBox \l {DialogButtonBox}
74 \value StyleReader.Drawer \l {Drawer}
75 \value StyleReader.Frame \l {Frame}
76 \value StyleReader.GroupBox \l {GroupBox}
77 \value StyleReader.HeaderViewDelegate \l {HorizontalHeaderViewDelegate} and \l {VerticalHeaderViewDelegate}
78 \value StyleReader.ItemDelegate \l {ItemDelegate}
79 \value StyleReader.Label \l {Label}
80 \value StyleReader.Menu \l {Menu}
81 \value StyleReader.MenuBar \l {MenuBar}
82 \value StyleReader.MenuBarItem \l {MenuBarItem}
83 \value StyleReader.MenuItem \l {MenuItem}
84 \value StyleReader.MenuSeparator \l {MenuSeparator}
85 \value StyleReader.Page \l {Page}
86 \value StyleReader.PageIndicator \l {PageIndicator}
87 \value StyleReader.Pane \l {Pane}
88 \value StyleReader.Popup \l {Popup}
89 \value StyleReader.ProgressBar \l {ProgressBar}
90 \value StyleReader.RadioButton \l {RadioButton}
91 \value StyleReader.RadioDelegate \l {RadioDelegate}
92 \value StyleReader.RoundButton \l {RoundButton}
93 \value StyleReader.ScrollBar \l {ScrollBar}
94 \value StyleReader.ScrollView \l {ScrollView}
95 \value StyleReader.SearchField \l {SearchField}
96 \value StyleReader.Slider \l {Slider}
97 \value StyleReader.SpinBox \l {SpinBox}
98 \value StyleReader.SwipeDelegate \l {SwipeDelegate}
99 \value StyleReader.Switch \l {Switch}
100 \value StyleReader.SwitchDelegate \l {SwitchDelegate}
101 \value StyleReader.TabBar \l {TabBar}
102 \value StyleReader.TabButton \l {TabButton}
103 \value StyleReader.TableViewDelegate \l {TableViewDelegate}
104 \value StyleReader.TextArea \l {TextArea}
105 \value StyleReader.TextField \l {TextField}
106 \value StyleReader.ToolBar \l {ToolBar}
107 \value StyleReader.ToolButton \l {ToolButton}
108 \value StyleReader.ToolSeparator \l {ToolSeparator}
109 \value StyleReader.ToolTip \l {ToolTip}
110 \value StyleReader.TreeViewDelegate \l {TreeViewDelegate}
111 \value StyleReader.Tumbler \l {Tumbler}
112
113 \sa {StyleVariation::controlType}{StyleVariation.controlType}
114*/
115
116/*!
117 \qmlproperty bool StyleReader::enabled
118
119 Whether the control is enabled.
120
121 Bind this to the control's \l {Item::enabled}{enabled} property.
122
123 The default value is \c true.
124
125 \sa {ControlStateStyle::disabled}{ControlStateStyle.disabled}
126*/
127
128/*!
129 \qmlproperty bool StyleReader::hovered
130
131 Whether the control is hovered.
132
133 Bind this to the control's \l {Control::hovered}{hovered} property.
134
135 The default value is \c false.
136
137 \sa {ControlStateStyle::hovered}{ControlStateStyle.hovered}
138*/
139
140/*!
141 \qmlproperty bool StyleReader::pressed
142
143 Whether the control is pressed.
144
145 Bind this to the control's \l {AbstractButton::pressed}{pressed} property.
146
147 The default value is \c false.
148
149 \sa {ControlStateStyle::pressed}{ControlStateStyle.pressed}
150*/
151
152/*!
153 \qmlproperty bool StyleReader::focused
154
155 Whether the control has active focus.
156
157 Bind this to the control's \l {Item::activeFocus}{activeFocus} property.
158
159 The default value is \c false.
160
161 \sa {ControlStateStyle::focused}{ControlStateStyle.focused}
162*/
163
164/*!
165 \qmlproperty bool StyleReader::checked
166
167 Whether the control is checked.
168
169 Bind this to the control's \l {AbstractButton::checked}{checked} property.
170
171 The default value is \c false.
172
173 \sa {ControlStateStyle::checked}{ControlStateStyle.checked}
174*/
175
176/*!
177 \qmlproperty bool StyleReader::highlighted
178
179 Whether the control is highlighted.
180
181 Bind this to the control's \l {Button::highlighted}{highlighted} property.
182
183 The default value is \c false.
184
185 \sa {ControlStateStyle::highlighted}{ControlStateStyle.highlighted}
186*/
187
188/*!
189 \qmlproperty bool StyleReader::vertical
190
191 Whether the control is oriented vertically.
192
193 Bind this to the control's \l {Slider::orientation}{orientation}.
194
195 The default value is \c false.
196
197 \sa {ControlStateStyle::vertical}{ControlStateStyle.vertical}
198*/
199
200/*!
201 \qmlproperty palette StyleReader::palette
202
203 The palette of the control. StyleKit uses this to resolve color properties
204 that bind the \l {Style::}{palette} in the \l Style.
205
206 Bind this to the control's \l {Control::palette}{palette} property.
207
208 \sa {Style::palette}{Style.palette}
209*/
210
211/*!
212 \qmlproperty font StyleReader::font
213 \readonly
214
215 The effective font for this control type, as defined by the
216 \l {AbstractStyle::fonts}{style}. This also takes into account any font
217 overrides set in the \l {ControlStyle::}{text} properties of the style.
218
219 Bind the control's \l {Control::font}{font} property to this property.
220
221 \note Unlike properties such as \l hovered, \l pressed and \l palette — which
222 is forwarded from the control to the StyleReader — \l font is an output. Bind the
223 control's \l {Control::font}{font} property to this value, not the other way around.
224*/
225
226/*!
227 \qmlproperty ControlStyleProperties StyleReader::global
228 \readonly
229
230 Provides direct access to the style properties, bypassing any ongoing
231 \l {ControlStyle::}{transition}.
232
233 While a state transition is ongoing, style properties read from a StyleReader
234 may return interpolated values. By prepending \c global to the property path,
235 you bypass the transition and get the end-state values immediately.
236
237 For example, when transitioning from \l hovered to \l pressed, \c {background.color}
238 may return an interpolated value between the \l {DelegateStyle::}{color} in the
239 \l {ControlStateStyle::}{hovered} state and the color in the
240 \l {ControlStateStyle::}{pressed} state. \c {global.background.color}, on the
241 other hand, returns the color in the pressed state directly.
242
243 \sa {StyleKit::transitionsEnabled}{transitionsEnabled}
244*/
245
246using namespace Qt::StringLiterals;
247
248static constexpr QLatin1StringView kAlternate1 = "A1"_L1;
249static constexpr QLatin1StringView kAlternate2 = "A2"_L1;
250
252{
253 if (!t)
254 return 0;
255
256 quint64 sig = 0;
257
258 // bit 0: bold defined, bit 1: bold value
259 if (t->isDefined(QQSK::Property::Bold)) {
260 sig |= (quint64(1) << 0);
261 if (t->styleProperty<bool>(QQSK::Property::Bold))
262 sig |= (quint64(1) << 1);
263 }
264 // bit 2: italic defined, bit 3: italic value
265 if (t->isDefined(QQSK::Property::Italic)) {
266 sig |= (quint64(1) << 2);
267 if (t->styleProperty<bool>(QQSK::Property::Italic))
268 sig |= (quint64(1) << 3);
269 }
270 // bit 4: pointSize defined, bits 5..: quantized pointSize
271 if (t->isDefined(QQSK::Property::PointSize)) {
272 sig |= (quint64(1) << 4);
273 const qreal ps = t->styleProperty<qreal>(QQSK::Property::PointSize);
274 // 64-bit signature: 5 bits used, 59 bits available for pointSize
275 constexpr int payloadBits = 64 - 5;
276 const qint64 maxQ = (quint64(1) << payloadBits) - 1;
277 const quint64 q = quint64(qBound<qint64>(0, qRound64(ps * 64.0), maxQ));
278 sig |= (q << 5);
279 }
280 return sig;
281}
282
283QList<QQStyleKitReader *> QQStyleKitReader::s_allReaders;
284QQStyleKitReader::PropertyChangesComponents QQStyleKitReader::s_propertyChangesComponents;
285
286QQStyleKitReader::QQStyleKitReader(QObject *parent)
287 : QQStyleKitControlProperties(QQSK::PropertyGroup::Control, parent)
288 , m_dontEmitChangedSignals(false)
289 , m_effectiveVariationsDirty(true)
290 , m_transitionsEnabled(true)
291 , m_completed(false)
292 , m_global(QQStyleKitControlProperties(QQSK::PropertyGroup::GlobalFlag, this))
293{
294}
295
296QQStyleKitReader::~QQStyleKitReader()
297{
298 s_allReaders.removeOne(this);
299}
300
301QQuickStateGroup *QQStyleKitReader::stateGroup()
302{
303 if (m_stateGroup)
304 return m_stateGroup;
305
306 /* Lazy create a QQuickStateGroup as soon as we have delegates
307 * that needs to be "tracked". That is, the user of this StyleKitReader
308 * has read one or more properties, so we need to check and emit changes for
309 * those properties whenever our state changes. */
310 const auto *stylePtr = style();
311 Q_ASSERT(stylePtr);
312
313 m_stateGroup = new QQuickStateGroup(this);
314
315 // Add two states that we can alternate between
316 auto statesProp = m_stateGroup->statesProperty();
317 QQuickState *alternate1 = new QQuickState(m_stateGroup);
318 QQuickState *alternate2 = new QQuickState(m_stateGroup);
319 alternate1->setName(kAlternate1);
320 alternate2->setName(kAlternate2);
321 m_stateGroup->statesProperty().append(&statesProp, alternate1);
322 m_stateGroup->statesProperty().append(&statesProp, alternate2);
323
324 QQmlComponent *controlComp = createControlChangesComponent();
325 instantiatePropertyChanges(controlComp);
326
327 return m_stateGroup;
328}
329
330QQmlComponent *QQStyleKitReader::createControlChangesComponent() const
331{
332 QQmlEngine *engine = qmlEngine(style());
333 auto key = PropertyChangesComponents::key_type{engine, u"control"_s};
334 if (auto r = s_propertyChangesComponents.value(key, nullptr))
335 return r.data();
336
337 const QString qmlControlCode = QString::fromUtf8(R"(
338 import QtQuick
339 PropertyChanges {
340 spacing: global.spacing
341 padding: global.padding
342 leftPadding: global.leftPadding
343 rightPadding: global.rightPadding
344 topPadding: global.topPadding
345 bottomPadding: global.bottomPadding
346 text.color: global.text.color
347 text.alignment: global.text.alignment
348 text.bold: global.text.bold
349 text.italic: global.text.italic
350 text.pointSize: global.text.pointSize
351 text.padding: global.text.padding
352 text.leftPadding: global.text.leftPadding
353 text.rightPadding: global.text.rightPadding
354 text.topPadding: global.text.topPadding
355 text.bottomPadding: global.text.bottomPadding
356 }
357 )");
358
359 // TODO: cache propertyName to component!
360 QQmlComponent *component = new QQmlComponent(engine, engine);
361 component->setData(qmlControlCode.toUtf8(), QUrl());
362 Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
363 s_propertyChangesComponents.insert(key, component);
364 QObject::connect(engine, &QObject::destroyed, engine, [key = std::move(key)] {
365 s_propertyChangesComponents.remove(key);
366 });
367 return component;
368}
369
370QQmlComponent *QQStyleKitReader::createDelegateChangesComponent(const QString &delegateName) const
371{
372 QQmlEngine *engine = qmlEngine(style());
373 auto key = PropertyChangesComponents::key_type{engine, delegateName};
374 if (auto r = s_propertyChangesComponents.value(key, nullptr))
375 return r.data();
376
377 static const QString qmlTemplateCode = QString::fromUtf8(R"(
378 import QtQuick
379 PropertyChanges { $ {
380 width: global.$.width
381 height: global.$.height
382 visible: global.$.visible
383 color: global.$.color
384 gradient: global.$.gradient
385 radius: global.$.radius
386 topLeftRadius: global.$.topLeftRadius
387 topRightRadius: global.$.topRightRadius
388 bottomLeftRadius: global.$.bottomLeftRadius
389 bottomRightRadius: global.$.bottomRightRadius
390 margins: global.$.margins
391 alignment: global.$.alignment
392 leftMargin: global.$.leftMargin
393 rightMargin: global.$.rightMargin
394 topMargin: global.$.topMargin
395 bottomMargin: global.$.bottomMargin
396 scale: global.$.scale
397 rotation: global.$.rotation
398 opacity: global.$.opacity
399 border.color: global.$.border.color
400 border.width: global.$.border.width
401 shadow.color: global.$.shadow.color
402 shadow.scale: global.$.shadow.scale
403 shadow.blur: global.$.shadow.blur
404 shadow.visible: global.$.shadow.visible
405 shadow.opacity: global.$.shadow.opacity
406 shadow.verticalOffset: global.$.shadow.verticalOffset
407 shadow.horizontalOffset: global.$.shadow.horizontalOffset
408 shadow.delegate: global.$.shadow.delegate
409 image.source: global.$.image.source
410 image.color: global.$.image.color
411 image.fillMode: global.$.image.fillMode
412 delegate: global.$.delegate
413 data: global.$.data
414 }}
415 )");
416
417 QString substitutedCode = qmlTemplateCode;
418 substitutedCode.replace('$'_L1, delegateName);
419 QQmlComponent *component = new QQmlComponent(engine, engine);
420 component->setData(substitutedCode.toUtf8(), QUrl());
421 Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
422 s_propertyChangesComponents.insert(key, component);
423 QObject::connect(engine, &QObject::destroyed, engine, [key = std::move(key)] {
424 s_propertyChangesComponents.remove(key);
425 });
426 return component;
427}
428
429void QQStyleKitReader::instantiatePropertyChanges(QQmlComponent *comp)
430{
431 QObject *obj = comp->create(qmlContext(this));
432 auto *propertyChanges = qobject_cast<QQuickPropertyChanges *>(obj);
433 Q_ASSERT(propertyChanges);
434
435 // setter for the "target" property is called setObject
436 propertyChanges->setObject(this);
437 /* set "explicit" to true, meaning that the StyleProperties shouldn't
438 * create bindings, but do one-off assignments. Bindings are not needed
439 * here since it's the state changes of this StyleKitReader that
440 * drives the property changes. The properties cannot change outside of
441 * a state change (or, if they do, it will trigger a full update equal
442 * to a theme change) */
443 propertyChanges->setIsExplicit(true);
444 /* We don't need to ever restore the properties back to default, since
445 * the group state will never be reset back to an empty string. This will
446 * hopefully avoid the generation of restore structures inside the StateGroup. */
447 propertyChanges->setRestoreEntryValues(false);
448
449 /* Add the new PropertyChanges object to both states, A1 and A2 */
450 for (QQuickState *state : stateGroup()->states()) {
451 auto changesProp = state->changes();
452 changesProp.append(&changesProp, propertyChanges);
453 }
454}
455
456void QQStyleKitReader::maybeTrackDelegates()
457{
458 forEachUsedDelegate(
459 [this](QQStyleKitDelegateProperties *delegate, QQSK::Delegate type, const QString &delegatePath){
460 if (m_trackedDelegates.testFlag(type)) {
461 // We're already tracking the delegate. So nothing needs to be done.
462 return;
463 }
464 if (!delegate->visible()) {
465 /* As an optimization, if the delegate is hidden, we don't track it. Most
466 * controls set background.visible to false, for example. If this, for
467 * whatever reason, is not wanted, set opacity to 0 instead. */
468 return;
469 }
470 /* Invariant: The application has read one or more properties for the given delegate
471 * from the Style, but we don't yet have a PropertyChanges object that can track
472 * changes to it (and run transitions). So we create one now. By lazy creating them this
473 * way, we avoid creating PropertyChanges for all the different delegates that a control
474 * _may_ have. Instead, we only track changes for the delegates it actually uses. */
475 m_trackedDelegates.setFlag(type);
476 QQmlComponent *comp = createDelegateChangesComponent(delegatePath);
477 instantiatePropertyChanges(comp);
478 });
479}
480
481void QQStyleKitReader::updateControl(bool applyStateChange)
482{
483 const QQStyleKitStyle *currentStyle = style();
484 if (!m_completed || !currentStyle || !currentStyle->loaded())
485 return;
486
487 if (applyStateChange) {
488 /* Alternate between two states to trigger a state change. The state group
489 * will, upon changing state, take care of reading the updated property values,
490 * compare them against the current ones in the local storage, and emit changes
491 * (possibly using a transition) if changed. Since the new state might change the
492 * transition, we need to update it first before we do the state change, so that
493 * it takes effect.
494 * If we have skipped tracking some delegates because they are hidden, we need to
495 * check again if this is still the case for the current state. Otherwise, we now
496 * need to track them. Untracked delegates are not backed by PropertyChanges objects,
497 * and hence, will not update when we do a state swap below.
498 * Note that the first time this function is called after start-up, none of the
499 * delegates are yet tracked, and therefore will be created now. */
500
501 maybeTrackDelegates();
502
503 auto transitionProp = stateGroup()->transitionsProperty();
504 const int transitionCountInStateGroup = transitionProp.count(&transitionProp);
505 const bool enabled = m_transitionsEnabled && QQStyleKit::qmlAttachedProperties()->transitionsEnabled();
506 QQuickTransition *transitionInStyle = enabled ? transition() : nullptr;
507 QQuickTransition *transitionInStateGroup =
508 transitionCountInStateGroup > 0 ? transitionProp.at(&transitionProp, 0) : nullptr;
509 if (transitionInStyle != transitionInStateGroup) {
510 transitionProp.clear(&transitionProp);
511 if (transitionInStyle)
512 transitionProp.append(&transitionProp, transitionInStyle);
513 }
514
515 switch (m_alternateState) {
516 case AlternateState::Alternate1:
517 m_alternateState = AlternateState::Alternate2;
518 stateGroup()->setState(kAlternate2);
519 break;
520 case AlternateState::Alternate2:
521 m_alternateState = AlternateState::Alternate1;
522 stateGroup()->setState(kAlternate1);
523 break;
524 default:
525 Q_UNREACHABLE();
526 }
527 } else {
528 // Since no state change occurs, the property values in
529 // the local storage no longer reflect this StyleReader's
530 // current state. Clear it to avoid returning stale values
531 // from subsequent property reads.
532 clearLocalStorage();
533 }
535 auto textOverrideSig = textFontOverridesSignature(global()->text());
536 if (m_lastTextFontOverridesSignature != textOverrideSig)
537 m_fontDirty = true;
538 m_lastTextFontOverridesSignature = textOverrideSig;
539 rebuildEffectiveFont();
540}
541
542void QQStyleKitReader::resetReadersForStyle(const QQStyleKitStyle *style)
544 for (QQStyleKitReader *reader : s_allReaders) {
545 if (reader->style() == style) {
546 reader->m_effectiveVariationsDirty = true;
547 reader->m_fontDirty = true;
548 reader->clearLocalStorage();
549 reader->rebuildEffectivePalette();
550 reader->rebuildEffectiveFont();
551 reader->emitChangedForAllStyleProperties(EmitFlag::AllProperties);
552 }
553 }
554}
555
556void QQStyleKitReader::populateLocalStorage()
557{
558 if (!m_completed) {
559 /* The local storage is used for implementing transitions and to ensure that we
560 * only emit changed signals for properties that actually change during a state
561 * change. During start-up, however, all properties are considered dirty, so we
562 * can delay populating the storage until the first time we get a state change
563 * after completed, to improve start-up performance. */
564 return;
565 }
566
567 if (!m_storage.isEmpty())
568 return;
569 const auto *stylePtr = style();
570 if (!stylePtr || !stylePtr->loaded())
571 return;
572
573 /* The local storage is empty, which is typically the case after an
574 * operation that should perform without a transition, such as a theme
575 * change or a change to a property value in the Style itself.
576 * Doing a transition in that case is unwanted and slow (since the
577 * operation typically affect all controls), so we short-cut the process by
578 * emitting changed signals directly for all the properties instead.
579 * Since that will render the local storage out-of-sync, we clear it at the
580 * same time to signal that it's 'dirty'. Which is why we need to sync it back
581 * up again now.
582 * Syncing the local storage before changing state (even if the values that
583 * end up in the storage should be exactly the same as those already
584 * showing), has the upshot that we can compare after the state change which
585 * properties has changed, which will limit the amount of changed signals we
586 * then need to emit. */
587 m_dontEmitChangedSignals = true;
588 updateControl();
589 m_dontEmitChangedSignals = false;
590}
591
592void QQStyleKitReader::clearLocalStorage()
593{
594 /* Clear all the local property overrides that has been set on this reader. Such
595 * overrides are typically interpolated values set by a transition during a state
596 * change. By clearing them, the controls will end up reading the property values
597 * directly from the Style instead. */
598 m_storage.clear();
599}
600
601QQSK::State QQStyleKitReader::controlState() const
602{
603 QQSK::State effectiveState = m_state;
604
605 if (!enabled()) {
606 // Some states are not valid if the control is disabled
607 effectiveState &= ~(QQSK::StateFlag::Pressed |
608 QQSK::StateFlag::Hovered |
609 QQSK::StateFlag::Highlighted |
610 QQSK::StateFlag::Focused |
611 QQSK::StateFlag::Hovered);
612 }
613
614 if (effectiveState == QQSK::StateFlag::Unspecified)
615 effectiveState.setFlag(QQSK::StateFlag::Normal);
616
617 return effectiveState;
618}
620QVariant QQStyleKitReader::readStyleProperty(PropertyStorageId key) const
621{
622 return m_storage.value(key);
623}
625void QQStyleKitReader::writeStyleProperty(PropertyStorageId key, const QVariant &value)
626{
627 m_storage.insert(key, value);
628}
629
630bool QQStyleKitReader::dontEmitChangedSignals() const
631{
632 return m_dontEmitChangedSignals;
633}
634
635QQStyleKitExtendableControlType QQStyleKitReader::controlType() const
636{
637 return m_type;
638}
639
640void QQStyleKitReader::setControlType(QQStyleKitExtendableControlType type)
641{
642 if (m_type == type)
643 return;
644
645 m_type = type;
646 populateLocalStorage();
647 emit controlTypeChanged();
648 updateControl();
649}
650
651#ifdef QT_DEBUG
652QQStyleKitReader::ControlType QQStyleKitReader::typeAsControlType() const
653{
654 /* Note: m_type is of type int to support extending the list
655 * of possible types from the Style itself. This function
656 * is here to for debugging purposes */
657 return ControlType(m_type);
658}
659#endif
660
661bool QQStyleKitReader::hovered() const
662{
663 return m_state.testFlag(QQSK::StateFlag::Hovered);
664}
665
666void QQStyleKitReader::setHovered(bool hovered)
668 if (hovered == QQStyleKitReader::hovered())
669 return;
670
671 populateLocalStorage();
672 m_state.setFlag(QQSK::StateFlag::Hovered, hovered);
673 emit hoveredChanged();
674 updateControl();
675}
676
677bool QQStyleKitReader::enabled() const
678{
679 return !m_state.testFlag(QQSK::StateFlag::Disabled);
680}
681
682void QQStyleKitReader::setEnabled(bool enabled)
684 if (enabled == QQStyleKitReader::enabled())
685 return;
686
687 populateLocalStorage();
688 m_state.setFlag(QQSK::StateFlag::Disabled, !enabled);
689 emit enabledChanged();
690 updateControl();
691}
692
693bool QQStyleKitReader::focused() const
694{
695 return m_state.testFlag(QQSK::StateFlag::Focused);
696}
697
698void QQStyleKitReader::setFocused(bool focused)
700 if (focused == QQStyleKitReader::focused())
701 return;
702
703 populateLocalStorage();
704 m_state.setFlag(QQSK::StateFlag::Focused, focused);
705 emit focusedChanged();
706 updateControl();
707}
708
709bool QQStyleKitReader::checked() const
710{
711 return m_state.testFlag(QQSK::StateFlag::Checked);
712}
713
714void QQStyleKitReader::setChecked(bool checked)
716 if (checked == QQStyleKitReader::checked())
717 return;
718
719 populateLocalStorage();
720 m_state.setFlag(QQSK::StateFlag::Checked, checked);
721 emit checkedChanged();
722 updateControl();
723}
724
725bool QQStyleKitReader::pressed() const
726{
727 return m_state.testFlag(QQSK::StateFlag::Pressed);
728}
729
730void QQStyleKitReader::setPressed(bool pressed)
731{
732 if (pressed == QQStyleKitReader::pressed())
733 return;
734
735 populateLocalStorage();
736 m_state.setFlag(QQSK::StateFlag::Pressed, pressed);
737 emit pressedChanged();
738 updateControl();
739}
741bool QQStyleKitReader::vertical() const
742{
743 return m_state.testFlag(QQSK::StateFlag::Vertical);
744}
745
746void QQStyleKitReader::setVertical(bool vertical)
747{
748 if (vertical == QQStyleKitReader::vertical())
749 return;
751 populateLocalStorage();
752 m_state.setFlag(QQSK::StateFlag::Vertical, vertical);
753 emit verticalChanged();
754 updateControl();
755}
756
757bool QQStyleKitReader::highlighted() const
758{
759 return m_state.testFlag(QQSK::StateFlag::Highlighted);
760}
761
762void QQStyleKitReader::setHighlighted(bool highlighted)
763{
764 if (highlighted == QQStyleKitReader::highlighted())
765 return;
766
767 populateLocalStorage();
768 m_state.setFlag(QQSK::StateFlag::Highlighted, highlighted);
769 emit highlightedChanged();
770 updateControl();
771}
772
773void QQStyleKitReader::setControlTypeAndState(QQStyleKitExtendableControlType controlType, QQSK::State flags,
774 bool applyStateChange)
775{
776 // Apply type and states in one shot to avoid calling
777 // populateLocalStorage() + updateControl() once per individual setter
778 const bool typeChanged = m_type != controlType;
779 const QQSK::State stateChanged = m_state ^ flags;
780
781 if (!typeChanged && !stateChanged)
782 return;
783
784 if (applyStateChange)
785 populateLocalStorage();
786
787 m_type = controlType;
788 m_state = flags;
789
790 updateControl(applyStateChange);
791}
792
793QObject *QQStyleKitReader::target() const
794{
795 return m_target.data();
796}
797
798void QQStyleKitReader::setTarget(QObject *target)
799{
800 /* A reader can optionally set a target object which represents
801 the current object that is being styled, ie: using this StyleReader
802 to read properties from the style. It is needed so that the widget style using
803 the StyleReader is able to post StyleAnimationUpdate events to the correct
804 target during a transition. */
805 m_target = target;
806}
807
808void QQStyleKitReader::setCompleted(bool completed)
810 const QQStyleKitStyle *currentStyle = style();
811 if (currentStyle && currentStyle->loaded()) {
812 clearLocalStorage();
813 rebuildEffectiveFont();
814 rebuildEffectivePalette();
815
816 /* Some style properties were read (e.g. by a Qt Quick Control) before this
817 * StyleReader was completed. Those reads were premature: the values depend not
818 * only on the style and theme, but also on the StyleReader's own state (such as
819 * its controlType, checked state, and so on), which may not have been fully
820 * initialized at that time. Now that all of it is in place, emit change signals
821 * for all properties to force the bindings to re-read them. */
822 emitChangedForAllStyleProperties(EmitFlag::AllProperties);
823 }
824
825 // From now on, we need to be notified when the style or theme changes
826 s_allReaders.append(this);
827
828 m_completed = completed;
829}
830
831bool QQStyleKitReader::transitionsEnabled() const
832{
833 return m_transitionsEnabled;
835
836void QQStyleKitReader::setTransitionsEnabled(bool enabled)
837{
838 if (m_transitionsEnabled == enabled)
839 return;
840 m_transitionsEnabled = enabled;
841}
842
843QQStyleKitStyle *QQStyleKitReader::explicitStyle() const
844{
845 return m_explicitStyle.data();
846}
847
848void QQStyleKitReader::setExplicitStyle(QQStyleKitStyle *style)
849{
850 if (m_explicitStyle == style)
851 return;
852 m_explicitStyle = style;
853
854 if (!m_explicitStyle || !m_explicitStyle->loaded()) {
855 clearLocalStorage();
856 return;
857 }
858
859 m_effectiveVariationsDirty = true;
860 m_fontDirty = true;
861 clearLocalStorage();
862 rebuildEffectivePalette();
863 rebuildEffectiveFont();
864 emitChangedForAllStyleProperties(EmitFlag::AllProperties);
865}
866
867QQuickPalette *QQStyleKitReader::palette() const
868{
869 return m_palette.data();
871
872void QQStyleKitReader::setPalette(QQuickPalette *palette)
873{
874 if (m_palette == palette)
875 return;
876
877 if (m_palette)
878 QObject::disconnect(m_palette, nullptr, this, nullptr);
879
880 m_palette = palette;
881 emit paletteChanged();
882
883 if (m_palette) {
884 // changed signal will be triggered when any role changes
885 QObject::connect(m_palette, &QQuickPalette::changed,
886 this, &QQStyleKitReader::onPaletteChanged);
887 }
888
889 onPaletteChanged();
890}
891
892QPalette QQStyleKitReader::effectivePalette() const
893{
894 return m_effectivePalette;
895}
896
897void QQStyleKitReader::onPaletteChanged()
898{
899 const QQStyleKitStyle *currentStyle = style();
900 if (!m_completed || !currentStyle || !currentStyle->loaded())
901 return;
902
903 if (rebuildEffectivePalette()) {
904 clearLocalStorage();
905 emitChangedForAllStyleProperties(EmitFlag::Colors);
906 }
907}
909bool QQStyleKitReader::rebuildEffectivePalette()
910{
911 auto mergedPalette = style()->paletteForControlType(this->controlType());
912 const auto stylePaletteResolveMask = mergedPalette.resolveMask();
913 if (m_palette) {
914 // The control palette takes precedence over the style palette
915 const auto controlPalette = m_palette->toQPalette();
916 mergedPalette = controlPalette.resolve(mergedPalette);
917 // Explicitly set the resolve mask to make sure it is not lost during the resolve operation
918 // when the control palette has a resolveMask of 0
919 mergedPalette.setResolveMask(stylePaletteResolveMask | controlPalette.resolveMask());
920 }
921 if (m_effectivePalette == mergedPalette)
922 return false;
923
924 m_effectivePalette = mergedPalette;
925 return true;
926}
927
928QFont QQStyleKitReader::font() const
929{
930 return m_font;
931}
932
933bool QQStyleKitReader::rebuildEffectiveFont()
934{
935 const QQStyleKitStyle *currentStyle = style();
936 if (!currentStyle || !currentStyle->loaded())
937 return false;
938
939 if (!m_fontDirty)
940 return false;
941
942 QFont font = currentStyle->fontForControlType(controlType());
943 const QQStyleKitTextProperties *textProps = global()->text();
944 if (textProps) {
945 if (textProps->isDefined(QQSK::Property::Bold))
946 font.setBold(textProps->bold());
947 if (textProps->isDefined(QQSK::Property::Italic))
948 font.setItalic(textProps->italic());
949 if (textProps->isDefined(QQSK::Property::PointSize))
950 font.setPointSizeF(textProps->pointSize());
951 }
952
953 if (m_font == font)
954 return false;
955
956 m_font = font;
957 emit fontChanged();
958 return true;
959}
960
961QQStyleKitControlProperties *QQStyleKitReader::global() const
962{
963 return &const_cast<QQStyleKitReader *>(this)->m_global;
964}
965
966void QQStyleKitReader::componentComplete()
967{
968 setCompleted(true);
969}
970
971QT_END_NAMESPACE
972
973#include "moc_qqstylekitreader_p.cpp"
Combined button and popup list for selecting options.
static quint64 textFontOverridesSignature(const QQStyleKitTextProperties *t)
static constexpr QLatin1StringView kAlternate1
static constexpr QLatin1StringView kAlternate2