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_effectiveFontDirty = true;
290 m_lastTextFontOverridesSignature = textOverrideSig;
291 rebuildEffectiveFont();
292}
293
295{
296 for (QQStyleKitReader *reader : s_allReaders) {
297 reader->m_effectiveVariationsDirty = true;
298 reader->clearLocalStorage();
299 reader->rebuildEffectivePalette();
300 reader->rebuildEffectiveFont();
301 reader->emitChangedForAllStyleProperties();
303}
304
305void QQStyleKitReader::populateLocalStorage()
306{
307 if (!m_storage.isEmpty())
308 return;
309 const auto *stylePtr = style();
310 if (!stylePtr || !stylePtr->loaded())
311 return;
312
313 /* The local storage is empty, which is typically the case after an
314 * operation that should perform without a transition, such as a theme
315 * change or a change to a property value in the Style itself.
316 * Doing a transition in that case is unwanted and slow (since the
317 * operation typically affect all controls), so we short-cut the process by
318 * emitting changed signals directly for all the properties instead.
319 * Since that will render the local storage out-of-sync, we clear it at the
320 * same time to signal that it's 'dirty'. Which is why we need to sync it back
321 * up again now.
322 * Syncing the local storage before changing state (even if the values that
323 * end up in the storage should be exactly the same as those already
324 * showing), has the upshot that we can compare after the state change which
325 * properties has changed, which will limit the amount of changed signals we
326 * then need to emit. */
327 m_dontEmitChangedSignals = true;
328 updateControl();
329 m_dontEmitChangedSignals = false;
330}
331
333{
334 /* Clear all the local property overrides that has been set on this reader. Such
335 * overrides are typically interpolated values set by a transition during a state
336 * change. By clearing them, the controls will end up reading the property values
337 * directly from the Style instead. */
338 m_storage.clear();
339}
340
342{
343 QQSK::State effectiveState = m_state;
344
345 if (!enabled()) {
346 // Some states are not valid if the control is disabled
347 effectiveState &= ~(QQSK::StateFlag::Pressed |
348 QQSK::StateFlag::Hovered |
349 QQSK::StateFlag::Highlighted |
350 QQSK::StateFlag::Focused |
351 QQSK::StateFlag::Hovered);
352 }
353
354 if (effectiveState == QQSK::StateFlag::Unspecified)
355 effectiveState.setFlag(QQSK::StateFlag::Normal);
356
357 return effectiveState;
358}
360QVariant QQStyleKitReader::readStyleProperty(PropertyStorageId key) const
361{
362 return m_storage.value(key);
363}
365void QQStyleKitReader::writeStyleProperty(PropertyStorageId key, const QVariant &value)
366{
367 m_storage.insert(key, value);
368}
369
370bool QQStyleKitReader::dontEmitChangedSignals() const
371{
372 return m_dontEmitChangedSignals;
373}
374
376{
377 return m_type;
378}
379
380void QQStyleKitReader::setType(QQStyleKitExtendableControlType type)
381{
382 if (m_type == type)
383 return;
384
385 m_type = type;
386 populateLocalStorage();
387 emit typeChanged();
388 updateControl();
389}
390
391#ifdef QT_DEBUG
392QQStyleKitReader::ControlType QQStyleKitReader::typeAsControlType() const
393{
394 /* Note: m_type is of type int to support extending the list
395 * of possible types from the Style itself. This function
396 * is here to for debugging purposes */
397 return ControlType(m_type);
398}
399#endif
400
401bool QQStyleKitReader::hovered() const
402{
403 return m_state.testFlag(QQSK::StateFlag::Hovered);
404}
405
406void QQStyleKitReader::setHovered(bool hovered)
408 if (hovered == QQStyleKitReader::hovered())
409 return;
410
411 populateLocalStorage();
412 m_state.setFlag(QQSK::StateFlag::Hovered, hovered);
413 emit hoveredChanged();
414 updateControl();
415}
416
417bool QQStyleKitReader::enabled() const
418{
419 return !m_state.testFlag(QQSK::StateFlag::Disabled);
420}
421
422void QQStyleKitReader::setEnabled(bool enabled)
424 if (enabled == QQStyleKitReader::enabled())
425 return;
426
427 populateLocalStorage();
428 m_state.setFlag(QQSK::StateFlag::Disabled, !enabled);
429 emit enabledChanged();
430 updateControl();
431}
432
433bool QQStyleKitReader::focused() const
434{
435 return m_state.testFlag(QQSK::StateFlag::Focused);
436}
437
438void QQStyleKitReader::setFocused(bool focused)
440 if (focused == QQStyleKitReader::focused())
441 return;
442
443 populateLocalStorage();
444 m_state.setFlag(QQSK::StateFlag::Focused, focused);
445 emit focusedChanged();
446 updateControl();
447}
448
449bool QQStyleKitReader::checked() const
450{
451 return m_state.testFlag(QQSK::StateFlag::Checked);
452}
453
454void QQStyleKitReader::setChecked(bool checked)
456 if (checked == QQStyleKitReader::checked())
457 return;
458
459 populateLocalStorage();
460 m_state.setFlag(QQSK::StateFlag::Checked, checked);
461 emit checkedChanged();
462 updateControl();
463}
464
465bool QQStyleKitReader::pressed() const
466{
467 return m_state.testFlag(QQSK::StateFlag::Pressed);
468}
469
470void QQStyleKitReader::setPressed(bool pressed)
471{
472 if (pressed == QQStyleKitReader::pressed())
473 return;
474
475 populateLocalStorage();
476 m_state.setFlag(QQSK::StateFlag::Pressed, pressed);
477 emit pressedChanged();
478 updateControl();
479}
481bool QQStyleKitReader::vertical() const
482{
483 return m_state.testFlag(QQSK::StateFlag::Vertical);
484}
485
486void QQStyleKitReader::setVertical(bool vertical)
487{
488 if (vertical == QQStyleKitReader::vertical())
489 return;
490
491 populateLocalStorage();
492 m_state.setFlag(QQSK::StateFlag::Vertical, vertical);
493 emit verticalChanged();
494 updateControl();
495}
496
497bool QQStyleKitReader::highlighted() const
498{
499 return m_state.testFlag(QQSK::StateFlag::Highlighted);
500}
501
502void QQStyleKitReader::setHighlighted(bool highlighted)
503{
504 if (highlighted == QQStyleKitReader::highlighted())
505 return;
506
507 populateLocalStorage();
508 m_state.setFlag(QQSK::StateFlag::Highlighted, highlighted);
509 emit highlightedChanged();
510 updateControl();
511}
512
513QQuickPalette *QQStyleKitReader::palette() const
514{
515 return m_palette.data();
517
518void QQStyleKitReader::setPalette(QQuickPalette *palette)
519{
520 if (m_palette == palette)
521 return;
522
523 if (m_palette)
524 QObject::disconnect(m_palette, nullptr, this, nullptr);
525
526 m_palette = palette;
527 emit paletteChanged();
528
529 if (m_palette) {
530 // changed signal will be triggered when any role changes
531 QObject::connect(m_palette, &QQuickPalette::changed,
532 this, &QQStyleKitReader::onPaletteChanged);
534
535 onPaletteChanged();
536}
537
539{
540 return m_effectivePalette;
541}
542
543void QQStyleKitReader::onPaletteChanged()
544{
546 if (!style || !style->loaded())
547 return;
548
549 if (rebuildEffectivePalette()) {
552 }
553}
554
555bool QQStyleKitReader::rebuildEffectivePalette()
556{
557 auto mergedPalette = style()->paletteForControlType(this->type());
558 const auto stylePaletteResolveMask = mergedPalette.resolveMask();
559 if (m_palette) {
560 // The control palette takes precedence over the style palette
561 const auto controlPalette = m_palette->toQPalette();
562 mergedPalette = controlPalette.resolve(mergedPalette);
563 // Explicitly set the resolve mask to make sure it is not lost during the resolve operation
564 // when the control palette has a resolveMask of 0
565 mergedPalette.setResolveMask(stylePaletteResolveMask | controlPalette.resolveMask());
566 }
567 if (m_effectivePalette == mergedPalette)
568 return false;
569
570 m_effectivePalette = mergedPalette;
571 return true;
572}
573
575{
576 return m_font;
577}
578
579void QQStyleKitReader::setFont(const QFont &font)
580{
581 if (m_font == font)
582 return;
583
584 m_font = font;
585 m_effectiveFontDirty = true;
586 emit fontChanged();
587
588 rebuildEffectiveFont();
589}
590
592{
593 return m_effectiveFont;
594}
595
596bool QQStyleKitReader::rebuildEffectiveFont()
597{
599 if (!style || !style->loaded())
600 return false;
601
602 if (!m_effectiveFontDirty)
603 return false;
604
605 // Rebuild font from style and control font
606 // Control font takes precedence over style font
607 QFont mergedFont = style->fontForControlType(this->type());
608 mergedFont = m_font.resolve(mergedFont);
609 mergedFont.setResolveMask(mergedFont.resolveMask() | m_font.resolveMask());
610
612 if (textProps) {
613 if (textProps->isDefined(QQSK::Property::Bold))
614 mergedFont.setBold(textProps->styleProperty<bool>(QQSK::Property::Bold));
615 if (textProps->isDefined(QQSK::Property::Italic))
616 mergedFont.setItalic(textProps->styleProperty<bool>(QQSK::Property::Italic));
617 if (textProps->isDefined(QQSK::Property::PointSize))
618 mergedFont.setPointSizeF(textProps->styleProperty<qreal>(QQSK::Property::PointSize));
619 }
620
621 if (m_effectiveFont == mergedFont)
622 return false;
623
624 m_effectiveFont = mergedFont;
625 emit effectiveFontChanged();
626 return true;
627}
628
630{
631 return &const_cast<QQStyleKitReader *>(this)->m_global;
632}
633
634QT_END_NAMESPACE
635
636#include "moc_qqstylekitreader_p.cpp"
QQStyleKitTextProperties * text() const
void setType(QQStyleKitExtendableControlType type)
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)
QFont effectiveFont() const
void setFont(const QFont &font)
QQStyleKitExtendableControlType type() const
QQStyleKitControlProperties * global() const
QPalette effectivePalette() const
static QList< QQStyleKitReader * > s_allReaders
void setVertical(bool vertical)
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