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
qwindows11style.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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// Qt-Security score:significant reason:default
4
6#include <qstylehints.h>
7#include <private/qstyleanimation_p.h>
8#include <private/qstyle_p.h>
9#include <private/qstylehelper_p.h>
10#include <private/qapplication_p.h>
11#include <private/qcombobox_p.h>
12#include <qstyleoption.h>
13#include <qpainter.h>
14#include <qpainterstateguard.h>
15#include <QGraphicsDropShadowEffect>
16#include <QLatin1StringView>
17#include <QtCore/qoperatingsystemversion.h>
18#include <QtWidgets/qcombobox.h>
19#if QT_CONFIG(commandlinkbutton)
20#include <QtWidgets/qcommandlinkbutton.h>
21#endif
22#include <QtWidgets/qgraphicsview.h>
23#include <QtWidgets/qlistview.h>
24#include <QtWidgets/qmenu.h>
25#if QT_CONFIG(mdiarea)
26#include <QtWidgets/qmdiarea.h>
27#endif
28#include <QtWidgets/qplaintextedit.h>
29#include <QtWidgets/qtextedit.h>
30#include <QtWidgets/qtreeview.h>
31#if QT_CONFIG(datetimeedit)
32# include <QtWidgets/qdatetimeedit.h>
33#endif
34#if QT_CONFIG(tabwidget)
35# include <QtWidgets/qtabwidget.h>
36#endif
37#if QT_CONFIG(menubar)
38# include <QtWidgets/qmenubar.h>
39#endif
40#if QT_CONFIG(tooltip)
41#include "private/qtooltip_p.h"
42#endif
43#include "qdrawutil.h"
44#include <chrono>
45#include <dwmapi.h>
46
47QT_BEGIN_NAMESPACE
48
49using namespace Qt::StringLiterals;
50
51static constexpr int topLevelRoundingRadius = 8; //Radius for toplevel items like popups for round corners
52static constexpr int secondLevelRoundingRadius = 4; //Radius for second level items like hovered menu item round corners
53static constexpr int contentItemHMargin = 4; // margin between content items (e.g. text and icon)
54static constexpr int contentHMargin = 2 * 3; // margin between rounded border and content (= rounded border margin * 3)
55
56
58{
59inline bool isChecked(const QStyleOption *option)
60{
61 return option->state.testAnyFlags(QStyle::State_On | QStyle::State_NoChange);
62}
63inline bool isDisabled(const QStyleOption *option)
64{
65 return !option->state.testFlag(QStyle::State_Enabled);
66}
67inline bool isPressed(const QStyleOption *option)
68{
69 return option->state.testFlag(QStyle::State_Sunken);
70}
71inline bool isSelected(const QStyleOption *option)
72{
73 return option->state.testFlag(QStyle::State_Selected);
74}
75inline bool isHover(const QStyleOption *option)
76{
77 return option->state.testFlag(QStyle::State_MouseOver);
78}
79inline bool isAutoRaise(const QStyleOption *option)
80{
81 return option->state.testFlag(QStyle::State_AutoRaise);
82}
83inline bool hasFocus(const QStyleOption *option)
84{
85 return option->state.testFlag(QStyle::State_HasFocus);
86}
88inline ControlState calcControlState(const QStyleOption *option)
89{
90 if (isDisabled(option))
92 if (isPressed(option))
94 if (isHover(option))
97};
98
99} // namespace StyleOptionHelper
100
101template <typename R, typename P, typename B>
102static inline void drawRoundedRect(QPainter *p,
103 R &&rect,
104 P &&pen,
105 B &&brush,
106 int radius = secondLevelRoundingRadius)
107{
108 p->setPen(pen);
109 p->setBrush(brush);
110 p->drawRoundedRect(rect, radius, radius);
111}
112
113static constexpr int percentToAlpha(double percent)
114{
115 return qRound(percent * 255. / 100.);
116}
117
118static constexpr std::array<QColor, 37> WINUI3ColorsLight {
119 QColor(0x00,0x00,0x00,percentToAlpha(3.73)), // subtleHighlightColor (fillSubtleSecondary)
120 QColor(0x00,0x00,0x00,percentToAlpha(2.41)), // subtlePressedColor (fillSubtleTertiary)
121 QColor(0x00,0x00,0x00,0x0F), //frameColorLight
122 QColor(0x00,0x00,0x00,percentToAlpha(60.63)), //frameColorStrong
123 QColor(0x00,0x00,0x00,percentToAlpha(21.69)), //frameColorStrongDisabled
124 QColor(0x00,0x00,0x00,0x72), //controlStrongFill
125 QColor(0x00,0x00,0x00,0x29), //controlStrokeSecondary
126 QColor(0x00,0x00,0x00,0x14), //controlStrokePrimary
127 QColor(0xFF,0xFF,0xFF,0xFF), //menuPanelFill
128 QColor(0x00,0x00,0x00,0x66), //controlStrokeOnAccentSecondary
129 QColor(0xFF,0xFF,0xFF,0xFF), //controlFillSolid
130 QColor(0x75,0x75,0x75,0x66), //surfaceStroke
131 QColor(0xFF,0xFF,0xFF,0xFF), //focusFrameInnerStroke
132 QColor(0x00,0x00,0x00,0xFF), //focusFrameOuterStroke
133 QColor(0xFF,0xFF,0xFF,percentToAlpha(70)), // fillControlDefault
134 QColor(0xF9,0xF9,0xF9,percentToAlpha(50)), // fillControlSecondary
135 QColor(0xF9,0xF9,0xF9,percentToAlpha(30)), // fillControlTertiary
136 QColor(0xF9,0xF9,0xF9,percentToAlpha(30)), // fillControlDisabled
137 QColor(0xFF,0xFF,0xFF,percentToAlpha(100)), // fillControlInputActive
138 QColor(0x00,0x00,0x00,percentToAlpha(2.41)), // fillControlAltSecondary
139 QColor(0x00,0x00,0x00,percentToAlpha(5.78)), // fillControlAltTertiary
140 QColor(0x00,0x00,0x00,percentToAlpha(9.24)), // fillControlAltQuarternary
141 QColor(0xFF,0xFF,0xFF,percentToAlpha(0.00)), // fillControlAltDisabled
142 QColor(0x00,0x00,0x00,percentToAlpha(100)), // fillAccentDefault
143 QColor(0x00,0x00,0x00,percentToAlpha(90)), // fillAccentSecondary
144 QColor(0x00,0x00,0x00,percentToAlpha(80)), // fillAccentTertiary
145 QColor(0x00,0x00,0x00,percentToAlpha(21.69)), // fillAccentDisabled
146 QColor(0xFF,0xFF,0xFF,percentToAlpha(70)), // fillMicaAltDefault
147 QColor(0xFF,0xFF,0xFF,percentToAlpha(0)), // fillMicaAltTransparent
148 QColor(0x00,0x00,0x00,percentToAlpha(3.73)), // fillMicaAltSecondary
149 QColor(0x00,0x00,0x00,percentToAlpha(89.56)), // textPrimary
150 QColor(0x00,0x00,0x00,percentToAlpha(60.63)), // textSecondary
151 QColor(0x00,0x00,0x00,percentToAlpha(36.14)), // textDisabled
152 QColor(0xFF,0xFF,0xFF,percentToAlpha(100)), // textOnAccentPrimary
153 QColor(0xFF,0xFF,0xFF,percentToAlpha(70)), // textOnAccentSecondary
154 QColor(0xFF,0xFF,0xFF,percentToAlpha(100)), // textOnAccentDisabled
155 QColor(0x00,0x00,0x00,percentToAlpha(8.03)), // dividerStrokeDefault
156};
157
158static constexpr std::array<QColor, 37> WINUI3ColorsDark {
159 QColor(0xFF,0xFF,0xFF,percentToAlpha(6.05)), // subtleHighlightColor (fillSubtleSecondary)
160 QColor(0xFF,0xFF,0xFF,percentToAlpha(4.19)), // subtlePressedColor (fillSubtleTertiary)
161 QColor(0xFF,0xFF,0xFF,0x12), //frameColorLight
162 QColor(0xFF,0xFF,0xFF,percentToAlpha(60.47)), //frameColorStrong
163 QColor(0xFF,0xFF,0xFF,percentToAlpha(15.81)), //frameColorStrongDisabled
164 QColor(0xFF,0xFF,0xFF,0x8B), //controlStrongFill
165 QColor(0xFF,0xFF,0xFF,0x18), //controlStrokeSecondary
166 QColor(0xFF,0xFF,0xFF,0x12), //controlStrokePrimary
167 QColor(0x0F,0x0F,0x0F,0xFF), //menuPanelFill
168 QColor(0xFF,0xFF,0xFF,0x14), //controlStrokeOnAccentSecondary
169 QColor(0x45,0x45,0x45,0xFF), //controlFillSolid
170 QColor(0x75,0x75,0x75,0x66), //surfaceStroke
171 QColor(0x00,0x00,0x00,0xFF), //focusFrameInnerStroke
172 QColor(0xFF,0xFF,0xFF,0xFF), //focusFrameOuterStroke
173 QColor(0xFF,0xFF,0xFF,percentToAlpha(6.05)), // fillControlDefault
174 QColor(0xFF,0xFF,0xFF,percentToAlpha(8.37)), // fillControlSecondary
175 QColor(0xFF,0xFF,0xFF,percentToAlpha(3.26)), // fillControlTertiary
176 QColor(0xFF,0xFF,0xFF,percentToAlpha(4.19)), // fillControlDisabled
177 QColor(0x1E,0x1E,0x1E,percentToAlpha(70)), // fillControlInputActive
178 QColor(0x00,0x00,0x00,percentToAlpha(10.0)), // fillControlAltDefault
179 QColor(0xFF,0xFF,0xFF,percentToAlpha(4.19)), // fillControlAltSecondary
180 QColor(0xFF,0xFF,0xFF,percentToAlpha(6.98)), // fillControlAltTertiafillCy
181 QColor(0xFF,0xFF,0xFF,percentToAlpha(0.00)), // controlAltDisabled
182 QColor(0x00,0x00,0x00,percentToAlpha(100)), // fillAccentDefault
183 QColor(0x00,0x00,0x00,percentToAlpha(90)), // fillAccentSecondary
184 QColor(0x00,0x00,0x00,percentToAlpha(80)), // fillAccentTertiary
185 QColor(0xFF,0xFF,0xFF,percentToAlpha(15.81)), // fillAccentDisabled
186 QColor(0x3A,0x3A,0x3A,percentToAlpha(45)), // fillMicaAltDefault
187 QColor(0x00,0x00,0x00,percentToAlpha(0)), // fillMicaAltTransparent
188 QColor(0xFF,0xFF,0xFF,percentToAlpha(6.05)), // fillMicaAltSecondary
189 QColor(0xFF,0xFF,0xFF,percentToAlpha(100)), // textPrimary
190 QColor(0xFF,0xFF,0xFF,percentToAlpha(78.6)), // textSecondary
191 QColor(0xFF,0xFF,0xFF,percentToAlpha(36.28)), // textDisabled
192 QColor(0x00,0x00,0x00,percentToAlpha(100)), // textOnAccentPrimary
193 QColor(0x00,0x00,0x00,percentToAlpha(70)), // textOnAccentSecondary
194 QColor(0xFF,0xFF,0xFF,percentToAlpha(53.02)), // textOnAccentDisabled
195 QColor(0xFF,0xFF,0xFF,percentToAlpha(8.37)), // dividerStrokeDefault
196};
197
198static constexpr std::array<std::array<QColor,37>, 2> WINUI3Colors {
199 WINUI3ColorsLight,
200 WINUI3ColorsDark
201};
202
203// Color of close Button in Titlebar (default + hover)
204static constexpr QColor shellCaptionCloseFillColorPrimary(0xC4,0x2B,0x1C,0xFF);
205static constexpr QColor shellCaptionCloseTextFillColorPrimary(0xFF,0xFF,0xFF,0xFF);
206// Color of close Button in Titlebar (pressed + disabled)
207static constexpr QColor shellCaptionCloseFillColorSecondary(0xC4,0x2B,0x1C,0xE6);
208static constexpr QColor shellCaptionCloseTextFillColorSecondary(0xFF,0xFF,0xFF,0xB3);
209
210
211#if QT_CONFIG(toolbutton)
212static void drawArrow(const QStyle *style, const QStyleOptionToolButton *toolbutton,
213 const QRect &rect, QPainter *painter, const QWidget *widget = nullptr)
214{
215 QStyle::PrimitiveElement pe;
216 switch (toolbutton->arrowType) {
217 case Qt::LeftArrow:
218 pe = QStyle::PE_IndicatorArrowLeft;
219 break;
220 case Qt::RightArrow:
221 pe = QStyle::PE_IndicatorArrowRight;
222 break;
223 case Qt::UpArrow:
224 pe = QStyle::PE_IndicatorArrowUp;
225 break;
226 case Qt::DownArrow:
227 pe = QStyle::PE_IndicatorArrowDown;
228 break;
229 default:
230 return;
231 }
232 QStyleOption arrowOpt = *toolbutton;
233 arrowOpt.rect = rect;
234 style->drawPrimitive(pe, &arrowOpt, painter, widget);
235}
236#endif // QT_CONFIG(toolbutton)
237
238static qreal radioButtonInnerRadius(QStyle::State state)
239{
240 qreal radius = 7.0;
241 if (state & QStyle::State_Sunken)
242 radius = 4.0f;
243 else if (state & QStyle::State_MouseOver && !(state & QStyle::State_On))
244 radius = 7.0f;
245 else if (state & QStyle::State_MouseOver && (state & QStyle::State_On))
246 radius = 5.0f;
247 else if (state & QStyle::State_On)
248 radius = 4.0f;
249 return radius;
250}
251
252static qreal sliderInnerRadius(QStyle::State state, bool insideHandle)
253{
254 const bool isEnabled = state & QStyle::State_Enabled;
255 if (isEnabled) {
256 if (state & QStyle::State_Sunken)
257 return 0.29;
258 else if (insideHandle)
259 return 0.71;
260 }
261 return 0.43;
262}
263/*!
264 \class QWindows11Style
265 \brief The QWindows11Style class provides a look and feel suitable for applications on Microsoft Windows 11.
266 \since 6.6
267 \ingroup appearance
268 \inmodule QtWidgets
269 \internal
270
271 \warning This style is only available on the Windows 11 platform and above.
272
273 \sa QWindows11Style QWindowsVistaStyle, QMacStyle, QFusionStyle
274*/
275QWindows11StylePrivate::QWindows11StylePrivate()
276{
277 nativeRoundedTopLevelWindows =
278 QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11_21H2;
279}
280
281/*!
282 Constructs a QWindows11Style object.
283*/
284QWindows11Style::QWindows11Style() : QWindows11Style(*new QWindows11StylePrivate)
285{
286}
287
288/*!
289 \internal
290 Constructs a QWindows11Style object.
291*/
292QWindows11Style::QWindows11Style(QWindows11StylePrivate &dd) : QWindowsVistaStyle(dd)
293{
294 Q_D(QWindows11Style);
295 const QStringList assetFontFamilies{ "Segoe Fluent Icons"_L1, "Segoe MDL2 Assets"_L1 };
296 d->assetFont = QFont(assetFontFamilies);
297 d->assetFont.setStyleStrategy(QFont::NoFontMerging);
298 highContrastTheme = QGuiApplicationPrivate::styleHints->colorScheme() == Qt::ColorScheme::Unknown;
299 colorSchemeIndex = QGuiApplicationPrivate::styleHints->colorScheme() == Qt::ColorScheme::Light ? 0 : 1;
300}
301
302/*!
303 Destructor.
304*/
305QWindows11Style::~QWindows11Style() = default;
306
307/*!
308 \internal
309 see drawPrimitive for comments on the animation support
310
311 */
312void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
313 QPainter *painter, const QWidget *widget) const
314{
315 QWindows11StylePrivate *d = const_cast<QWindows11StylePrivate*>(d_func());
316
317 const auto drawTitleBarButton = [&](ComplexControl control, SubControl sc, Icon ico) {
318 using namespace StyleOptionHelper;
319 const QRect buttonRect = proxy()->subControlRect(control, option, sc, widget);
320 if (buttonRect.isValid()) {
321 const bool hover = option->activeSubControls == sc && isHover(option);
322 if (hover)
323 painter->fillRect(buttonRect, winUI3Color(subtleHighlightColor));
324 painter->setPen(option->palette.color(QPalette::WindowText));
325 painter->drawText(buttonRect, Qt::AlignCenter, fluentIcon(ico));
326 }
327 };
328 const auto drawTitleBarCloseButton = [&](ComplexControl control, SubControl sc, Icon ico) {
329 using namespace StyleOptionHelper;
330 const QRect buttonRect = proxy()->subControlRect(control, option, sc, widget);
331 if (buttonRect.isValid()) {
332 const auto state = (option->activeSubControls == sc) ? calcControlState(option)
333 : ControlState::Normal;
334 QPen pen;
335 switch (state) {
337 painter->fillRect(buttonRect, shellCaptionCloseFillColorPrimary);
338 pen = shellCaptionCloseTextFillColorPrimary;
339 break;
341 painter->fillRect(buttonRect, shellCaptionCloseFillColorSecondary);
342 pen = shellCaptionCloseTextFillColorSecondary;
343 break;
344 case ControlState::Disabled:
345 case ControlState::Normal:
346 pen = option->palette.color(QPalette::WindowText);
347 break;
348 }
349 painter->setPen(pen);
350 painter->drawText(buttonRect, Qt::AlignCenter, fluentIcon(ico));
351 }
352 };
353
354
355 State state = option->state;
356 SubControls sub = option->subControls;
357 State flags = option->state;
358 if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow())
359 flags |= State_MouseOver;
360
361 QPainterStateGuard psg(painter);
362 painter->setRenderHint(QPainter::Antialiasing);
363 if (d->transitionsEnabled() && option->styleObject) {
364 if (control == CC_Slider) {
365 if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
366 QObject *styleObject = option->styleObject; // Can be widget or qquickitem
367
368 QRectF thumbRect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
369 const qreal outerRadius = qMin(8.0, (slider->orientation == Qt::Horizontal ? thumbRect.height() / 2.0 : thumbRect.width() / 2.0) - 1);
370 bool isInsideHandle = option->activeSubControls == SC_SliderHandle;
371
372 bool oldIsInsideHandle = styleObject->property("_q_insidehandle").toBool();
373 State oldState = State(styleObject->property("_q_stylestate").toInt());
374 SubControls oldActiveControls = SubControls(styleObject->property("_q_stylecontrols").toInt());
375
376 QRectF oldRect = styleObject->property("_q_stylerect").toRect();
377 styleObject->setProperty("_q_insidehandle", isInsideHandle);
378 styleObject->setProperty("_q_stylestate", int(state));
379 styleObject->setProperty("_q_stylecontrols", int(option->activeSubControls));
380 styleObject->setProperty("_q_stylerect", option->rect);
381 if (option->styleObject->property("_q_end_radius").isNull())
382 option->styleObject->setProperty("_q_end_radius", outerRadius * 0.43);
383
384 bool doTransition = (((state & State_Sunken) != (oldState & State_Sunken)
385 || (oldIsInsideHandle != isInsideHandle)
386 || (oldActiveControls != option->activeSubControls))
387 && state & State_Enabled);
388
389 if (oldRect != option->rect) {
390 doTransition = false;
391 d->stopAnimation(styleObject);
392 styleObject->setProperty("_q_inner_radius", outerRadius * 0.43);
393 }
394
395 if (doTransition) {
396 QNumberStyleAnimation *t = new QNumberStyleAnimation(styleObject);
397 t->setStartValue(styleObject->property("_q_inner_radius").toFloat());
398 t->setEndValue(outerRadius * sliderInnerRadius(state, isInsideHandle));
399 styleObject->setProperty("_q_end_radius", t->endValue());
400
401 t->setStartTime(d->animationTime());
402 t->setDuration(150);
403 d->startAnimation(t);
404 }
405 }
406 } else if (control == CC_ScrollBar) {
407 QObject *styleObject = option->styleObject;
408 State oldState = State(styleObject->property("_q_stylestate").toInt());
409 const bool wasMouseOver = oldState.testFlag(State_MouseOver);
410 const bool isMouseOver = state.testFlag(State_MouseOver);
411
412 if (wasMouseOver != isMouseOver) {
413 styleObject->setProperty("_q_stylestate", int(state));
414 auto anim = qobject_cast<QNumberStyleAnimation *>(d->animation(styleObject));
415 constexpr int durationMS = 100;
416 qreal startValue = isMouseOver ? 0 : 1;
417 int curDurationMS = durationMS;
418 if (anim) {
419 startValue = anim->currentValue();
420 curDurationMS = durationMS - (anim->duration() - anim->currentLoopTime());
421 d->stopAnimation(option->styleObject);
422 }
423 anim = new QNumberStyleAnimation(styleObject);
424 anim->setStartValue(startValue);
425 anim->setEndValue(isMouseOver ? 1 : 0);
426 anim->setDuration(curDurationMS);
427 d->startAnimation(anim);
428 }
429 }
430 }
431
432 switch (control) {
433#if QT_CONFIG(spinbox)
434 case CC_SpinBox:
435 if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
436 const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
437 drawRoundedRect(painter, frameRect, Qt::NoPen, inputFillBrush(option, widget));
438
439 if (sb->frame && (sub & SC_SpinBoxFrame))
440 drawLineEditFrame(painter, frameRect, option);
441
442 const auto drawUpDown = [&](QStyle::SubControl sc,
443 QAbstractSpinBox::StepEnabledFlag flag) {
444 const bool isEnabled =
445 state.testFlag(QStyle::State_Enabled) && sb->stepEnabled.testFlag(flag);
446 const bool isUp = sc == SC_SpinBoxUp;
447 const QRect rect = proxy()->subControlRect(CC_SpinBox, option, sc, widget);
448 if (isEnabled && sb->activeSubControls & sc)
449 drawRoundedRect(painter, rect.adjusted(1, 1, -1, -2), Qt::NoPen,
450 winUI3Color(subtleHighlightColor));
451 painter->setFont(d->assetFont);
452 painter->setPen(sb->palette.color(isEnabled ? QPalette::Active : QPalette::Disabled,
453 QPalette::ButtonText));
454 painter->setBrush(Qt::NoBrush);
455 painter->drawText(rect, Qt::AlignCenter, fluentIcon(isUp ? Icon::ChevronUp : Icon::ChevronDown));
456 };
457 if (sub & SC_SpinBoxUp)
458 drawUpDown(SC_SpinBoxUp, QAbstractSpinBox::StepUpEnabled);
459 if (sub & SC_SpinBoxDown)
460 drawUpDown(SC_SpinBoxDown, QAbstractSpinBox::StepDownEnabled);
461 if (state & State_KeyboardFocusChange && state & State_HasFocus) {
462 QStyleOptionFocusRect fropt;
463 fropt.QStyleOption::operator=(*option);
464 proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
465 }
466 }
467 break;
468#endif // QT_CONFIG(spinbox)
469#if QT_CONFIG(slider)
470 case CC_Slider:
471 if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
472 const auto &slrect = slider->rect;
473 const bool isHorizontal = slider->orientation == Qt::Horizontal;
474 const QRectF handleRect(proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget));
475 const QPointF handleCenter(handleRect.center());
476
477 if (sub & SC_SliderGroove) {
478 QRectF rect = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget);
479 QRectF leftRect;
480 QRectF rightRect;
481
482 if (isHorizontal) {
483 rect = QRectF(rect.left() + 2, rect.center().y() - 2, rect.width() - 2, 4);
484 leftRect = QRectF(rect.left(), rect.top(), handleCenter.x() - rect.left(),
485 rect.height());
486 rightRect = QRectF(handleCenter.x(), rect.top(),
487 rect.width() - handleCenter.x(),
488 rect.height());
489 } else {
490 rect = QRect(rect.center().x() - 2, rect.top() + 2, 4, rect.height() - 2);
491 leftRect = QRectF(rect.left(), rect.top(), rect.width(),
492 handleCenter.y() - rect.top());
493 rightRect = QRectF(rect.left(), handleCenter.y(), rect.width(),
494 rect.height() - handleCenter.y());
495 }
496 if (slider->upsideDown)
497 qSwap(leftRect, rightRect);
498
499 painter->setPen(Qt::NoPen);
500 painter->setBrush(calculateAccentColor(option));
501 painter->drawRoundedRect(leftRect,1,1);
502 painter->setBrush(WINUI3Colors[colorSchemeIndex][controlStrongFill]);
503 painter->drawRoundedRect(rightRect,1,1);
504 }
505 if (sub & SC_SliderTickmarks) {
506 int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget);
507 int ticks = slider->tickPosition;
508 int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget);
509 int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);
510 int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);
511 int interval = slider->tickInterval;
512 if (interval <= 0) {
513 interval = slider->singleStep;
514 if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval,
515 available)
516 - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
517 0, available) < 3)
518 interval = slider->pageStep;
519 }
520 if (!interval)
521 interval = 1;
522 int fudge = len / 2;
523 painter->setPen(slider->palette.text().color());
524 QVarLengthArray<QLineF, 32> lines;
525 int v = slider->minimum;
526 while (v <= slider->maximum + 1) {
527 if (v == slider->maximum + 1 && interval == 1)
528 break;
529 const int v_ = qMin(v, slider->maximum);
530 int tickLength = (v_ == slider->minimum || v_ >= slider->maximum) ? 4 : 3;
531 int pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, v_,
532 available, slider->upsideDown);
533 pos += fudge;
534 if (isHorizontal) {
535 if (ticks & QSlider::TicksAbove) {
536 lines.append(QLineF(pos, tickOffset - 0.5,
537 pos, tickOffset - tickLength - 0.5));
538 }
539
540 if (ticks & QSlider::TicksBelow) {
541 lines.append(QLineF(pos, tickOffset + thickness + 0.5,
542 pos, tickOffset + thickness + tickLength + 0.5));
543 }
544 } else {
545 if (ticks & QSlider::TicksAbove) {
546 lines.append(QLineF(tickOffset - 0.5, pos,
547 tickOffset - tickLength - 0.5, pos));
548 }
549
550 if (ticks & QSlider::TicksBelow) {
551 lines.append(QLineF(tickOffset + thickness + 0.5, pos,
552 tickOffset + thickness + tickLength + 0.5, pos));
553 }
554 }
555 // in the case where maximum is max int
556 int nextInterval = v + interval;
557 if (nextInterval < v)
558 break;
559 v = nextInterval;
560 }
561 if (!lines.isEmpty()) {
562 QPainterStateGuard psg(painter);
563 painter->translate(slrect.topLeft());
564 painter->drawLines(lines.constData(), lines.size());
565 }
566 }
567 if (sub & SC_SliderHandle) {
568 const qreal outerRadius = qMin(8.0, (isHorizontal ? handleRect.height() / 2.0 : handleRect.width() / 2.0) - 1);
569 float innerRadius = outerRadius * 0.43;
570
571 if (option->styleObject) {
572 const QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation *>(d->animation(option->styleObject));
573 if (animation != nullptr) {
574 innerRadius = animation->currentValue();
575 option->styleObject->setProperty("_q_inner_radius", innerRadius);
576 } else {
577 bool isInsideHandle = option->activeSubControls == SC_SliderHandle;
578 innerRadius = outerRadius * sliderInnerRadius(state, isInsideHandle);
579 }
580 }
581
582 painter->setPen(Qt::NoPen);
583 painter->setBrush(winUI3Color(controlFillSolid));
584 painter->drawEllipse(handleCenter, outerRadius, outerRadius);
585 painter->setBrush(calculateAccentColor(option));
586 painter->drawEllipse(handleCenter, innerRadius, innerRadius);
587
588 painter->setPen(winUI3Color(controlStrokeSecondary));
589 painter->setBrush(Qt::NoBrush);
590 painter->drawEllipse(handleCenter, outerRadius + 0.5, outerRadius + 0.5);
591 }
592 if (slider->state & State_HasFocus) {
593 QStyleOptionFocusRect fropt;
594 fropt.QStyleOption::operator=(*slider);
595 fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget);
596 proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
597 }
598 }
599 break;
600#endif
601#if QT_CONFIG(combobox)
602 case CC_ComboBox:
603 if (const QStyleOptionComboBox *combobox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
604 const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
605 QStyleOption opt(*option);
606 opt.state.setFlag(QStyle::State_On, false);
607 drawRoundedRect(painter, frameRect, Qt::NoPen,
608 combobox->editable ? inputFillBrush(option, widget)
609 : controlFillBrush(&opt, ControlType::Control));
610
611 if (combobox->frame)
612 drawLineEditFrame(painter, frameRect, combobox, combobox->editable);
613
614 const bool hasFocus = state & State_HasFocus;
615
616 if (sub & SC_ComboBoxArrow) {
617 QRectF rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
618 painter->setFont(d->assetFont);
619 painter->setPen(controlTextColor(option, true));
620 painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed));
621 }
622 if (state & State_KeyboardFocusChange && hasFocus) {
623 QStyleOptionFocusRect fropt;
624 fropt.QStyleOption::operator=(*option);
625 proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
626 }
627 }
628 break;
629#endif // QT_CONFIG(combobox)
630 case CC_ScrollBar:
631 if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
632 const bool vertical = scrollbar->orientation == Qt::Vertical;
633 const bool isMouseOver = state & State_MouseOver;
634 const auto prx = proxy();
635
636 QPainterStateGuard psg(painter);
637
638 const auto anim = qobject_cast<QNumberStyleAnimation *>(d->animation(option->styleObject));
639 qreal widthFactor = isMouseOver ? 1.0 : 0.0;
640 if (anim)
641 widthFactor = anim->currentValue();
642
643 if (widthFactor > 0.6) {
644 const bool isRtl = option->direction == Qt::RightToLeft;
645
646 QFont f = QFont(d->assetFont);
647 f.setPointSize(6);
648 painter->setFont(f);
649 painter->setPen(winUI3Color(controlStrongFill));
650
651 const QRectF rectAdd =
652 prx->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget);
653 const auto strAdd = vertical
654 ? Icon::CaretDownSolid8
655 : (isRtl ? Icon::CaretLeftSolid8 : Icon::CaretRightSolid8);
656 painter->drawText(rectAdd, Qt::AlignCenter, fluentIcon(strAdd));
657
658 const QRectF rectSub =
659 prx->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget);
660 const auto strSub = vertical
661 ? Icon::CaretUpSolid8
662 : (isRtl ? Icon::CaretRightSolid8 : Icon::CaretLeftSolid8);
663 painter->drawText(rectSub, Qt::AlignCenter, fluentIcon(strSub));
664 }
665 if (sub & SC_ScrollBarSlider) {
666 QRectF rect = prx->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
667 const QPointF center = rect.center();
668 if (vertical) {
669 auto w = rect.width() / 2. * widthFactor;
670 if (w < 2)
671 w = 2;
672 const auto ofs = rect.width() / 2 - w;
673 rect.setWidth(w);
674 rect.moveCenter(QPointF(center.x() + ofs, center.y()));
675 }
676 else {
677 auto h = rect.height() / 2. * widthFactor;
678 if (h < 2)
679 h = 2;
680 const auto ofs = rect.height() / 2 - h;
681 rect.setHeight(h);
682 rect.moveCenter(QPointF(center.x(), center.y() + ofs));
683 }
684 drawRoundedRect(painter, rect, Qt::NoPen, winUI3Color(controlStrongFill), 3);
685 }
686 }
687 break;
688 case CC_MdiControls:{
689 QFont buttonFont = QFont(d->assetFont);
690 buttonFont.setPointSize(8);
691 painter->setFont(buttonFont);
692 drawTitleBarCloseButton(CC_MdiControls, SC_MdiCloseButton, Icon::ChromeClose);
693 drawTitleBarButton(CC_MdiControls, SC_MdiNormalButton, Icon::ChromeRestore);
694 drawTitleBarButton(CC_MdiControls, SC_MdiMinButton, Icon::ChromeMinimize);
695 }
696 break;
697 case CC_TitleBar:
698 if (const auto* titlebar = qstyleoption_cast<const QStyleOptionTitleBar*>(option)) {
699 painter->setPen(Qt::NoPen);
700 painter->setPen(WINUI3Colors[colorSchemeIndex][surfaceStroke]);
701 painter->setBrush(titlebar->palette.button());
702 painter->drawRect(titlebar->rect);
703
704 // draw title
705 QRect textRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarLabel, widget);
706 QColor textColor = titlebar->palette.color(titlebar->titleBarState & Qt::WindowActive ? QPalette::Active : QPalette::Disabled,QPalette::WindowText);
707 painter->setPen(textColor);
708 // Note workspace also does elliding but it does not use the correct font
709 QString title = painter->fontMetrics().elidedText(titlebar->text, Qt::ElideRight, textRect.width() - 14);
710 painter->drawText(textRect.adjusted(1, 1, -1, -1), title, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter));
711
712 QFont buttonFont = QFont(d->assetFont);
713 buttonFont.setPointSize(8);
714 painter->setFont(buttonFont);
715 auto shouldDrawButton = [titlebar](SubControl sc, Qt::WindowType flag) {
716 return (titlebar->subControls & sc) && (titlebar->titleBarFlags & flag);
717 };
718
719 // min button
720 if (shouldDrawButton(SC_TitleBarMinButton, Qt::WindowMinimizeButtonHint) &&
721 !(titlebar->titleBarState & Qt::WindowMinimized)) {
722 drawTitleBarButton(CC_TitleBar, SC_TitleBarMinButton, Icon::ChromeMinimize);
723 }
724
725 // max button
726 if (shouldDrawButton(SC_TitleBarMaxButton, Qt::WindowMaximizeButtonHint) &&
727 !(titlebar->titleBarState & Qt::WindowMaximized)) {
728 drawTitleBarButton(CC_TitleBar, SC_TitleBarMaxButton, Icon::ChromeMaximize);
729 }
730
731 // close button
732 if (shouldDrawButton(SC_TitleBarCloseButton, Qt::WindowSystemMenuHint))
733 drawTitleBarCloseButton(CC_TitleBar, SC_TitleBarCloseButton, Icon::ChromeClose);
734
735 // normalize button
736 if ((titlebar->subControls & SC_TitleBarNormalButton) &&
737 (((titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint) &&
738 (titlebar->titleBarState & Qt::WindowMinimized)) ||
739 ((titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint) &&
740 (titlebar->titleBarState & Qt::WindowMaximized)))) {
741 drawTitleBarButton(CC_TitleBar, SC_TitleBarNormalButton, Icon::ChromeRestore);
742 }
743
744 // context help button
745 if (shouldDrawButton(SC_TitleBarContextHelpButton, Qt::WindowContextHelpButtonHint))
746 drawTitleBarButton(CC_TitleBar, SC_TitleBarContextHelpButton, Icon::Help);
747
748 // shade button
749 if (shouldDrawButton(SC_TitleBarShadeButton, Qt::WindowShadeButtonHint))
750 drawTitleBarButton(CC_TitleBar, SC_TitleBarShadeButton, Icon::ChevronUpSmall);
751
752 // unshade button
753 if (shouldDrawButton(SC_TitleBarUnshadeButton, Qt::WindowShadeButtonHint))
754 drawTitleBarButton(CC_TitleBar, SC_TitleBarUnshadeButton, Icon::ChevronDownSmall);
755
756 // window icon for system menu
757 if (shouldDrawButton(SC_TitleBarSysMenu, Qt::WindowSystemMenuHint)) {
758 const QRect iconRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarSysMenu, widget);
759 if (iconRect.isValid()) {
760 if (!titlebar->icon.isNull()) {
761 titlebar->icon.paint(painter, iconRect);
762 } else {
763 QStyleOption tool = *titlebar;
764 const auto extent = proxy()->pixelMetric(PM_SmallIconSize, &tool, widget);
765 const auto dpr = QStyleHelper::getDpr(widget);
766 const auto icon = proxy()->standardIcon(SP_TitleBarMenuButton, &tool, widget);
767 const auto pm = icon.pixmap(QSize(extent, extent), dpr);
768 proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pm);
769 }
770 }
771 }
772 }
773 break;
774#if QT_CONFIG(toolbutton)
775 case CC_ToolButton:
776 if (const auto toolbutton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
777 auto prx = proxy();
778 const bool isSplitButton =
779 toolbutton->features.testFlag(QStyleOptionToolButton::MenuButtonPopup);
780 const auto fw = prx->pixelMetric(PM_DefaultFrameWidth, option, widget);
781 const auto buttonRect = prx->subControlRect(control, toolbutton, SC_ToolButton, widget);
782 const auto menuareaRect = isSplitButton
783 ? prx->subControlRect(control, toolbutton, SC_ToolButtonMenu, widget)
784 : QRect();
785
786 State bflags = toolbutton->state;
787 State mflags = toolbutton->state;
788 if (toolbutton->activeSubControls.testFlag(SC_ToolButton)) {
789 mflags &= ~(State_Sunken | State_MouseOver);
790 if (bflags.testFlag(State_Sunken))
791 mflags |= State_Raised;
792 }
793 if (toolbutton->activeSubControls.testFlag(SC_ToolButtonMenu)) {
794 bflags &= ~(State_Sunken | State_MouseOver);
795 if (mflags.testFlag(State_Sunken))
796 bflags |= State_Raised;
797 }
798
799 QStyleOption tool = *toolbutton;
800 if (toolbutton->subControls.testFlag(SC_ToolButton)) {
801 QPainterStateGuard psg(painter);
802 if (isSplitButton)
803 painter->setClipRect(buttonRect);
804 tool.state = bflags;
805 prx->drawPrimitive(PE_PanelButtonTool, &tool, painter, widget);
806 if (isSplitButton) {
807 if (state.testFlag(State_Enabled)) {
808 painter->setPen(winUI3Color(controlStrokePrimary));
809 const auto top = buttonRect.topRight() + QPoint(0, fw + 1);
810 const auto btm = buttonRect.bottomRight() - QPoint(0, fw);
811 painter->drawLine(top, btm);
812 }
813 }
814 }
815
816 if (toolbutton->state.testFlag(State_HasFocus)) {
817 QStyleOptionFocusRect fr;
818 fr.QStyleOption::operator=(*toolbutton);
819 prx->drawPrimitive(PE_FrameFocusRect, &fr, painter, widget);
820 }
821 QStyleOptionToolButton label = *toolbutton;
822 label.state = bflags;
823 label.rect = buttonRect.marginsRemoved(QMargins(fw, fw, fw, fw));
824
825 if (toolbutton->subControls.testFlag(SC_ToolButtonMenu)) {
826 QPainterStateGuard psg(painter);
827 painter->setClipRect(menuareaRect);
828 tool.state = mflags;
829 prx->drawPrimitive(PE_PanelButtonTool, &tool, painter, widget);
830
831 const int fontSize = painter->font().pointSize();
832 QFont f(d->assetFont);
833 f.setPointSize(qRound(fontSize * 0.9f)); // a little bit smaller
834 painter->setFont(f);
835 painter->setPen(controlTextColor(option));
836 const QRect textRect(menuareaRect.topLeft(), menuareaRect.size() - QSize(fw, 0));
837 painter->drawText(textRect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed));
838
839 } else if (toolbutton->features.testFlag(QStyleOptionToolButton::HasMenu)) {
840 const int mbi = qRound(prx->pixelMetric(PM_MenuButtonIndicator, toolbutton, widget) * 0.3);
841 const QRect &ir = toolbutton->rect;
842 QRect rect(ir.right() - mbi - 2 * fw, ir.bottom() - mbi - fw, mbi,
843 mbi);
844 rect = visualRect(toolbutton->direction, buttonRect, rect);
845 const QRect lblRect = label.rect - QMargins(0, 0, rect.width(), 0);
846 label.rect = visualRect(toolbutton->direction, buttonRect, lblRect);
847 painter->setFont(QFont(d->assetFont));
848 painter->setPen(controlTextColor(option));
849 painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed));
850 }
851 prx->drawControl(CE_ToolButtonLabel, &label, painter, widget);
852 }
853 break;
854#endif // QT_CONFIG(toolbutton)
855 default:
856 QWindowsVistaStyle::drawComplexControl(control, option, painter, widget);
857 }
858}
859
860void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
861 QPainter *painter,
862 const QWidget *widget) const {
863 QWindows11StylePrivate *d = const_cast<QWindows11StylePrivate*>(d_func());
864
865 const State state = option->state;
866 QPainterStateGuard psg(painter);
867 painter->setRenderHint(QPainter::Antialiasing);
868 if (d->transitionsEnabled() && option->styleObject && (element == PE_IndicatorCheckBox || element == PE_IndicatorRadioButton)) {
869 QObject *styleObject = option->styleObject; // Can be widget or qquickitem
870 int oldState = styleObject->property("_q_stylestate").toInt();
871 styleObject->setProperty("_q_stylestate", int(option->state));
872 styleObject->setProperty("_q_stylerect", option->rect);
873 bool doTransition = (((state & State_Sunken) != (oldState & State_Sunken)
874 || ((state & State_MouseOver) != (oldState & State_MouseOver))
875 || (state & State_On) != (oldState & State_On))
876 && state & State_Enabled);
877 if (doTransition) {
878 if (element == PE_IndicatorRadioButton) {
879 QNumberStyleAnimation *t = new QNumberStyleAnimation(styleObject);
880 t->setStartValue(styleObject->property("_q_inner_radius").toFloat());
881 t->setEndValue(radioButtonInnerRadius(state));
882 styleObject->setProperty("_q_end_radius", t->endValue());
883 t->setStartTime(d->animationTime());
884 t->setDuration(150);
885 d->startAnimation(t);
886 }
887 else if (element == PE_IndicatorCheckBox) {
888 if ((oldState & State_Off && state & State_On) || (oldState & State_NoChange && state & State_On)) {
889 QNumberStyleAnimation *t = new QNumberStyleAnimation(styleObject);
890 t->setStartValue(0.0f);
891 t->setEndValue(1.0f);
892 t->setStartTime(d->animationTime());
893 t->setDuration(150);
894 d->startAnimation(t);
895 }
896 }
897 }
898 }
899
900 switch (element) {
901 case PE_IndicatorArrowUp:
902 case PE_IndicatorArrowDown:
903 case PE_IndicatorArrowRight:
904 case PE_IndicatorArrowLeft: {
905 const QRect &r = option->rect;
906 if (r.width() <= 1 || r.height() <= 1)
907 break;
908 Icon ico = Icon::Help;
909 switch (element) {
910 case PE_IndicatorArrowUp:
911 ico = Icon::ChevronUpMed;
912 break;
913 case PE_IndicatorArrowDown:
914 ico = Icon::ChevronDownMed;
915 break;
916 case PE_IndicatorArrowLeft:
917 ico = Icon::ChevronLeftMed;
918 break;
919 case PE_IndicatorArrowRight:
920 ico = Icon::ChevronRightMed;
921 break;
922 default:
923 break;
924 }
925 QPainterStateGuard psg(painter);
926 if (option->state.testFlag(State_Sunken)) {
927 const auto bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget);
928 const auto bsy = proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget);
929 if (bsx != 0 || bsy != 0)
930 painter->translate(bsx, bsy);
931 }
932 painter->setFont(d->assetFont);
933 painter->setPen(option->palette.buttonText().color());
934 painter->setBrush(option->palette.buttonText());
935 painter->drawText(option->rect, Qt::AlignCenter, fluentIcon(ico));
936 break;
937 }
938 case PE_FrameFocusRect: {
939 if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(option)) {
940 if (!(fropt->state & State_KeyboardFocusChange))
941 break;
942 QRectF focusRect = option->rect;
943 focusRect = focusRect.marginsRemoved(QMarginsF(1.5,1.5,1.5,1.5));
944 painter->setPen(winUI3Color(focusFrameInnerStroke));
945 painter->drawRoundedRect(focusRect,4,4);
946
947 focusRect = focusRect.marginsAdded(QMarginsF(1.0,1.0,1.0,1.0));
948 painter->setPen(QPen(winUI3Color(focusFrameOuterStroke),1));
949 painter->drawRoundedRect(focusRect,4,4);
950 }
951 break;
952 }
953 case PE_PanelTipLabel: {
954 const auto rect = QRectF(option->rect).marginsRemoved(QMarginsF(0.5, 0.5, 0.5, 0.5));
955 const auto pen = highContrastTheme ? option->palette.buttonText().color()
956 : winUI3Color(frameColorLight);
957 drawRoundedRect(painter, rect, pen, option->palette.toolTipBase());
958 break;
959 }
960 case PE_FrameTabWidget:
961#if QT_CONFIG(tabwidget)
962 if (const QStyleOptionTabWidgetFrame *frame = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) {
963 QPainterStateGuard psg(painter);
964 const auto clipRegion = painter->clipRegion();
965
966 painter->setPen(highContrastTheme ? frame->palette.buttonText().color()
967 : winUI3Color(frameColorLight));
968 painter->setBrush(frame->palette.base());
969
970 const auto &rect = option->rect;
971 QRect upperRect = rect;
972 upperRect.setHeight(rect.height() / 2);
973 QRect lowerRect = rect;
974 lowerRect.setY(lowerRect.y() + rect.height() / 2);
975 painter->setClipRegion(clipRegion.isNull() ? upperRect : clipRegion & upperRect);
976 painter->drawRect(rect);
977 painter->setClipRegion(clipRegion.isNull() ? lowerRect : clipRegion & lowerRect);
978 painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
979
980 }
981#endif // QT_CONFIG(tabwidget)
982 break;
983 case PE_FrameGroupBox:
984 if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
985 const auto pen = highContrastTheme ? frame->palette.buttonText().color()
986 : winUI3Color(frameColorStrong);
987 if (frame->features & QStyleOptionFrame::Flat) {
988 painter->setBrush(Qt::NoBrush);
989 painter->setPen(pen);
990 const QRect &fr = frame->rect;
991 QPoint p1(fr.x(), fr.y() + 1);
992 QPoint p2(fr.x() + fr.width(), p1.y());
993 painter->drawLine(p1, p2);
994 } else {
995 const auto frameRect = QRectF(frame->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
996 drawRoundedRect(painter, frameRect, pen, Qt::NoBrush);
997 }
998 }
999 break;
1000 case PE_IndicatorHeaderArrow:
1001 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
1002 const auto indicator = header->sortIndicator;
1003 if (indicator != QStyleOptionHeader::None) {
1004 QPainterStateGuard psg(painter);
1005 QFont f(d->assetFont);
1006 f.setPointSize(option->rect.height());
1007 painter->setFont(f);
1008 painter->setPen(header->palette.text().color());
1009 const auto ico = indicator == QStyleOptionHeader::SortUp ? Icon::ChevronDown
1010 : Icon::ChevronUp;
1011 painter->drawText(option->rect, Qt::AlignCenter, fluentIcon(ico));
1012 }
1013 }
1014 break;
1015 case PE_IndicatorCheckBox: {
1016 const bool isOn = option->state & State_On;
1017 const bool isPartial = option->state & State_NoChange;
1018
1019 const QRectF rect = option->rect;
1020 const QPointF center = rect.center();
1021
1022 drawRoundedRect(painter, option->rect, borderPenControlAlt(option),
1023 controlFillBrush(option, ControlType::ControlAlt));
1024
1025 if (isOn) {
1026 QFont f(d->assetFont);
1027 f.setPointSize(8);
1028 painter->setFont(f);
1029 painter->setPen(controlTextColor(option));
1030 const auto *animation =
1031 qobject_cast<QNumberStyleAnimation *>(d->animation(option->styleObject));
1032 const qreal clipWidth = animation ? animation->currentValue() : 1.0;
1033 const QChar ico = fluentIcon(Icon::AcceptMedium);
1034 QFontMetrics fm(d->assetFont);
1035 QRectF clipRect = fm.boundingRect(ico);
1036 clipRect.moveCenter(center);
1037 clipRect.setLeft(rect.x() + (rect.width() - clipRect.width()) / 2.0 + 0.5);
1038 clipRect.setWidth(clipWidth * clipRect.width());
1039 painter->drawText(clipRect, Qt::AlignVCenter | Qt::AlignLeft, ico);
1040 } else if (isPartial) {
1041 QFont f(d->assetFont);
1042 f.setPointSize(6);
1043 painter->setFont(f);
1044 painter->setPen(controlTextColor(option));
1045 painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::Dash12));
1046 }
1047 }
1048 break;
1049 case PE_IndicatorBranch: {
1050 if (option->state & State_Children) {
1051 const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option);
1052 const bool isReverse = option->direction == Qt::RightToLeft;
1053 const bool isOpen = option->state & QStyle::State_Open;
1054 const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget);
1055 QFont f(d->assetFont);
1056 f.setPointSize(8);
1057 painter->setFont(f);
1058 if (view && view->alternatingRowColors() && vopt && vopt->state & State_Selected)
1059 painter->setPen(winUI3Color(textOnAccentPrimary));
1060 else
1061 painter->setPen(option->palette.color(isOpen ? QPalette::Active : QPalette::Disabled,
1062 QPalette::WindowText));
1063 const auto ico = isOpen ? Icon::ChevronDownMed
1064 : (isReverse ? Icon::ChevronLeftMed
1065 : Icon::ChevronRightMed);
1066 painter->drawText(option->rect, Qt::AlignCenter, fluentIcon(ico));
1067 }
1068 }
1069 break;
1070 case PE_IndicatorRadioButton: {
1071 const auto *animation =
1072 qobject_cast<QNumberStyleAnimation *>(d->animation(option->styleObject));
1073 const qreal innerRadius =
1074 animation ? animation->currentValue() : radioButtonInnerRadius(state);
1075 const QRectF rect = option->rect;
1076 const QPointF center = rect.center();
1077
1078 if (option->styleObject)
1079 option->styleObject->setProperty("_q_inner_radius", innerRadius);
1080 painter->setPen(borderPenControlAlt(option));
1081 painter->setBrush(controlFillBrush(option, ControlType::ControlAlt));
1082 if (option->state.testFlag(State_On)) {
1083 QPainterPath path;
1084 path.addEllipse(center, 7.5, 7.5);
1085 path.addEllipse(center, innerRadius, innerRadius);
1086 painter->drawPath(path);
1087 // Text On Accent/Primary
1088 painter->setBrush(option->palette.window().color());
1089 painter->drawEllipse(center, innerRadius, innerRadius);
1090 } else {
1091 painter->drawEllipse(center, 7.5, 7.5);
1092 }
1093 }
1094 break;
1095 case PE_PanelButtonTool:
1096 case PE_PanelButtonBevel:{
1097 const bool isEnabled = state & QStyle::State_Enabled;
1098 const bool isMouseOver = state & QStyle::State_MouseOver;
1099 const bool isAutoRaise = state & QStyle::State_AutoRaise;
1100 const int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
1101 const QRectF rect = option->rect.marginsRemoved(QMargins(fw, fw, fw, fw));
1102 if (element == PE_PanelButtonTool && ((!isMouseOver && isAutoRaise) || !isEnabled))
1103 painter->setPen(Qt::NoPen);
1104 else
1105 painter->setPen(winUI3Color(controlStrokePrimary));
1106 painter->setBrush(controlFillBrush(option, ControlType::Control));
1107 if (element == PE_PanelButtonTool && widget) {
1108 const auto name = widget->objectName();
1109 if (name == "ScrollLeftButton"_L1 || name == "ScrollRightButton"_L1) {
1110 painter->setPen(Qt::NoPen);
1111 if (isMouseOver)
1112 painter->setBrush(controlFillBrush(option, ControlType::ControlAlt));
1113 else
1114 painter->setBrush(Qt::NoBrush);
1115 }
1116 }
1117 painter->drawRoundedRect(rect,
1118 secondLevelRoundingRadius, secondLevelRoundingRadius);
1119 }
1120 break;
1121 case PE_FrameDefaultButton:
1122 painter->setPen(option->palette.accent().color());
1123 painter->setBrush(Qt::NoBrush);
1124 painter->drawRoundedRect(option->rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
1125 break;
1126 case PE_FrameMenu:
1127 break;
1128 case PE_PanelMenu: {
1129 if (!d->nativeRoundedTopLevelWindows) {
1130 const QRect rect = option->rect.marginsRemoved(QMargins(2, 2, 2, 2));
1131 painter->setPen(highContrastTheme ? QPen(option->palette.windowText().color(), 2)
1132 : winUI3Color(frameColorLight));
1133 painter->setBrush(winUI3Color(menuPanelFill));
1134 painter->drawRoundedRect(rect, topLevelRoundingRadius, topLevelRoundingRadius);
1135 }
1136 break;
1137 }
1138 case PE_PanelLineEdit:
1139 if (const auto *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
1140 const bool isInSpinBox =
1141 widget && qobject_cast<const QAbstractSpinBox *>(widget->parent()) != nullptr;
1142 const bool isInComboBox =
1143 widget && qobject_cast<const QComboBox *>(widget->parent()) != nullptr;
1144 if (!isInSpinBox && !isInComboBox) {
1145 const auto frameRect =
1146 QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
1147 drawRoundedRect(painter, frameRect, Qt::NoPen, inputFillBrush(option, widget));
1148 if (panel->lineWidth > 0)
1149 proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget);
1150 }
1151 }
1152 break;
1153 case PE_FrameLineEdit: {
1154 const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
1155 drawLineEditFrame(painter, frameRect, option);
1156 if (state & State_KeyboardFocusChange && state & State_HasFocus) {
1157 QStyleOptionFocusRect fropt;
1158 fropt.QStyleOption::operator=(*option);
1159 proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
1160 }
1161 break;
1162 }
1163 case PE_Frame: {
1164 if (const auto *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
1165 const auto rect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
1166 if (qobject_cast<const QComboBoxPrivateContainer *>(widget)) {
1167 if (d->nativeRoundedTopLevelWindows)
1168 break;
1169 QPen pen;
1170 if (highContrastTheme)
1171 pen = QPen(option->palette.windowText().color(), 2);
1172 else
1173 pen = Qt::NoPen;
1174 drawRoundedRect(painter, rect, pen, WINUI3Colors[colorSchemeIndex][menuPanelFill]);
1175 } else
1176 drawRoundedRect(painter, rect, Qt::NoPen, option->palette.brush(QPalette::Base));
1177
1178 if (frame->frameShape == QFrame::NoFrame)
1179 break;
1180
1181 const bool isEditable = qobject_cast<const QTextEdit *>(widget) != nullptr
1182 || qobject_cast<const QPlainTextEdit *>(widget) != nullptr;
1183 drawLineEditFrame(painter, rect, option, isEditable);
1184 }
1185 break;
1186 }
1187 case PE_PanelItemViewItem:
1188 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
1189 if (vopt->backgroundBrush.style() != Qt::NoBrush) {
1190 QPainterStateGuard psg(painter);
1191 painter->setBrushOrigin(vopt->rect.topLeft());
1192 painter->fillRect(vopt->rect, vopt->backgroundBrush);
1193 }
1194 }
1195 break;
1196 case PE_PanelItemViewRow:
1197 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
1198 // this is only called from a QTreeView to paint
1199 // - the tree branch decoration (incl. selected/hovered or not)
1200 // - the (alternate) background of the item in always unselected state
1201 const QRect &rect = vopt->rect;
1202 const bool isRtl = option->direction == Qt::RightToLeft;
1203 if (rect.width() <= 0)
1204 break;
1205
1206 if (vopt->features & QStyleOptionViewItem::Alternate) {
1207 QPalette::ColorGroup cg =
1208 (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled))
1209 ? QPalette::Normal
1210 : QPalette::Disabled;
1211 if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active))
1212 cg = QPalette::Inactive;
1213 painter->fillRect(rect, option->palette.brush(cg, QPalette::AlternateBase));
1214 }
1215
1216 if (option->state & State_Selected && !highContrastTheme) {
1217 // keep in sync with CE_ItemViewItem QListView indicator painting
1218 if (!qobject_cast<const QTableView *>(widget)) {
1219 const auto col = option->palette.accent().color();
1220 painter->setBrush(col);
1221 painter->setPen(col);
1222 const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f;
1223 const auto yOfs = rect.height() / 4.;
1224 QRectF r(QPointF(xPos, rect.y() + yOfs),
1225 QPointF(xPos + 1, rect.y() + rect.height() - yOfs));
1226 painter->drawRoundedRect(r, 1, 1);
1227 }
1228 }
1229
1230 const bool isTreeDecoration = vopt->features.testFlag(
1231 QStyleOptionViewItem::IsDecorationForRootColumn);
1232 if (isTreeDecoration && vopt->state.testAnyFlags(State_Selected | State_MouseOver) &&
1233 vopt->showDecorationSelected) {
1234 const bool onlyOne = vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne ||
1235 vopt->viewItemPosition == QStyleOptionViewItem::Invalid;
1236 bool isFirst = vopt->viewItemPosition == QStyleOptionViewItem::Beginning;
1237 bool isLast = vopt->viewItemPosition == QStyleOptionViewItem::End;
1238
1239 if (onlyOne)
1240 isFirst = true;
1241
1242 if (isRtl) {
1243 isFirst = !isFirst;
1244 isLast = !isLast;
1245 }
1246
1247 const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget);
1248 painter->setBrush(view->alternatingRowColors() && state & State_Selected ? calculateAccentColor(option) : WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
1249 painter->setPen(Qt::NoPen);
1250 if (isFirst) {
1251 QPainterStateGuard psg(painter);
1252 painter->setClipRect(rect);
1253 painter->drawRoundedRect(rect.marginsRemoved(QMargins(2, 2, -secondLevelRoundingRadius, 2)),
1254 secondLevelRoundingRadius, secondLevelRoundingRadius);
1255 } else if (isLast) {
1256 QPainterStateGuard psg(painter);
1257 painter->setClipRect(rect);
1258 painter->drawRoundedRect(rect.marginsRemoved(QMargins(-secondLevelRoundingRadius, 2, 2, 2)),
1259 secondLevelRoundingRadius, secondLevelRoundingRadius);
1260 } else {
1261 painter->drawRect(vopt->rect.marginsRemoved(QMargins(0, 2, 0, 2)));
1262 }
1263 }
1264 }
1265 break;
1266 case PE_Widget:
1267 break;
1268 case QStyle::PE_FrameWindow:
1269 if (const auto *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
1270
1271 QRectF rect= option->rect;
1272 int fwidth = int((frm->lineWidth + frm->midLineWidth) / QWindowsStylePrivate::nativeMetricScaleFactor(widget));
1273
1274 QRectF bottomLeftCorner = QRectF(rect.left() + 1.0,
1275 rect.bottom() - 1.0 - secondLevelRoundingRadius,
1276 secondLevelRoundingRadius,
1277 secondLevelRoundingRadius);
1278 QRectF bottomRightCorner = QRectF(rect.right() - 1.0 - secondLevelRoundingRadius,
1279 rect.bottom() - 1.0 - secondLevelRoundingRadius,
1280 secondLevelRoundingRadius,
1281 secondLevelRoundingRadius);
1282
1283 //Draw Mask
1284 if (widget != nullptr) {
1285 QBitmap mask(widget->width(), widget->height());
1286 mask.clear();
1287
1288 QPainter maskPainter(&mask);
1289 maskPainter.setRenderHint(QPainter::Antialiasing);
1290 maskPainter.setBrush(Qt::color1);
1291 maskPainter.setPen(Qt::NoPen);
1292 maskPainter.drawRoundedRect(option->rect,secondLevelRoundingRadius,secondLevelRoundingRadius);
1293 const_cast<QWidget*>(widget)->setMask(mask);
1294 }
1295
1296 //Draw Window
1297 painter->setPen(QPen(frm->palette.base(), fwidth));
1298 painter->drawLine(QPointF(rect.left(), rect.top()),
1299 QPointF(rect.left(), rect.bottom() - fwidth));
1300 painter->drawLine(QPointF(rect.left() + fwidth, rect.bottom()),
1301 QPointF(rect.right() - fwidth, rect.bottom()));
1302 painter->drawLine(QPointF(rect.right(), rect.top()),
1303 QPointF(rect.right(), rect.bottom() - fwidth));
1304
1305 painter->setPen(WINUI3Colors[colorSchemeIndex][surfaceStroke]);
1306 painter->drawLine(QPointF(rect.left() + 0.5, rect.top() + 0.5),
1307 QPointF(rect.left() + 0.5, rect.bottom() - 0.5 - secondLevelRoundingRadius));
1308 painter->drawLine(QPointF(rect.left() + 0.5 + secondLevelRoundingRadius, rect.bottom() - 0.5),
1309 QPointF(rect.right() - 0.5 - secondLevelRoundingRadius, rect.bottom() - 0.5));
1310 painter->drawLine(QPointF(rect.right() - 0.5, rect.top() + 1.5),
1311 QPointF(rect.right() - 0.5, rect.bottom() - 0.5 - secondLevelRoundingRadius));
1312
1313 painter->setPen(Qt::NoPen);
1314 painter->setBrush(frm->palette.base());
1315 painter->drawPie(bottomRightCorner.marginsAdded(QMarginsF(2.5,2.5,0.0,0.0)),
1316 270 * 16,90 * 16);
1317 painter->drawPie(bottomLeftCorner.marginsAdded(QMarginsF(0.0,2.5,2.5,0.0)),
1318 -90 * 16,-90 * 16);
1319
1320 painter->setPen(WINUI3Colors[colorSchemeIndex][surfaceStroke]);
1321 painter->setBrush(Qt::NoBrush);
1322 painter->drawArc(bottomRightCorner,
1323 0 * 16,-90 * 16);
1324 painter->drawArc(bottomLeftCorner,
1325 -90 * 16,-90 * 16);
1326 }
1327 break;
1328#if QT_CONFIG(tabbar)
1329 case PE_FrameTabBarBase:
1330 if (const auto *tab = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) {
1331 QPainterStateGuard psg(painter, QPainterStateGuard::InitialState::NoSave);
1332 const auto clipRegion = painter->clipRegion();
1333
1334 painter->setPen(highContrastTheme ? tab->palette.buttonText().color()
1335 : winUI3Color(frameColorLight));
1336 painter->setBrush(tab->palette.base());
1337 QRect upperRect = tab->rect;
1338 upperRect.setHeight(tab->rect.height() / 2);
1339 QRect lowerRect = tab->rect;
1340 lowerRect.setY(lowerRect.y() + tab->rect.height() / 2);
1341 painter->setClipRegion(clipRegion.isNull() ? upperRect : clipRegion & upperRect);
1342 painter->drawRoundedRect(tab->rect, secondLevelRoundingRadius,
1343 secondLevelRoundingRadius);
1344 painter->setClipRegion(clipRegion.isNull() ? lowerRect : clipRegion & lowerRect);
1345 painter->drawRect(tab->rect);
1346 }
1347 break;
1348#endif // QT_CONFIG(tabbar)
1349 case PE_IndicatorTabTearLeft:
1350 case PE_IndicatorTabTearRight:
1351 break;
1352 default:
1353 QWindowsVistaStyle::drawPrimitive(element, option, painter, widget);
1354 }
1355}
1356
1357/*!
1358 \internal
1359*/
1360void QWindows11Style::drawControl(ControlElement element, const QStyleOption *option,
1361 QPainter *painter, const QWidget *widget) const
1362{
1363 Q_D(const QWindows11Style);
1364
1365 QPainterStateGuard psg(painter);
1366 painter->setRenderHint(QPainter::Antialiasing);
1367 switch (element) {
1368 case QStyle::CE_ComboBoxLabel:
1369#if QT_CONFIG(combobox)
1370 if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
1371 painter->setPen(controlTextColor(option, true));
1372 QStyleOptionComboBox newOption = *cb;
1373 newOption.rect.adjust(4,0,-4,0);
1374 QCommonStyle::drawControl(element, &newOption, painter, widget);
1375 }
1376#endif // QT_CONFIG(combobox)
1377 break;
1378#if QT_CONFIG(tabbar)
1379 case CE_TabBarTabShape:
1380 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
1381 QPainterStateGuard psg(painter, QPainterStateGuard::InitialState::NoSave);
1382 const bool isSelected = tab->state.testFlag(State_Selected);
1383 const auto clipRegion = painter->clipRegion();
1384 const bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab
1385 || tab->position == QStyleOptionTab::Moving;
1386 auto leftMargin = (tab->position == QStyleOptionTab::Beginning || onlyOne) ? 1 : 0;
1387 auto rightMargin = (tab->position == QStyleOptionTab::End || onlyOne) ? 1 : 0;
1388 if (QCommonStylePrivate::rtl(option))
1389 std::swap(leftMargin, rightMargin);
1390
1391 QRectF tabRect = tab->rect.marginsRemoved(QMargins(leftMargin, 1, rightMargin, -3));
1392 painter->setPen(highContrastTheme ? tab->palette.buttonText().color() : winUI3Color(frameColorLight));
1393 painter->setBrush(tab->palette.base());
1394 if (isSelected) {
1395 painter->setBrush(winUI3Color(fillMicaAltDefault));
1396 } else {
1397 if (tab->state.testFlag(State_Sunken))
1398 painter->setBrush(winUI3Color(fillMicaAltDefault));
1399 else if (tab->state.testFlag(State_MouseOver))
1400 painter->setBrush(winUI3Color(fillMicaAltSecondary));
1401 else
1402 painter->setBrush(winUI3Color(fillMicaAltTransparent));
1403 }
1404 QRect upperRect = tab->rect;
1405 upperRect.setHeight(tab->rect.height() / 2.);
1406 QRect lowerRect = tab->rect;
1407 lowerRect.setY(lowerRect.y() + tab->rect.height() / 2.);
1408 painter->setClipRegion(clipRegion.isNull() ? upperRect : clipRegion & upperRect);
1409 painter->drawRoundedRect(tabRect, secondLevelRoundingRadius, secondLevelRoundingRadius);
1410 painter->setClipRegion(clipRegion.isNull() ? lowerRect : clipRegion & lowerRect);
1411 painter->drawRect(tabRect);
1412 }
1413 break;
1414 case CE_TabBarTabLabel:
1415 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
1416 const bool isEnabled = tab->state.testFlags(State_Enabled);
1417 const bool isSelected = tab->state.testFlags(State_Selected);
1418
1419 QRect tr = tab->rect;
1420 bool verticalTabs = tab->shape == QTabBar::RoundedEast
1421 || tab->shape == QTabBar::RoundedWest || tab->shape == QTabBar::TriangularEast
1422 || tab->shape == QTabBar::TriangularWest;
1423
1424 int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;
1425 if (!proxy()->styleHint(SH_UnderlineShortcut, option, widget))
1426 alignment |= Qt::TextHideMnemonic;
1427
1428 QPainterStateGuard psg(painter, QPainterStateGuard::InitialState::NoSave);
1429 if (verticalTabs) {
1430 psg.save();
1431 int newX, newY, newRot;
1432 if (tab->shape == QTabBar::RoundedEast || tab->shape == QTabBar::TriangularEast) {
1433 newX = tr.width() + tr.x();
1434 newY = tr.y();
1435 newRot = 90;
1436 } else {
1437 newX = tr.x();
1438 newY = tr.y() + tr.height();
1439 newRot = -90;
1440 }
1441 QTransform m = QTransform::fromTranslate(newX, newY);
1442 m.rotate(newRot);
1443 painter->setTransform(m, true);
1444 }
1445 QRect iconRect;
1446 d->tabLayout(tab, widget, &tr, &iconRect);
1447
1448 // compute tr again, unless tab is moving, because the style may override subElementRect
1449 if (tab->position != QStyleOptionTab::TabPosition::Moving)
1450 tr = proxy()->subElementRect(SE_TabBarTabText, option, widget);
1451
1452 if (!tab->icon.isNull()) {
1453 const auto mode = isEnabled ? QIcon::Normal : QIcon::Disabled;
1454 const auto state = isSelected ? QIcon::On : QIcon::Off;
1455 tab->icon.paint(painter, iconRect, Qt::AlignCenter, mode, state);
1456 }
1457
1458 const auto textRole = widget ? widget->foregroundRole() : QPalette::WindowText;
1459 if (tab->palette.isBrushSet(QPalette::Current, textRole))
1460 painter->setPen(tab->palette.color(QPalette::Current, textRole));
1461 else
1462 painter->setPen(winUI3Color(isSelected ? textPrimary : textSecondary));
1463 proxy()->drawItemText(painter, tr, alignment, tab->palette, isEnabled, tab->text);
1464 }
1465 break;
1466#endif // QT_CONFIG(tabbar)
1467 case CE_ToolButtonLabel:
1468#if QT_CONFIG(toolbutton)
1469 if (const QStyleOptionToolButton *toolbutton
1470 = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
1471 QRect rect = toolbutton->rect;
1472 int shiftX = 0;
1473 int shiftY = 0;
1474 if (toolbutton->state & (State_Sunken | State_On)) {
1475 shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, toolbutton, widget);
1476 shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, toolbutton, widget);
1477 }
1478 // Arrow type always overrules and is always shown
1479 bool hasArrow = toolbutton->features & QStyleOptionToolButton::Arrow;
1480 if (((!hasArrow && toolbutton->icon.isNull()) && !toolbutton->text.isEmpty())
1481 || toolbutton->toolButtonStyle == Qt::ToolButtonTextOnly) {
1482 int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;
1483 if (!proxy()->styleHint(SH_UnderlineShortcut, toolbutton, widget))
1484 alignment |= Qt::TextHideMnemonic;
1485 rect.translate(shiftX, shiftY);
1486 painter->setFont(toolbutton->font);
1487 const QString text = d->toolButtonElideText(toolbutton, rect, alignment);
1488 painter->setPen(controlTextColor(option));
1489 proxy()->drawItemText(painter, rect, alignment, toolbutton->palette,
1490 toolbutton->state & State_Enabled, text);
1491 } else {
1492 QPixmap pm;
1493 QSize pmSize = toolbutton->iconSize;
1494 if (!toolbutton->icon.isNull()) {
1495 QIcon::State state = toolbutton->state & State_On ? QIcon::On : QIcon::Off;
1496 QIcon::Mode mode;
1497 if (!(toolbutton->state & State_Enabled))
1498 mode = QIcon::Disabled;
1499 else if ((toolbutton->state & State_MouseOver) && (toolbutton->state & State_AutoRaise))
1500 mode = QIcon::Active;
1501 else
1502 mode = QIcon::Normal;
1503 pm = toolbutton->icon.pixmap(toolbutton->rect.size().boundedTo(toolbutton->iconSize), painter->device()->devicePixelRatio(),
1504 mode, state);
1505 pmSize = pm.size() / pm.devicePixelRatio();
1506 }
1507
1508 if (toolbutton->toolButtonStyle != Qt::ToolButtonIconOnly) {
1509 painter->setFont(toolbutton->font);
1510 QRect pr = rect,
1511 tr = rect;
1512 int alignment = Qt::TextShowMnemonic | Qt::AlignCenter;
1513 if (!proxy()->styleHint(SH_UnderlineShortcut, toolbutton, widget))
1514 alignment |= Qt::TextHideMnemonic;
1515
1516 if (toolbutton->toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
1517 pr.setHeight(pmSize.height() + 4); //### 4 is currently hardcoded in QToolButton::sizeHint()
1518 tr.adjust(0, pr.height() - 1, 0, -1);
1519 } else {
1520 pr.setWidth(pmSize.width() + 4); //### 4 is currently hardcoded in QToolButton::sizeHint()
1521 tr.adjust(pr.width(), 0, 0, 0);
1522 }
1523 pr.translate(shiftX, shiftY);
1524 if (hasArrow) {
1525 drawArrow(proxy(), toolbutton, pr, painter, widget);
1526 } else {
1527 const auto vr = QStyle::visualRect(toolbutton->direction, rect, pr);
1528 proxy()->drawItemPixmap(painter, vr, Qt::AlignCenter, pm);
1529 }
1530 tr.translate(shiftX, shiftY);
1531 painter->setPen(controlTextColor(option));
1532 const QString text = d->toolButtonElideText(toolbutton, tr, alignment);
1533 const auto vr = QStyle::visualRect(toolbutton->direction, rect, tr);
1534 proxy()->drawItemText(painter, vr, alignment, toolbutton->palette,
1535 toolbutton->state & State_Enabled, text);
1536 } else {
1537 rect.translate(shiftX, shiftY);
1538 if (hasArrow) {
1539 drawArrow(proxy(), toolbutton, rect, painter, widget);
1540 } else {
1541 proxy()->drawItemPixmap(painter, rect, Qt::AlignCenter, pm);
1542 }
1543 }
1544 }
1545 }
1546#endif // QT_CONFIG(toolbutton)
1547 break;
1548 case QStyle::CE_ShapedFrame:
1549 if (const QStyleOptionFrame *f = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
1550 int frameShape = f->frameShape;
1551 int frameShadow = QFrame::Plain;
1552 if (f->state & QStyle::State_Sunken)
1553 frameShadow = QFrame::Sunken;
1554 else if (f->state & QStyle::State_Raised)
1555 frameShadow = QFrame::Raised;
1556
1557 int lw = f->lineWidth;
1558 int mlw = f->midLineWidth;
1559
1560 switch (frameShape) {
1561 case QFrame::Box:
1562 if (frameShadow == QFrame::Plain)
1563 qDrawPlainRoundedRect(painter, f->rect, secondLevelRoundingRadius, secondLevelRoundingRadius, highContrastTheme == true ? f->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorStrong], lw);
1564 else
1565 qDrawShadeRect(painter, f->rect, f->palette, frameShadow == QFrame::Sunken, lw, mlw);
1566 break;
1567 case QFrame::Panel:
1568 if (frameShadow == QFrame::Plain)
1569 qDrawPlainRoundedRect(painter, f->rect, secondLevelRoundingRadius, secondLevelRoundingRadius, highContrastTheme == true ? f->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorStrong], lw);
1570 else
1571 qDrawShadePanel(painter, f->rect, f->palette, frameShadow == QFrame::Sunken, lw);
1572 break;
1573 default:
1574 QWindowsVistaStyle::drawControl(element, option, painter, widget);
1575 }
1576 }
1577 break;
1578#if QT_CONFIG(progressbar)
1579 case CE_ProgressBarGroove:
1580 if (const auto baropt = qstyleoption_cast<const QStyleOptionProgressBar*>(option)) {
1581 QRect rect = option->rect;
1582 QPointF center = rect.center();
1583 if (baropt->state & QStyle::State_Horizontal) {
1584 rect.setHeight(1);
1585 rect.moveTop(center.y());
1586 } else {
1587 rect.setWidth(1);
1588 rect.moveLeft(center.x());
1589 }
1590 painter->setPen(Qt::NoPen);
1591 painter->setBrush(Qt::gray);
1592 painter->drawRect(rect);
1593 }
1594 break;
1595 case CE_ProgressBarContents:
1596 if (const auto baropt = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
1597 QPainterStateGuard psg(painter);
1598 QRectF rect = option->rect;
1599 painter->translate(rect.topLeft());
1600 rect.translate(-rect.topLeft());
1601
1602 constexpr qreal progressBarThickness = 3;
1603 constexpr qreal progressBarHalfThickness = progressBarThickness / 2.0;
1604
1605 const auto isIndeterminate = baropt->maximum == 0 && baropt->minimum == 0;
1606 const auto orientation =
1607 (baropt->state & QStyle::State_Horizontal) ? Qt::Horizontal : Qt::Vertical;
1608 const auto inverted = baropt->invertedAppearance;
1609 const auto reverse = (baropt->direction == Qt::RightToLeft) ^ inverted;
1610 // If the orientation is vertical, we use a transform to rotate
1611 // the progress bar 90 degrees (counter)clockwise. This way we can use the
1612 // same rendering code for both orientations.
1613 if (orientation == Qt::Vertical) {
1614 rect = QRectF(rect.left(), rect.top(), rect.height(),
1615 rect.width()); // flip width and height
1616 QTransform m;
1617 if (inverted) {
1618 m.rotate(90);
1619 m.translate(0, -rect.height() + 1);
1620 } else {
1621 m.rotate(-90);
1622 m.translate(-rect.width(), 0);
1623 }
1624 painter->setTransform(m, true);
1625 } else if (reverse) {
1626 QTransform m = QTransform::fromScale(-1, 1);
1627 m.translate(-rect.width(), 0);
1628 painter->setTransform(m, true);
1629 }
1630 const qreal offset = (int(rect.height()) % 2 == 0) ? 0.5f : 0.0f;
1631
1632 if (isIndeterminate) {
1633#if QT_CONFIG(animation)
1634 auto anim = d->animation(option->styleObject);
1635 if (!anim) {
1636 auto anim = new QStyleAnimation(option->styleObject);
1637 anim->setFrameRate(QStyleAnimation::SixtyFps);
1638 d->startAnimation(anim);
1639 }
1640 constexpr auto loopDurationMSec = 4000;
1641 const auto elapsedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(
1642 std::chrono::system_clock::now());
1643 const auto elapsed = elapsedTime.time_since_epoch().count();
1644 const auto handleCenter = (elapsed % loopDurationMSec) / float(loopDurationMSec);
1645 const auto isLongHandle = (elapsed / loopDurationMSec) % 2 == 0;
1646 const auto lengthFactor = (isLongHandle ? 33.0f : 25.0f) / 100.0f;
1647#else
1648 constexpr auto handleCenter = 0.5f;
1649 constexpr auto lengthFactor = 1;
1650#endif
1651 const auto begin = qMax(handleCenter * (1 + lengthFactor) - lengthFactor, 0.0f);
1652 const auto end = qMin(handleCenter * (1 + lengthFactor), 1.0f);
1653 const auto barBegin = begin * rect.width();
1654 const auto barEnd = end * rect.width();
1655 rect = QRectF(QPointF(rect.left() + barBegin, rect.top()),
1656 QPointF(rect.left() + barEnd, rect.bottom()));
1657 } else {
1658#if QT_CONFIG(animation)
1659 d->stopAnimation(option->styleObject);
1660#endif
1661 const auto fillPercentage = (float(baropt->progress - baropt->minimum))
1662 / (float(baropt->maximum - baropt->minimum));
1663 rect.setWidth(rect.width() * fillPercentage);
1664 }
1665 const QPointF center = rect.center();
1666 rect.setHeight(progressBarThickness);
1667 rect.moveTop(center.y() - progressBarHalfThickness - offset);
1668 drawRoundedRect(painter, rect, Qt::NoPen, baropt->palette.accent());
1669 }
1670 break;
1671 case CE_ProgressBarLabel:
1672 if (const auto baropt = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
1673 const bool vertical = !(baropt->state & QStyle::State_Horizontal);
1674 if (!vertical) {
1675 proxy()->drawItemText(painter, baropt->rect, Qt::AlignCenter | Qt::TextSingleLine,
1676 baropt->palette, baropt->state & State_Enabled, baropt->text,
1677 QPalette::Text);
1678 }
1679 }
1680 break;
1681#endif // QT_CONFIG(progressbar)
1682 case CE_PushButtonLabel:
1683 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
1684 QStyleOptionButton btnCopy(*btn);
1685 btnCopy.rect = btn->rect.marginsRemoved(QMargins(contentHMargin, 0, contentHMargin, 0));
1686 btnCopy.palette.setBrush(QPalette::ButtonText, controlTextColor(option));
1687 QCommonStyle::drawControl(element, &btnCopy, painter, widget);
1688 }
1689 break;
1690 case CE_PushButtonBevel:
1691 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
1692 using namespace StyleOptionHelper;
1693 QPainterStateGuard psg(painter);
1694
1695 const auto rect = QRectF(btn->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
1696 if (btn->features.testFlag(QStyleOptionButton::Flat)) {
1697 const QBrush brush = isPressed(option)
1698 ? winUI3Color(subtlePressedColor)
1699 : (isHover(option) ? winUI3Color(subtleHighlightColor) : Qt::transparent);
1700 drawRoundedRect(painter, rect, Qt::NoPen, brush);
1701 } else {
1702 const bool defaultButton = btn->features.testFlag(QStyleOptionButton::DefaultButton);
1703 const QPen pen = defaultButton ? option->palette.color(QPalette::Accent)
1704 : winUI3Color(controlStrokePrimary);
1705 drawRoundedRect(painter, rect, pen, controlFillBrush(option, ControlType::Control));
1706 }
1707 if (btn->features.testFlag(QStyleOptionButton::HasMenu)) {
1708 QRect textRect = btn->rect.marginsRemoved(QMargins(contentHMargin, 0, contentHMargin, 0));
1709 const auto indSize = proxy()->pixelMetric(PM_MenuButtonIndicator, btn, widget);
1710 const auto indRect =
1711 QRect(btn->rect.right() - indSize - contentItemHMargin, textRect.top(),
1712 indSize + contentItemHMargin, btn->rect.height());
1713 const auto vindRect = visualRect(btn->direction, btn->rect, indRect);
1714 textRect.setWidth(textRect.width() - indSize);
1715
1716 int fontSize = painter->font().pointSize();
1717 QFont f(d->assetFont);
1718 f.setPointSize(qRound(fontSize * 0.9f)); // a little bit smaller
1719 painter->setFont(f);
1720 painter->setPen(controlTextColor(option));
1721 painter->drawText(vindRect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed));
1722 }
1723 }
1724 break;
1725 case CE_MenuBarItem:
1726 if (const auto *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
1727 using namespace StyleOptionHelper;
1728
1729 constexpr int hPadding = 11;
1730 constexpr int topPadding = 4;
1731 constexpr int bottomPadding = 6;
1732 QStyleOptionMenuItem newMbi = *mbi;
1733
1734 if (auto mbiV2 = qstyleoption_cast<const QStyleOptionMenuItemV2 *>(option))
1735 newMbi.state.setFlag(State_Sunken, mbiV2->mouseDown);
1736
1737 newMbi.font.setPointSize(10);
1738 newMbi.palette.setColor(QPalette::ButtonText, controlTextColor(&newMbi));
1739 if (!isDisabled(&newMbi)) {
1740 QPen pen(Qt::NoPen);
1741 QBrush brush(Qt::NoBrush);
1742 if (highContrastTheme) {
1743 pen = QPen(newMbi.palette.highlight().color(), 2);
1744 brush = newMbi.palette.window();
1745 } else if (isPressed(&newMbi)) {
1746 brush = winUI3Color(subtlePressedColor);
1747 } else if (isHover(&newMbi)) {
1748 brush = winUI3Color(subtleHighlightColor);
1749 }
1750 if (pen != Qt::NoPen || brush != Qt::NoBrush) {
1751 const QRect rect = mbi->rect.marginsRemoved(QMargins(5, 0, 5, 0));
1752 drawRoundedRect(painter, rect, pen, brush);
1753 }
1754 }
1755 newMbi.rect.adjust(hPadding,topPadding,-hPadding,-bottomPadding);
1756 painter->setFont(newMbi.font);
1757 QCommonStyle::drawControl(element, &newMbi, painter, widget);
1758 }
1759 break;
1760
1761#if QT_CONFIG(menu)
1762 case CE_MenuEmptyArea:
1763 break;
1764
1765 case CE_MenuItem:
1766 if (const auto *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
1767 using namespace StyleOptionHelper;
1768
1769 const auto visualMenuRect = [&](const QRect &rect) {
1770 return visualRect(option->direction, menuitem->rect, rect);
1771 };
1772
1773 const bool checked =
1774 menuitem->checkType != QStyleOptionMenuItem::NotCheckable && menuitem->checked;
1775
1776 const QRect rect = menuitem->rect.marginsRemoved(QMargins(2,2,2,2));
1777 if (!isDisabled(menuitem)) {
1778 QPen pen(Qt::NoPen);
1779 QBrush brush(Qt::NoBrush);
1780 if (highContrastTheme) {
1781 pen = QPen(menuitem->palette.highlight().color(), 2);
1782 brush = menuitem->palette.window();
1783 } else if (isPressed(menuitem)) {
1784 brush = winUI3Color(subtlePressedColor);
1785 } else if (isSelected(menuitem)) { // == hover
1786 brush = winUI3Color(subtleHighlightColor);
1787 }
1788 if (pen != Qt::NoPen || brush != Qt::NoBrush)
1789 drawRoundedRect(painter, rect, pen, brush);
1790 }
1791 if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) {
1792 constexpr int yoff = 1;
1793 painter->setPen(highContrastTheme ? menuitem->palette.buttonText().color() : winUI3Color(dividerStrokeDefault));
1794 painter->drawLine(menuitem->rect.topLeft() + QPoint(0, yoff),
1795 menuitem->rect.topRight() + QPoint(0, yoff));
1796 break;
1797 }
1798
1799 int xOffset = contentHMargin;
1800 // WinUI3 draws, in contrast to former windows styles, the checkmark and icon separately
1801 const auto checkMarkWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget);
1802 if (checked) {
1803 QRect vRect(visualMenuRect(QRect(rect.x() + xOffset, rect.y(),
1804 checkMarkWidth, rect.height())));
1805 QPainterStateGuard psg(painter);
1806 painter->setFont(d->assetFont);
1807 painter->setPen(option->palette.text().color());
1808 painter->drawText(vRect, Qt::AlignCenter, fluentIcon(Icon::CheckMark));
1809 }
1810 if (menuitem->menuHasCheckableItems)
1811 xOffset += checkMarkWidth + contentItemHMargin;
1812 if (!menuitem->icon.isNull()) {
1813 // 4 is added to maxIconWidth in qmenu.cpp to PM_SmallIconSize
1814 QRect vRect(visualMenuRect(QRect(rect.x() + xOffset,
1815 rect.y(),
1816 menuitem->maxIconWidth - 4,
1817 rect.height())));
1818 const auto mode = isDisabled(menuitem)
1819 ? QIcon::Disabled
1820 : (isSelected(menuitem) ? QIcon::Active : QIcon::Normal);
1821 const auto size = proxy()->pixelMetric(PM_SmallIconSize, option, widget);
1822 QRect pmr(QPoint(0, 0), QSize(size, size));
1823 pmr.moveCenter(vRect.center());
1824 menuitem->icon.paint(painter, pmr, Qt::AlignCenter, mode,
1825 checked ? QIcon::On : QIcon::Off);
1826 }
1827 if (menuitem->maxIconWidth > 0)
1828 xOffset += menuitem->maxIconWidth - 4 + contentItemHMargin;
1829
1830 QStringView s(menuitem->text);
1831 if (!s.isEmpty()) { // draw text
1832 QPoint tl(rect.left() + xOffset, rect.top());
1833 QPoint br(rect.right() - menuitem->reservedShortcutWidth - contentHMargin,
1834 rect.bottom());
1835 QRect textRect(tl, br);
1836 QRect vRect(visualMenuRect(textRect));
1837
1838 QColor penColor;
1839 if (highContrastTheme) {
1840 penColor = menuitem->palette.color(
1841 isSelected(menuitem) ? QPalette::HighlightedText : QPalette::Text);
1842 } else {
1843 QStyleOption tmpOpt(*option);
1844 tmpOpt.state.setFlag(State_Sunken, false);
1845 penColor = controlTextColor(&tmpOpt);
1846 }
1847 painter->setPen(penColor);
1848
1849 qsizetype t = s.indexOf(u'\t');
1850 int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
1851 if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
1852 text_flags |= Qt::TextHideMnemonic;
1853 text_flags |= Qt::AlignLeft;
1854 // a submenu doesn't paint a possible shortcut in WinUI3
1855 if (t >= 0 && menuitem->menuItemType != QStyleOptionMenuItem::SubMenu) {
1856 QRect shortcutRect(QPoint(textRect.right(), textRect.top()),
1857 QPoint(rect.right(), textRect.bottom()));
1858 QRect vShortcutRect(visualMenuRect(shortcutRect));
1859 const QString textToDraw = s.mid(t + 1).toString();
1860 painter->drawText(vShortcutRect, text_flags, textToDraw);
1861 s = s.left(t);
1862 }
1863 QFont font = menuitem->font;
1864 if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem)
1865 font.setBold(true);
1866 painter->setFont(font);
1867 const QString textToDraw = s.left(t).toString();
1868 painter->drawText(vRect, text_flags, textToDraw);
1869 }
1870 if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow
1871 int fontSize = menuitem->font.pointSize();
1872 QFont f(d->assetFont);
1873 f.setPointSize(qRound(fontSize * 0.9f)); // a little bit smaller
1874 painter->setFont(f);
1875 int yOfs = qRound(fontSize / 3.0f); // an offset to align the '>' with the baseline of the text
1876 QPoint tl(rect.right() - 2 * QWindowsStylePrivate::windowsArrowHMargin - contentItemHMargin,
1877 rect.top() + yOfs);
1878 QRect submenuRect(tl, rect.bottomRight());
1879 QRect vSubMenuRect = visualMenuRect(submenuRect);
1880 painter->setPen(option->palette.text().color());
1881 const bool isReverse = option->direction == Qt::RightToLeft;
1882 const auto ico = isReverse ? Icon::ChevronLeftMed : Icon::ChevronRightMed;
1883 painter->drawText(vSubMenuRect, Qt::AlignCenter, fluentIcon(ico));
1884 }
1885 }
1886 break;
1887#endif // QT_CONFIG(menu)
1888 case CE_MenuBarEmptyArea: {
1889 break;
1890 }
1891 case CE_HeaderEmptyArea:
1892 break;
1893 case CE_HeaderSection: {
1894 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
1895 painter->setPen(Qt::NoPen);
1896 painter->setBrush(header->palette.button());
1897 painter->drawRect(header->rect);
1898 const bool isRtl = option->direction == Qt::RightToLeft;
1899 const QPointF tr = isRtl ? option->rect.topLeft() : option->rect.topRight();
1900 const QPointF br = isRtl ? option->rect.bottomLeft() : option->rect.bottomRight();
1901 const QPointF bl = isRtl ? option->rect.bottomRight() : option->rect.bottomLeft();
1902 constexpr QPointF trOfs = QPointF(0.5, 0.0);
1903 constexpr QPointF brOfs = QPointF(0.5, 0.5);
1904 constexpr QPointF blOfs = QPointF(0.0, 0.5);
1905 const std::array<QPointF, 3> points = { tr + trOfs, br + brOfs, bl + blOfs };
1906 QPen pen(highContrastTheme ? header->palette.buttonText().color()
1907 : winUI3Color(frameColorLight));
1908 pen.setJoinStyle(Qt::MiterJoin);
1909 painter->setPen(pen);
1910 painter->drawPolyline(points.data(), int(points.size()));
1911 }
1912 break;
1913 }
1914 case CE_ItemViewItem: {
1915 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
1916 const auto p = proxy();
1917 QRect checkRect = p->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
1918 QRect iconRect = p->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
1919 QRect textRect = p->subElementRect(SE_ItemViewItemText, vopt, widget);
1920
1921 // draw the background
1922 proxy()->drawPrimitive(PE_PanelItemViewItem, option, painter, widget);
1923
1924 const QRect &rect = vopt->rect;
1925 const bool isRtl = option->direction == Qt::RightToLeft;
1926 bool onlyOne = vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne ||
1927 vopt->viewItemPosition == QStyleOptionViewItem::Invalid;
1928 bool isFirst = vopt->viewItemPosition == QStyleOptionViewItem::Beginning;
1929 bool isLast = vopt->viewItemPosition == QStyleOptionViewItem::End;
1930
1931 const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget);
1932 if (qobject_cast<const QTableView *>(view)) {
1933 onlyOne = true;
1934 } else {
1935 // the tree decoration already painted the left side of the rounded rect
1936 if (vopt->features.testFlag(QStyleOptionViewItem::IsDecoratedRootColumn) &&
1937 vopt->showDecorationSelected) {
1938 isFirst = false;
1939 if (onlyOne) {
1940 onlyOne = false;
1941 isLast = true;
1942 }
1943 }
1944
1945 if (isRtl) {
1946 if (isFirst) {
1947 isFirst = false;
1948 isLast = true;
1949 } else if (isLast) {
1950 isFirst = true;
1951 isLast = false;
1952 }
1953 }
1954 }
1955
1956 const bool highlightCurrent = vopt->state.testAnyFlags(State_Selected | State_MouseOver);
1957 if (highlightCurrent) {
1958 if (highContrastTheme) {
1959 painter->setBrush(vopt->palette.highlight());
1960 } else {
1961 painter->setBrush(view && view->alternatingRowColors() && vopt->state & State_Selected
1962 ? calculateAccentColor(option)
1963 : winUI3Color(subtleHighlightColor));
1964 }
1965 } else {
1966 painter->setBrush(vopt->backgroundBrush);
1967 }
1968 painter->setPen(Qt::NoPen);
1969
1970 if (onlyOne) {
1971 painter->drawRoundedRect(rect.marginsRemoved(QMargins(2, 2, 2, 2)),
1972 secondLevelRoundingRadius, secondLevelRoundingRadius);
1973 } else if (isFirst) {
1974 QPainterStateGuard psg(painter);
1975 painter->setClipRect(rect);
1976 painter->drawRoundedRect(rect.marginsRemoved(QMargins(2, 2, -secondLevelRoundingRadius, 2)),
1977 secondLevelRoundingRadius, secondLevelRoundingRadius);
1978 } else if (isLast) {
1979 QPainterStateGuard psg(painter);
1980 painter->setClipRect(rect);
1981 painter->drawRoundedRect(rect.marginsRemoved(QMargins(-secondLevelRoundingRadius, 2, 2, 2)),
1982 secondLevelRoundingRadius, secondLevelRoundingRadius);
1983 } else {
1984 painter->drawRect(rect.marginsRemoved(QMargins(0, 2, 0, 2)));
1985 }
1986
1987 // draw the check mark
1988 if (vopt->features & QStyleOptionViewItem::HasCheckIndicator) {
1989 QStyleOptionViewItem option(*vopt);
1990 option.rect = checkRect;
1991 option.state = option.state & ~QStyle::State_HasFocus;
1992
1993 switch (vopt->checkState) {
1994 case Qt::Unchecked:
1995 option.state |= QStyle::State_Off;
1996 break;
1997 case Qt::PartiallyChecked:
1998 option.state |= QStyle::State_NoChange;
1999 break;
2000 case Qt::Checked:
2001 option.state |= QStyle::State_On;
2002 break;
2003 }
2004 proxy()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &option, painter, widget);
2005 }
2006
2007 // draw the icon
2008 if (iconRect.isValid()) {
2009 QIcon::Mode mode = QIcon::Normal;
2010 if (!(vopt->state & QStyle::State_Enabled))
2011 mode = QIcon::Disabled;
2012 else if (vopt->state & QStyle::State_Selected)
2013 mode = QIcon::Selected;
2014 QIcon::State state = vopt->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
2015 vopt->icon.paint(painter, iconRect, vopt->decorationAlignment, mode, state);
2016 }
2017
2018 if (highlightCurrent && highContrastTheme) {
2019 painter->setPen(vopt->palette.base().color());
2020 } else if ((view && view->alternatingRowColors() && highlightCurrent && vopt->state & State_Selected)) {
2021 painter->setPen(winUI3Color(textOnAccentPrimary));
2022 } else {
2023 painter->setPen(vopt->palette.text().color());
2024 }
2025 d->viewItemDrawText(painter, vopt, textRect);
2026
2027 // paint a vertical marker for QListView
2028 if (vopt->state & State_Selected && !highContrastTheme) {
2029 if (const QListView *lv = qobject_cast<const QListView *>(widget);
2030 lv && lv->viewMode() != QListView::IconMode) {
2031 const auto col = vopt->palette.accent().color();
2032 painter->setBrush(col);
2033 painter->setPen(col);
2034 const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f;
2035 const auto yOfs = rect.height() / 4.;
2036 QRectF r(QPointF(xPos, rect.y() + yOfs),
2037 QPointF(xPos + 1, rect.y() + rect.height() - yOfs));
2038 painter->drawRoundedRect(r, 1, 1);
2039 }
2040 }
2041 }
2042 break;
2043 }
2044 default:
2045 QWindowsVistaStyle::drawControl(element, option, painter, widget);
2046 }
2047}
2048
2049int QWindows11Style::styleHint(StyleHint hint, const QStyleOption *opt,
2050 const QWidget *widget, QStyleHintReturn *returnData) const {
2051 switch (hint) {
2052 case QStyle::SH_Menu_AllowActiveAndDisabled:
2053 return 0;
2054 case SH_GroupBox_TextLabelColor:
2055 if (opt!=nullptr && widget!=nullptr)
2056 return opt->palette.text().color().rgba();
2057 return 0;
2058 case QStyle::SH_ItemView_ShowDecorationSelected:
2059 return 1;
2060 case QStyle::SH_Slider_AbsoluteSetButtons:
2061 return Qt::LeftButton;
2062 case QStyle::SH_Slider_PageSetButtons:
2063 return 0;
2064 case SH_SpinControls_DisableOnBounds:
2065 return 1;
2066 default:
2067 return QWindowsVistaStyle::styleHint(hint, opt, widget, returnData);
2068 }
2069}
2070
2071QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOption *option,
2072 const QWidget *widget) const
2073{
2074 QRect ret;
2075 switch (element) {
2076 case QStyle::SE_RadioButtonIndicator:
2077 case QStyle::SE_CheckBoxIndicator: {
2078 ret = QWindowsVistaStyle::subElementRect(element, option, widget);
2079 const auto ofs =
2080 QCommonStylePrivate::rtl(option) ? -contentItemHMargin : +contentItemHMargin;
2081 ret.moveLeft(ret.left() + ofs);
2082 break;
2083 }
2084 case QStyle::SE_ComboBoxFocusRect:
2085 case QStyle::SE_CheckBoxFocusRect:
2086 case QStyle::SE_RadioButtonFocusRect:
2087 case QStyle::SE_PushButtonFocusRect:
2088 ret = option->rect;
2089 break;
2090 case QStyle::SE_LineEditContents:
2091 ret = option->rect.adjusted(4,0,-4,0);
2092 break;
2093 case SE_ItemViewItemCheckIndicator:
2094 case SE_ItemViewItemDecoration:
2095 case SE_ItemViewItemText: {
2096 ret = QWindowsVistaStyle::subElementRect(element, option, widget);
2097 if (!ret.isValid() || highContrastTheme)
2098 return ret;
2099
2100 if (const QListView *lv = qobject_cast<const QListView *>(widget);
2101 lv && lv->viewMode() != QListView::IconMode) {
2102 const int xOfs = contentHMargin;
2103 const bool isRtl = option->direction == Qt::RightToLeft;
2104 if (isRtl) {
2105 ret.moveRight(ret.right() - xOfs);
2106 if (ret.left() < option->rect.left())
2107 ret.setLeft(option->rect.left());
2108 } else {
2109 ret.moveLeft(ret.left() + xOfs);
2110 if (ret.right() > option->rect.right())
2111 ret.setRight(option->rect.right());
2112 }
2113 }
2114 break;
2115 }
2116#if QT_CONFIG(progressbar)
2117 case SE_ProgressBarGroove:
2118 case SE_ProgressBarContents:
2119 case SE_ProgressBarLabel:
2120 if (const auto *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
2121 QStyleOptionProgressBar optCopy(*pb);
2122 // we only support label right from content
2123 optCopy.textAlignment = Qt::AlignRight;
2124 return QWindowsVistaStyle::subElementRect(element, &optCopy, widget);
2125 }
2126 break;
2127#endif // QT_CONFIG(progressbar)
2128#if QT_CONFIG(toolbar)
2129 case SE_TabBarScrollLeftButton:
2130 case SE_TabBarScrollRightButton: {
2131 const bool isRightButton = element == SE_TabBarScrollRightButton;
2132 const bool vertical = option->rect.width() < option->rect.height();
2133 const int buttonWidth = proxy()->pixelMetric(PM_TabBarScrollButtonWidth, option, widget);
2134
2135 if (vertical) {
2136 const int yOfs =
2137 isRightButton ? option->rect.height() - buttonWidth : 0;
2138 const QSize sz(option->rect.width(), buttonWidth);
2139 const QPoint tl(option->rect.topLeft() + QPoint(0, yOfs));
2140 ret = QRect(tl, sz);
2141 } else {
2142 const int xOfs =
2143 isRightButton ? option->rect.width() - buttonWidth : 0;
2144 const QSize sz(buttonWidth, option->rect.height());
2145 const QPoint tl(option->rect.topLeft() + QPoint(xOfs, 0));
2146 ret = QRect(tl, sz);
2147 ret = visualRect(widget->layoutDirection(), option->rect, ret);
2148 }
2149 break;
2150 }
2151 case SE_TabBarTearIndicatorLeft:
2152 case SE_TabBarTearIndicatorRight:
2153 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
2154 const bool isRightButton = element == SE_TabBarTearIndicatorRight;
2155 const int buttonWidth =
2156 proxy()->pixelMetric(PM_TabBarScrollButtonWidth, nullptr, widget);
2157 switch (tab->shape) {
2158 case QTabBar::RoundedNorth:
2159 case QTabBar::TriangularNorth:
2160 case QTabBar::RoundedSouth:
2161 case QTabBar::TriangularSouth: {
2162 const auto ofs = isRightButton
2163 ? option->rect.width() - 2 - buttonWidth - contentItemHMargin
2164 : 2 + buttonWidth + contentItemHMargin;
2165 const QPoint tl(tab->rect.topLeft() + QPoint(ofs, 0));
2166 ret = QRect(tl, QSize(1, option->rect.height()));
2167 break;
2168 }
2169 case QTabBar::RoundedWest:
2170 case QTabBar::TriangularWest:
2171 case QTabBar::RoundedEast:
2172 case QTabBar::TriangularEast: {
2173 const auto ofs = isRightButton
2174 ? option->rect.height() - 2 - buttonWidth - contentItemHMargin
2175 : 2 + buttonWidth + contentItemHMargin;
2176 const QPoint tl(tab->rect.topLeft() + QPoint(0, ofs));
2177 ret = QRect(tl, QSize(option->rect.width(), 1));
2178 break;
2179 }
2180 default:
2181 break;
2182 }
2183 ret = visualRect(option->direction, option->rect, ret);
2184 }
2185 break;
2186#endif // QT_CONFIG(toolbar)
2187 case SE_HeaderLabel: {
2188 const int margin = proxy()->pixelMetric(PM_HeaderMargin, option, widget);
2189 ret = option->rect.marginsRemoved({ margin, margin, margin, margin });
2190 break;
2191 }
2192 case SE_HeaderArrow: {
2193 ret = option->rect;
2194 if (const auto *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
2195 // the sort indicator is always show above in the win11 style
2196 if (header->sortIndicator != QStyleOptionHeader::None) {
2197 const int arrowSize = proxy()->pixelMetric(PM_HeaderMarkSize, option, widget);
2198 const int xCenter = ret.center().x();
2199 ret = QRect(xCenter - arrowSize / 2, ret.top(), arrowSize, arrowSize);
2200 }
2201 }
2202 break;
2203 }
2204 case SE_PushButtonContents: {
2205 int border = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
2206 ret = option->rect.marginsRemoved(QMargins(border, border, border, border));
2207 break;
2208 }
2209 default:
2210 ret = QWindowsVistaStyle::subElementRect(element, option, widget);
2211 }
2212 return ret;
2213}
2214
2215/*!
2216 \internal
2217 */
2218QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOptionComplex *option,
2219 SubControl subControl, const QWidget *widget) const
2220{
2221 QRect ret;
2222
2223 switch (control) {
2224#if QT_CONFIG(spinbox)
2225 case CC_SpinBox:
2226 if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
2227 const bool hasButtons = spinbox->buttonSymbols != QAbstractSpinBox::NoButtons;
2228 const int fw = spinbox->frame
2229 ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget)
2230 : 0;
2231 int textFieldLength = spinbox->rect.width() - 2 * fw;
2232 int buttonHeight = 0;
2233 int buttonWidth = 0;
2234 if (hasButtons) {
2235 const QWidget *w = qobject_cast<QWidget *>(spinbox->styleObject);
2236 buttonWidth = w
2237 ? widthFromChar(w->font(), Icon::ChevronUp, m_fontPoint2ChevronUpWidth)
2238 : spinbox->fontMetrics.horizontalAdvance(fluentIcon(Icon::ChevronUp));
2239 buttonHeight = spinbox->rect.height() - 4 * fw; // 4 * fw - some more space around the buttons
2240 textFieldLength -= 2 * (buttonWidth + fw) + 2 * fw;
2241 }
2242 const QPoint topLeft(spinbox->rect.topLeft() + QPoint(fw, fw));
2243 switch (subControl) {
2244 case SC_SpinBoxUp:
2245 case SC_SpinBoxDown: {
2246 if (!hasButtons)
2247 return QRect();
2248 const int yOfs = ((spinbox->rect.height() - 2 * fw) - buttonHeight) / 2;
2249 ret = QRect(topLeft.x() + textFieldLength + fw, topLeft.y() + yOfs, buttonWidth,
2250 buttonHeight);
2251 if (subControl == SC_SpinBoxDown)
2252 ret.moveRight(ret.right() + buttonWidth + fw);
2253 break;
2254 }
2255 case SC_SpinBoxEditField:
2256 ret = QRect(topLeft,
2257 QPoint(topLeft.x() + textFieldLength, spinbox->rect.bottom() - fw));
2258 break;
2259 case SC_SpinBoxFrame:
2260 ret = spinbox->rect;
2261 default:
2262 break;
2263 }
2264 ret = visualRect(spinbox->direction, spinbox->rect, ret);
2265 }
2266 break;
2267 case CC_TitleBar:
2268 if (const QStyleOptionTitleBar *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
2269 SubControl sc = subControl;
2270 ret = QCommonStyle::subControlRect(control, option, subControl, widget);
2271 static constexpr int indent = 3;
2272 static constexpr int controlWidthMargin = 2;
2273 const int controlHeight = titlebar->rect.height();
2274 const int controlWidth = 46;
2275 const int iconSize = proxy()->pixelMetric(QStyle::PM_TitleBarButtonIconSize, option, widget);
2276 int offset = -(controlWidthMargin + indent);
2277
2278 bool isMinimized = titlebar->titleBarState & Qt::WindowMinimized;
2279 bool isMaximized = titlebar->titleBarState & Qt::WindowMaximized;
2280
2281 switch (sc) {
2282 case SC_TitleBarLabel:
2283 if (titlebar->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) {
2284 ret = titlebar->rect;
2285 if (titlebar->titleBarFlags & Qt::WindowSystemMenuHint)
2286 ret.adjust(iconSize + controlWidthMargin + indent, 0, -controlWidth, 0);
2287 if (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint)
2288 ret.adjust(0, 0, -controlWidth, 0);
2289 if (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint)
2290 ret.adjust(0, 0, -controlWidth, 0);
2291 if (titlebar->titleBarFlags & Qt::WindowShadeButtonHint)
2292 ret.adjust(0, 0, -controlWidth, 0);
2293 if (titlebar->titleBarFlags & Qt::WindowContextHelpButtonHint)
2294 ret.adjust(0, 0, -controlWidth, 0);
2295 }
2296 break;
2297 case SC_TitleBarContextHelpButton:
2298 if (titlebar->titleBarFlags & Qt::WindowContextHelpButtonHint)
2299 offset += controlWidth;
2300 Q_FALLTHROUGH();
2301 case SC_TitleBarMinButton:
2302 if (!isMinimized && (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint))
2303 offset += controlWidth;
2304 else if (sc == SC_TitleBarMinButton)
2305 break;
2306 Q_FALLTHROUGH();
2307 case SC_TitleBarNormalButton:
2308 if (isMinimized && (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint))
2309 offset += controlWidth;
2310 else if (isMaximized && (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint))
2311 offset += controlWidth;
2312 else if (sc == SC_TitleBarNormalButton)
2313 break;
2314 Q_FALLTHROUGH();
2315 case SC_TitleBarMaxButton:
2316 if (!isMaximized && (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint))
2317 offset += controlWidth;
2318 else if (sc == SC_TitleBarMaxButton)
2319 break;
2320 Q_FALLTHROUGH();
2321 case SC_TitleBarShadeButton:
2322 if (!isMinimized && (titlebar->titleBarFlags & Qt::WindowShadeButtonHint))
2323 offset += controlWidth;
2324 else if (sc == SC_TitleBarShadeButton)
2325 break;
2326 Q_FALLTHROUGH();
2327 case SC_TitleBarUnshadeButton:
2328 if (isMinimized && (titlebar->titleBarFlags & Qt::WindowShadeButtonHint))
2329 offset += controlWidth;
2330 else if (sc == SC_TitleBarUnshadeButton)
2331 break;
2332 Q_FALLTHROUGH();
2333 case SC_TitleBarCloseButton:
2334 if (titlebar->titleBarFlags & Qt::WindowSystemMenuHint)
2335 offset += controlWidth;
2336 else if (sc == SC_TitleBarCloseButton)
2337 break;
2338 ret.setRect(titlebar->rect.right() - offset, titlebar->rect.top(),
2339 controlWidth, controlHeight);
2340 break;
2341 case SC_TitleBarSysMenu:
2342 if (titlebar->titleBarFlags & Qt::WindowSystemMenuHint) {
2343 const auto yOfs = titlebar->rect.top() + (titlebar->rect.height() - iconSize) / 2;
2344 ret.setRect(titlebar->rect.left() + controlWidthMargin + indent, yOfs, iconSize,
2345 iconSize);
2346 }
2347 break;
2348 default:
2349 break;
2350 }
2351 if (widget && isMinimized && titlebar->rect.width() < offset)
2352 const_cast<QWidget*>(widget)->resize(controlWidthMargin + indent + offset + iconSize + controlWidthMargin, controlWidth);
2353 ret = visualRect(titlebar->direction, titlebar->rect, ret);
2354 }
2355 break;
2356#endif // Qt_NO_SPINBOX
2357 case CC_ScrollBar:
2358 {
2359 ret = QCommonStyle::subControlRect(control, option, subControl, widget);
2360
2361 if (subControl == SC_ScrollBarAddLine || subControl == SC_ScrollBarSubLine) {
2362 if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
2363 if (scrollbar->orientation == Qt::Vertical)
2364 ret = ret.adjusted(2,2,-2,-3);
2365 else
2366 ret = ret.adjusted(3,2,-2,-2);
2367 }
2368 }
2369 break;
2370 }
2371 case CC_ComboBox: {
2372 if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
2373 const auto indicatorWidth =
2374 proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget);
2375 switch (subControl) {
2376 case SC_ComboBoxArrow: {
2377 const int fw =
2378 cb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, cb, widget) : 0;
2379 const int buttonHeight =
2380 qMin(cb->rect.height() - 3 * fw, cb->fontMetrics.height() * 5 / 4);
2381 const QSize buttonSize(buttonHeight * 6 / 5, buttonHeight);
2382 const int textFieldLength = cb->rect.width() - 2 * fw - buttonSize.width();
2383 const QPoint topLeft(cb->rect.topLeft() + QPoint(fw, fw));
2384 const int yOfs = ((cb->rect.height() - 2 * fw) - buttonSize.height()) / 2;
2385 ret = QRect(topLeft.x() + textFieldLength, topLeft.y() + yOfs, buttonSize.width(),
2386 buttonSize.height());
2387 ret = visualRect(option->direction, option->rect, ret);
2388 break;
2389 }
2390 case SC_ComboBoxEditField: {
2391 ret = option->rect;
2392 if (cb->frame) {
2393 const int fw = proxy()->pixelMetric(PM_ComboBoxFrameWidth, cb, widget);
2394 ret = ret.marginsRemoved(QMargins(fw, fw, fw, fw));
2395 }
2396 ret.setWidth(ret.width() - indicatorWidth - contentHMargin * 2);
2397 ret = visualRect(option->direction, option->rect, ret);
2398 break;
2399 }
2400 default:
2401 ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
2402 break;
2403 }
2404 }
2405 break;
2406 }
2407#if QT_CONFIG(groupbox)
2408 case CC_GroupBox: {
2409 ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
2410 switch (subControl) {
2411 case SC_GroupBoxCheckBox:
2412 ret.moveTop(1);
2413 break;
2414 default:
2415 break;
2416 }
2417 break;
2418 }
2419#endif // QT_CONFIG(groupbox)
2420 default:
2421 ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
2422 }
2423 return ret;
2424}
2425
2426/*!
2427 \internal
2428 */
2429QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *option,
2430 const QSize &size, const QWidget *widget) const
2431{
2432 QSize contentSize(size);
2433
2434 switch (type) {
2435
2436#if QT_CONFIG(menubar)
2437 case CT_MenuBarItem:
2438 if (!contentSize.isEmpty()) {
2439 constexpr int hMargin = 2 * 6;
2440 constexpr int hPadding = 2 * 11;
2441 constexpr int itemHeight = 32;
2442 contentSize.setWidth(contentSize.width() + hMargin + hPadding);
2443#if QT_CONFIG(tabwidget)
2444 if (widget->parent() && !qobject_cast<const QTabWidget *>(widget->parent()))
2445#endif
2446 contentSize.setHeight(itemHeight);
2447 }
2448 break;
2449#endif
2450#if QT_CONFIG(menu)
2451 case CT_MenuItem:
2452 if (const auto *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
2453 int width = size.width();
2454 int height;
2455 if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) {
2456 width = 10;
2457 height = 3;
2458 } else {
2459 height = menuItem->fontMetrics.height() + 8;
2460 if (!menuItem->icon.isNull()) {
2461 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, option, widget);
2462 height = qMax(height,
2463 menuItem->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4);
2464 }
2465 }
2466 if (menuItem->text.contains(u'\t'))
2467 width += contentItemHMargin; // the text width is already in
2468 if (menuItem->menuItemType == QStyleOptionMenuItem::SubMenu)
2469 width += 2 * QWindowsStylePrivate::windowsArrowHMargin + contentItemHMargin;
2470 if (menuItem->menuItemType == QStyleOptionMenuItem::DefaultItem) {
2471 const QFontMetrics fm(menuItem->font);
2472 QFont fontBold = menuItem->font;
2473 fontBold.setBold(true);
2474 const QFontMetrics fmBold(fontBold);
2475 width += fmBold.horizontalAdvance(menuItem->text) - fm.horizontalAdvance(menuItem->text);
2476 }
2477 // in contrast to windowsvista, the checkmark and icon are drawn separately
2478 if (menuItem->menuHasCheckableItems) {
2479 const auto checkMarkWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget);
2480 width += checkMarkWidth + contentItemHMargin * 2;
2481 }
2482 // we have an icon and it's already in the given size, only add margins
2483 // 4 is added in qmenu.cpp to PM_SmallIconSize
2484 if (menuItem->maxIconWidth > 0)
2485 width += contentItemHMargin * 2 + menuItem->maxIconWidth - 4;
2486 width += 2 * 2; // margins for rounded border
2487 width += 2 * contentHMargin;
2488 if (width < 100) // minimum size
2489 width = 100;
2490 contentSize = QSize(width, height);
2491 }
2492 break;
2493#endif // QT_CONFIG(menu)
2494#if QT_CONFIG(spinbox)
2495 case CT_SpinBox: {
2496 if (const auto *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
2497 // Add button + frame widths
2498 const bool hasButtons = (spinbox->buttonSymbols != QAbstractSpinBox::NoButtons);
2499 const int margins = 0;
2500 int buttonWidth = 0;
2501 if (hasButtons) {
2502 buttonWidth = widget
2503 ? widthFromChar(widget->font(), Icon::ChevronUp, m_fontPoint2ChevronUpWidth)
2504 : spinbox->fontMetrics.horizontalAdvance(fluentIcon(Icon::ChevronUp));
2505 }
2506 const int frameWidth = spinbox->frame
2507 ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget)
2508 : 0;
2509
2510 contentSize += QSize(2 * buttonWidth + 2 * frameWidth + 2 * margins, 2 * frameWidth);
2511 }
2512 break;
2513 }
2514#endif
2515#if QT_CONFIG(combobox)
2516 case CT_ComboBox:
2517 if (const auto *comboBoxOpt = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
2518 contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); // don't rely on QWindowsThemeData
2519 contentSize += QSize(0, 4); // for the lineedit frame
2520 if (comboBoxOpt->subControls & SC_ComboBoxArrow) {
2521 const auto w = proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget);
2522 contentSize.rwidth() += w + contentItemHMargin;
2523 }
2524 }
2525 break;
2526#endif
2527 case CT_LineEdit: {
2528 if (qstyleoption_cast<const QStyleOptionFrame *>(option)) {
2529 contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); // don't rely on QWindowsThemeData
2530 contentSize += QSize(0, 4); // for the lineedit frame
2531 }
2532 break;
2533 }
2534 case CT_HeaderSection:
2535 // windows vista does not honor the indicator (as it was drawn above the text, not on the
2536 // side) so call QWindowsStyle::styleHint directly to get the correct size hint
2537 contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget);
2538 break;
2539 case CT_RadioButton:
2540 case CT_CheckBox:
2541 if (const auto *buttonOpt = qstyleoption_cast<const QStyleOptionButton *>(option)) {
2542 const auto p = proxy();
2543 const bool isRadio = (type == CT_RadioButton);
2544
2545 const int width = p->pixelMetric(
2546 isRadio ? PM_ExclusiveIndicatorWidth : PM_IndicatorWidth, option, widget);
2547 const int height = p->pixelMetric(
2548 isRadio ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
2549
2550 int margins = 2 * contentItemHMargin;
2551 if (!buttonOpt->icon.isNull() || !buttonOpt->text.isEmpty()) {
2552 margins += p->pixelMetric(isRadio ? PM_RadioButtonLabelSpacing
2553 : PM_CheckBoxLabelSpacing,
2554 option, widget);
2555 }
2556
2557 contentSize += QSize(width + margins, 4);
2558 contentSize.setHeight(qMax(size.height(), height + 2 * contentItemHMargin));
2559 }
2560 break;
2561
2562 // the indicator needs 2px more in width when there is no text, not needed when
2563 // the style draws the text
2564 contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
2565 if (size.width() == 0)
2566 contentSize.rwidth() += 2;
2567 break;
2568 case CT_PushButton: {
2569 contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
2570 // we want our own horizontal spacing
2571 const int oldMargin = proxy()->pixelMetric(PM_ButtonMargin, option, widget);
2572 contentSize.rwidth() += 2 * contentHMargin - oldMargin;
2573 break;
2574 }
2575 case CT_ToolButton: {
2576 contentSize = size;
2577 if (const auto tb = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
2578 // no separate menu, only dropdown icon
2579 if (!tb->subControls.testFlag(SC_ToolButtonMenu)) {
2580 if (tb->features.testFlag(QStyleOptionToolButton::HasMenu))
2581 contentSize.rwidth() += 2;
2582 }
2583 if (tb->toolButtonStyle == Qt::ToolButtonTextBesideIcon
2584 || tb->toolButtonStyle == Qt::ToolButtonIconOnly) {
2585 contentSize.rheight() = qMax(contentSize.height(), tb->iconSize.height() + 4);
2586 }
2587 }
2588 const auto fw = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
2589 contentSize += QSize(contentHMargin + 2 * fw, 2 * fw);
2590 break;
2591 }
2592 case CT_ItemViewItem: {
2593 if (const auto *viewItemOpt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
2594 if (const QListView *lv = qobject_cast<const QListView *>(widget);
2595 lv && lv->viewMode() != QListView::IconMode) {
2596 QStyleOptionViewItem vOpt(*viewItemOpt);
2597 // viewItemSize only takes PM_FocusFrameHMargin into account but no additional
2598 // margin, therefore adjust it here for a correct width during layouting when
2599 // WrapText is enabled
2600 vOpt.rect.setRight(vOpt.rect.right() - contentHMargin);
2601 contentSize = QWindowsVistaStyle::sizeFromContents(type, &vOpt, size, widget);
2602 contentSize.rwidth() += contentHMargin;
2603 contentSize.rheight() += 2 * contentHMargin;
2604
2605 } else {
2606 contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
2607 }
2608 }
2609 break;
2610 }
2611 default:
2612 contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
2613 break;
2614 }
2615
2616 return contentSize;
2617}
2618
2619
2620/*!
2621 \internal
2622 */
2623int QWindows11Style::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
2624{
2625 Q_D(const QWindows11Style);
2626 int res = 0;
2627
2628 switch (metric) {
2629 case QStyle::PM_IndicatorWidth:
2630 case QStyle::PM_IndicatorHeight:
2631 case QStyle::PM_ExclusiveIndicatorWidth:
2632 case QStyle::PM_ExclusiveIndicatorHeight:
2633 res = 16;
2634 break;
2635 case PM_SliderThickness: // full height of a slider
2636 if (auto opt = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
2637 // hard-coded in qslider.cpp, but we need a little bit more
2638 constexpr auto TickSpace = 5;
2639 if (opt->tickPosition & QSlider::TicksAbove)
2640 res += 6 - TickSpace;
2641 if (opt->tickPosition & QSlider::TicksBelow)
2642 res += 6 - TickSpace;
2643 }
2644 Q_FALLTHROUGH();
2645 case PM_SliderControlThickness: // size of the control handle
2646 case PM_SliderLength: // same because handle is a circle with r=8
2647 res += 2 * 8;
2648 break;
2649 case PM_RadioButtonLabelSpacing:
2650 case PM_CheckBoxLabelSpacing:
2651 res = 2 * contentItemHMargin;
2652 break;
2653 case QStyle::PM_TitleBarButtonIconSize:
2654 res = 16;
2655 break;
2656 case QStyle::PM_TitleBarButtonSize:
2657 res = 32;
2658 break;
2659#if QT_CONFIG(toolbar)
2660 case PM_TabBarScrollButtonWidth:
2661 res = 16 + contentItemHMargin;
2662 break;
2663 case PM_ToolBarExtensionExtent:
2664 res = int(QStyleHelper::dpiScaled(32., option));
2665 break;
2666 case PM_ToolBarHandleExtent:
2667 res = int(QStyleHelper::dpiScaled(8., option));
2668 break;
2669#endif // QT_CONFIG(toolbar)
2670 case QStyle::PM_ScrollBarExtent:
2671 res = 12;
2672 break;
2673 case QStyle::PM_SubMenuOverlap:
2674 res = -1;
2675 break;
2676 case PM_MenuButtonIndicator: {
2677 res = contentItemHMargin;
2678 if (widget) {
2679 const int fontSize = widget->font().pointSize();
2680 auto it = m_fontPoint2ChevronDownMedWidth.find(fontSize);
2681 if (it == m_fontPoint2ChevronDownMedWidth.end()) {
2682 QFont f(d->assetFont);
2683 f.setPointSize(qRound(fontSize * 0.9f)); // a little bit smaller
2684 QFontMetrics fm(f);
2685 const auto width = fm.horizontalAdvance(fluentIcon(Icon::ChevronDownMed));
2686 m_fontPoint2ChevronDownMedWidth.insert(fontSize, width);
2687 res += width;
2688 } else {
2689 res += it.value();
2690 }
2691 } else {
2692 res += 12;
2693 }
2694 break;
2695 }
2696 case PM_ComboBoxFrameWidth:
2697 case PM_SpinBoxFrameWidth:
2698 case PM_DefaultFrameWidth:
2699 res = 2;
2700 break;
2701 case PM_ButtonShiftHorizontal:
2702 case PM_ButtonShiftVertical:
2703 case PM_TabBarTabShiftHorizontal:
2704 case PM_TabBarTabShiftVertical:
2705 case PM_TabBarBaseOverlap:
2706 case PM_TabBarBaseHeight:
2707 res = 0;
2708 break;
2709 case PM_TreeViewIndentation:
2710 res = 30;
2711 break;
2712 case PM_ProgressBarChunkWidth:
2713 res = 0; // no chunks on windows11
2714 break;
2715 case PM_HeaderMarkSize:
2716 res = 6;
2717 break;
2718 default:
2719 res = QWindowsVistaStyle::pixelMetric(metric, option, widget);
2720 }
2721
2722 return res;
2723}
2724
2725void QWindows11Style::polish(QWidget* widget)
2726{
2727 Q_D(QWindows11Style);
2728
2729#if QT_CONFIG(commandlinkbutton)
2730 if (!qobject_cast<QCommandLinkButton *>(widget))
2731#endif // QT_CONFIG(commandlinkbutton)
2732 QWindowsVistaStyle::polish(widget);
2733
2734 const bool isScrollBar = qobject_cast<QScrollBar *>(widget);
2735 const auto comboBoxContainer = qobject_cast<const QComboBoxPrivateContainer *>(widget);
2736 const auto isComboBoxContainer = !d->nativeRoundedTopLevelWindows && comboBoxContainer != nullptr;
2737 const auto isMenu = !d->nativeRoundedTopLevelWindows && qobject_cast<QMenu *>(widget) != nullptr;
2738
2739#if QT_CONFIG(menubar)
2740 if (qobject_cast<QMenuBar *>(widget)) {
2741 constexpr int itemHeight = 32;
2742 if (widget->maximumHeight() < itemHeight) {
2743 widget->setProperty("_q_original_menubar_maxheight", widget->maximumHeight());
2744 widget->setMaximumHeight(itemHeight);
2745 }
2746 }
2747#endif
2748 if (comboBoxContainer && d->nativeRoundedTopLevelWindows) {
2749 auto view = comboBoxContainer->itemView();
2750 if (view && view->viewport())
2751 view->viewport()->setAutoFillBackground(false);
2752 }
2753 if (isScrollBar || isMenu || isComboBoxContainer) {
2754 bool wasCreated = widget->testAttribute(Qt::WA_WState_Created);
2755 bool layoutDirection = widget->testAttribute(Qt::WA_RightToLeft);
2756 widget->setAttribute(Qt::WA_OpaquePaintEvent,false);
2757 widget->setAttribute(Qt::WA_TranslucentBackground);
2758 if (!isScrollBar)
2759 widget->setWindowFlag(Qt::FramelessWindowHint);
2760 widget->setWindowFlag(Qt::NoDropShadowWindowHint);
2761 widget->setAttribute(Qt::WA_RightToLeft, layoutDirection);
2762 widget->setAttribute(Qt::WA_WState_Created, wasCreated);
2763 if (!isScrollBar) {
2764 bool inGraphicsView = widget->graphicsProxyWidget() != nullptr;
2765 if (!inGraphicsView && comboBoxContainer && comboBoxContainer->parentWidget())
2766 inGraphicsView = comboBoxContainer->parentWidget()->graphicsProxyWidget() != nullptr;
2767 if (!inGraphicsView) { // for menus and combobox containers...
2768 QGraphicsDropShadowEffect *dropshadow = new QGraphicsDropShadowEffect(widget);
2769 dropshadow->setBlurRadius(3);
2770 dropshadow->setXOffset(3);
2771 dropshadow->setYOffset(3);
2772 widget->setGraphicsEffect(dropshadow);
2773 }
2774 }
2775 } else if (QComboBox* cb = qobject_cast<QComboBox*>(widget)) {
2776 if (cb->isEditable()) {
2777 QLineEdit *le = cb->lineEdit();
2778 le->setFrame(false);
2779 }
2780 } else if (qobject_cast<QTextEdit *>(widget)
2781 || qobject_cast<QPlainTextEdit *>(widget)) {
2782 const auto *scrollarea = static_cast<QAbstractScrollArea *>(widget);
2783 // These two widgets have rounded inner borders
2784 if (scrollarea->frameShape() == QFrame::StyledPanel) {
2785 const auto vp = scrollarea->viewport();
2786 const bool isAutoFillBackground = vp->autoFillBackground();
2787 vp->setProperty("_q_original_autofill_background", isAutoFillBackground);
2788 vp->setAutoFillBackground(false);
2789 }
2790 } else if (auto table = qobject_cast<QTableView *>(widget)) {
2791 // QTreeView & QListView are already set in the base windowsvista style
2792 table->viewport()->setAttribute(Qt::WA_Hover, true);
2793 }
2794}
2795
2796void QWindows11Style::unpolish(QWidget *widget)
2797{
2798 Q_D(QWindows11Style);
2799#if QT_CONFIG(commandlinkbutton)
2800 if (!qobject_cast<QCommandLinkButton *>(widget))
2801#endif // QT_CONFIG(commandlinkbutton)
2802 QWindowsVistaStyle::unpolish(widget);
2803
2804#if QT_CONFIG(menubar)
2805 if (qobject_cast<QMenuBar *>(widget) && !widget->property("_q_original_menubar_maxheight").isNull()) {
2806 widget->setMaximumHeight(widget->property("_q_original_menubar_maxheight").toInt());
2807 widget->setProperty("_q_original_menubar_maxheight", QVariant());
2808 }
2809#endif
2810 const bool isScrollBar = qobject_cast<QScrollBar *>(widget);
2811 const auto comboBoxContainer = qobject_cast<const QComboBoxPrivateContainer *>(widget);
2812 const auto isComboBoxContainer = !d->nativeRoundedTopLevelWindows && comboBoxContainer != nullptr;
2813 const auto isMenu = !d->nativeRoundedTopLevelWindows && qobject_cast<QMenu *>(widget) != nullptr;
2814
2815 if (comboBoxContainer && d->nativeRoundedTopLevelWindows) {
2816 auto view = comboBoxContainer->itemView();
2817 // see QAbstractScrollAreaPrivate::init()
2818 if (view && view->viewport())
2819 view->viewport()->setAutoFillBackground(true);
2820 }
2821 if (isScrollBar || isMenu || isComboBoxContainer) {
2822 widget->setAttribute(Qt::WA_OpaquePaintEvent, true);
2823 widget->setAttribute(Qt::WA_TranslucentBackground, false);
2824 widget->setWindowFlag(Qt::FramelessWindowHint, false);
2825 widget->setWindowFlag(Qt::NoDropShadowWindowHint, false);
2826 }
2827
2828 if (const auto *scrollarea = qobject_cast<QAbstractScrollArea *>(widget);
2829 scrollarea
2830#if QT_CONFIG(mdiarea)
2831 && !qobject_cast<QMdiArea *>(widget)
2832#endif
2833 ) {
2834 const auto vp = scrollarea->viewport();
2835 const auto wasAutoFillBackground = vp->property("_q_original_autofill_background").toBool();
2836 vp->setAutoFillBackground(wasAutoFillBackground);
2837 vp->setProperty("_q_original_autofill_background", QVariant());
2838 }
2839 dwmSetWindowCornerPreference(widget, false);
2840}
2841
2842void QWindows11Style::polish(QApplication *app)
2843{
2844 Q_D(const QWindows11Style);
2845 if (d->nativeRoundedTopLevelWindows)
2846 app->installEventFilter(this);
2847 QWindowsVistaStyle::polish(app);
2848}
2849
2850void QWindows11Style::unpolish(QApplication *app)
2851{
2852 Q_D(const QWindows11Style);
2853 if (d->nativeRoundedTopLevelWindows)
2854 app->removeEventFilter(this);
2855 QWindowsVistaStyle::unpolish(app);
2856}
2857
2858bool QWindows11Style::eventFilter(QObject *obj, QEvent *event)
2859{
2860 // QEvent::WinIdChange is too early so we have to wait for QEvent::Show
2861 // QEvent::WindowStateChange is needed to update the corners when the
2862 // window enters or leaves fullscreen
2863 if (event->type() == QEvent::Show || event->type() == QEvent::WindowStateChange)
2864 dwmSetWindowCornerPreference(qobject_cast<QWidget *>(obj), true);
2865 return QWindowsVistaStyle::eventFilter(obj, event);
2866}
2867
2868void QWindows11Style::dwmSetWindowCornerPreference(const QWidget *widget, bool bSet) const
2869{
2870 Q_D(const QWindows11Style);
2871 if (!d->nativeRoundedTopLevelWindows)
2872 return;
2873#ifdef Q_CC_MSVC
2874 static constexpr auto dmwmaWindowCornerPreference = DWMWA_WINDOW_CORNER_PREFERENCE;
2875 static constexpr auto dwmcpRound = DWMWCP_ROUND;
2876 static constexpr auto dwmcpRoundSmall = DWMWCP_ROUNDSMALL;
2877 static constexpr auto dwmcpDefault = DWMWCP_DEFAULT;
2878#else
2879 // MinGW 13.1.0 does not provide this
2880 static constexpr auto dmwmaWindowCornerPreference = 33;
2881 static constexpr auto dwmcpRound = 2;
2882 static constexpr auto dwmcpRoundSmall = 3;
2883 static constexpr auto dwmcpDefault = 0;
2884#endif
2885 if (widget && widget->isWindow() && widget->testAttribute(Qt::WA_WState_Created)
2886 && !widget->windowFlags().testFlag(Qt::FramelessWindowHint)) {
2887 const auto window = widget->windowHandle();
2888 if (window && window->handle()) {
2889 const auto wId = reinterpret_cast<HWND>(widget->winId());
2890 if (wId != 0) {
2891 const bool isToolTip =
2892#if QT_CONFIG(tooltip)
2893 qobject_cast<const QTipLabel *>(widget) != nullptr;
2894#else
2895 false;
2896#endif
2897 // A fullscreen window must not have rounded corners because
2898 // then it would not cover the whole screen (QTBUG-147453)
2899 const bool round = bSet && !widget->isFullScreen();
2900 uint32_t pref = round ? (isToolTip ? dwmcpRoundSmall : dwmcpRound) : dwmcpDefault;
2901 DwmSetWindowAttribute(wId, dmwmaWindowCornerPreference, &pref, sizeof(pref));
2902 }
2903 }
2904 }
2905}
2906
2907/*
2908The colors for Windows 11 are taken from the official WinUI3 Figma style at
2909https://www.figma.com/community/file/1159947337437047524
2910*/
2911#define SET_IF_UNRESOLVED(GROUP, ROLE, VALUE)
2912 if (!result.isBrushSet(QPalette::Inactive, ROLE) || styleSheetChanged)
2913 result.setColor(GROUP, ROLE, VALUE)
2914
2915static void populateLightSystemBasePalette(QPalette &result)
2916{
2917 static QString oldStyleSheet;
2918 const bool styleSheetChanged = oldStyleSheet != qApp->styleSheet();
2919
2920 constexpr QColor textColor = QColor(0x00,0x00,0x00,0xE4);
2921 constexpr QColor textDisabled = QColor(0x00, 0x00, 0x00, 0x5C);
2922 constexpr QColor btnFace = QColor(0xFF, 0xFF, 0xFF, 0xB3);
2923 constexpr QColor base = QColor(0xFF, 0xFF, 0xFF, 0xFF);
2924 constexpr QColor alternateBase = QColor(0x00, 0x00, 0x00, 0x09);
2925 const QColor btnHighlight = result.accent().color();
2926 const QColor btnColor = result.button().color();
2927
2928 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Highlight, btnHighlight);
2929 SET_IF_UNRESOLVED(QPalette::Active, QPalette::WindowText, textColor);
2930 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Button, btnFace);
2931 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Light, btnColor.lighter(150));
2932 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Dark, btnColor.darker(200));
2933 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Mid, btnColor.darker(150));
2934 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Text, textColor);
2935 SET_IF_UNRESOLVED(QPalette::Active, QPalette::BrightText, btnHighlight);
2936 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Base, base);
2937 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Window, QColor(0xF3,0xF3,0xF3,0xFF));
2938 SET_IF_UNRESOLVED(QPalette::Active, QPalette::ButtonText, textColor);
2939 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Midlight, btnColor.lighter(125));
2940 SET_IF_UNRESOLVED(QPalette::Active, QPalette::Shadow, Qt::black);
2941 SET_IF_UNRESOLVED(QPalette::Active, QPalette::ToolTipBase, result.window().color());
2942 SET_IF_UNRESOLVED(QPalette::Active, QPalette::ToolTipText, result.windowText().color());
2943 SET_IF_UNRESOLVED(QPalette::Active, QPalette::AlternateBase, alternateBase);
2944
2945 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Highlight, btnHighlight);
2946 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::WindowText, textColor);
2947 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Button, btnFace);
2948 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Light, btnColor.lighter(150));
2949 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Dark, btnColor.darker(200));
2950 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Mid, btnColor.darker(150));
2951 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Text, textColor);
2952 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::BrightText, btnHighlight);
2953 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Base, base);
2954 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Window, QColor(0xF3,0xF3,0xF3,0xFF));
2955 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::ButtonText, textColor);
2956 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Midlight, btnColor.lighter(125));
2957 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Shadow, Qt::black);
2958 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::ToolTipBase, result.window().color());
2959 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::ToolTipText, result.windowText().color());
2960 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::AlternateBase, alternateBase);
2961
2962 result.setColor(QPalette::Disabled, QPalette::WindowText, textDisabled);
2963
2964 if (result.midlight() == result.button())
2965 result.setColor(QPalette::Midlight, btnColor.lighter(110));
2966 oldStyleSheet = qApp->styleSheet();
2967}
2968
2969static void populateDarkSystemBasePalette(QPalette &result)
2970{
2971 static QString oldStyleSheet;
2972 const bool styleSheetChanged = oldStyleSheet != qApp->styleSheet();
2973
2974 constexpr QColor alternateBase = QColor(0xFF, 0xFF, 0xFF, 0x0F);
2975
2976 SET_IF_UNRESOLVED(QPalette::Active, QPalette::AlternateBase, alternateBase);
2977
2978 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::AlternateBase, alternateBase);
2979
2980 oldStyleSheet = qApp->styleSheet();
2981}
2982
2983/*!
2984 \internal
2985 */
2986void QWindows11Style::polish(QPalette& result)
2987{
2988 highContrastTheme = QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Unknown;
2989 colorSchemeIndex = QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Light ? 0 : 1;
2990
2991 if (!highContrastTheme && colorSchemeIndex == 0)
2992 populateLightSystemBasePalette(result);
2993 else if (!highContrastTheme && colorSchemeIndex == 1)
2994 populateDarkSystemBasePalette(result);
2995
2996 const bool styleSheetChanged = false; // so the macro works
2997
2998 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Button, result.button().color());
2999 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Window, result.window().color());
3000 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Light, result.light().color());
3001 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Dark, result.dark().color());
3002 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Accent, result.accent().color());
3003 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Highlight, result.highlight().color());
3004 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::HighlightedText, result.highlightedText().color());
3005 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Text, result.text().color());
3006 SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::WindowText, result.windowText().color());
3007
3008 auto *d = const_cast<QWindows11StylePrivate *>(d_func());
3009 d->m_standardIcons.clear();
3010}
3011
3012QPixmap QWindows11Style::standardPixmap(StandardPixmap standardPixmap,
3013 const QStyleOption *option,
3014 const QWidget *widget) const
3015{
3016 switch (standardPixmap) {
3017 case SP_TabScrollLeftButton:
3018 case SP_TabScrollRightButton:
3019 case SP_TabScrollUpButton:
3020 case SP_TabScrollDownButton:
3021 case SP_ToolBarHorizontalExtensionButton:
3022 case SP_ToolBarVerticalExtensionButton: {
3023 const int size = proxy()->pixelMetric(PM_ToolBarExtensionExtent, option, widget);
3024 return standardIcon(standardPixmap, option, widget).pixmap(size);
3025 }
3026 default:
3027 break;
3028 }
3029 return QWindowsVistaStyle::standardPixmap(standardPixmap, option, widget);
3030}
3031
3032QIcon QWindows11Style::standardIcon(StandardPixmap standardIcon,
3033 const QStyleOption *option,
3034 const QWidget *widget) const
3035{
3036 const auto getIcon = [&](Icon ico, double scale) {
3037 auto *d = const_cast<QWindows11StylePrivate *>(d_func());
3038 QIcon &icon = d->m_standardIcons[ico];
3039 if (icon.isNull()) {
3040 auto engine = new WinFontIconEngine(fluentIcon(ico), d->assetFont);
3041 engine->setScale(scale);
3042 icon = QIcon(engine);
3043 }
3044 return icon;
3045 };
3046
3047 switch (standardIcon) {
3048 case SP_TitleBarMinButton:
3049 return getIcon(Icon::ChromeMinimize, 0.7);
3050 case SP_TitleBarMaxButton:
3051 return getIcon(Icon::ChromeMaximize, 0.7);
3052 case SP_TitleBarCloseButton:
3053 return getIcon(Icon::ChromeClose, 0.7);
3054 case SP_TitleBarNormalButton:
3055 return getIcon(Icon::ChromeRestore, 0.7);
3056 case SP_LineEditClearButton:
3057 return getIcon(Icon::Clear, 0.7);
3058 case SP_ToolBarHorizontalExtensionButton:
3059 case SP_ToolBarVerticalExtensionButton:
3060 return getIcon(Icon::More, 1.0);
3061 case SP_TabCloseButton:
3062 return getIcon(Icon::ChromeClose, 0.6);
3063 case SP_TabScrollLeftButton:
3064 return getIcon(Icon::CaretLeftSolid8, 0.5);
3065 case SP_TabScrollRightButton:
3066 return getIcon(Icon::CaretRightSolid8, 0.5);
3067 case SP_TabScrollUpButton:
3068 return getIcon(Icon::CaretUpSolid8, 0.5);
3069 case SP_TabScrollDownButton:
3070 return getIcon(Icon::CaretDownSolid8, 0.5);
3071 case SP_ArrowUp:
3072 return getIcon(Icon::ChevronUpMed, 1.0);
3073 case SP_ArrowDown:
3074 return getIcon(Icon::ChevronDownMed, 1.0);
3075 case SP_ArrowLeft:
3076 return getIcon(Icon::ChevronLeftMed, 1.0);
3077 case SP_ArrowRight:
3078 return getIcon(Icon::ChevronRightMed, 1.0);
3079 default:
3080 break;
3081 }
3082 return QWindowsVistaStyle::standardIcon(standardIcon, option, widget);
3083}
3084
3085QPixmap QWindows11Style::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap,
3086 const QStyleOption *option) const
3087{
3088 if (iconMode == QIcon::Disabled)
3089 return QWindowsVistaStyle::generatedIconPixmap(iconMode, pixmap, option);
3090 return pixmap;
3091}
3092
3093
3094QColor QWindows11Style::calculateAccentColor(const QStyleOption *option) const
3095{
3096 using namespace StyleOptionHelper;
3097 if (isDisabled(option))
3098 return winUI3Color(fillAccentDisabled);
3099 const auto alphaColor = isPressed(option) ? fillAccentTertiary
3100 : isHover(option) ? fillAccentSecondary
3101 : fillAccentDefault;
3102 const auto alpha = winUI3Color(alphaColor);
3103 QColor col = option->palette.accent().color();
3104 col.setAlpha(alpha.alpha());
3105 return col;
3106}
3107
3108QPen QWindows11Style::borderPenControlAlt(const QStyleOption *option) const
3109{
3110 using namespace StyleOptionHelper;
3111 if (isChecked(option))
3112 return Qt::NoPen; // same color as fill color, so no pen needed
3113 if (highContrastTheme)
3114 return option->palette.buttonText().color();
3115 if (isDisabled(option) || isPressed(option))
3116 return winUI3Color(frameColorStrongDisabled);
3117 return winUI3Color(frameColorStrong);
3118}
3119
3120QBrush QWindows11Style::controlFillBrush(const QStyleOption *option, ControlType controlType) const
3121{
3122 using namespace StyleOptionHelper;
3123 static constexpr WINUI3Color colorEnums[2][4] = {
3124 // Light & Dark Control
3127 // Light & Dark Control Alt
3130 };
3131
3132 if (option->palette.isBrushSet(QPalette::Current, QPalette::Button))
3133 return option->palette.brush(QPalette::Current, QPalette::Button);
3134
3135 if (!isChecked(option) && isAutoRaise(option))
3136 return Qt::NoBrush;
3137
3138 // checked is the same for Control (Buttons) and Control Alt (Radiobuttons/Checkboxes)
3139 if (isChecked(option))
3140 return calculateAccentColor(option);
3141
3142 const auto state = calcControlState(option);
3143 return winUI3Color(colorEnums[int(controlType)][int(state)]);
3144}
3145
3146QBrush QWindows11Style::inputFillBrush(const QStyleOption *option, const QWidget *widget) const
3147{
3148 // slightly different states than in controlFillBrush
3149 using namespace StyleOptionHelper;
3150 const auto role = widget ? widget->backgroundRole() : QPalette::Window;
3151 if (option->palette.isBrushSet(QPalette::Current, role))
3152 return option->palette.brush(QPalette::Current, role);
3153
3154 if (isDisabled(option))
3155 return winUI3Color(fillControlDisabled);
3156 if (hasFocus(option))
3157 return winUI3Color(fillControlInputActive);
3158 if (isHover(option))
3159 return winUI3Color(fillControlSecondary);
3160 return winUI3Color(fillControlDefault);
3161}
3162
3163QColor QWindows11Style::controlTextColor(const QStyleOption *option, bool ignoreIsChecked) const
3164{
3165 using namespace StyleOptionHelper;
3166 static constexpr WINUI3Color colorEnums[2][4] = {
3167 // Control, unchecked
3169 // Control, checked
3171 };
3172
3173 if (option->palette.isBrushSet(QPalette::Current, QPalette::ButtonText))
3174 return option->palette.color(QPalette::Current, QPalette::ButtonText);
3175
3176 const int colorIndex = !ignoreIsChecked && isChecked(option) ? 1 : 0;
3177 const auto state = calcControlState(option);
3178 return winUI3Color(colorEnums[colorIndex][int(state)]);
3179}
3180
3181void QWindows11Style::drawLineEditFrame(QPainter *p, const QRectF &rect, const QStyleOption *o, bool isEditable) const
3182{
3183 const bool isHovered = o->state & State_MouseOver;
3184 const auto frameCol = highContrastTheme
3185 ? o->palette.color(isHovered ? QPalette::Accent
3186 : QPalette::ButtonText)
3187 : winUI3Color(frameColorLight);
3188 drawRoundedRect(p, rect, frameCol, Qt::NoBrush);
3189
3190 if (!isEditable || StyleOptionHelper::isDisabled(o))
3191 return;
3192
3193 const bool hasFocus = o->state & State_HasFocus;
3194 const auto underlineCol = hasFocus
3195 ? o->palette.color(QPalette::Accent)
3196 : colorSchemeIndex == 0 ? QColor(0x80, 0x80, 0x80)
3197 : QColor(0xa0, 0xa0, 0xa0);
3198 QPainterStateGuard psg(p);
3199 if (hasFocus) {
3200 p->setClipRect(rect.marginsRemoved(QMarginsF(0, rect.height() - 1.5, 0, -1)));
3201 const QRectF r(rect.top(), rect.left(), rect.width(), rect.height() - 1);
3202 drawRoundedRect(p, r, QPen(underlineCol, 2), Qt::NoBrush);
3203 } else {
3204 p->setClipRect(rect.marginsRemoved(QMarginsF(0, rect.height() - 0.5, 0, -1)));
3205 drawRoundedRect(p, rect, underlineCol, Qt::NoBrush);
3206 }
3207}
3208
3209QColor QWindows11Style::winUI3Color(enum WINUI3Color col) const
3210{
3211 return WINUI3Colors[colorSchemeIndex][col];
3212}
3213
3214template <typename C>
3215int QWindows11Style::widthFromChar(const QFont &widgetFont, Icon icon, C &cacheContainer) const
3216{
3217 Q_D(const QWindows11Style);
3218 int fontSize = widgetFont.pointSize();
3219 if (fontSize <= 0)
3220 fontSize = widgetFont.pixelSize() * -1;
3221 auto it = cacheContainer.find(fontSize);
3222 if (it == cacheContainer.end()) {
3223 QFont f(d->assetFont);
3224 QFontMetrics fm(f.resolve(widgetFont));
3225 const auto width = fm.horizontalAdvance(fluentIcon(icon));
3226 cacheContainer.insert(fontSize, width);
3227 return width;
3228 }
3229 return it.value();
3230}
3231
3232#undef SET_IF_UNRESOLVED
3233
3234QT_END_NAMESPACE
friend class QPainter
QPainter * painter() const
Returns the paint engine's painter.
\inmodule QtCore\reentrant
Definition qpoint.h:232
\inmodule QtCore\reentrant
Definition qpoint.h:30
bool isDisabled(const QStyleOption *option)
ControlState calcControlState(const QStyleOption *option)
bool hasFocus(const QStyleOption *option)
bool isChecked(const QStyleOption *option)
bool isSelected(const QStyleOption *option)
bool isPressed(const QStyleOption *option)
bool isHover(const QStyleOption *option)
bool isAutoRaise(const QStyleOption *option)
#define qApp
static constexpr std::array< std::array< QColor, 37 >, 2 > WINUI3Colors
#define SET_IF_UNRESOLVED(GROUP, ROLE, VALUE)
static constexpr std::array< QColor, 37 > WINUI3ColorsDark
static constexpr QColor shellCaptionCloseFillColorSecondary(0xC4, 0x2B, 0x1C, 0xE6)
static qreal sliderInnerRadius(QStyle::State state, bool insideHandle)
static qreal radioButtonInnerRadius(QStyle::State state)
static constexpr QColor shellCaptionCloseFillColorPrimary(0xC4, 0x2B, 0x1C, 0xFF)
static constexpr int contentHMargin
static void populateLightSystemBasePalette(QPalette &result)
static constexpr std::array< QColor, 37 > WINUI3ColorsLight
static constexpr int percentToAlpha(double percent)
static void populateDarkSystemBasePalette(QPalette &result)
static constexpr int contentItemHMargin
static constexpr QColor shellCaptionCloseTextFillColorPrimary(0xFF, 0xFF, 0xFF, 0xFF)
static void drawRoundedRect(QPainter *p, R &&rect, P &&pen, B &&brush, int radius=secondLevelRoundingRadius)
static constexpr int topLevelRoundingRadius
static constexpr QColor shellCaptionCloseTextFillColorSecondary(0xFF, 0xFF, 0xFF, 0xB3)
static constexpr int secondLevelRoundingRadius
WINUI3Color
@ fillControlAltSecondary
@ fillControlTertiary
@ textOnAccentPrimary
@ fillControlDefault
@ fillControlDisabled
@ subtleHighlightColor
@ textPrimary
@ textDisabled
@ fillControlSecondary
@ textSecondary
@ textOnAccentSecondary
@ textOnAccentDisabled
@ fillControlAltQuarternary
@ fillControlAltTertiary
@ fillControlAltDisabled