init
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
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
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
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
275
Telegram/lib_ui/ui/toast/toast_widget.cpp
Normal file
275
Telegram/lib_ui/ui/toast/toast_widget.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user