1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickstylehelper_p.h" |
5 | #include "qquickstyleoption.h" |
6 | #include "qquickstyle_p.h" |
7 | |
8 | #include <QtCore/qmath.h> |
9 | #include <QtGui/qpainter.h> |
10 | #include <QtGui/qpixmapcache.h> |
11 | #include <QtGui/qwindow.h> |
12 | #include <QtGui/private/qhighdpiscaling_p.h> |
13 | #include <QtGui/private/qguiapplication_p.h> |
14 | #include <QtGui/private/qmath_p.h> |
15 | #include <QtGui/private/qhexstring_p.h> |
16 | |
17 | #include <qmetaobject.h> |
18 | #include <qstringbuilder.h> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | Q_GUI_EXPORT int qt_defaultDpiX(); |
23 | |
24 | namespace QQC2 { |
25 | |
26 | namespace QStyleHelper { |
27 | |
28 | QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size) |
29 | { |
30 | const QStyleOptionComplex *complexOption = qstyleoption_cast<const QStyleOptionComplex *>(opt: option); |
31 | QString tmp = key % HexString<uint>(option->state) |
32 | % HexString<uint>(option->direction) |
33 | % HexString<uint>(complexOption ? uint(complexOption->activeSubControls) : 0u) |
34 | % HexString<quint64>(option->palette.cacheKey()) |
35 | % HexString<uint>(size.width()) |
36 | % HexString<uint>(size.height()); |
37 | |
38 | if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt: option)) { |
39 | tmp = tmp % HexString<uint>(spinBox->buttonSymbols) |
40 | % HexString<uint>(spinBox->stepEnabled) |
41 | % QLatin1Char(spinBox->frame ? '1' : '0'); ; |
42 | } |
43 | |
44 | return tmp; |
45 | } |
46 | |
47 | #ifdef Q_OS_DARWIN |
48 | static const qreal qstyleBaseDpi = 72; |
49 | #else |
50 | static const qreal qstyleBaseDpi = 96; |
51 | #endif |
52 | |
53 | qreal dpi(const QStyleOption *option) |
54 | { |
55 | #ifndef Q_OS_DARWIN |
56 | // Prioritize the application override, except for on macOS where |
57 | // we have historically not supported the AA_Use96Dpi flag. |
58 | if (QCoreApplication::testAttribute(attribute: Qt::AA_Use96Dpi)) |
59 | return 96; |
60 | #endif |
61 | |
62 | // Expect that QStyleOption::QFontMetrics::QFont has the correct DPI set |
63 | if (option) |
64 | return option->fontMetrics.fontDpi(); |
65 | |
66 | return qstyleBaseDpi; |
67 | } |
68 | |
69 | qreal dpiScaled(qreal value, qreal dpi) |
70 | { |
71 | return value * dpi / qstyleBaseDpi; |
72 | } |
73 | |
74 | qreal dpiScaled(qreal value, const QPaintDevice *device) |
75 | { |
76 | return dpiScaled(value, dpi: device->logicalDpiX()); |
77 | } |
78 | |
79 | qreal dpiScaled(qreal value, const QStyleOption *option) |
80 | { |
81 | return dpiScaled(value, dpi: dpi(option)); |
82 | } |
83 | |
84 | #if QT_CONFIG(accessibility) |
85 | bool isInstanceOf(QObject *obj, QAccessible::Role role) |
86 | { |
87 | bool match = false; |
88 | QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(obj); |
89 | match = iface && iface->role() == role; |
90 | return match; |
91 | } |
92 | |
93 | // Searches for an ancestor of a particular accessible role |
94 | bool hasAncestor(QObject *obj, QAccessible::Role role) |
95 | { |
96 | bool found = false; |
97 | QObject *parent = obj ? obj->parent() : nullptr; |
98 | while (parent && !found) { |
99 | if (isInstanceOf(obj: parent, role)) |
100 | found = true; |
101 | parent = parent->parent(); |
102 | } |
103 | return found; |
104 | } |
105 | #endif |
106 | |
107 | int calcBigLineSize(int radius) |
108 | { |
109 | int bigLineSize = radius / 6; |
110 | if (bigLineSize < 4) |
111 | bigLineSize = 4; |
112 | if (bigLineSize > radius / 2) |
113 | bigLineSize = radius / 2; |
114 | return bigLineSize; |
115 | } |
116 | |
117 | static QPointF calcRadialPos(const QStyleOptionSlider *dial, qreal offset) |
118 | { |
119 | const int width = dial->rect.width(); |
120 | const int height = dial->rect.height(); |
121 | const int r = qMin(a: width, b: height) / 2; |
122 | const int currentSliderPosition = dial->upsideDown ? dial->sliderPosition : (dial->maximum - dial->sliderPosition); |
123 | qreal a = 0; |
124 | qreal startAngle = (90. - dial->startAngle) * Q_PI / 180.; |
125 | qreal spanAngle = (dial->endAngle - dial->startAngle) * Q_PI / 180.; |
126 | if (dial->maximum == dial->minimum) |
127 | a = Q_PI / 2; |
128 | else |
129 | a = (startAngle - (currentSliderPosition - dial->minimum) * spanAngle |
130 | / (dial->maximum - dial->minimum)); |
131 | qreal xc = width / 2.0; |
132 | qreal yc = height / 2.0; |
133 | qreal len = r - QStyleHelper::calcBigLineSize(radius: r) - 3; |
134 | qreal back = offset * len; |
135 | QPointF pos(QPointF(xc + back * qCos(v: a), yc - back * qSin(v: a))); |
136 | return pos; |
137 | } |
138 | |
139 | qreal angle(const QPointF &p1, const QPointF &p2) |
140 | { |
141 | static const qreal rad_factor = 180 / Q_PI; |
142 | qreal _angle = 0; |
143 | |
144 | if (p1.x() == p2.x()) { |
145 | if (p1.y() < p2.y()) |
146 | _angle = 270; |
147 | else |
148 | _angle = 90; |
149 | } else { |
150 | qreal x1, x2, y1, y2; |
151 | |
152 | if (p1.x() <= p2.x()) { |
153 | x1 = p1.x(); y1 = p1.y(); |
154 | x2 = p2.x(); y2 = p2.y(); |
155 | } else { |
156 | x2 = p1.x(); y2 = p1.y(); |
157 | x1 = p2.x(); y1 = p2.y(); |
158 | } |
159 | |
160 | qreal m = -(y2 - y1) / (x2 - x1); |
161 | _angle = qAtan(v: m) * rad_factor; |
162 | |
163 | if (p1.x() < p2.x()) |
164 | _angle = 180 - _angle; |
165 | else |
166 | _angle = -_angle; |
167 | } |
168 | return _angle; |
169 | } |
170 | |
171 | QPolygonF calcLines(const QStyleOptionSlider *dial) |
172 | { |
173 | QPolygonF poly; |
174 | int width = dial->rect.width(); |
175 | int height = dial->rect.height(); |
176 | qreal r = qMin(a: width, b: height) / 2; |
177 | int bigLineSize = calcBigLineSize(radius: int(r)); |
178 | |
179 | qreal xc = width / 2 + 0.5; |
180 | qreal yc = height / 2 + 0.5; |
181 | const int ns = dial->tickInterval; |
182 | if (!ns) // Invalid values may be set by Qt Designer. |
183 | return poly; |
184 | int notches = (dial->maximum + ns - 1 - dial->minimum) / ns; |
185 | if (notches <= 0) |
186 | return poly; |
187 | if (dial->maximum < dial->minimum || dial->maximum - dial->minimum > 1000) { |
188 | int maximum = dial->minimum + 1000; |
189 | notches = (maximum + ns - 1 - dial->minimum) / ns; |
190 | } |
191 | |
192 | poly.resize(size: 2 + 2 * notches); |
193 | int smallLineSize = bigLineSize / 2; |
194 | for (int i = 0; i <= notches; ++i) { |
195 | qreal angle = dial->dialWrapping ? Q_PI * 3 / 2 - i * 2 * Q_PI / notches |
196 | : (Q_PI * 8 - i * 10 * Q_PI / notches) / 6; |
197 | qreal s = qSin(v: angle); |
198 | qreal c = qCos(v: angle); |
199 | if (i == 0 || (((ns * i) % (dial->pageStep ? dial->pageStep : 1)) == 0)) { |
200 | poly[2 * i] = QPointF(xc + (r - bigLineSize) * c, |
201 | yc - (r - bigLineSize) * s); |
202 | poly[2 * i + 1] = QPointF(xc + r * c, yc - r * s); |
203 | } else { |
204 | poly[2 * i] = QPointF(xc + (r - 1 - smallLineSize) * c, |
205 | yc - (r - 1 - smallLineSize) * s); |
206 | poly[2 * i + 1] = QPointF(xc + (r - 1) * c, yc -(r - 1) * s); |
207 | } |
208 | } |
209 | return poly; |
210 | } |
211 | |
212 | // This will draw a nice and shiny QDial for us. We don't want |
213 | // all the shinyness in QWindowsStyle, hence we place it here |
214 | |
215 | void drawDial(const QStyleOptionSlider *option, QPainter *painter) |
216 | { |
217 | QPalette pal = option->palette; |
218 | const int width = option->rect.width(); |
219 | const int height = option->rect.height(); |
220 | const bool enabled = option->state & QStyle::State_Enabled; |
221 | qreal r = qMin(a: width, b: height) / 2; |
222 | r -= r/50; |
223 | const qreal penSize = r/20.0; |
224 | |
225 | painter->save(); |
226 | painter->setRenderHint(hint: QPainter::Antialiasing); |
227 | |
228 | // Draw notches |
229 | if (option->subControls & QStyle::SC_DialTickmarks) { |
230 | painter->setPen(option->palette.dark().color().darker(f: 120)); |
231 | painter->drawLines(pointPairs: QStyleHelper::calcLines(dial: option)); |
232 | } |
233 | |
234 | // setting color before BEGIN_STYLE_PIXMAPCACHE since |
235 | // otherwise it is not set when the image is in the cache |
236 | QColor buttonColor = pal.button().color().toHsv(); |
237 | buttonColor.setHsv(h: buttonColor .hue(), |
238 | s: qMin(a: 140, b: buttonColor .saturation()), |
239 | v: qMax(a: 180, b: buttonColor.value())); |
240 | |
241 | // Cache dial background |
242 | BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("qdial" )) |
243 | p->setRenderHint(hint: QPainter::Antialiasing); |
244 | |
245 | const qreal d_ = r / 6; |
246 | const qreal dx = option->rect.x() + d_ + (width - 2 * r) / 2 + 1; |
247 | const qreal dy = option->rect.y() + d_ + (height - 2 * r) / 2 + 1; |
248 | |
249 | QRectF br = QRectF(dx + 0.5, dy + 0.5, |
250 | int(r * 2 - 2 * d_ - 2), |
251 | int(r * 2 - 2 * d_ - 2)); |
252 | |
253 | if (enabled) { |
254 | // Drop shadow |
255 | qreal shadowSize = qMax(a: 1.0, b: penSize/2.0); |
256 | QRectF shadowRect= br.adjusted(xp1: -2*shadowSize, yp1: -2*shadowSize, |
257 | xp2: 2*shadowSize, yp2: 2*shadowSize); |
258 | QRadialGradient shadowGradient(shadowRect.center().x(), |
259 | shadowRect.center().y(), shadowRect.width()/2.0, |
260 | shadowRect.center().x(), shadowRect.center().y()); |
261 | shadowGradient.setColorAt(pos: qreal(0.91), color: QColor(0, 0, 0, 40)); |
262 | shadowGradient.setColorAt(pos: qreal(1.0), color: Qt::transparent); |
263 | p->setBrush(shadowGradient); |
264 | p->setPen(Qt::NoPen); |
265 | p->translate(dx: shadowSize, dy: shadowSize); |
266 | p->drawEllipse(r: shadowRect); |
267 | p->translate(dx: -shadowSize, dy: -shadowSize); |
268 | |
269 | // Main gradient |
270 | QRadialGradient gradient(br.center().x() - br.width()/3, dy, |
271 | br.width()*1.3, br.center().x(), |
272 | br.center().y() - br.height()/2); |
273 | gradient.setColorAt(pos: 0, color: buttonColor.lighter(f: 110)); |
274 | gradient.setColorAt(pos: qreal(0.5), color: buttonColor); |
275 | gradient.setColorAt(pos: qreal(0.501), color: buttonColor.darker(f: 102)); |
276 | gradient.setColorAt(pos: 1, color: buttonColor.darker(f: 115)); |
277 | p->setBrush(gradient); |
278 | } else { |
279 | p->setBrush(Qt::NoBrush); |
280 | } |
281 | |
282 | p->setPen(QPen(buttonColor.darker(f: 280))); |
283 | p->drawEllipse(r: br); |
284 | p->setBrush(Qt::NoBrush); |
285 | p->setPen(buttonColor.lighter(f: 110)); |
286 | p->drawEllipse(r: br.adjusted(xp1: 1, yp1: 1, xp2: -1, yp2: -1)); |
287 | |
288 | if (option->state & QStyle::State_HasFocus) { |
289 | QColor highlight = pal.highlight().color().toHsv(); |
290 | highlight.setHsv(h: highlight.hue(), |
291 | s: qMin(a: 160, b: highlight.saturation()), |
292 | v: qMax(a: 230, b: highlight.value())); |
293 | highlight.setAlpha(127); |
294 | p->setPen(QPen(highlight, 2.0)); |
295 | p->setBrush(Qt::NoBrush); |
296 | p->drawEllipse(r: br.adjusted(xp1: -1, yp1: -1, xp2: 1, yp2: 1)); |
297 | } |
298 | |
299 | END_STYLE_PIXMAPCACHE |
300 | |
301 | QPointF dp = calcRadialPos(dial: option, offset: qreal(0.70)); |
302 | buttonColor = buttonColor.lighter(f: 104); |
303 | buttonColor.setAlphaF(0.8f); |
304 | const qreal ds = r/qreal(7.0); |
305 | QRectF dialRect(dp.x() - ds, dp.y() - ds, 2*ds, 2*ds); |
306 | QRadialGradient dialGradient(dialRect.center().x() + dialRect.width()/2, |
307 | dialRect.center().y() + dialRect.width(), |
308 | dialRect.width()*2, |
309 | dialRect.center().x(), dialRect.center().y()); |
310 | dialGradient.setColorAt(pos: 1, color: buttonColor.darker(f: 140)); |
311 | dialGradient.setColorAt(pos: qreal(0.4), color: buttonColor.darker(f: 120)); |
312 | dialGradient.setColorAt(pos: 0, color: buttonColor.darker(f: 110)); |
313 | if (penSize > 3.0) { |
314 | painter->setPen(QPen(QColor(0, 0, 0, 25), penSize)); |
315 | painter->drawLine(p1: calcRadialPos(dial: option, offset: qreal(0.90)), p2: calcRadialPos(dial: option, offset: qreal(0.96))); |
316 | } |
317 | |
318 | painter->setBrush(dialGradient); |
319 | painter->setPen(QColor(255, 255, 255, 150)); |
320 | painter->drawEllipse(r: dialRect.adjusted(xp1: -1, yp1: -1, xp2: 1, yp2: 1)); |
321 | painter->setPen(QColor(0, 0, 0, 80)); |
322 | painter->drawEllipse(r: dialRect); |
323 | painter->restore(); |
324 | } |
325 | |
326 | void drawBorderPixmap(const QPixmap &pixmap, QPainter *painter, const QRect &rect, |
327 | int left, int top, int right, |
328 | int bottom) |
329 | { |
330 | QSize size = pixmap.size(); |
331 | //painter->setRenderHint(QPainter::SmoothPixmapTransform); |
332 | |
333 | //top |
334 | if (top > 0) { |
335 | painter->drawPixmap(targetRect: QRect(rect.left() + left, rect.top(), rect.width() -right - left, top), pixmap, |
336 | sourceRect: QRect(left, 0, size.width() -right - left, top)); |
337 | |
338 | //top-left |
339 | if(left > 0) |
340 | painter->drawPixmap(targetRect: QRect(rect.left(), rect.top(), left, top), pixmap, |
341 | sourceRect: QRect(0, 0, left, top)); |
342 | |
343 | //top-right |
344 | if (right > 0) |
345 | painter->drawPixmap(targetRect: QRect(rect.left() + rect.width() - right, rect.top(), right, top), pixmap, |
346 | sourceRect: QRect(size.width() - right, 0, right, top)); |
347 | } |
348 | |
349 | //left |
350 | if (left > 0) |
351 | painter->drawPixmap(targetRect: QRect(rect.left(), rect.top()+top, left, rect.height() - top - bottom), pixmap, |
352 | sourceRect: QRect(0, top, left, size.height() - bottom - top)); |
353 | |
354 | //center |
355 | painter->drawPixmap(targetRect: QRect(rect.left() + left, rect.top()+top, rect.width() -right - left, |
356 | rect.height() - bottom - top), pixmap, |
357 | sourceRect: QRect(left, top, size.width() -right -left, |
358 | size.height() - bottom - top)); |
359 | //right |
360 | if (right > 0) |
361 | painter->drawPixmap(targetRect: QRect(rect.left() +rect.width() - right, rect.top()+top, right, rect.height() - top - bottom), pixmap, |
362 | sourceRect: QRect(size.width() - right, top, right, size.height() - bottom - top)); |
363 | |
364 | //bottom |
365 | if (bottom > 0) { |
366 | painter->drawPixmap(targetRect: QRect(rect.left() +left, rect.top() + rect.height() - bottom, |
367 | rect.width() - right - left, bottom), pixmap, |
368 | sourceRect: QRect(left, size.height() - bottom, |
369 | size.width() - right - left, bottom)); |
370 | //bottom-left |
371 | if (left > 0) |
372 | painter->drawPixmap(targetRect: QRect(rect.left(), rect.top() + rect.height() - bottom, left, bottom), pixmap, |
373 | sourceRect: QRect(0, size.height() - bottom, left, bottom)); |
374 | |
375 | //bottom-right |
376 | if (right > 0) |
377 | painter->drawPixmap(targetRect: QRect(rect.left() + rect.width() - right, rect.top() + rect.height() - bottom, right, bottom), pixmap, |
378 | sourceRect: QRect(size.width() - right, size.height() - bottom, right, bottom)); |
379 | |
380 | } |
381 | } |
382 | |
383 | WidgetSizePolicy widgetSizePolicy(const QStyleOption *opt) |
384 | { |
385 | if (opt && opt->state & QStyle::State_Mini) |
386 | return SizeMini; |
387 | else if (opt && opt->state & QStyle::State_Small) |
388 | return SizeSmall; |
389 | |
390 | return SizeDefault; |
391 | } |
392 | |
393 | QColor backgroundColor(const QPalette &pal) |
394 | { |
395 | // if (qobject_cast<const QScrollBar *>(widget) && widget->parent() && |
396 | // qobject_cast<const QAbstractScrollArea *>(widget->parent()->parent())) |
397 | // return widget->parentWidget()->parentWidget()->palette().color(QPalette::Base); |
398 | return pal.color(cr: QPalette::Base); |
399 | } |
400 | |
401 | } |
402 | |
403 | } // namespace QQC2 |
404 | |
405 | QT_END_NAMESPACE |
406 | |