tdesktop/Telegram/SourceFiles/ui/empty_userpic.cpp

489 lines
11 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/empty_userpic.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/animation_value.h"
#include "ui/emoji_config.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h" // style::IconButton
#include "styles/style_info.h" // st::topBarCall
namespace Ui {
namespace {
[[nodiscard]] bool IsExternal(const QString &name) {
return !name.isEmpty()
&& (name.front() == QChar(0))
&& QStringView(name).mid(1) == u"external"_q;
}
[[nodiscard]] bool IsInaccessible(const QString &name) {
return !name.isEmpty()
&& (name.front() == QChar(0))
&& QStringView(name).mid(1) == u"inaccessible"_q;
}
void PaintSavedMessagesInner(
QPainter &p,
int x,
int y,
int size,
const style::color &fg) {
// |<----width----->|
//
// XXXXXXXXXXXXXXXXXX ---
// X X |
// X X |
// X X |
// X X height
// X XX X | ---
// X XX XX X | |
// X XX XX X | add
// X XX XX X | |
// XX XX --- ---
const auto thinkness = base::SafeRound(size * 0.055);
const auto increment = int(thinkness) % 2 + (size % 2);
const auto width = base::SafeRound(size * 0.15) * 2 + increment;
const auto height = base::SafeRound(size * 0.19) * 2 + increment;
const auto add = base::SafeRound(size * 0.064);
const auto left = x + (size - width) / 2;
const auto top = y + (size - height) / 2;
const auto right = left + width;
const auto bottom = top + height;
const auto middle = (left + right) / 2;
const auto half = (top + bottom) / 2;
p.setBrush(Qt::NoBrush);
auto pen = fg->p;
pen.setWidthF(thinkness);
pen.setCapStyle(Qt::FlatCap);
{
// XXXXXXXXXXXXXXXXXX
// X X
// X X
// X X
// X X
// X X
pen.setJoinStyle(Qt::RoundJoin);
p.setPen(pen);
QPainterPath path;
path.moveTo(left, half);
path.lineTo(left, top);
path.lineTo(right, top);
path.lineTo(right, half);
p.drawPath(path);
}
{
// X X
// X XX X
// X XX XX X
// X XX XX X
// X XX XX X
// XX XX
pen.setJoinStyle(Qt::MiterJoin);
p.setPen(pen);
QPainterPath path;
path.moveTo(left, half);
path.lineTo(left, bottom);
path.lineTo(middle, bottom - add);
path.lineTo(right, bottom);
path.lineTo(right, half);
p.drawPath(path);
}
}
void PaintIconInner(
QPainter &p,
int x,
int y,
int size,
int defaultSize,
const style::icon &icon,
const style::color &fg) {
if (size == defaultSize) {
const auto rect = QRect{ x, y, size, size };
icon.paintInCenter(
p,
rect,
fg->c);
} else {
p.save();
const auto ratio = size / float64(defaultSize);
p.translate(x + size / 2., y + size / 2.);
p.scale(ratio, ratio);
const auto skip = defaultSize;
const auto rect = QRect{ -skip, -skip, 2 * skip, 2 * skip };
icon.paintInCenter(
p,
rect,
fg->c);
p.restore();
}
}
void PaintRepliesMessagesInner(
QPainter &p,
int x,
int y,
int size,
const style::color &fg) {
PaintIconInner(
p,
x,
y,
size,
st::defaultDialogRow.photoSize,
st::dialogsRepliesUserpic,
fg);
}
void PaintExternalMessagesInner(
QPainter &p,
int x,
int y,
int size,
const style::color &fg) {
PaintIconInner(
p,
x,
y,
size,
st::msgPhotoSize,
st::topBarCall.icon,
fg);
}
void PaintInaccessibleAccountInner(
QPainter &p,
int x,
int y,
int size,
const style::color &fg) {
if (size > st::defaultDialogRow.photoSize) {
PaintIconInner(
p,
x,
y,
size,
st::infoProfilePhotoInnerSize,
st::infoProfileInaccessibleUserpic,
fg);
} else {
PaintIconInner(
p,
x,
y,
size,
st::defaultDialogRow.photoSize,
st::dialogsInaccessibleUserpic,
fg);
}
}
[[nodiscard]] QImage Generate(int size, Fn<void(QPainter&)> callback) {
auto result = QImage(
QSize(size, size) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(style::DevicePixelRatio());
result.fill(Qt::transparent);
{
Painter p(&result);
callback(p);
}
return result;
}
} // namespace
EmptyUserpic::EmptyUserpic(const BgColors &colors, const QString &name)
: _colors(colors) {
fillString(name);
}
QString EmptyUserpic::ExternalName() {
return QChar(0) + u"external"_q;
}
QString EmptyUserpic::InaccessibleName() {
return QChar(0) + u"inaccessible"_q;
}
uint8 EmptyUserpic::ColorIndex(uint64 id) {
return DecideColorIndex(id);
}
EmptyUserpic::BgColors EmptyUserpic::UserpicColor(uint8 colorIndex) {
const EmptyUserpic::BgColors colors[] = {
{ st::historyPeer1UserpicBg, st::historyPeer1UserpicBg2 },
{ st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 },
{ st::historyPeer3UserpicBg, st::historyPeer3UserpicBg2 },
{ st::historyPeer4UserpicBg, st::historyPeer4UserpicBg2 },
{ st::historyPeer5UserpicBg, st::historyPeer5UserpicBg2 },
{ st::historyPeer6UserpicBg, st::historyPeer6UserpicBg2 },
{ st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 },
{ st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 },
};
return colors[ColorIndexToPaletteIndex(colorIndex)];
}
void EmptyUserpic::paint(
QPainter &p,
int x,
int y,
int outerWidth,
int size,
Fn<void()> paintBackground) const {
x = style::RightToLeft() ? (outerWidth - x - size) : x;
const auto fontsize = (size * 13) / 33;
auto font = st::historyPeerUserpicFont->f;
font.setPixelSize(fontsize);
PainterHighQualityEnabler hq(p);
{
auto gradient = QLinearGradient(x, y, x, y + size);
gradient.setStops({
{ 0., _colors.color1->c },
{ 1., _colors.color2->c }
});
p.setBrush(gradient);
}
p.setPen(Qt::NoPen);
paintBackground();
if (IsExternal(_string)) {
PaintExternalMessagesInner(p, x, y, size, st::historyPeerUserpicFg);
} else if (IsInaccessible(_string)) {
PaintInaccessibleAccountInner(
p,
x,
y,
size,
st::historyPeerUserpicFg);
} else {
p.setFont(font);
p.setBrush(Qt::NoBrush);
p.setPen(st::historyPeerUserpicFg);
p.drawText(
QRect(x, y, size, size),
_string,
QTextOption(style::al_center));
}
}
void EmptyUserpic::paintCircle(
QPainter &p,
int x,
int y,
int outerWidth,
int size) const {
paint(p, x, y, outerWidth, size, [&] {
p.drawEllipse(x, y, size, size);
});
}
void EmptyUserpic::paintRounded(
QPainter &p,
int x,
int y,
int outerWidth,
int size,
int radius) const {
paint(p, x, y, outerWidth, size, [&] {
p.drawRoundedRect(x, y, size, size, radius, radius);
});
}
void EmptyUserpic::paintSquare(
QPainter &p,
int x,
int y,
int outerWidth,
int size) const {
paint(p, x, y, outerWidth, size, [&] {
p.fillRect(x, y, size, size, p.brush());
});
}
void EmptyUserpic::PaintSavedMessages(
QPainter &p,
int x,
int y,
int outerWidth,
int size) {
auto bg = QLinearGradient(x, y, x, y + size);
bg.setStops({
{ 0., st::historyPeerSavedMessagesBg->c },
{ 1., st::historyPeerSavedMessagesBg2->c }
});
const auto &fg = st::historyPeerUserpicFg;
PaintSavedMessages(p, x, y, outerWidth, size, QBrush(bg), fg);
}
void EmptyUserpic::PaintSavedMessages(
QPainter &p,
int x,
int y,
int outerWidth,
int size,
QBrush bg,
const style::color &fg) {
x = style::RightToLeft() ? (outerWidth - x - size) : x;
PainterHighQualityEnabler hq(p);
p.setBrush(std::move(bg));
p.setPen(Qt::NoPen);
p.drawEllipse(x, y, size, size);
PaintSavedMessagesInner(p, x, y, size, fg);
}
QImage EmptyUserpic::GenerateSavedMessages(int size) {
return Generate(size, [&](QPainter &p) {
PaintSavedMessages(p, 0, 0, size, size);
});
}
void EmptyUserpic::PaintRepliesMessages(
QPainter &p,
int x,
int y,
int outerWidth,
int size) {
auto bg = QLinearGradient(x, y, x, y + size);
bg.setStops({
{ 0., st::historyPeerSavedMessagesBg->c },
{ 1., st::historyPeerSavedMessagesBg2->c }
});
const auto &fg = st::historyPeerUserpicFg;
PaintRepliesMessages(p, x, y, outerWidth, size, QBrush(bg), fg);
}
void EmptyUserpic::PaintRepliesMessages(
QPainter &p,
int x,
int y,
int outerWidth,
int size,
QBrush bg,
const style::color &fg) {
x = style::RightToLeft() ? (outerWidth - x - size) : x;
PainterHighQualityEnabler hq(p);
p.setBrush(bg);
p.setPen(Qt::NoPen);
p.drawEllipse(x, y, size, size);
PaintRepliesMessagesInner(p, x, y, size, fg);
}
QImage EmptyUserpic::GenerateRepliesMessages(int size) {
return Generate(size, [&](QPainter &p) {
PaintRepliesMessages(p, 0, 0, size, size);
});
}
std::pair<uint64, uint64> EmptyUserpic::uniqueKey() const {
const auto first = (uint64(0xFFFFFFFFU) << 32)
| anim::getPremultiplied(_colors.color1->c);
auto second = uint64(0);
memcpy(
&second,
_string.constData(),
std::min(sizeof(second), size_t(_string.size()) * sizeof(QChar)));
return { first, second };
}
QPixmap EmptyUserpic::generate(int size) {
auto result = QImage(
QSize(size, size) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(style::DevicePixelRatio());
result.fill(Qt::transparent);
{
auto p = QPainter(&result);
paintCircle(p, 0, 0, size, size);
}
return Ui::PixmapFromImage(std::move(result));
}
void EmptyUserpic::fillString(const QString &name) {
if (IsExternal(name) || IsInaccessible(name)) {
_string = name;
return;
}
QList<QString> letters;
QList<int> levels;
auto level = 0;
auto letterFound = false;
auto ch = name.constData(), end = ch + name.size();
while (ch != end) {
auto emojiLength = 0;
if (Ui::Emoji::Find(ch, end, &emojiLength)) {
ch += emojiLength;
} else if (ch->isHighSurrogate()) {
++ch;
if (ch != end && ch->isLowSurrogate()) {
++ch;
}
} else if (!letterFound && ch->isLetterOrNumber()) {
letterFound = true;
if (ch + 1 != end && Ui::Text::IsDiacritic(*(ch + 1))) {
letters.push_back(QString(ch, 2));
levels.push_back(level);
++ch;
} else {
letters.push_back(QString(ch, 1));
levels.push_back(level);
}
++ch;
} else {
if (*ch == ' ') {
level = 0;
letterFound = false;
} else if (letterFound && *ch == '-') {
level = 1;
letterFound = true;
}
++ch;
}
}
// We prefer the second letter to be after ' ', but it can also be after '-'.
_string = QString();
if (!letters.isEmpty()) {
_string += letters.front();
auto bestIndex = 0;
auto bestLevel = 2;
for (auto i = letters.size(); i != 1;) {
if (levels[--i] < bestLevel) {
bestIndex = i;
bestLevel = levels[i];
}
}
if (bestIndex > 0) {
_string += letters[bestIndex];
}
}
_string = _string.toUpper();
}
EmptyUserpic::~EmptyUserpic() = default;
} // namespace Ui