/* 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 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 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 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 letters; QList 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