QtCanpool 3.3.0
载入中...
搜索中...
未找到
qxtoast.cpp
浏览该文件的文档.
1
5#include "qxtoast.h"
6#include "qxtoast_p.h"
7
8#include <QEasingCurve>
9#include <QEvent>
10#include <QGraphicsOpacityEffect>
11#include <QLabel>
12#include <QMouseEvent>
13#include <QPaintEvent>
14#include <QPainter>
15#include <QPainterPath>
16#include <QPropertyAnimation>
17#include <QResizeEvent>
18#include <QStyle>
19#include <QTimer>
20
22
23namespace
24{
25
27const int kPadding = 10;
29const int kIconSize = 16;
30const int kIconSpacing = 8;
32const int kMinPlateHeight = 34;
34const int kCornerRadius = 4;
36const int kAccentWidth = 3;
38const int kPlateAlpha = 236;
40const int kShadowRings = 3;
41const int kShadowAlpha = 30;
43const int kAchromaticHue = 203;
45const int kMinAccentSaturation = 140;
46const int kMinAccentLightness = 100;
47const int kMaxAccentLightness = 170;
48
53int levelHue(QxToast::Level level, int fallback)
54{
55 switch (level) {
56 case QxToast::Success:
57 return 120;
58 case QxToast::Warning:
59 return 45;
60 case QxToast::Error:
61 return 0;
62 case QxToast::Information:
63 break;
64 }
65 return fallback;
66}
67
68QColor levelAccent(QxToast::Level level, const QColor &highlight)
69{
70 int hue = levelHue(level, highlight.hslHue());
71 if (hue < 0) {
72 hue = kAchromaticHue;
73 }
74
75 // The theme decides how loud the accent is; these two clamps only keep it
76 // readable on whatever surface the theme paints the plate with.
77 const int saturation = qMax(highlight.hslSaturation(), kMinAccentSaturation);
78 const int lightness = qBound(kMinAccentLightness, highlight.lightness(), kMaxAccentLightness);
79 return QColor::fromHsl(hue, saturation, lightness);
80}
81
82} // namespace
83
84QxToastPrivate::QxToastPrivate() = default;
85
86void QxToastPrivate::init()
87{
88 Q_Q(QxToast);
89
90 // A toast never takes the focus and never activates anything: a notification
91 // may not interrupt what the user is typing. Setting the attribute on a
92 // child widget costs nothing and states the contract.
93 q->setFocusPolicy(Qt::NoFocus);
94 q->setAttribute(Qt::WA_ShowWithoutActivating, true);
95 q->setCursor(Qt::PointingHandCursor);
96
97 m_icon = new QLabel(q);
98 m_icon->setFixedSize(kIconSize, kIconSize);
99 m_icon->setAttribute(Qt::WA_TransparentForMouseEvents, true);
100
101 m_label = new QLabel(m_text, q);
102 m_label->setWordWrap(true);
103 m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
104 // Clicks belong to the toast, not to the text inside it.
105 m_label->setAttribute(Qt::WA_TransparentForMouseEvents, true);
106
107 m_timer = new QTimer(q);
108 m_timer->setSingleShot(true);
109 QObject::connect(m_timer, &QTimer::timeout, q, &QxToast::dismiss);
110
111 m_effect = new QGraphicsOpacityEffect(q);
112 m_effect->setOpacity(0.0);
113 q->setGraphicsEffect(m_effect);
114
115 m_fade = new QPropertyAnimation(m_effect, "opacity", q);
116 m_fade->setEasingCurve(QEasingCurve::InOutQuad);
117 QObject::connect(m_fade, &QPropertyAnimation::finished, q, [this]() {
118 if (m_dismissWhenFaded) {
119 finishDismiss();
120 }
121 });
122
123 applyLevel();
124 q->resize(q->sizeHint());
125}
126
127void QxToastPrivate::applyLevel()
128{
129 Q_Q(QxToast);
130
131 // The icon comes from the style rather than from a resource of our own: it
132 // is the artwork the platform already draws for exactly these four
133 // meanings, and it keeps its meaning when the palette changes.
134 QStyle::StandardPixmap standard = QStyle::SP_MessageBoxInformation;
135 switch (m_level) {
136 case QxToast::Success:
137 standard = QStyle::SP_DialogApplyButton;
138 break;
139 case QxToast::Warning:
140 standard = QStyle::SP_MessageBoxWarning;
141 break;
142 case QxToast::Error:
143 standard = QStyle::SP_MessageBoxCritical;
144 break;
145 case QxToast::Information:
146 break;
147 }
148 m_icon->setPixmap(q->style()->standardIcon(standard).pixmap(kIconSize, kIconSize));
149 m_label->setText(m_text);
150
151 // The plate is a tooltip shaped surface, so the text is drawn with the role
152 // a tooltip would use - which is the role the theme keeps in step with the
153 // plate.
154 QPalette labelPalette = m_label->palette();
155 labelPalette.setColor(QPalette::WindowText, q->palette().color(QPalette::ToolTipText));
156 m_label->setPalette(labelPalette);
157}
158
159int QxToastPrivate::plateWidth(int outerWidth) const
160{
161 const int bare = qMax(1, outerWidth - 2 * QxToast::ShadowMargin);
162 // Narrower than the minimum is allowed - the host may simply be that small -
163 // but wider than the maximum is not.
164 return qBound(qMin(QxToast::MinPlateWidth, bare), bare, QxToast::MaxPlateWidth);
165}
166
167int QxToastPrivate::textWidthFor(int plate)
168{
169 return qMax(1, plate - 2 * kPadding - kIconSize - kIconSpacing);
170}
171
172int QxToastPrivate::outerHeightFor(int outerWidth) const
173{
174 const int textHeight = m_label ? m_label->heightForWidth(textWidthFor(plateWidth(outerWidth))) : 0;
175 return qMax(kMinPlateHeight, 2 * kPadding + textHeight) + 2 * QxToast::ShadowMargin;
176}
177
178void QxToastPrivate::placeChildren()
179{
180 Q_Q(QxToast);
181
182 const int plate = plateWidth(q->width());
183 const int plateLeft = (q->width() - plate) / 2;
184 const int plateTop = QxToast::ShadowMargin;
185 const int plateHeight = qMax(0, q->height() - 2 * QxToast::ShadowMargin);
186
187 m_icon->move(plateLeft + kPadding, plateTop + qMax(0, (plateHeight - kIconSize) / 2));
188 m_label->setGeometry(plateLeft + kPadding + kIconSize + kIconSpacing, plateTop + kPadding, textWidthFor(plate),
189 qMax(0, plateHeight - 2 * kPadding));
190}
191
192void QxToastPrivate::startFade(qreal to, bool dismissWhenFaded)
193{
194 Q_Q(QxToast);
195
196 m_dismissWhenFaded = dismissWhenFaded;
197 m_fade->stop();
198
199 if (m_fadeDuration <= 0) {
200 // No animation: the state is taken straight away, which is what lets a
201 // test walk the whole life cycle without waiting for a clock.
202 m_effect->setOpacity(to);
203 if (dismissWhenFaded) {
204 finishDismiss();
205 }
206 return;
207 }
208
209 m_fade->setDuration(m_fadeDuration);
210 m_fade->setStartValue(m_effect->opacity());
211 m_fade->setEndValue(to);
212 m_fade->start();
213}
214
215void QxToastPrivate::finishDismiss()
216{
217 Q_Q(QxToast);
218
219 q->hide();
220 Q_EMIT q->dismissed();
221}
222
223QxToast::QxToast(const QString &text, Level level, QWidget *parent)
224 : QWidget(parent)
225 , d_ptr(new QxToastPrivate())
226{
227 Q_D(QxToast);
228 d->setPublic(this);
229 d->m_text = text;
230 d->m_level = level;
231 d->init();
232}
233
238
239QString QxToast::text() const
240{
241 Q_D(const QxToast);
242 return d->m_text;
243}
244
245void QxToast::setText(const QString &text)
246{
247 Q_D(QxToast);
248 if (d->m_text == text) {
249 return;
250 }
251
252 const QSize before = sizeHint();
253 d->m_text = text;
254 d->applyLevel();
255 if (sizeHint() != before) {
256 Q_EMIT sizeHintChanged();
257 }
258}
259
261{
262 Q_D(const QxToast);
263 return d->m_level;
264}
265
267{
268 Q_D(QxToast);
269 if (d->m_level == level) {
270 return;
271 }
272 d->m_level = level;
273 d->applyLevel();
274 update();
275}
276
278{
279 Q_D(const QxToast);
280 return d->m_timeout;
281}
282
283void QxToast::setTimeout(int timeoutMs)
284{
285 Q_D(QxToast);
286 d->m_timeout = timeoutMs;
287}
288
290{
291 Q_D(const QxToast);
292 return d->m_fadeDuration;
293}
294
296{
297 Q_D(QxToast);
298 d->m_fadeDuration = qMax(0, ms);
299}
300
302{
303 Q_D(const QxToast);
304 return d->m_paused;
305}
306
308{
309 Q_D(const QxToast);
310 return d->m_dismissing;
311}
312
314{
315 Q_D(const QxToast);
316 return levelAccent(d->m_level, palette().color(QPalette::Highlight));
317}
318
319QSize QxToast::sizeHint() const
320{
321 const int width = MaxPlateWidth + 2 * ShadowMargin;
322 return QSize(width, heightForWidth(width));
323}
324
325int QxToast::heightForWidth(int width) const
326{
327 Q_D(const QxToast);
328 return d->outerHeightFor(width);
329}
330
332{
333 Q_D(QxToast);
334 if (d->m_dismissing) {
335 return;
336 }
337
338 d->m_paused = false;
339 show();
340 // Siblings such as the ribbon and the docks are already in the host; the
341 // toast has to go above them rather than behind them.
342 raise();
343 d->placeChildren();
344 d->startFade(1.0, false);
345
346 if (d->m_timeout > 0) {
347 d->m_timer->start(d->m_timeout);
348 }
349}
350
352{
353 Q_D(QxToast);
354 d->m_paused = false;
355 d->m_timer->stop();
356}
357
359{
360 Q_D(QxToast);
361 if (d->m_dismissing) {
362 return;
363 }
364
365 d->m_dismissing = true;
366 d->m_paused = false;
367 d->m_timer->stop();
368
369 // The manager listens to this and takes the toast out of its stack before
370 // the fade even starts, so the state it reports is the state on screen.
371 Q_EMIT dismissRequested();
372 d->startFade(0.0, true);
373}
374
375void QxToast::paintEvent(QPaintEvent *event)
376{
377 Q_UNUSED(event);
378
379 QPainter painter(this);
380 painter.setRenderHint(QPainter::Antialiasing, true);
381
382 const QRectF plate(ShadowMargin, ShadowMargin, qMax(0, width() - 2 * ShadowMargin),
383 qMax(0, height() - 2 * ShadowMargin));
384
385 // A real drop shadow would need a translucent top level window. A few
386 // fading rings cost less, need no window manager and are enough for an
387 // overlay that always sits on a background we know.
388 const QColor shadow = palette().color(QPalette::Shadow);
389 painter.setPen(Qt::NoPen);
390 for (int ring = kShadowRings; ring >= 1; --ring) {
391 QColor layer = shadow;
392 layer.setAlpha(kShadowAlpha / ring);
393 painter.setBrush(layer);
394 painter.drawRoundedRect(plate.adjusted(-ring, -ring + 1, ring, ring + 1), kCornerRadius + ring,
395 kCornerRadius + ring);
396 }
397
398 QColor background = palette().color(QPalette::ToolTipBase);
399 background.setAlpha(kPlateAlpha);
400 QColor border = palette().color(QPalette::Mid);
401 border.setAlpha(kPlateAlpha);
402
403 QPainterPath platePath;
404 platePath.addRoundedRect(plate.adjusted(0.5, 0.5, -0.5, -0.5), kCornerRadius, kCornerRadius);
405 painter.setBrush(background);
406 painter.setPen(QPen(border, 1));
407 painter.drawPath(platePath);
408
409 // The accent runs down the left edge; clipping it to the plate keeps the
410 // rounded corners intact.
411 painter.save();
412 painter.setClipPath(platePath);
413 painter.setPen(Qt::NoPen);
414 painter.setBrush(accentColor());
415 painter.drawRect(QRectF(plate.left(), plate.top(), kAccentWidth, plate.height()));
416 painter.restore();
417}
418
419void QxToast::resizeEvent(QResizeEvent *event)
420{
421 Q_D(QxToast);
422 QWidget::resizeEvent(event);
423 d->placeChildren();
424}
425
426bool QxToast::event(QEvent *event)
427{
428 Q_D(QxToast);
429
430 switch (event->type()) {
431 case QEvent::Enter:
432 // Hovering holds the countdown back instead of dismissing: a message
433 // that is being read must not vanish under the pointer.
434 if (!d->m_timer->isActive()) {
435 break;
436 }
437 d->m_remaining = d->m_timer->remainingTime();
438 d->m_timer->stop();
439 d->m_paused = true;
440 break;
441 case QEvent::Leave:
442 if (!d->m_paused) {
443 break;
444 }
445 d->m_paused = false;
446 if (d->m_remaining > 0) {
447 d->m_timer->start(d->m_remaining);
448 }
449 break;
450 default:
451 break;
452 }
453
454 return QWidget::event(event);
455}
456
457void QxToast::mouseReleaseEvent(QMouseEvent *event)
458{
459 QWidget::mouseReleaseEvent(event);
460 if (event->button() != Qt::LeftButton) {
461 return;
462 }
463
464 Q_EMIT clicked();
465 dismiss();
466}
467
The state behind QxToast: the message, the level, the clock and the two animations.
Definition qxtoast_p.h:30
One short message, shown over the window it belongs to and gone a few seconds later.
Definition qxtoast.h:54
bool isPaused() const
Whether the pointer is holding the countdown back right now.
Definition qxtoast.cpp:301
Level
What the message is about; it selects the icon and the accent colour.
Definition qxtoast.h:58
void dismiss()
Takes the toast off screen: the countdown stops, the fade out runs and dismissed() follows once the w...
Definition qxtoast.cpp:358
void paintEvent(QPaintEvent *event) override
Definition qxtoast.cpp:375
void stop()
Stops the countdown and leaves the toast on screen.
Definition qxtoast.cpp:351
int timeout() const
Milliseconds left before the toast dismisses itself, zero or less when it is to stay until something ...
Definition qxtoast.cpp:277
void setFadeDuration(int ms)
Zero turns the animation off, which makes the life cycle immediate.
Definition qxtoast.cpp:295
static constexpr int ShadowMargin
Pixels of room the widget keeps around its plate for the drop shadow.
Definition qxtoast.h:75
static constexpr int MaxPlateWidth
Definition qxtoast.h:82
void sizeHintChanged()
The preferred size changed and whoever placed the toast should look again.
bool isDismissing() const
True from the moment dismiss() is called until the widget is gone.
Definition qxtoast.cpp:307
void clicked()
The user clicked the toast.
bool event(QEvent *event) override
Definition qxtoast.cpp:426
~QxToast() override
Definition qxtoast.cpp:234
void start()
Puts the toast on screen, fades it in and starts the countdown.
Definition qxtoast.cpp:331
int fadeDuration() const
Definition qxtoast.cpp:289
void setText(const QString &text)
Replaces the message; sizeHintChanged() follows when the size changed.
Definition qxtoast.cpp:245
int heightForWidth(int width) const override
Height of the whole widget when it is width wide, text wrapping included.
Definition qxtoast.cpp:325
QColor accentColor() const
The colour the level is drawn with.
Definition qxtoast.cpp:313
void resizeEvent(QResizeEvent *event) override
Definition qxtoast.cpp:419
void setLevel(Level level)
Definition qxtoast.cpp:266
void mouseReleaseEvent(QMouseEvent *event) override
Definition qxtoast.cpp:457
Level level() const
Definition qxtoast.cpp:260
void dismissRequested()
The toast wants to leave; emitted before the fade out starts.
QString text() const
Definition qxtoast.cpp:239
QSize sizeHint() const override
Definition qxtoast.cpp:319
void setTimeout(int timeoutMs)
Definition qxtoast.cpp:283
#define QX_APP_END_NAMESPACE
#define QX_APP_BEGIN_NAMESPACE
#define QX_FINI_PRIVATE()
QColor text
QColor highlight