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
16using namespace Qt::StringLiterals;
17
18static const QString kAlternate1 = "A1"_L1;
19static const QString kAlternate2 = "A2"_L1;
20
22{
23 if (!t)
24 return 0;
25
26 quint64 sig = 0;
27
28 // bit 0: bold defined, bit 1: bold value
29 if (t->isDefined(QQSK::Property::Bold)) {
30 sig |= (quint64(1) << 0);
31 if (t->styleProperty<bool>(QQSK::Property::Bold))
32 sig |= (quint64(1) << 1);
33 }
34 // bit 2: italic defined, bit 3: italic value
35 if (t->isDefined(QQSK::Property::Italic)) {
36 sig |= (quint64(1) << 2);
37 if (t->styleProperty<bool>(QQSK::Property::Italic))
38 sig |= (quint64(1) << 3);
39 }
40 // bit 4: pointSize defined, bits 5..: quantized pointSize
41 if (t->isDefined(QQSK::Property::PointSize)) {
42 sig |= (quint64(1) << 4);
43 const qreal ps = t->styleProperty<qreal>(QQSK::Property::PointSize);
44 // 64-bit signature: 5 bits used, 59 bits available for pointSize
45 constexpr int payloadBits = 64 - 5;
46 const qint64 maxQ = (quint64(1) << payloadBits) - 1;
47 const quint64 q = quint64(qBound<qint64>(0, qRound64(ps * 64.0), maxQ));
48 sig |= (q << 5);
49 }
50 return sig;
51}
52
54QMap<QString, QQmlComponent *> QQStyleKitReader::s_propertyChangesComponents;
55
56QQStyleKitReader::QQStyleKitReader(QObject *parent)
57 : QQStyleKitControlProperties(QQSK::PropertyGroup::Control, parent)
58 , m_dontEmitChangedSignals(false)
59 , m_effectiveVariationsDirty(true)
60 , m_global(QQStyleKitControlProperties(QQSK::PropertyGroup::GlobalFlag, this))
61{
62 s_allReaders.append(this);
63}
64
66{
67 s_allReaders.removeOne(this);
68}
69
70QQuickStateGroup *QQStyleKitReader::stateGroup()
71{
72 if (m_stateGroup)
73 return m_stateGroup;
74
75 /* Lazy create a StyleKitReaderStateGroup as soon as we have delegates
76 * that needs to be "tracked". That is, the user of this StyleKitReader
77 * has read one or more properties, so we need to check and emit changes for
78 * those properties whenever our state changes. */
79 const auto *stylePtr = style();
80 Q_ASSERT(stylePtr);
81
82 m_stateGroup = new QQuickStateGroup(this);
83
84 // Add two states that we can alternate between
85 auto statesProp = m_stateGroup->statesProperty();
86 QQuickState *alternate1 = new QQuickState(m_stateGroup);
87 QQuickState *alternate2 = new QQuickState(m_stateGroup);
88 alternate1->setName(kAlternate1);
89 alternate2->setName(kAlternate2);
90 m_stateGroup->statesProperty().append(&statesProp, alternate1);
91 m_stateGroup->statesProperty().append(&statesProp, alternate2);
92
93 QQmlComponent *controlComp = createControlChangesComponent();
94 instantiatePropertyChanges(controlComp);
95
96 return m_stateGroup;
97}
98
99QQmlComponent *QQStyleKitReader::createControlChangesComponent() const
100{
101 static const QLatin1String propertyName("control"_L1);
102 if (s_propertyChangesComponents.contains(propertyName))
103 return s_propertyChangesComponents.value(propertyName);
104
105 const QString qmlControlCode = QString::fromUtf8(R"(
106 import QtQuick
107 PropertyChanges {
108 spacing: global.spacing
109 padding: global.padding
110 leftPadding: global.leftPadding
111 rightPadding: global.rightPadding
112 topPadding: global.topPadding
113 bottomPadding: global.bottomPadding
114 text.color: global.text.color
115 text.alignment: global.text.alignment
116 text.bold: global.text.bold
117 text.italic: global.text.italic
118 text.pointSize: global.text.pointSize
119 text.padding: global.text.padding
120 text.leftPadding: global.text.leftPadding
121 text.rightPadding: global.text.rightPadding
122 text.topPadding: global.text.topPadding
123 text.bottomPadding: global.text.bottomPadding
124 }
125 )");
126
127 // TODO: cache propertyName to component!
128 QQmlComponent *component = new QQmlComponent(qmlEngine(style()));
129 component->setData(qmlControlCode.toUtf8(), QUrl());
130 Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
131 s_propertyChangesComponents.insert(propertyName, component);
132 return component;
133}
134
135QQmlComponent *QQStyleKitReader::createDelegateChangesComponent(const QString &delegateName) const
136{
137 if (s_propertyChangesComponents.contains(delegateName))
138 return s_propertyChangesComponents.value(delegateName);
139
140 static const QString qmlTemplateCode = QString::fromUtf8(R"(
141 import QtQuick
142 PropertyChanges { $ {
143 implicitWidth: global.$.implicitWidth
144 implicitHeight: global.$.implicitHeight
145 visible: global.$.visible
146 color: global.$.color
147 gradient: global.$.gradient
148 radius: global.$.radius
149 topLeftRadius: global.$.topLeftRadius
150 topRightRadius: global.$.topRightRadius
151 bottomLeftRadius: global.$.bottomLeftRadius
152 bottomRightRadius: global.$.bottomRightRadius
153 margins: global.$.margins
154 alignment: global.$.alignment
155 leftMargin: global.$.leftMargin
156 rightMargin: global.$.rightMargin
157 topMargin: global.$.topMargin
158 bottomMargin: global.$.bottomMargin
159 scale: global.$.scale
160 rotation: global.$.rotation
161 opacity: global.$.opacity
162 border.color: global.$.border.color
163 border.width: global.$.border.width
164 shadow.color: global.$.shadow.color
165 shadow.scale: global.$.shadow.scale
166 shadow.blur: global.$.shadow.blur
167 shadow.visible: global.$.shadow.visible
168 shadow.opacity: global.$.shadow.opacity
169 shadow.verticalOffset: global.$.shadow.verticalOffset
170 shadow.horizontalOffset: global.$.shadow.horizontalOffset
171 shadow.delegate: global.$.shadow.delegate
172 image.source: global.$.image.source
173 image.color: global.$.image.color
174 image.fillMode: global.$.image.fillMode
175 delegate: global.$.delegate
176 data: global.$.data
177 }}
178 )");
179
180 QString substitutedCode = qmlTemplateCode;
181 substitutedCode.replace('$'_L1, delegateName);
182 QQmlComponent *component = new QQmlComponent(qmlEngine(style()));
183 component->setData(substitutedCode.toUtf8(), QUrl());
184 Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
185 s_propertyChangesComponents.insert(delegateName, component);
186 return component;
187}
188
189void QQStyleKitReader::instantiatePropertyChanges(QQmlComponent *comp)
190{
191 QObject *obj = comp->create(qmlContext(this));
192 auto *propertyChanges = qobject_cast<QQuickPropertyChanges *>(obj);
193 Q_ASSERT(propertyChanges);
194
195 // setter for the "target" property is called setObject
196 propertyChanges->setObject(this);
197 /* set "explicit" to true, meaning that the StyleProperties shouldn't
198 * create bindings, but do one-off assignments. Bindings are not needed
199 * here since it's the state changes of this StyleKitReader that
200 * drives the property changes. The properties cannot change outside of
201 * a state change (or, if they do, it will trigger a full update equal
202 * to a theme change) */
203 propertyChanges->setIsExplicit(true);
204 /* We don't need to ever restore the properties back to default, since
205 * the group state will never be reset back to an empty string. This will
206 * hopefully avoid the generation of restore structures inside the StateGroup. */
207 propertyChanges->setRestoreEntryValues(false);
208
209 /* Add the new PropertyChanges object to both states, A1 and A2 */
210 for (QQuickState *state : stateGroup()->states()) {
211 auto changesProp = state->changes();
212 changesProp.append(&changesProp, propertyChanges);
213 }
214}
215
216void QQStyleKitReader::maybeTrackDelegates()
217{
218 forEachUsedDelegate(
219 [this](QQStyleKitDelegateProperties *delegate, QQSK::Delegate type, const QString &delegatePath){
220 if (m_trackedDelegates.testFlag(type)) {
221 // We're already tracking the delegate. So nothing needs to be done.
222 return;
223 }
224 if (!delegate->visible()) {
225 /* As an optimization, if the delegate is hidden, we don't track it. Most
226 * controls set background.visible to false, for example. If this, for
227 * whatever reason, is not wanted, set opacity to 0 instead. */
228 return;
229 }
230 /* Invariant: The application has read one or more properties for the given delegate
231 * from the Style, but we don't yet have a PropertyChanges object that can track
232 * changes to it (and run transitions). So we create one now. By lazy creating them this
233 * way, we avoid creating PropertyChanges for all the different delegates that a control
234 * _may_ have. Instead, we only track changes for the delegates it actually uses. */
235 m_trackedDelegates.setFlag(type);
236 QQmlComponent *comp = createDelegateChangesComponent(delegatePath);
237 instantiatePropertyChanges(comp);
238 });
239}
240
241void QQStyleKitReader::updateControl()
242{
244 if (!style || !style->loaded())
245 return;
246
247 /* Alternate between two states to trigger a state change. The state group
248 * will, upon changing state, take care of reading the updated property values,
249 * compare them against the current ones in the local storage, and emit changes
250 * (possibly using a transition) if changed. Since the new state might change the
251 * transition, we need to update it first before we do the state change, so that
252 * it takes effect.
253 * If we have skipped tracking some delegates because they are hidden, we need to
254 * check again if this is still the case for the current state. Otherwise, we now
255 * need to track them. Untracked delegates are not backed by PropertyChanges objects,
256 * and hence, will not update when we do a state swap below.
257 * Note that the first time this function is called after start-up, none of the
258 * delegates are yet tracked, and therefore will be created now. */
259
260 maybeTrackDelegates();
261
262 auto transitionProp = stateGroup()->transitionsProperty();
263 const int transitionCountInStateGroup = transitionProp.count(&transitionProp);
264 const bool enabled = QQStyleKit::qmlAttachedProperties()->transitionsEnabled();
265 QQuickTransition *transitionInStyle = enabled ? transition() : nullptr;
266 QQuickTransition *transitionInStateGroup =
267 transitionCountInStateGroup > 0 ? transitionProp.at(&transitionProp, 0) : nullptr;
268 if (transitionInStyle != transitionInStateGroup) {
269 transitionProp.clear(&transitionProp);
270 if (transitionInStyle)
271 transitionProp.append(&transitionProp, transitionInStyle);
272 }
273
274 switch (m_alternateState) {
275 case AlternateState::Alternate1:
276 m_alternateState = AlternateState::Alternate2;
277 stateGroup()->setState(kAlternate2);
278 break;
279 case AlternateState::Alternate2:
280 m_alternateState = AlternateState::Alternate1;
281 stateGroup()->setState(kAlternate1);
282 break;
283 default:
284 Q_UNREACHABLE();
285 }
286
287 auto textOverrideSig = textFontOverridesSignature(global()->text());
288 if (m_lastTextFontOverridesSignature != textOverrideSig)
289 m_fontDirty = true;
290 m_lastTextFontOverridesSignature = textOverrideSig;
291 rebuildEffectiveFont();
292}
293
295{
296 for (QQStyleKitReader *reader : s_allReaders) {
297 reader->m_effectiveVariationsDirty = true;
298 reader->m_fontDirty = true;
299 reader->clearLocalStorage();
300 reader->rebuildEffectivePalette();
301 reader->rebuildEffectiveFont();
302 reader->emitChangedForAllStyleProperties(EmitFlag::AllProperties);
304}
305
306void QQStyleKitReader::populateLocalStorage()
307{
308 if (!m_storage.isEmpty())
309 return;
310 const auto *stylePtr = style();
311 if (!stylePtr || !stylePtr->loaded())
312 return;
313
314 /* The local storage is empty, which is typically the case after an
315 * operation that should perform without a transition, such as a theme
316 * change or a change to a property value in the Style itself.
317 * Doing a transition in that case is unwanted and slow (since the
318 * operation typically affect all controls), so we short-cut the process by
319 * emitting changed signals directly for all the properties instead.
320 * Since that will render the local storage out-of-sync, we clear it at the
321 * same time to signal that it's 'dirty'. Which is why we need to sync it back
322 * up again now.
323 * Syncing the local storage before changing state (even if the values that
324 * end up in the storage should be exactly the same as those already
325 * showing), has the upshot that we can compare after the state change which
326 * properties has changed, which will limit the amount of changed signals we
327 * then need to emit. */
328 m_dontEmitChangedSignals = true;
329 updateControl();
330 m_dontEmitChangedSignals = false;
331}
332
334{
335 /* Clear all the local property overrides that has been set on this reader. Such
336 * overrides are typically interpolated values set by a transition during a state
337 * change. By clearing them, the controls will end up reading the property values
338 * directly from the Style instead. */
339 m_storage.clear();
340}
341
343{
344 QQSK::State effectiveState = m_state;
345
346 if (!enabled()) {
347 // Some states are not valid if the control is disabled
348 effectiveState &= ~(QQSK::StateFlag::Pressed |
349 QQSK::StateFlag::Hovered |
350 QQSK::StateFlag::Highlighted |
351 QQSK::StateFlag::Focused |
352 QQSK::StateFlag::Hovered);
353 }
354
355 if (effectiveState == QQSK::StateFlag::Unspecified)
356 effectiveState.setFlag(QQSK::StateFlag::Normal);
357
358 return effectiveState;
359}
361QVariant QQStyleKitReader::readStyleProperty(PropertyStorageId key) const
362{
363 return m_storage.value(key);
364}
366void QQStyleKitReader::writeStyleProperty(PropertyStorageId key, const QVariant &value)
367{
368 m_storage.insert(key, value);
369}
370
371bool QQStyleKitReader::dontEmitChangedSignals() const
372{
373 return m_dontEmitChangedSignals;
374}
375
380
381void QQStyleKitReader::setControlType(QQStyleKitExtendableControlType type)
382{
383 if (m_type == type)
384 return;
385
386 m_type = type;
387 populateLocalStorage();
388 emit controlTypeChanged();
389 updateControl();
390}
391
392#ifdef QT_DEBUG
393QQStyleKitReader::ControlType QQStyleKitReader::typeAsControlType() const
394{
395 /* Note: m_type is of type int to support extending the list
396 * of possible types from the Style itself. This function
397 * is here to for debugging purposes */
398 return ControlType(m_type);
399}
400#endif
401
402bool QQStyleKitReader::hovered() const
403{
404 return m_state.testFlag(QQSK::StateFlag::Hovered);
405}
406
407void QQStyleKitReader::setHovered(bool hovered)
409 if (hovered == QQStyleKitReader::hovered())
410 return;
411
412 populateLocalStorage();
413 m_state.setFlag(QQSK::StateFlag::Hovered, hovered);
414 emit hoveredChanged();
415 updateControl();
416}
417
418bool QQStyleKitReader::enabled() const
419{
420 return !m_state.testFlag(QQSK::StateFlag::Disabled);
421}
422
423void QQStyleKitReader::setEnabled(bool enabled)
425 if (enabled == QQStyleKitReader::enabled())
426 return;
427
428 populateLocalStorage();
429 m_state.setFlag(QQSK::StateFlag::Disabled, !enabled);
430 emit enabledChanged();
431 updateControl();
432}
433
434bool QQStyleKitReader::focused() const
435{
436 return m_state.testFlag(QQSK::StateFlag::Focused);
437}
438
439void QQStyleKitReader::setFocused(bool focused)
441 if (focused == QQStyleKitReader::focused())
442 return;
443
444 populateLocalStorage();
445 m_state.setFlag(QQSK::StateFlag::Focused, focused);
446 emit focusedChanged();
447 updateControl();
448}
449
450bool QQStyleKitReader::checked() const
451{
452 return m_state.testFlag(QQSK::StateFlag::Checked);
453}
454
455void QQStyleKitReader::setChecked(bool checked)
457 if (checked == QQStyleKitReader::checked())
458 return;
459
460 populateLocalStorage();
461 m_state.setFlag(QQSK::StateFlag::Checked, checked);
462 emit checkedChanged();
463 updateControl();
464}
465
466bool QQStyleKitReader::pressed() const
467{
468 return m_state.testFlag(QQSK::StateFlag::Pressed);
469}
470
471void QQStyleKitReader::setPressed(bool pressed)
472{
473 if (pressed == QQStyleKitReader::pressed())
474 return;
475
476 populateLocalStorage();
477 m_state.setFlag(QQSK::StateFlag::Pressed, pressed);
478 emit pressedChanged();
479 updateControl();
480}
482bool QQStyleKitReader::vertical() const
483{
484 return m_state.testFlag(QQSK::StateFlag::Vertical);
485}
486
487void QQStyleKitReader::setVertical(bool vertical)
488{
489 if (vertical == QQStyleKitReader::vertical())
490 return;
491
492 populateLocalStorage();
493 m_state.setFlag(QQSK::StateFlag::Vertical, vertical);
494 emit verticalChanged();
495 updateControl();
496}
497
498bool QQStyleKitReader::highlighted() const
499{
500 return m_state.testFlag(QQSK::StateFlag::Highlighted);
501}
502
503void QQStyleKitReader::setHighlighted(bool highlighted)
504{
505 if (highlighted == QQStyleKitReader::highlighted())
506 return;
507
508 populateLocalStorage();
509 m_state.setFlag(QQSK::StateFlag::Highlighted, highlighted);
510 emit highlightedChanged();
511 updateControl();
512}
513
514QQuickPalette *QQStyleKitReader::palette() const
515{
516 return m_palette.data();
518
519void QQStyleKitReader::setPalette(QQuickPalette *palette)
520{
521 if (m_palette == palette)
522 return;
523
524 if (m_palette)
525 QObject::disconnect(m_palette, nullptr, this, nullptr);
526
527 m_palette = palette;
528 emit paletteChanged();
529
530 if (m_palette) {
531 // changed signal will be triggered when any role changes
532 QObject::connect(m_palette, &QQuickPalette::changed,
533 this, &QQStyleKitReader::onPaletteChanged);
534 }
535
536 onPaletteChanged();
537}
538
540{
541 return m_effectivePalette;
542}
543
544void QQStyleKitReader::onPaletteChanged()
545{
547 if (!style || !style->loaded())
548 return;
549
550 if (rebuildEffectivePalette()) {
552 emitChangedForAllStyleProperties(EmitFlag::Colors);
553 }
554}
555
556bool QQStyleKitReader::rebuildEffectivePalette()
557{
558 auto mergedPalette = style()->paletteForControlType(this->controlType());
559 const auto stylePaletteResolveMask = mergedPalette.resolveMask();
560 if (m_palette) {
561 // The control palette takes precedence over the style palette
562 const auto controlPalette = m_palette->toQPalette();
563 mergedPalette = controlPalette.resolve(mergedPalette);
564 // Explicitly set the resolve mask to make sure it is not lost during the resolve operation
565 // when the control palette has a resolveMask of 0
566 mergedPalette.setResolveMask(stylePaletteResolveMask | controlPalette.resolveMask());
567 }
568 if (m_effectivePalette == mergedPalette)
569 return false;
570
571 m_effectivePalette = mergedPalette;
572 return true;
573}
574
576{
577 return m_font;
578}
579
580bool QQStyleKitReader::rebuildEffectiveFont()
581{
583 if (!style || !style->loaded())
584 return false;
585
586 if (!m_fontDirty)
587 return false;
588
589 QFont font = style->fontForControlType(controlType());
591 if (textProps) {
592 if (textProps->isDefined(QQSK::Property::Bold))
593 font.setBold(textProps->bold());
594 if (textProps->isDefined(QQSK::Property::Italic))
595 font.setItalic(textProps->italic());
596 if (textProps->isDefined(QQSK::Property::PointSize))
597 font.setPointSizeF(textProps->pointSize());
598 }
599
600 if (m_font == font)
601 return false;
602
603 m_font = font;
604 emit fontChanged();
605 return true;
606}
607
609{
610 return &const_cast<QQStyleKitReader *>(this)->m_global;
611}
612
613QT_END_NAMESPACE
614
615#include "moc_qqstylekitreader_p.cpp"
QQStyleKitTextProperties * text() const
QVariant readStyleProperty(PropertyStorageId key) const
void setHovered(bool hovered)
void setPressed(bool pressed)
QQSK::State controlState() const
void setFocused(bool focused)
void setPalette(QQuickPalette *palette)
QQStyleKitControlProperties * global() const
void setControlType(QQStyleKitExtendableControlType type)
QPalette effectivePalette() const
static QList< QQStyleKitReader * > s_allReaders
void setVertical(bool vertical)
QQStyleKitExtendableControlType controlType() const
QQuickPalette * palette() const
void setEnabled(bool enabled)
void setHighlighted(bool highlighted)
void setChecked(bool checked)
void writeStyleProperty(PropertyStorageId key, const QVariant &value)
static QQStyleKitStyle * current()
Combined button and popup list for selecting options.
static quint64 textFontOverridesSignature(const QQStyleKitTextProperties *t)
static const QString kAlternate2
static const QString kAlternate1