Added corner tail when bubble in premium box is at edge.

This commit is contained in:
23rd 2022-05-28 06:50:40 +03:00
parent 8fbdd36ca0
commit de08e1d9a9
2 changed files with 118 additions and 50 deletions

View file

@ -1079,6 +1079,7 @@ ringtonesBoxButton: SettingsButton(defaultSettingsButton) {
ringtonesBoxSkip: 7px; ringtonesBoxSkip: 7px;
premiumBubblePadding: margins(14px, 0px, 14px, 0px); premiumBubblePadding: margins(14px, 0px, 14px, 0px);
premiumBubblePenWidth: 6;
premiumBubbleHeight: 40px; premiumBubbleHeight: 40px;
premiumBubbleSkip: 8px; premiumBubbleSkip: 8px;
premiumBubbleWidthLimit: 80px; premiumBubbleWidthLimit: 80px;

View file

@ -62,6 +62,8 @@ constexpr auto kStepAfterDeflection = kStepBeforeDeflection
class Bubble final { class Bubble final {
public: public:
using EdgeProgress = float64;
Bubble( Bubble(
Fn<void()> updateCallback, Fn<void()> updateCallback,
TextFactory textFactory, TextFactory textFactory,
@ -71,15 +73,18 @@ public:
[[nodiscard]] int height() const; [[nodiscard]] int height() const;
[[nodiscard]] int width() const; [[nodiscard]] int width() const;
[[nodiscard]] int bubbleRadius() const; [[nodiscard]] int bubbleRadius() const;
[[nodiscard]] int countMaxWidth(int maxCounter) const;
void setCounter(int value); void setCounter(int value);
void setTailEdge(std::optional<Qt::Edge> edge); void setTailEdge(EdgeProgress edge);
void paintBubble(Painter &p, const QRect &r, const QBrush &brush); void paintBubble(Painter &p, const QRect &r, const QBrush &brush);
[[nodiscard]] rpl::producer<> widthChanges() const; [[nodiscard]] rpl::producer<> widthChanges() const;
[[nodiscard]] rpl::producer<> updateRequests() const; [[nodiscard]] rpl::producer<> updateRequests() const;
private: private:
[[nodiscard]] int filledWidth() const;
const Fn<void()> _updateCallback; const Fn<void()> _updateCallback;
const TextFactory _textFactory; const TextFactory _textFactory;
@ -92,7 +97,7 @@ private:
const int _textTop; const int _textTop;
int _counter = -1; int _counter = -1;
std::optional<Qt::Edge> _tailEdge; EdgeProgress _tailEdge = 0.;
rpl::event_stream<> _widthChanges; rpl::event_stream<> _widthChanges;
@ -115,6 +120,8 @@ Bubble::Bubble(
_numberAnimation.setWidthChangedCallback([=] { _numberAnimation.setWidthChangedCallback([=] {
_widthChanges.fire({}); _widthChanges.fire({});
}); });
_numberAnimation.setText(_textFactory(0), 0);
_numberAnimation.finishAnimating();
} }
int Bubble::counter() const { int Bubble::counter() const {
@ -129,14 +136,27 @@ int Bubble::bubbleRadius() const {
return (_height - _tailSize.height()) / 2; return (_height - _tailSize.height()) / 2;
} }
int Bubble::width() const { int Bubble::filledWidth() const {
return _padding.left() return _padding.left()
+ _icon->width() + _icon->width()
+ st::premiumBubbleTextSkip + st::premiumBubbleTextSkip
+ _numberAnimation.countWidth()
+ _padding.right(); + _padding.right();
} }
int Bubble::width() const {
return filledWidth() + _numberAnimation.countWidth();
}
int Bubble::countMaxWidth(int maxCounter) const {
auto numbers = Ui::NumbersAnimation(_font, [] {});
numbers.setDisabledMonospace(true);
numbers.setDuration(0);
numbers.setText(_textFactory(0), 0);
numbers.setText(_textFactory(maxCounter), maxCounter);
numbers.finishAnimating();
return filledWidth() + numbers.maxWidth();
}
void Bubble::setCounter(int value) { void Bubble::setCounter(int value) {
if (_counter != value) { if (_counter != value) {
_counter = value; _counter = value;
@ -144,8 +164,8 @@ void Bubble::setCounter(int value) {
} }
} }
void Bubble::setTailEdge(std::optional<Qt::Edge> edge) { void Bubble::setTailEdge(EdgeProgress edge) {
_tailEdge = edge; _tailEdge = std::clamp(edge, 0., 1.);
} }
void Bubble::paintBubble(Painter &p, const QRect &r, const QBrush &brush) { void Bubble::paintBubble(Painter &p, const QRect &r, const QBrush &brush) {
@ -153,33 +173,56 @@ void Bubble::paintBubble(Painter &p, const QRect &r, const QBrush &brush) {
return; return;
} }
const auto bubbleRect = r - style::margins{ 0, 0, 0, _tailSize.height() }; const auto penWidth = st::premiumBubblePenWidth;
const auto penWidthHalf = penWidth / 2;
const auto bubbleRect = r - style::margins(
penWidthHalf,
penWidthHalf,
penWidthHalf,
_tailSize.height() + penWidthHalf);
{ {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(brush);
const auto radius = bubbleRadius(); const auto radius = bubbleRadius();
auto pathTail = QPainterPath(); auto pathTail = QPainterPath();
const auto offset = bubbleRect.topLeft() const auto tailWHalf = _tailSize.width() / 2.;
+ QPoint( const auto progress = _tailEdge;
(_tailEdge.value_or(Qt::TopEdge) == Qt::RightEdge)
? (bubbleRect.width() - _tailSize.width() - radius)
: (bubbleRect.width() - _tailSize.width()) / 2,
bubbleRect.height())
- QPoint(0, 1);
pathTail.moveTo(offset); const auto tailTop = bubbleRect.y() + bubbleRect.height();
pathTail.lineTo(QPoint(_tailSize.width() / 2, _tailSize.height()) const auto tailLeftFull = bubbleRect.x()
+ offset); + (bubbleRect.width() * 0.5)
pathTail.lineTo(offset + QPoint(_tailSize.width(), 0)); - tailWHalf;
pathTail.lineTo(offset); const auto tailLeft = bubbleRect.x()
+ (bubbleRect.width() * 0.5 * (progress + 1.))
- tailWHalf;
const auto tailCenter = tailLeft + tailWHalf;
const auto tailRight = [&] {
const auto max = bubbleRect.x() + bubbleRect.width();
const auto right = tailLeft + _tailSize.width();
const auto bottomMax = max - radius;
return (right > bottomMax)
? std::max(float64(tailCenter), float64(bottomMax))
: right;
}();
pathTail.moveTo(tailLeftFull, tailTop);
pathTail.lineTo(tailLeft, tailTop);
pathTail.lineTo(tailCenter, tailTop + _tailSize.height());
pathTail.lineTo(tailRight, tailTop);
pathTail.lineTo(tailRight, tailTop - radius);
pathTail.moveTo(tailLeftFull, tailTop);
auto pathBubble = QPainterPath(); auto pathBubble = QPainterPath();
pathBubble.setFillRule(Qt::WindingFill); pathBubble.setFillRule(Qt::WindingFill);
pathBubble.addRoundedRect(bubbleRect, radius, radius); pathBubble.addRoundedRect(bubbleRect, radius, radius);
p.fillPath(pathTail + pathBubble, p.brush()); PainterHighQualityEnabler hq(p);
p.setPen(QPen(
brush,
penWidth,
Qt::SolidLine,
Qt::RoundCap,
Qt::RoundJoin));
p.setBrush(brush);
p.drawPath(pathTail + pathBubble);
} }
p.setPen(st::activeButtonFg); p.setPen(st::activeButtonFg);
p.setFont(_font); p.setFont(_font);
@ -187,8 +230,8 @@ void Bubble::paintBubble(Painter &p, const QRect &r, const QBrush &brush) {
_icon->paint( _icon->paint(
p, p,
iconLeft, iconLeft,
r.y() + (bubbleRect.height() - _icon->height()) / 2, bubbleRect.y() + (bubbleRect.height() - _icon->height()) / 2,
r.width()); bubbleRect.width());
_numberAnimation.paint( _numberAnimation.paint(
p, p,
iconLeft + _icon->width() + st::premiumBubbleTextSkip, iconLeft + _icon->width() + st::premiumBubbleTextSkip,
@ -219,6 +262,7 @@ private:
const int _currentCounter; const int _currentCounter;
const int _maxCounter; const int _maxCounter;
Bubble _bubble; Bubble _bubble;
const int _maxBubbleWidth;
Ui::Animations::Simple _appearanceAnimation; Ui::Animations::Simple _appearanceAnimation;
QSize _spaceForDeflection; QSize _spaceForDeflection;
@ -227,6 +271,10 @@ private:
float64 _deflection; float64 _deflection;
bool _ignoreDeflection = false;
float64 _stepBeforeDeflection;
float64 _stepAfterDeflection;
}; };
BubbleWidget::BubbleWidget( BubbleWidget::BubbleWidget(
@ -240,7 +288,10 @@ BubbleWidget::BubbleWidget(
, _currentCounter(current) , _currentCounter(current)
, _maxCounter(maxCounter) , _maxCounter(maxCounter)
, _bubble([=] { update(); }, std::move(textFactory), icon) , _bubble([=] { update(); }, std::move(textFactory), icon)
, _deflection(kDeflection) { , _maxBubbleWidth(_bubble.countMaxWidth(_maxCounter))
, _deflection(kDeflection)
, _stepBeforeDeflection(kStepBeforeDeflection)
, _stepAfterDeflection(kStepAfterDeflection) {
const auto resizeTo = [=](int w, int h) { const auto resizeTo = [=](int w, int h) {
_deflection = (w > st::premiumBubbleWidthLimit) _deflection = (w > st::premiumBubbleWidthLimit)
? kDeflectionSmall ? kDeflectionSmall
@ -260,12 +311,13 @@ BubbleWidget::BubbleWidget(
const auto moveEndPoint = _currentCounter / float64(_maxCounter); const auto moveEndPoint = _currentCounter / float64(_maxCounter);
const auto computeLeft = [=](float64 pointRatio, float64 animProgress) { const auto computeLeft = [=](float64 pointRatio, float64 animProgress) {
const auto &padding = st::boxRowPadding; const auto &padding = st::boxRowPadding;
const auto left = _bubble.bubbleRadius() + padding.left(); const auto halfWidth = (_maxBubbleWidth / 2);
const auto right = _bubble.bubbleRadius() + padding.right(); const auto left = padding.left();
const auto right = padding.right();
return ((parent->width() - left - right) return ((parent->width() - left - right)
* pointRatio * pointRatio
* animProgress) * animProgress)
- (_bubble.width() / 2) - halfWidth
+ left; + left;
}; };
@ -273,42 +325,51 @@ BubbleWidget::BubbleWidget(
showFinishes showFinishes
) | rpl::take(1) | rpl::start_with_next([=] { ) | rpl::take(1) | rpl::start_with_next([=] {
const auto computeEdge = [=] { const auto computeEdge = [=] {
return computeLeft(1., 1.); return parent->width()
- st::boxRowPadding.right()
- _maxBubbleWidth;
}; };
const auto checkBubbleEdges = [&]() -> std::optional<Qt::Edge> { const auto checkBubbleEdges = [&]() -> Bubble::EdgeProgress {
const auto finish = computeLeft(moveEndPoint, 1.); const auto finish = computeLeft(moveEndPoint, 1.);
if (finish >= computeEdge()) { const auto edge = computeEdge();
return Qt::RightEdge; return (finish >= edge)
} ? (finish - edge) / (_maxBubbleWidth / 2.)
return std::nullopt; : 0.;
}; };
_bubble.setTailEdge(checkBubbleEdges()); const auto bubbleEdge = checkBubbleEdges();
_ignoreDeflection = bubbleEdge;
if (_ignoreDeflection) {
_stepBeforeDeflection = 1.;
_stepAfterDeflection = 1.;
}
_appearanceAnimation.start([=](float64 value) { _appearanceAnimation.start([=](float64 value) {
const auto moveProgress = std::clamp( const auto moveProgress = std::clamp(
(value / kStepBeforeDeflection), (value / _stepBeforeDeflection),
0., 0.,
1.); 1.);
const auto counterProgress = std::clamp( const auto counterProgress = std::clamp(
(value / kStepAfterDeflection), (value / _stepAfterDeflection),
0., 0.,
1.); 1.);
moveToLeft( moveToLeft(
std::clamp( computeLeft(moveEndPoint, moveProgress)
int(computeLeft(moveEndPoint, moveProgress)), - (_maxBubbleWidth / 2.) * bubbleEdge,
0,
int(computeEdge())),
0); 0);
const auto counter = int(0 + counterProgress * _currentCounter); const auto counter = int(0 + counterProgress * _currentCounter);
if (!(counter % 4) || counterProgress > 0.8) { // if (!(counter % 4) || counterProgress > 0.8) {
_bubble.setCounter(counter); _bubble.setCounter(counter);
} // }
const auto edgeProgress = value * bubbleEdge;
_bubble.setTailEdge(edgeProgress);
update(); update();
}, },
0., 0.,
1., 1.,
st::premiumBubbleSlideDuration, st::premiumBubbleSlideDuration
* (_ignoreDeflection ? kStepBeforeDeflection : 1.),
anim::easeOutCirc); anim::easeOutCirc);
}, lifetime()); }, lifetime());
} }
@ -318,6 +379,10 @@ bool BubbleWidget::animating() const {
} }
void BubbleWidget::paintEvent(QPaintEvent *e) { void BubbleWidget::paintEvent(QPaintEvent *e) {
if (_bubble.counter() <= 0) {
return;
}
Painter p(this); Painter p(this);
const auto padding = QMargins( const auto padding = QMargins(
@ -336,16 +401,16 @@ void BubbleWidget::paintEvent(QPaintEvent *e) {
const auto progress = _appearanceAnimation.value(1.); const auto progress = _appearanceAnimation.value(1.);
const auto scaleProgress = std::clamp( const auto scaleProgress = std::clamp(
(progress / kStepBeforeDeflection), (progress / _stepBeforeDeflection),
0., 0.,
1.); 1.);
const auto scale = scaleProgress; const auto scale = scaleProgress;
const auto rotationProgress = std::clamp( const auto rotationProgress = std::clamp(
(progress - kStepBeforeDeflection) / (1. - kStepBeforeDeflection), (progress - _stepBeforeDeflection) / (1. - _stepBeforeDeflection),
0., 0.,
1.); 1.);
const auto rotationProgressReverse = std::clamp( const auto rotationProgressReverse = std::clamp(
(progress - kStepAfterDeflection) / (1. - kStepAfterDeflection), (progress - _stepAfterDeflection) / (1. - _stepAfterDeflection),
0., 0.,
1.); 1.);
@ -353,8 +418,10 @@ void BubbleWidget::paintEvent(QPaintEvent *e) {
const auto offsetY = bubbleRect.y() + bubbleRect.height(); const auto offsetY = bubbleRect.y() + bubbleRect.height();
p.translate(offsetX, offsetY); p.translate(offsetX, offsetY);
p.scale(scale, scale); p.scale(scale, scale);
p.rotate(rotationProgress * _deflection if (!_ignoreDeflection) {
- rotationProgressReverse * _deflection); p.rotate(rotationProgress * _deflection
- rotationProgressReverse * _deflection);
}
p.translate(-offsetX, -offsetY); p.translate(-offsetX, -offsetY);
} }