mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Added corner tail when bubble in premium box is at edge.
This commit is contained in:
parent
8fbdd36ca0
commit
de08e1d9a9
2 changed files with 118 additions and 50 deletions
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue