10#include <QGraphicsOpacityEffect>
15#include <QPainterPath>
16#include <QPropertyAnimation>
17#include <QResizeEvent>
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;
53int levelHue(QxToast::Level level,
int fallback)
56 case QxToast::Success:
58 case QxToast::Warning:
62 case QxToast::Information:
68QColor levelAccent(QxToast::Level level,
const QColor &
highlight)
70 int hue = levelHue(level,
highlight.hslHue());
77 const int saturation = qMax(
highlight.hslSaturation(), kMinAccentSaturation);
78 const int lightness = qBound(kMinAccentLightness,
highlight.lightness(), kMaxAccentLightness);
79 return QColor::fromHsl(hue, saturation, lightness);
84QxToastPrivate::QxToastPrivate() =
default;
86void QxToastPrivate::init()
93 q->setFocusPolicy(Qt::NoFocus);
94 q->setAttribute(Qt::WA_ShowWithoutActivating,
true);
95 q->setCursor(Qt::PointingHandCursor);
97 m_icon =
new QLabel(q);
98 m_icon->setFixedSize(kIconSize, kIconSize);
99 m_icon->setAttribute(Qt::WA_TransparentForMouseEvents,
true);
101 m_label =
new QLabel(m_text, q);
102 m_label->setWordWrap(
true);
103 m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
105 m_label->setAttribute(Qt::WA_TransparentForMouseEvents,
true);
107 m_timer =
new QTimer(q);
108 m_timer->setSingleShot(
true);
109 QObject::connect(m_timer, &QTimer::timeout, q, &QxToast::dismiss);
111 m_effect =
new QGraphicsOpacityEffect(q);
112 m_effect->setOpacity(0.0);
113 q->setGraphicsEffect(m_effect);
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) {
124 q->resize(q->sizeHint());
127void QxToastPrivate::applyLevel()
134 QStyle::StandardPixmap standard = QStyle::SP_MessageBoxInformation;
136 case QxToast::Success:
137 standard = QStyle::SP_DialogApplyButton;
139 case QxToast::Warning:
140 standard = QStyle::SP_MessageBoxWarning;
143 standard = QStyle::SP_MessageBoxCritical;
145 case QxToast::Information:
148 m_icon->setPixmap(q->style()->standardIcon(standard).pixmap(kIconSize, kIconSize));
149 m_label->setText(m_text);
154 QPalette labelPalette = m_label->palette();
155 labelPalette.setColor(QPalette::WindowText, q->palette().color(QPalette::ToolTipText));
156 m_label->setPalette(labelPalette);
159int QxToastPrivate::plateWidth(
int outerWidth)
const
161 const int bare = qMax(1, outerWidth - 2 * QxToast::ShadowMargin);
164 return qBound(qMin(QxToast::MinPlateWidth, bare), bare, QxToast::MaxPlateWidth);
167int QxToastPrivate::textWidthFor(
int plate)
169 return qMax(1, plate - 2 * kPadding - kIconSize - kIconSpacing);
172int QxToastPrivate::outerHeightFor(
int outerWidth)
const
174 const int textHeight = m_label ? m_label->heightForWidth(textWidthFor(plateWidth(outerWidth))) : 0;
175 return qMax(kMinPlateHeight, 2 * kPadding + textHeight) + 2 * QxToast::ShadowMargin;
178void QxToastPrivate::placeChildren()
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);
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));
192void QxToastPrivate::startFade(qreal to,
bool dismissWhenFaded)
196 m_dismissWhenFaded = dismissWhenFaded;
199 if (m_fadeDuration <= 0) {
202 m_effect->setOpacity(to);
203 if (dismissWhenFaded) {
209 m_fade->setDuration(m_fadeDuration);
210 m_fade->setStartValue(m_effect->opacity());
211 m_fade->setEndValue(to);
215void QxToastPrivate::finishDismiss()
220 Q_EMIT q->dismissed();
223QxToast::QxToast(
const QString &
text,
Level level, QWidget *parent)
248 if (d->m_text ==
text) {
269 if (d->m_level ==
level) {
286 d->m_timeout = timeoutMs;
292 return d->m_fadeDuration;
298 d->m_fadeDuration = qMax(0, ms);
310 return d->m_dismissing;
316 return levelAccent(d->m_level, palette().color(QPalette::Highlight));
328 return d->outerHeightFor(width);
334 if (d->m_dismissing) {
344 d->startFade(1.0,
false);
346 if (d->m_timeout > 0) {
347 d->m_timer->start(d->m_timeout);
361 if (d->m_dismissing) {
365 d->m_dismissing =
true;
372 d->startFade(0.0,
true);
379 QPainter painter(
this);
380 painter.setRenderHint(QPainter::Antialiasing,
true);
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);
398 QColor background = palette().color(QPalette::ToolTipBase);
399 background.setAlpha(kPlateAlpha);
400 QColor border = palette().color(QPalette::Mid);
401 border.setAlpha(kPlateAlpha);
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);
412 painter.setClipPath(platePath);
413 painter.setPen(Qt::NoPen);
415 painter.drawRect(QRectF(plate.left(), plate.top(), kAccentWidth, plate.height()));
422 QWidget::resizeEvent(
event);
430 switch (
event->type()) {
434 if (!d->m_timer->isActive()) {
437 d->m_remaining = d->m_timer->remainingTime();
446 if (d->m_remaining > 0) {
447 d->m_timer->start(d->m_remaining);
454 return QWidget::event(
event);
459 QWidget::mouseReleaseEvent(
event);
460 if (
event->button() != Qt::LeftButton) {
The state behind QxToast: the message, the level, the clock and the two animations.
One short message, shown over the window it belongs to and gone a few seconds later.
bool isPaused() const
Whether the pointer is holding the countdown back right now.
Level
What the message is about; it selects the icon and the accent colour.
void dismiss()
Takes the toast off screen: the countdown stops, the fade out runs and dismissed() follows once the w...
void paintEvent(QPaintEvent *event) override
void stop()
Stops the countdown and leaves the toast on screen.
int timeout() const
Milliseconds left before the toast dismisses itself, zero or less when it is to stay until something ...
void setFadeDuration(int ms)
Zero turns the animation off, which makes the life cycle immediate.
static constexpr int ShadowMargin
Pixels of room the widget keeps around its plate for the drop shadow.
static constexpr int MaxPlateWidth
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.
void clicked()
The user clicked the toast.
bool event(QEvent *event) override
void start()
Puts the toast on screen, fades it in and starts the countdown.
void setText(const QString &text)
Replaces the message; sizeHintChanged() follows when the size changed.
int heightForWidth(int width) const override
Height of the whole widget when it is width wide, text wrapping included.
QColor accentColor() const
The colour the level is drawn with.
void resizeEvent(QResizeEvent *event) override
void setLevel(Level level)
void mouseReleaseEvent(QMouseEvent *event) override
void dismissRequested()
The toast wants to leave; emitted before the fade out starts.
QSize sizeHint() const override
void setTimeout(int timeoutMs)
#define QX_APP_END_NAMESPACE
#define QX_APP_BEGIN_NAMESPACE
#define QX_FINI_PRIVATE()