Files
tdesktop/Telegram/SourceFiles/ui/effects/unique_gift_message_bubble.cpp
John Preston 4f3b7fba37 Narrow unique gift cards to their own content
Task: 2026/08/17/narrow-unique-gift-cards-to-their-content
2026-08-17 23:00:37 +04:00

280 lines
7.2 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "ui/effects/unique_gift_message_bubble.h"
#include "ui/text/text.h"
#include "ui/painter.h"
#include <QtGui/QPainterPath>
#include "styles/style_basic.h"
#include "styles/style_chat.h"
namespace Ui::UniqueGiftMessageBubble {
namespace {
constexpr auto kFillOpacity = 1. / 6.;
constexpr auto kOutlineOpacity = 0.5;
constexpr auto kArcControl = 0.5522847498;
constexpr auto kTailTipControlX = 0.5459278926;
constexpr auto kTailJoinX = 0.7999998407;
constexpr auto kTailJoinControlX = 0.0073620031;
constexpr auto kTailFirstControlX = 0.2123168130;
constexpr auto kTailMiddleX = 0.3823594269;
constexpr auto kTailSecondControlX = 0.5524008667;
constexpr auto kTailThirdControlX = 0.7668863569;
constexpr auto kTailTipY = 0.0719294110;
constexpr auto kTailTipControlY = 0.1729529402;
constexpr auto kTailStartControlY = 0.4158705899;
constexpr auto kTailJoinControlY = 0.1595529381;
constexpr auto kTailMiddleY = 0.4270352921;
struct Widths {
int body = 0;
int text = 0;
};
[[nodiscard]] Widths ResolveWidths(
const style::UniqueGiftMessageBubble &st,
const QMargins &hostPadding,
int outerWidth,
int naturalTextWidth) {
const auto availableWidth = std::max(
outerWidth - hostPadding.left() - hostPadding.right(),
0);
const auto maximumBodyWidth = std::max(
availableWidth - st.avatarSize - st.tailSize.width(),
0);
const auto minimumBodyWidth = std::min(
2 * st.radius + st.tailSize.width(),
maximumBodyWidth);
const auto horizontalPadding = st.textPadding.left()
+ st.textPadding.right();
const auto minimumTextWidth = std::max(
minimumBodyWidth - horizontalPadding,
0);
const auto maximumTextWidth = std::max(
maximumBodyWidth - horizontalPadding,
minimumTextWidth);
const auto textWidth = std::clamp(
naturalTextWidth,
minimumTextWidth,
maximumTextWidth);
return {
.body = std::clamp(
textWidth + horizontalPadding,
minimumBodyWidth,
maximumBodyWidth),
.text = textWidth,
};
}
} // namespace
int MaximumTextWidth(
const style::UniqueGiftMessageBubble &st,
const QMargins &hostPadding,
int outerWidth,
int naturalTextWidth) {
return ResolveWidths(
st,
hostPadding,
outerWidth,
naturalTextWidth).text;
}
Layout ResolveLayout(
const style::UniqueGiftMessageBubble &st,
const QMargins &hostPadding,
int outerWidth,
const Text::String &text) {
const auto maximum = MaximumTextWidth(
st,
hostPadding,
outerWidth,
text.maxWidth());
const auto minimum = ResolveWidths(st, hostPadding, outerWidth, 0).text;
const auto size = Text::CountOptimalTextSize(text, minimum, maximum);
return ComputeLayout(
st,
hostPadding,
outerWidth,
std::max(text.countWidth(size.width()), minimum),
size.height());
}
Layout ComputeLayout(
const style::UniqueGiftMessageBubble &st,
const QMargins &hostPadding,
int outerWidth,
int textWidth,
int textHeight) {
const auto widths = ResolveWidths(
st,
hostPadding,
outerWidth,
textWidth);
const auto bodyHeight = std::max(
textHeight + st.textPadding.top() + st.textPadding.bottom(),
2 * st.radius);
const auto rowHeight = std::max(st.avatarSize, bodyHeight);
const auto availableWidth = std::max(
outerWidth - hostPadding.left() - hostPadding.right(),
0);
const auto gap = st.tailSize.width();
const auto groupWidth = st.avatarSize + gap + widths.body;
const auto groupLeft = hostPadding.left()
+ ((availableWidth - groupWidth) / 2);
const auto rowTop = hostPadding.top();
const auto bottom = rowTop + rowHeight;
const auto avatar = QRect(
groupLeft,
bottom - st.avatarSize,
st.avatarSize,
st.avatarSize);
const auto body = QRect(
groupLeft + st.avatarSize + gap,
bottom - bodyHeight,
widths.body,
bodyHeight);
return {
.avatar = avatar,
.body = body,
.text = body.marginsRemoved(st.textPadding),
.pathBounds = QRect(
body.x() - st.tailSize.width(),
body.y(),
body.width() + st.tailSize.width(),
body.height()),
.rowHeight = rowHeight,
.sectionWidth = hostPadding.left() + groupWidth + hostPadding.right(),
.sectionHeight = rowTop + rowHeight + hostPadding.bottom(),
};
}
QPainterPath Path(
const style::UniqueGiftMessageBubble &st,
const Layout &layout) {
const auto outer = QRectF(layout.body);
if (outer.isEmpty()) {
return {};
}
const auto strokeInset = st::lineWidth / 2.;
const auto body = outer.marginsRemoved(QMarginsF(
strokeInset,
strokeInset,
strokeInset,
strokeInset));
const auto radius = std::max(
std::min(outer.height() / 2., float64(st.radius)) - strokeInset,
0.);
const auto arcControl = radius * kArcControl;
const auto tailWidth = float64(st.tailSize.width());
const auto tailHeight = std::min(
float64(st.tailSize.height()),
body.height());
const auto left = body.left();
const auto top = body.top();
const auto right = body.right();
const auto bottom = body.bottom();
// The supplied contour uses three independent scalable anchors.
// Its leftward points are normalized to the tail width, while the
// rightward return points are normalized to the body corner radius.
// Vertical distances from the bottom are normalized to the tail height,
// preserving the original cubic character without stretching the tail.
auto result = QPainterPath();
result.moveTo(left + radius, top);
result.lineTo(right - radius, top);
result.cubicTo(
right - radius + arcControl,
top,
right,
top + radius - arcControl,
right,
top + radius);
result.lineTo(right, bottom - radius);
result.cubicTo(
right,
bottom - radius + arcControl,
right - radius + arcControl,
bottom,
right - radius,
bottom);
result.lineTo(left + radius, bottom);
result.cubicTo(
left + radius * kTailThirdControlX,
bottom,
left + radius * kTailSecondControlX,
bottom - tailHeight * kTailMiddleY,
left + radius * kTailMiddleX,
bottom - tailHeight * kTailMiddleY);
result.cubicTo(
left + radius * kTailFirstControlX,
bottom - tailHeight * kTailJoinControlY,
left - tailWidth * kTailJoinControlX,
bottom,
left - tailWidth * kTailJoinX,
bottom);
result.lineTo(left - tailWidth, bottom);
result.cubicTo(
left - tailWidth,
bottom,
left - tailWidth,
bottom - tailHeight * kTailTipY,
left - tailWidth,
bottom - tailHeight * kTailTipY);
result.cubicTo(
left - tailWidth * kTailTipControlX,
bottom - tailHeight * kTailTipControlY,
left,
bottom - tailHeight * kTailStartControlY,
left,
bottom - tailHeight);
result.lineTo(left, top + radius);
result.cubicTo(
left,
top + radius - arcControl,
left + radius - arcControl,
top,
left + radius,
top);
result.closeSubpath();
return result;
}
void Paint(
QPainter &p,
const style::UniqueGiftMessageBubble &st,
const Layout &layout) {
const auto path = Path(st, layout);
p.save();
{
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
p.setOpacity(kFillOpacity);
p.drawPath(path);
p.setBrush(Qt::NoBrush);
p.setPen(QPen(
Qt::white,
st::lineWidth,
Qt::SolidLine,
Qt::RoundCap,
Qt::RoundJoin));
p.setOpacity(kOutlineOpacity);
p.drawPath(path);
}
p.restore();
}
} // namespace Ui::UniqueGiftMessageBubble