Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
276 lines
7.1 KiB
C++
276 lines
7.1 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/toast/toast_widget.h"
|
|
|
|
#include "ui/image/image_prepare.h"
|
|
#include "ui/text/text_utilities.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/tooltip.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "styles/palette.h"
|
|
#include "styles/style_widgets.h"
|
|
|
|
#include <QtGui/QtEvents>
|
|
|
|
namespace Ui::Toast::internal {
|
|
namespace {
|
|
|
|
[[nodiscard]] TextWithEntities ComputeText(const Config &config) {
|
|
auto result = config.text;
|
|
if (!config.title.isEmpty()) {
|
|
result = Text::Bold(
|
|
config.title
|
|
).append('\n').append(std::move(result));
|
|
}
|
|
return config.singleline
|
|
? TextUtilities::SingleLine(std::move(result))
|
|
: result;
|
|
}
|
|
|
|
[[nodiscard]] object_ptr<RpWidget> MakeContent(
|
|
not_null<Widget*> parent,
|
|
Config &config) {
|
|
if (config.content) {
|
|
config.content->setParent(parent);
|
|
config.content->show();
|
|
return std::move(config.content);
|
|
}
|
|
auto lifetime = rpl::lifetime();
|
|
const auto st = lifetime.make_state<style::FlatLabel>(
|
|
st::defaultFlatLabel);
|
|
st->style = config.st->style;
|
|
st->textFg = st::toastFg;
|
|
st->palette = config.st->palette;
|
|
st->minWidth = config.padding
|
|
? style::ConvertScale(1) // We don't really know.
|
|
: (config.st->minWidth
|
|
- config.st->padding.left()
|
|
- config.st->padding.right());
|
|
st->maxHeight = config.st->style.font->height
|
|
* (config.singleline ? 1 : config.maxlines);
|
|
|
|
auto result = object_ptr<FlatLabel>(parent, QString(), *st);
|
|
const auto raw = result.data();
|
|
|
|
raw->lifetime().add(std::move(lifetime));
|
|
auto context = config.textContext;
|
|
context.repaint = [raw] { raw->update(); };
|
|
raw->setMarkedText(ComputeText(config), context);
|
|
raw->setClickHandlerFilter(std::move(config.filter));
|
|
raw->show();
|
|
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] bool HasLinksOrSpoilers(const TextWithEntities &text) {
|
|
for (const auto &entity : text.entities) {
|
|
switch (entity.type()) {
|
|
case EntityType::Url:
|
|
case EntityType::CustomUrl:
|
|
case EntityType::Email:
|
|
case EntityType::Spoiler: return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Widget::Widget(QWidget *parent, Config &&config)
|
|
: RpWidget(parent)
|
|
, _st(config.st)
|
|
, _roundRect(_st->radius, st::toastBg)
|
|
, _attach(config.attach)
|
|
, _addToAttach(config.addToAttachSide
|
|
? std::move(config.addToAttachSide)
|
|
: rpl::single(0))
|
|
, _content(MakeContent(this, config))
|
|
, _padding(config.padding
|
|
? std::move(config.padding) | rpl::map(rpl::mappers::_1 + _st->padding)
|
|
: rpl::single(_st->padding) | rpl::type_erased)
|
|
, _adaptive(config.adaptive) {
|
|
if (HasLinksOrSpoilers(config.text) || config.acceptinput) {
|
|
setMouseTracking(true);
|
|
} else {
|
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
}
|
|
|
|
rpl::combine(
|
|
_addToAttach.value(),
|
|
_padding.value()
|
|
) | rpl::on_next([=] {
|
|
parentResized();
|
|
}, lifetime());
|
|
|
|
crl::on_main_update_requests(
|
|
) | rpl::on_next([=] {//const auto filter = [=](not_null<QEvent*> e) {
|
|
if (_attach == RectPart::None && _shownLevel < 1.) {
|
|
scheduleChildrenPaintRestore();
|
|
}
|
|
}, lifetime());
|
|
|
|
show();
|
|
}
|
|
|
|
void Widget::parentResized() {
|
|
updateGeometry();
|
|
}
|
|
|
|
void Widget::updateGeometry() {
|
|
auto width = _st->maxWidth;
|
|
const auto padding = _padding.current();
|
|
const auto added = padding.left() + padding.right();
|
|
const auto natural = _content->naturalWidth();
|
|
const auto max = (natural > 0) ? natural : _content->width();
|
|
accumulate_min(width, max + added);
|
|
accumulate_min(
|
|
width,
|
|
parentWidget()->width() - _st->margin.left() - _st->margin.right());
|
|
if (_adaptive) {
|
|
width = FindNiceTooltipWidth(0, width - added, [&](int width) {
|
|
_content->resizeToWidth(width);
|
|
return _content->heightNoMargins();
|
|
}) + added;
|
|
}
|
|
_content->resizeToWidth(width - added);
|
|
const auto minHeight = _st->icon.empty()
|
|
? 0
|
|
: (_st->icon.height() + 2 * _st->iconPosition.y());
|
|
const auto normalHeight = padding.top()
|
|
+ _content->heightNoMargins()
|
|
+ padding.bottom();
|
|
const auto height = std::max(minHeight, normalHeight);
|
|
const auto top = padding.top() + ((height - normalHeight) / 2);
|
|
_content->moveToLeft(padding.left(), top);
|
|
|
|
const auto rect = QRect(0, 0, width, height);
|
|
const auto outer = parentWidget()->size();
|
|
const auto full = QPoint(outer.width(), outer.height());
|
|
const auto middle = QPoint(
|
|
(outer.width() - width) / 2,
|
|
(outer.height() - height) / 2);
|
|
_updateShownGeometry = [=](float64 level) {
|
|
const auto interpolated = [&](int from, int to) {
|
|
return anim::interpolate(from, to, level);
|
|
};
|
|
setGeometry(rect.translated([&] {
|
|
const auto add = _addToAttach.current();
|
|
switch (_attach) {
|
|
case RectPart::None:
|
|
return middle;
|
|
case RectPart::Left:
|
|
return QPoint(
|
|
interpolated(-width, _st->margin.left() + add),
|
|
middle.y());
|
|
case RectPart::Top:
|
|
return QPoint(
|
|
middle.x(),
|
|
interpolated(-height, _st->margin.top() + add));
|
|
case RectPart::Right:
|
|
return QPoint(
|
|
full.x() - interpolated(
|
|
0,
|
|
width + _st->margin.right() + add),
|
|
middle.y());
|
|
case RectPart::Bottom:
|
|
return QPoint(
|
|
middle.x(),
|
|
full.y() - interpolated(
|
|
0,
|
|
height + _st->margin.bottom() + add));
|
|
}
|
|
Unexpected("Slide side in Toast::Widget::updateGeometry.");
|
|
}()));
|
|
};
|
|
_updateShownGeometry(_shownLevel);
|
|
}
|
|
|
|
void Widget::setShownLevel(float64 shownLevel) {
|
|
if (_shownLevel == shownLevel) {
|
|
return;
|
|
}
|
|
_shownLevel = shownLevel;
|
|
if (_attach != RectPart::None) {
|
|
_updateShownGeometry(_shownLevel);
|
|
} else {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void Widget::paintToProxy() {
|
|
const auto ratio = devicePixelRatio();
|
|
const auto full = size() * ratio;
|
|
if (_shownProxy.size() != full) {
|
|
_shownProxy = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
|
}
|
|
_shownProxy.setDevicePixelRatio(ratio);
|
|
_shownProxy.fill(Qt::transparent);
|
|
|
|
auto q = QPainter(&_shownProxy);
|
|
const auto saved = std::exchange(_shownLevel, 1.);
|
|
Ui::RenderWidget(q, this);
|
|
_shownLevel = saved;
|
|
}
|
|
|
|
void Widget::disableChildrenPaintOnce() {
|
|
toggleChildrenPaint(false);
|
|
scheduleChildrenPaintRestore();
|
|
}
|
|
|
|
void Widget::toggleChildrenPaint(bool enabled) {
|
|
_childrenPaintDisabled = !enabled;
|
|
for (const auto child : children()) {
|
|
if (child->isWidgetType()) {
|
|
static_cast<QWidget*>(child)->setAttribute(
|
|
Qt::WA_UpdatesDisabled,
|
|
_childrenPaintDisabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Widget::scheduleChildrenPaintRestore() {
|
|
if (_childrenPaintRestoreScheduled) {
|
|
return;
|
|
}
|
|
_childrenPaintRestoreScheduled = true;
|
|
Ui::PostponeCall(this, [=] {
|
|
_childrenPaintRestoreScheduled = false;
|
|
if (_childrenPaintDisabled) {
|
|
toggleChildrenPaint(true);
|
|
}
|
|
});
|
|
}
|
|
|
|
void Widget::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
|
|
const auto opacity = (_attach == RectPart::None)
|
|
? _shownLevel
|
|
: 1.;
|
|
if (opacity < 1.) {
|
|
paintToProxy();
|
|
p.setOpacity(opacity);
|
|
p.drawImage(0, 0, _shownProxy);
|
|
disableChildrenPaintOnce();
|
|
return;
|
|
}
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
_roundRect.paint(p, rect());
|
|
|
|
if (!_st->icon.empty()) {
|
|
_st->icon.paint(
|
|
p,
|
|
_st->iconPosition.x(),
|
|
_st->iconPosition.y(),
|
|
width());
|
|
}
|
|
}
|
|
|
|
} // namespace Ui::Toast::internal
|